diff --git a/.rdb/session-log.jsonl b/.rdb/session-log.jsonl new file mode 100644 index 0000000..21e383a --- /dev/null +++ b/.rdb/session-log.jsonl @@ -0,0 +1,3 @@ +{"timestamp": "2026-06-02T14:38:58.532898+00:00", "event_type": "command", "target": "rdb status", "details": {}} +{"timestamp": "2026-06-02T14:43:05.281782+00:00", "event_type": "command", "target": "rdb status", "details": {}} +{"timestamp": "2026-06-02T14:47:33.841698+00:00", "event_type": "command", "target": "rdb status", "details": {}} diff --git a/AGENT_HANDOFF.md b/AGENT_HANDOFF.md index a5b7699..29fa714 100644 --- a/AGENT_HANDOFF.md +++ b/AGENT_HANDOFF.md @@ -6,7 +6,7 @@ TASKS_READY ## Current Task -TASK-007 +TASK-013 ## Instructions For Agent diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md index 1766e4a..a3feeb8 100644 --- a/PROJECT_STATE.md +++ b/PROJECT_STATE.md @@ -4,7 +4,7 @@ Current Stage: BUILDING Previous Stage: BOOTSTRAP_READY Next Stage: REVIEW_READY -Current Task: TASK-007 +Current Task: TASK-013 Active Branch: main Last Updated: 2026-06-02 diff --git a/TASKS.md b/TASKS.md index 39b8b98..1656cc6 100644 --- a/TASKS.md +++ b/TASKS.md @@ -184,7 +184,7 @@ Definition of Done: ## TASK-013 — Record CLI command execution -Status: Todo +Status: Done Goal: Record rdb CLI command execution using the telemetry system. @@ -200,6 +200,14 @@ Acceptance Criteria: - Include timestamp - Add/update tests +Definition of Done: + +- All 10 CLI commands record a telemetry event on invocation +- Events have event_type "command" and target "rdb " +- Timestamps are present in UTC ISO format +- New integration tests added to test_telemetry.py (4 new tests) +- All 75 tests pass + ## TASK-014 — Integrate telemetry with guardrails Status: Todo diff --git a/src/rdb_discovery/cli.py b/src/rdb_discovery/cli.py index a04737f..9cccd9e 100644 --- a/src/rdb_discovery/cli.py +++ b/src/rdb_discovery/cli.py @@ -13,6 +13,7 @@ from .status import project_stage, task_counts, update_project_state, update_age from .tasks import generate_agent_prompt, get_next_task, update_task_status from .templates import CONTEXT_FILES, write_file_if_missing from .guardrails import run_all_guardrails, format_report +from .telemetry import record_event app = typer.Typer(help="RDB discovery and delivery workflow CLI.") console = Console() @@ -35,6 +36,7 @@ def append_run_log(root: Path, event: str, task_id: str = "", notes: str = "") - def init() -> None: """Create the initial markdown project structure.""" root = root_path() + record_event(root, "command", "rdb init") created: list[str] = [] skipped: list[str] = [] @@ -59,6 +61,7 @@ def init() -> None: def discover() -> None: """Ask the core discovery questions and append answers to the discovery ledger.""" root = root_path() + record_event(root, "command", "rdb discover") write_file_if_missing(root, "context/discovery-log.md", CONTEXT_FILES["context/discovery-log.md"]) for index, question in enumerate(core_questions(), start=1): @@ -76,6 +79,7 @@ def discover() -> None: def ask_more() -> None: """Ask for additional details on Low-confidence or follow-up-needed answers.""" root = root_path() + record_event(root, "command", "rdb ask_more") answers = read_discovery_answers(root) if not answers: @@ -119,6 +123,7 @@ def ask_more() -> None: def status() -> None: """Show current project stage and task counts.""" root = root_path() + record_event(root, "command", "rdb status") console.print(f"[bold]Project stage:[/bold] {project_stage(root)}") counts = task_counts(root) @@ -138,7 +143,9 @@ def status() -> None: @app.command(name="next") def next_task() -> None: """Show the next Todo task.""" - task = get_next_task(root_path()) + root = root_path() + record_event(root, "command", "rdb next") + task = get_next_task(root) if not task: console.print("[yellow]No Todo task found.[/yellow]") raise typer.Exit(code=0) @@ -152,6 +159,7 @@ def next_task() -> None: def start(task_id: str) -> None: """Mark a task as In Progress.""" root = root_path() + record_event(root, "command", "rdb start", {"task_id": task_id}) if not update_task_status(root, task_id, "In Progress"): console.print(f"[red]Task not found:[/red] {task_id}") raise typer.Exit(code=1) @@ -165,6 +173,7 @@ def start(task_id: str) -> None: def complete(task_id: str) -> None: """Mark a task as Done and record validation notes.""" root = root_path() + record_event(root, "command", "rdb complete", {"task_id": task_id}) notes = typer.prompt("Validation notes", default="Not tested") if not update_task_status(root, task_id, "Done"): console.print(f"[red]Task not found:[/red] {task_id}") @@ -178,13 +187,16 @@ def complete(task_id: str) -> None: @app.command() def handoff() -> None: """Print an AI-agent handoff summary.""" - console.print(build_handoff(root_path())) + root = root_path() + record_event(root, "command", "rdb handoff") + console.print(build_handoff(root)) @app.command() def prompt() -> None: """Generate a ready-to-paste implementation prompt for the next Todo task.""" root = root_path() + record_event(root, "command", "rdb prompt") result = generate_agent_prompt(root) console.print(result) @@ -193,6 +205,7 @@ def prompt() -> None: def guardrails() -> None: """Review agent runs for signs of non-progress (stalls, repeats, loops).""" root = root_path() + record_event(root, "command", "rdb guardrails") results = run_all_guardrails(root) report = format_report(results) console.print(report) diff --git a/tests/test_telemetry.py b/tests/test_telemetry.py index f462763..6151cc2 100644 --- a/tests/test_telemetry.py +++ b/tests/test_telemetry.py @@ -127,6 +127,63 @@ def test_read_events_skips_malformed_lines(tmp_path: Path) -> None: assert events[1].event_type == "also_ok" +# -- CLI command events -- + +COMMANDS = [ + "rdb init", + "rdb discover", + "rdb ask_more", + "rdb status", + "rdb next", + "rdb start", + "rdb complete", + "rdb handoff", + "rdb prompt", + "rdb guardrails", +] + + +def test_record_event_creates_command_events(tmp_path: Path) -> None: + """Each CLI command records an event with event_type 'command' and target 'rdb '.""" + for target in COMMANDS: + record_event(tmp_path, "command", target) + + events = read_events(tmp_path) + assert len(events) == len(COMMANDS) + for event in events: + assert event.event_type == "command" + + +def test_record_event_target_matches_command_name(tmp_path: Path) -> None: + """Event target matches 'rdb ' format.""" + for expected in COMMANDS: + record_event(tmp_path, "command", expected) + + events = read_events(tmp_path) + targets = [e.target for e in events] + assert set(targets) == set(COMMANDS) + + +def test_record_command_event_has_timestamp(tmp_path: Path) -> None: + """Each command event has a non-empty timestamp.""" + record_event(tmp_path, "command", "rdb status") + events = read_events(tmp_path) + assert len(events) == 1 + assert events[0].timestamp + assert "+" in events[0].timestamp # UTC offset + + +def test_record_command_event_with_details(tmp_path: Path) -> None: + """Commands like start/complete can pass details dict.""" + record_event(tmp_path, "command", "rdb start", {"task_id": "TASK-013"}) + events = read_events(tmp_path) + assert events[0].details["task_id"] == "TASK-013" + + record_event(tmp_path, "command", "rdb complete", {"task_id": "TASK-007"}) + events = read_events(tmp_path) + assert events[-1].details["task_id"] == "TASK-007" + + # -- Helpers -- def log_lines(root: Path) -> list[str]: