Quickstart: CLI Event Log Integration
Target Audience: Developers implementing event log functionality Prerequisites: spec-kitty 2.x installed, spec-kitty-events library integrated Estimated Time: 15 minutes
Overview
This quickstart demonstrates how to: 1. Emit events when workflow state changes 2. Read events to reconstruct current state 3. Query event history with filters 4. Handle conflicts and errors
Setup
Install Dependencies
# Clone spec-kitty (2.x branch)
git checkout 2.x
# Install with spec-kitty-events dependency
pip install -e .
# Verify installation
spec-kitty --version # Should show 2.x version
Initialize Project
# Create new project
mkdir my-project && cd my-project
git init
# Initialize spec-kitty (creates .kittify/ directory)
spec-kitty init
# Verify event log directory created
ls -la .kittify/events/ # Should exist but be empty initially
Usage Examples
Example 1: Emit Event on Status Change
Scenario: User moves work package from "planned" to "doing"
Command:
spec-kitty agent tasks move-task WP01 --to doing
What Happens Internally:
# In src/specify_cli/cli/commands/agent.py
from specify_cli.events.middleware import with_event_store
@with_event_store
def move_task(wp_id: str, lane: str, event_store: EventStore):
# 1. Load current WP state
current_status = load_wp_status(wp_id)
# 2. Validate transition
if not is_valid_transition(current_status, lane):
raise ValidationError(f"Cannot transition from {current_status} to {lane}")
# 3. Update frontmatter (existing logic)
update_wp_frontmatter(wp_id, status=lane)
# 4. Emit event (NEW)
event_store.emit(
event_type="WPStatusChanged",
entity_id=wp_id,
entity_type="WorkPackage",
actor=get_current_agent(),
causation_id=f"cmd-{uuid.uuid4()}",
payload={
"feature_slug": get_current_feature(),
"old_status": current_status,
"new_status": lane,
"reason": "User requested transition"
}
)
print(f"✓ Moved {wp_id} to {lane}")
Result:
# Event appended to .kittify/events/2026-01-27.jsonl
cat .kittify/events/2026-01-27.jsonl
{
"event_id": "01HN3R5K8D1234567890ABCDEF",
"event_type": "WPStatusChanged",
"event_version": 1,
"lamport_clock": 1,
"entity_id": "WP01",
"entity_type": "WorkPackage",
"timestamp": "2026-01-27T10:30:00Z",
"actor": "claude-implementer",
"causation_id": "cmd-abc123",
"correlation_id": null,
"payload": {
"feature_slug": "025-cli-event-log-integration",
"old_status": "planned",
"new_status": "doing",
"reason": "User requested transition"
}
}
Clock State Updated:
cat .kittify/clock.json
{
"value": 2,
"last_updated": "2026-01-27T10:30:00Z"
}
Example 2: Read Status from Event Log
Scenario: User checks project status
Command:
spec-kitty status
What Happens Internally:
# In src/specify_cli/cli/commands/status.py
from specify_cli.events.middleware import with_event_store
@with_event_store
def status(feature: str | None, event_store: EventStore):
# 1. Read all WPStatusChanged events for current feature
events = event_store.read(
event_type="WPStatusChanged",
# entity_id not specified → reads all WPs
)
# 2. Reconstruct status for each WP
wp_statuses = {}
for event in sorted(events, key=lambda e: e.lamport_clock):
wp_id = event.entity_id
new_status = event.payload["new_status"]
wp_statuses[wp_id] = new_status
# 3. Display kanban board
display_kanban(wp_statuses)
Output:
┌─────────────────────────────────────────────────────┐
│ Feature: 025-cli-event-log-integration │
├─────────────┬─────────────┬─────────────┬───────────┤
│ Planned │ Doing │ For Review │ Done │
├─────────────┼─────────────┼─────────────┼───────────┤
│ WP02 │ WP01 ← YOU │ WP03 │ │
│ WP04 │ │ │ │
└─────────────┴─────────────┴─────────────┴───────────┘
Progress: ▓▓▓░░░░░░░ 30% (1/4 WPs doing, 1 for_review)
Example 3: Query Event History
Scenario: Developer wants to see all events for a specific WP
Command:
# Built-in query (future enhancement)
spec-kitty agent events query --entity WP01 --type WPStatusChanged
Python API (for programmatic access):
from pathlib import Path
from specify_cli.events.store import EventStore
# Initialize event store
repo_root = Path(".")
event_store = EventStore(repo_root)
# Query events for WP01
events = event_store.read(
entity_id="WP01",
event_type="WPStatusChanged"
)
# Display timeline
for event in sorted(events, key=lambda e: e.lamport_clock):
print(f"Clock {event.lamport_clock}: {event.payload['old_status']} → {event.payload['new_status']}")
Output:
Clock 1: planned → doing
Clock 5: doing → for_review
Clock 8: for_review → done
Example 4: Handle Conflict (Concurrent Operations)
Scenario: Two agents modify WP status concurrently (simulated)
Simulation:
# Agent A (offline, clock=5)
event_a = Event(
event_id="01HN3R5K8D1111111111111111",
lamport_clock=5,
entity_id="WP03",
event_type="WPStatusChanged",
payload={"old_status": "doing", "new_status": "for_review"}
)
# Agent B (offline, clock=5 - same clock!)
event_b = Event(
event_id="01HN3R5K8E2222222222222222",
lamport_clock=5,
entity_id="WP03",
event_type="WPStatusChanged",
payload={"old_status": "doing", "new_status": "rejected"}
)
# Both events written to log when agents come online
Conflict Detection:
from specify_cli.events.reader import EventReader
reader = EventReader(repo_root)
events = reader.read(entity_id="WP03", event_type="WPStatusChanged")
# Group by clock
events_by_clock = {}
for event in events:
clock = event.lamport_clock
if clock not in events_by_clock:
events_by_clock[clock] = []
events_by_clock[clock].append(event)
# Detect conflict
for clock, clock_events in events_by_clock.items():
if len(clock_events) > 1:
print(f"⚠️ Conflict detected at clock {clock}!")
print(f" Events: {[e.event_id for e in clock_events]}")
# Apply LWW merge rule
clock_events.sort(key=lambda e: e.event_id) # Lexicographic sort of ULIDs
winning_event = clock_events[-1]
print(f" Applying LWW: Event {winning_event.event_id} wins")
print(f" Result: WP03 status = {winning_event.payload['new_status']}")
Output:
⚠️ Conflict detected at clock 5!
Events: ['01HN3R5K8D1111111111111111', '01HN3R5K8E2222222222222222']
Applying LWW: Event 01HN3R5K8E2222222222222222 wins
Result: WP03 status = rejected
Status Display (shows conflict warning):
spec-kitty status
⚠️ Conflict resolved for WP03 (clock 5): 2 concurrent status changes detected.
Applied merge rule: Last-Write-Wins (event 01HN3R5K8E2222222222222222)
Final status: rejected
Example 5: Error Logging (Invalid Transition)
Scenario: Agent attempts invalid state transition
Command (will fail):
spec-kitty agent tasks move-task WP01 --to done
Current State: WP01 is in "planned" Requested: Move to "done" Valid Transitions from "planned": Only ["doing"]
What Happens Internally:
from specify_cli.events.middleware import with_event_store, with_error_storage
@with_event_store
@with_error_storage
def move_task(wp_id: str, lane: str, event_store: EventStore, error_storage: ErrorStorage):
current_status = load_wp_status(wp_id)
if not is_valid_transition(current_status, lane):
# Log error event
error_storage.log(
error_type="StateTransitionError",
entity_id=wp_id,
attempted_operation=f"move_task {wp_id} --to {lane}",
reason=f"Cannot transition from '{current_status}' to '{lane}'",
context={
"current_status": current_status,
"requested_status": lane,
"valid_transitions": get_valid_transitions(current_status)
}
)
# Raise error to user
raise ValidationError(
f"Invalid transition: {current_status} → {lane}. "
f"Valid transitions: {get_valid_transitions(current_status)}"
)
# ... rest of logic
Output:
❌ Error: Invalid transition: planned → done
Valid transitions from 'planned': doing
Error logged to .kittify/errors/2026-01-27.jsonl
Error Log:
cat .kittify/errors/2026-01-27.jsonl
{
"error_id": "01HN3R5K8F9876543210FEDCBA",
"error_type": "StateTransitionError",
"entity_id": "WP01",
"attempted_operation": "move_task WP01 --to done",
"reason": "Cannot transition from 'planned' to 'done'",
"timestamp": "2026-01-27T10:45:00Z",
"context": {
"current_status": "planned",
"requested_status": "done",
"valid_transitions": ["doing"]
}
}
Manus Pattern (agent learns from error):
# Future enhancement: Agent reviews error log before operating
def check_past_errors(entity_id: str) -> list[ErrorEvent]:
"""Check if similar errors occurred previously."""
errors = error_storage.read(entity_id=entity_id, error_type="StateTransitionError")
if errors:
print(f"⚠️ Warning: {len(errors)} previous transition errors for {entity_id}")
for error in errors:
print(f" - {error.reason}")
return errors
Example 6: Rebuild Index (Corruption Recovery)
Scenario: SQLite index becomes corrupted or out of sync
Symptoms:
spec-kitty status
# Output: ⚠️ Warning: Index out of sync. Rebuilding...
Manual Rebuild:
# Delete corrupted index
rm .kittify/events/index.db
# Rebuild from JSONL source of truth
spec-kitty agent events rebuild-index
What Happens Internally:
from specify_cli.events.index import EventIndex
def rebuild_index(events_dir: Path):
index = EventIndex(events_dir / "index.db")
# 1. Drop and recreate tables
index._drop_tables()
index._create_tables()
# 2. Read all JSONL files
for jsonl_file in sorted(events_dir.glob("*.jsonl")):
print(f"Processing {jsonl_file.name}...")
for line in jsonl_file.read_text().splitlines():
try:
event = Event.from_json(line)
index.update(event)
except json.JSONDecodeError:
print(f" ⚠️ Skipping invalid JSON line")
continue
print("✓ Index rebuilt successfully")
Output:
Processing 2026-01-25.jsonl... (12 events)
Processing 2026-01-26.jsonl... (27 events)
Processing 2026-01-27.jsonl... (8 events)
✓ Index rebuilt successfully (47 total events)
Integration with Existing Commands
/spec-kitty.specify → Emits SpecCreated
/spec-kitty.specify
# Input: Create event log integration feature
# Event emitted:
{
"event_type": "SpecCreated",
"entity_id": "025-cli-event-log-integration",
"entity_type": "FeatureSpec",
"payload": {
"title": "CLI Event Log Integration",
"mission": "software-dev",
"created_by": "claude-planner"
}
}
/spec-kitty.tasks → Emits WPCreated (multiple)
/spec-kitty.tasks
# Generates WP01, WP02, WP03, ...
# Events emitted (one per WP):
{
"event_type": "WPCreated",
"entity_id": "WP01",
"payload": {
"work_package_id": "WP01",
"title": "Implement EventStore adapter",
"dependencies": []
}
}
/spec-kitty.implement → Emits WorkspaceCreated
spec-kitty implement WP01
# Creates .worktrees/025-cli-event-log-WP01/
# Event emitted:
{
"event_type": "WorkspaceCreated",
"entity_id": "WP01",
"payload": {
"work_package_id": "WP01",
"worktree_path": ".worktrees/025-cli-event-log-integration-WP01",
"branch_name": "025-cli-event-log-integration-WP01"
}
}
Testing Event Integration
Unit Test Example
# tests/events/test_emitter.py
import pytest
from specify_cli.events.store import EventStore
from pathlib import Path
def test_emit_wp_status_changed(tmp_path: Path):
"""Test event emission for WP status change."""
# Setup
event_store = EventStore(tmp_path)
# Act
event = event_store.emit(
event_type="WPStatusChanged",
entity_id="WP01",
entity_type="WorkPackage",
actor="test-agent",
payload={
"feature_slug": "test-feature",
"old_status": "planned",
"new_status": "doing"
}
)
# Assert
assert event.event_id # ULID generated
assert event.lamport_clock == 1 # First event
assert event.event_type == "WPStatusChanged"
assert event.payload["new_status"] == "doing"
# Verify persisted to JSONL
jsonl_files = list((tmp_path / ".kittify" / "events").glob("*.jsonl"))
assert len(jsonl_files) == 1
# Verify indexed
events = event_store.read(entity_id="WP01")
assert len(events) == 1
assert events[0].event_id == event.event_id
Integration Test Example
# tests/integration/test_event_workflow.py
def test_full_workflow_with_events(tmp_path: Path):
"""Test complete workflow: specify → tasks → implement → status."""
# 1. Create spec
run_command(["spec-kitty", "agent", "feature", "setup-spec"])
# 2. Verify SpecCreated event
event_store = EventStore(tmp_path)
events = event_store.read(event_type="SpecCreated")
assert len(events) == 1
# 3. Generate tasks
run_command(["spec-kitty", "agent", "feature", "finalize-tasks"])
# 4. Verify WPCreated events
wp_events = event_store.read(event_type="WPCreated")
assert len(wp_events) > 0
# 5. Move WP status
run_command(["spec-kitty", "agent", "tasks", "move-task", "WP01", "--to", "doing"])
# 6. Verify WPStatusChanged event
status_events = event_store.read(entity_id="WP01", event_type="WPStatusChanged")
assert len(status_events) == 1
assert status_events[0].payload["new_status"] == "doing"
# 7. Check status command reads from events
output = run_command(["spec-kitty", "status"])
assert "WP01" in output
assert "Doing" in output
Performance Monitoring
Measure Event Write Latency
import time
from specify_cli.events.store import EventStore
def benchmark_emit():
event_store = EventStore(Path("."))
start = time.time()
for i in range(100):
event_store.emit(
event_type="WPStatusChanged",
entity_id=f"WP{i:02d}",
entity_type="WorkPackage",
actor="benchmark",
payload={"old_status": "planned", "new_status": "doing"}
)
end = time.time()
avg_latency = (end - start) / 100 * 1000 # Convert to ms
print(f"Average event write latency: {avg_latency:.2f}ms")
# Target: <15ms
Measure Status Reconstruction Time
def benchmark_status_reconstruction():
event_store = EventStore(Path("."))
start = time.time()
events = event_store.read(event_type="WPStatusChanged")
wp_statuses = {}
for event in sorted(events, key=lambda e: e.lamport_clock):
wp_statuses[event.entity_id] = event.payload["new_status"]
end = time.time()
print(f"Status reconstruction time: {(end - start) * 1000:.2f}ms")
print(f"Events processed: {len(events)}")
# Target: <50ms for 100 events
Troubleshooting
Problem: Events not appearing in log
Check:
# Verify .kittify/events/ directory exists
ls -la .kittify/events/
# Check file permissions
ls -l .kittify/events/*.jsonl
Solution: Ensure .kittify/ directory is initialized:
spec-kitty init # Recreates directory structure
Problem: Clock value not incrementing
Check:
# Inspect clock file
cat .kittify/clock.json
# Look for write errors in logs
spec-kitty agent events debug-clock
Solution: Rebuild clock from event log:
rm .kittify/clock.json
spec-kitty agent events rebuild-clock
Problem: Status command shows stale state
Likely Cause: Index out of sync with JSONL
Solution: Rebuild index:
spec-kitty agent events rebuild-index
Problem: File locking errors (Windows)
Symptom: IOError: [Errno 11] Resource temporarily unavailable
Cause: POSIX file locking not available on this platform
Solution: Check WSL availability or disable file locking (development mode only):
# In config
event_store = EventStore(repo_root, use_file_locking=False)
Next Steps
After completing this quickstart:
1. Read data-model.md: Understand entity relationships and schema 2. Review contracts/: Inspect JSON schemas for event validation 3. Explore spec-kitty-events docs: Learn about CRDT merge rules, conflict detection 4. Implement WP integration: Start with WPStatusChanged event emission in commands 5. Write tests: Follow test examples in tests/events/
Additional Resources
- Architecture:
architecture/adrs/2026-01-27-11-dual-repository-pattern.md(Git dependency setup) - Research:
kitty-specs/025-cli-event-log-integration/research.md(design rationale) - Constitution:
.kittify/memory/constitution.md(spec-kitty-events integration requirements) - spec-kitty-events docs: https://github.com/Priivacy-ai/spec-kitty-events/blob/main/README.md
Questions or Issues? Open a GitHub issue or check existing discussions.