Compare commits
10
Commits
13f5e10f6f
...
9f6933423b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f6933423b | ||
|
|
65dda68dbe | ||
|
|
152096d433 | ||
|
|
c72ad6995e | ||
|
|
a0b28e7d8d | ||
|
|
5bad6ad0f8 | ||
|
|
250cf9316a | ||
|
|
1c2faa3162 | ||
|
|
34f7364c86 | ||
|
|
3ccdea330a |
@@ -15,3 +15,140 @@
|
||||
{"timestamp": "2026-06-02T16:01:01.182387+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:05:14.118862+00:00", "event_type": "command", "target": "rdb complete", "details": {"task_id": "TASK-015"}}
|
||||
{"timestamp": "2026-06-02T16:05:53.807097+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:07:10.365159+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:07:20.003871+00:00", "event_type": "command", "target": "rdb prompt", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:13:04.692285+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:13:54.568414+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:15:17.596114+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:19:12.880619+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:19:17.322970+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:25:21.115719+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:25:26.933534+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:25:36.969679+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:25:40.245143+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:25:42.679647+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:25:52.729189+00:00", "event_type": "command", "target": "rdb prompt", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:27:38.835967+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:27:46.509757+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:28:22.926960+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:28:27.925682+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:29:40.982618+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:29:43.405985+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:29:59.879010+00:00", "event_type": "command", "target": "rdb prompt", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:47:40.958013+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:57:02.286495+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:57:05.774698+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-02T16:57:29.853052+00:00", "event_type": "command", "target": "rdb prompt", "details": {}}
|
||||
{"timestamp": "2026-06-02T17:05:41.754949+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-02T17:07:48.597850+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-02T17:10:43.959746+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-02T17:10:48.601943+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-02T17:10:57.679687+00:00", "event_type": "command", "target": "rdb prompt", "details": {}}
|
||||
{"timestamp": "2026-06-02T17:22:38.052771+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-02T17:35:19.867986+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-03T15:52:18.064218+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-03T15:52:21.772271+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-03T15:59:27.187091+00:00", "event_type": "command", "target": "rdb prompt", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:06:45.302298+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:08:49.404465+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:12:09.213759+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:13:19.590940+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:15:34.075091+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:20:37.309119+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:20:41.625422+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:20:45.764811+00:00", "event_type": "command", "target": "rdb prompt", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:28:07.458337+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:30:17.726863+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:31:32.041267+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:32:48.717496+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:36:14.760622+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:36:14.783409+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:48:59.085912+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:48:59.111197+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:48:59.174102+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:51:06.935561+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:51:06.956310+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:51:06.984767+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:52:21.749548+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:52:21.770919+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:52:21.809957+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:52:21.950628+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:56:58.542853+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:57:26.778515+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:58:40.502126+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:58:40.531803+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:58:40.575979+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:58:40.679715+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-03T17:59:54.816018+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-03T18:02:22.183984+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-04T05:14:08.600178+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-04T05:14:24.061826+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-04T05:14:38.336849+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-04T05:14:49.894695+00:00", "event_type": "command", "target": "rdb prompt", "details": {}}
|
||||
{"timestamp": "2026-06-04T05:19:45.385984+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T05:19:45.410121+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T05:19:45.438892+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T05:19:45.530897+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-04T05:21:43.139977+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-04T05:39:05.966198+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T05:39:05.991663+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T05:39:06.022398+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T05:39:06.155411+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-04T05:51:40.975066+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T05:51:41.000251+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T05:51:41.030414+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T05:51:41.111721+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:06:03.436361+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:27:22.527128+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:27:22.551624+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:27:22.585367+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:27:22.683899+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:33:49.312031+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:33:49.331790+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:33:49.361065+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:33:49.443458+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:38:18.966356+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:38:18.991294+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:38:19.019668+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:38:19.108828+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:38:19.717117+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:42:57.711062+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:43:06.047568+00:00", "event_type": "command", "target": "rdb prompt", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:44:15.981811+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:46:32.334206+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:46:32.354546+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:46:32.384086+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T06:46:32.469898+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-04T07:55:17.697713+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-04T08:04:05.989316+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-04T08:05:02.054038+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T08:05:02.078565+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T08:05:02.105459+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T08:05:02.195107+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-04T08:07:55.244668+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-04T08:12:44.332202+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T08:12:44.356488+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T08:12:44.386225+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T08:12:44.484757+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-04T08:24:16.640627+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-04T08:41:45.929837+00:00", "event_type": "command", "target": "rdb complete", "details": {"task_id": "TASK-023"}}
|
||||
{"timestamp": "2026-06-04T08:45:33.415489+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-04T08:46:14.771099+00:00", "event_type": "command", "target": "rdb prompt", "details": {}}
|
||||
{"timestamp": "2026-06-04T09:15:26.855951+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-04T09:16:03.623379+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T09:16:03.649310+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T09:16:03.678552+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T09:16:03.768573+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-04T10:58:23.669753+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T10:58:23.696327+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T10:58:23.726939+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T10:58:23.821232+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
{"timestamp": "2026-06-04T11:15:13.529302+00:00", "event_type": "command", "target": "rdb status", "details": {}}
|
||||
{"timestamp": "2026-06-04T11:15:20.344313+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-04T11:23:06.251069+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-04T11:23:20.288400+00:00", "event_type": "command", "target": "rdb next", "details": {}}
|
||||
{"timestamp": "2026-06-04T11:23:25.065294+00:00", "event_type": "command", "target": "rdb prompt", "details": {}}
|
||||
{"timestamp": "2026-06-04T11:44:23.218539+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T11:44:23.245046+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T11:44:23.277672+00:00", "event_type": "command", "target": "rdb context-status", "details": {}}
|
||||
{"timestamp": "2026-06-04T11:44:23.373376+00:00", "event_type": "command", "target": "rdb generate", "details": {}}
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ TASKS_READY
|
||||
|
||||
## Current Task
|
||||
|
||||
TASK-015
|
||||
TASK-023
|
||||
|
||||
## Instructions For Agent
|
||||
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ Current Stage: BUILDING
|
||||
Previous Stage: BOOTSTRAP_READY
|
||||
Next Stage: REVIEW_READY
|
||||
|
||||
Current Task: TASK-015
|
||||
Current Task: TASK-025
|
||||
Active Branch: main
|
||||
|
||||
Last Updated: 2026-06-02
|
||||
Last Updated: 2026-06-04
|
||||
|
||||
@@ -41,6 +41,7 @@ rdb init # Create standard project structure and context files
|
||||
rdb discover # Ask 10 core discovery questions, record answers
|
||||
rdb guardrails # Check for agent stalls and state inconsistencies
|
||||
rdb prompt # Generate implementation prompt for the next task
|
||||
rdb context-status # Report health and completeness of project context
|
||||
rdb start TASK-001 # Mark a task as in progress
|
||||
rdb complete TASK-001# Mark a task as done
|
||||
rdb status # Show current stage and active task
|
||||
|
||||
@@ -13,3 +13,4 @@ TASK-002 marked Done — agent prompt generation complete.
|
||||
| 2026-06-02T12:51:20 | Task completed | TASK-007 | enhanced prompts |
|
||||
| 2026-06-02T13:45:00 | Task completed | TASK-012 | telemetry foundation — writer, JSONL storage, event reader, 12 tests passed |
|
||||
| 2026-06-02T17:05:41 | Task completed | TASK-015 | updated README.md |
|
||||
| 2026-06-04T09:41:51 | Task completed | TASK-023 | updated |
|
||||
|
||||
@@ -260,7 +260,7 @@ Acceptance Criteria:
|
||||
|
||||
## TASK-016 — Define standard context file templates
|
||||
|
||||
Status: Todo
|
||||
Status: Done
|
||||
|
||||
Goal:
|
||||
Define the standard context files that rdb-discovery should help generate.
|
||||
@@ -282,39 +282,509 @@ Acceptance Criteria:
|
||||
- Keep templates simple markdown
|
||||
- Add/update tests
|
||||
|
||||
## TASK-017 — Generate context files from discovery answers
|
||||
## TASK-017 — Support task roles in generated prompts
|
||||
|
||||
Status: Done
|
||||
|
||||
Goal:
|
||||
Allow tasks to define the agent role used by `rdb prompt`.
|
||||
|
||||
Implementation Gap:
|
||||
|
||||
Tasks can now include a `Role:` field, but `rdb prompt` still always generates prompts beginning with `You are an implementation agent.`
|
||||
|
||||
Acceptance Criteria:
|
||||
|
||||
- Parse optional `Role:` field from task markdown
|
||||
- If `Role:` exists, use it in the generated prompt opening
|
||||
- If `Role:` is missing, default to `Implementation Agent`
|
||||
- Add/update tests
|
||||
- Do not change task execution behaviour
|
||||
|
||||
Definition of Done:
|
||||
|
||||
- `Role: Architecture Agent` generates `You are an architecture agent.`
|
||||
- Tasks without a role still generate implementation prompts
|
||||
- Tests pass
|
||||
|
||||
## TASK-018 — Role-based context selection
|
||||
|
||||
Status: Done
|
||||
|
||||
Role: Implementation Agent
|
||||
|
||||
Goal:
|
||||
|
||||
Allow `rdb prompt` to select different context files based on task role.
|
||||
|
||||
Implementation Gap:
|
||||
|
||||
Tasks can now define a role, but every generated prompt still asks the agent to read the same set of files regardless of task type.
|
||||
|
||||
Acceptance Criteria:
|
||||
|
||||
- Architecture Agent receives architecture-focused context
|
||||
- Implementation Agent receives implementation-focused context
|
||||
- Documentation Agent receives documentation-focused context
|
||||
- If no role exists, use current default file list
|
||||
- Add/update tests
|
||||
|
||||
Definition of Done:
|
||||
|
||||
- Context files differ by role
|
||||
- Existing prompts remain backward compatible
|
||||
- Tests pass
|
||||
|
||||
## TASK-019 — Define discovery-to-context mappings
|
||||
|
||||
Status: Done
|
||||
|
||||
Role: Architecture Agent
|
||||
|
||||
Goal:
|
||||
|
||||
Define how discovery answers should be transformed into project context files.
|
||||
|
||||
Implementation Gap:
|
||||
|
||||
Discovery answers are collected and stored, but there is no documented mapping between discovery questions and the context files they should populate.
|
||||
|
||||
Acceptance Criteria:
|
||||
|
||||
- Every discovery question maps to one or more context files
|
||||
- Every discovery question maps to a specific section within those files
|
||||
- Mapping is documented in markdown
|
||||
- Mapping is understandable by future agents
|
||||
- No context generation implementation yet
|
||||
- Add/update tests if required
|
||||
|
||||
Definition of Done:
|
||||
|
||||
- Mapping document exists
|
||||
- Mapping covers all discovery questions
|
||||
- Future implementation work is clearly defined
|
||||
|
||||
Result
|
||||
|
||||
Created `context/discovery-context-mapping.md` which documents:
|
||||
|
||||
- Reference table of all 10 core discovery questions (Q-001 through Q-010)
|
||||
- Reference table of all target context files and their purpose
|
||||
- Detailed mapping for each question to primary and secondary context files with specific section guidance
|
||||
- Summary question-to-file matrix for quick reference
|
||||
- Implementation notes defining how future code should read discovery answers and populate context files
|
||||
- Constraints for future implementation (preserve existing content, skip low-confidence answers)
|
||||
- Test requirements for when code is eventually written
|
||||
|
||||
Added `tests/test_discovery_mapping.py` with 6 tests validating:
|
||||
|
||||
- Mapping file existence
|
||||
- All 10 core questions are present
|
||||
- Table-format documentation section exists
|
||||
- Summary matrix section exists
|
||||
- Future implementation notes exist (and clarify no code has been implemented yet)
|
||||
- All context files from templates.py are referenced in the mapping
|
||||
|
||||
No context generation implementation was added — this task is a planning artifact only.
|
||||
|
||||
## TASK-020 — Generate context files from discovery mappings
|
||||
|
||||
Status: Done
|
||||
|
||||
Role: Implementation Agent
|
||||
|
||||
Goal:
|
||||
|
||||
Generate context file content using the approved
|
||||
discovery-context-mapping.md document.
|
||||
|
||||
Implementation Gap:
|
||||
|
||||
Mappings now exist, but discovery answers are not yet transformed into context file content.
|
||||
|
||||
Acceptance Criteria:
|
||||
|
||||
- Read discovery-context-mapping.md ✓
|
||||
- Read discovery-log.md ✓
|
||||
- Populate mapped sections in context files ✓
|
||||
- Create missing context files safely ✓
|
||||
- Do not overwrite existing content ✓
|
||||
- Skip low-confidence answers ✓
|
||||
- Add/update tests ✓
|
||||
|
||||
Definition of Done:
|
||||
|
||||
- Discovery answers appear in the correct context files ✓
|
||||
- Existing content is preserved ✓
|
||||
- Tests pass ✓ (114 tests, all passing)
|
||||
|
||||
Result
|
||||
|
||||
Created `src/rdb_discovery/generate_context.py` module with:
|
||||
|
||||
- `CONTEXT_MAP`: Rules mapping each of the 10 discovery questions to target context files and sections (body-fill, table-row, or append-new-section strategies)
|
||||
- `generate_context_files(root, min_confidence)`: Main entry point that reads discovery answers, filters by confidence, applies mapping rules, and writes/updates context files safely
|
||||
- Three write strategies: TBD-replacement for empty sections, content-appending for existing body-text sections, table-row insertion for risks.md and assumptions.md, and new-section appending when headers don't exist yet
|
||||
|
||||
Added `rdb generate` CLI command (accepts `--min-confidence` option).
|
||||
|
||||
Fixed a parsing bug in `discovery.py`: escaped pipe characters (`\|`) in discovery answers were creating spurious extra columns during markdown table splitting — now handled with placeholder-based escaping.
|
||||
|
||||
Added `tests/test_generate_context.py` with 15 tests:
|
||||
|
||||
- Mapping completeness (all 10 questions, all target files)
|
||||
- Confidence filtering (Low → skipped, High/Medium → generated)
|
||||
- Body text filling (TBD replacement, existing content append)
|
||||
- Table row generation (risks.md and assumptions.md formats)
|
||||
- Content preservation verification
|
||||
- CLI command availability and error handling
|
||||
- Missing file creation safety
|
||||
- End-to-end integration flow
|
||||
|
||||
## TASK-021 — Context health report
|
||||
|
||||
Status: Done
|
||||
|
||||
Role: Implementation Agent
|
||||
|
||||
Goal:
|
||||
|
||||
Provide a single command that reports the health and completeness of project context.
|
||||
|
||||
Implementation Gap:
|
||||
|
||||
Context files can now be generated, but there is no way to assess whether sufficient context exists for effective AI-assisted development.
|
||||
|
||||
Acceptance Criteria:
|
||||
|
||||
- Add `rdb context-status` ✓
|
||||
- Report expected context files ✓
|
||||
- Report missing context files ✓
|
||||
- Report sections still containing TBD placeholders ✓
|
||||
- Report low-confidence discovery answers ✓
|
||||
- Display an overall health score ✓
|
||||
- Add/update tests ✓ (26 tests)
|
||||
|
||||
Definition of Done:
|
||||
|
||||
- Command runs successfully ✓
|
||||
- Missing context is reported clearly ✓
|
||||
- Health score is displayed ✓
|
||||
- Tests pass ✓ (140 total, all passing)
|
||||
|
||||
Result
|
||||
|
||||
Created `src/rdb_discovery/context_status.py` module with:
|
||||
|
||||
- `_expected_files()` — returns the 18 expected context file paths
|
||||
- `_check_expected_files()` — checks each file for existence and size
|
||||
- `_check_tbd_sections()` — scans context files for TBD/TDB placeholders (skips blank lines)
|
||||
- `_check_low_confidence()` — reads discovery-log.md for low-confidence answers
|
||||
- `compute_health_score()` — 0-100 score with weighted breakdown (45 pts file completeness, 30 pts no TBDs, 15 pts no low-conf, 10 pts discovery data)
|
||||
- `context_status()` — orchestrates all checks and returns structured report
|
||||
|
||||
Added `rdb context-status` CLI command with:
|
||||
|
||||
- Rich table of expected files with presence/absence indicators
|
||||
- TBD placeholder listing with file, section, and line number
|
||||
- Low-confidence discovery answer listing with ID and confidence level
|
||||
- Color-coded health score (green ≥ 70, yellow ≥ 40, red < 40)
|
||||
- Summary line showing counts
|
||||
|
||||
Added `tests/test_context_status.py` with 26 tests across 4 classes:
|
||||
|
||||
- TestExpectedFiles — file existence detection
|
||||
- TestTbdDetection — TBD/TDB placeholder scanning
|
||||
- TestLowConfidence — low-confidence answer detection
|
||||
- TestHealthScore — score computation and degradation
|
||||
- TestContextStatus — structured report verification
|
||||
- TestCLICommand — CLI registration, output, and edge cases
|
||||
- TestHealthScoreColor — score boundary validation
|
||||
|
||||
All 140 tests pass.
|
||||
|
||||
## TASK-022 — Expand discovery coverage
|
||||
|
||||
Status: Done
|
||||
|
||||
Role: Architecture Agent
|
||||
|
||||
Goal:
|
||||
Collect enough information to populate all standard context files.
|
||||
|
||||
Implementation Gap:
|
||||
Several context files remain mostly placeholders because discovery questions do not collect the information required to populate them.
|
||||
|
||||
Acceptance Criteria:
|
||||
|
||||
- Review all context templates ✓
|
||||
- Identify unmapped sections ✓
|
||||
- Add additional discovery questions where required ✓
|
||||
- Update discovery-to-context mapping ✓
|
||||
- Add/update tests ✓
|
||||
|
||||
Definition of Done:
|
||||
|
||||
- Every major template section has a discovery source ✓
|
||||
- Discovery-to-context mapping updated ✓
|
||||
- Tests pass ✓ (140, all passing)
|
||||
|
||||
Result
|
||||
|
||||
Added 4 new grouped discovery questions (Q-011 through Q-014) covering all major unmapped sections:
|
||||
|
||||
| Question ID | Category | Target Sections |
|
||||
| ----------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
||||
| Q-011 | Organisation/project ownership | company-context.md Mission, project-brief.md Target Audience |
|
||||
| Q-012 | Technology stack and repository structure | development-context.md Tech Stack, Coding Standards; architecture.md Overview, Core Components |
|
||||
| Q-013 | Infrastructure/deployment/security | infrastructure-context.md Hosting, Environments, Monitoring & Alerting, Security |
|
||||
| Q-014 | Agent/developer workflow | agent-guidelines.md Purpose and Preferred Tools; repository-context.md Purpose and Contributing |
|
||||
|
||||
Coverage expanded from 16 section targets (10 questions) to 28 section targets (14 questions).
|
||||
|
||||
Files modified:
|
||||
|
||||
- `src/rdb_discovery/templates.py` — Added 4 new questions to CORE_QUESTIONS
|
||||
- `src/rdb_discovery/generate_context.py` — Added Q-011 through Q-014 mappings; updated all_questions list in \_write_table_row
|
||||
- `context/discovery-context-mapping.md` — Added 4 new question entries, detailed mapping tables, and summary matrix rows
|
||||
- `tests/test_generate_context.py` — Updated question count assertions to use dynamic CORE_QUESTIONS
|
||||
- `tests/test_discovery_mapping.py` — Renamed test to match dynamic question count
|
||||
- `tests/test_discovery.py` — Updated assertion from exact 10 to >= 10
|
||||
|
||||
## TASK-023 — Improve generated context quality
|
||||
|
||||
Status: Done
|
||||
|
||||
Role: Implementation Agent
|
||||
|
||||
Goal:
|
||||
Improve the quality and usefulness of generated context files after `rdb generate`.
|
||||
|
||||
Implementation Gap:
|
||||
`rdb generate` now writes discovery answers into some context files, but several useful context files remain mostly placeholders and some answers are mapped to weak or incorrect sections.
|
||||
|
||||
Acceptance Criteria:
|
||||
|
||||
- Populate `company-context.md` when discovery answers include users, product purpose, or stakeholders
|
||||
- Populate `agent-guidelines.md` when answers include tools, constraints, risks, or testing preferences
|
||||
- Populate `repository-context.md` when answers include project purpose, dependencies, or contribution/testing approach
|
||||
- Do not map testing answers into timeline/milestone sections
|
||||
- Fix `TDB` placeholder typos to `TBD`
|
||||
- Preserve existing non-placeholder content
|
||||
- Add/update regression tests using the current sample discovery-log data
|
||||
|
||||
Definition of Done:
|
||||
|
||||
- Running `rdb generate` on the sample discovery log produces useful content in more than just product/project brief files
|
||||
- Context files contain fewer irrelevant placeholders
|
||||
- Tests pass
|
||||
|
||||
## TASK-024 — Add objectivity and efficiency guidelines
|
||||
|
||||
Status: Done
|
||||
|
||||
Role: Documentation Agent
|
||||
|
||||
Goal:
|
||||
Reduce token waste and unnecessary agreement in agent interactions.
|
||||
|
||||
Implementation Gap:
|
||||
Agent guidance does not yet explicitly discourage praise, repeated context reading, task restatement, or unnecessary narrative output.
|
||||
|
||||
Acceptance Criteria:
|
||||
|
||||
- Update `context/agent-guidelines.md`
|
||||
- Add objectivity guidance
|
||||
- Add context-discipline guidance
|
||||
- Add communication-efficiency guidance
|
||||
- Add execution-efficiency guidance
|
||||
- Keep guidance concise and role-neutral
|
||||
- Add/update tests only if required
|
||||
|
||||
Definition of Done:
|
||||
|
||||
- `context/agent-guidelines.md` includes objectivity and efficiency guidance
|
||||
- Guidance helps reduce token usage without preventing useful technical reasoning
|
||||
- Existing workflow remains unchanged
|
||||
|
||||
## TASK-025 — Define discovery gap analysis model
|
||||
|
||||
Status: Done
|
||||
|
||||
Role: Architecture Agent
|
||||
|
||||
Goal:
|
||||
Define how missing or weak project context should be detected and converted into targeted follow-up discovery questions.
|
||||
|
||||
Acceptance Criteria:
|
||||
|
||||
- Define gap detection rules
|
||||
- Define gap severity levels
|
||||
- Define question generation rules
|
||||
- Define question prioritisation rules
|
||||
- Define mapping between missing context sections and follow-up questions
|
||||
- Document the model
|
||||
- Define the follow-on implementation task
|
||||
- No implementation
|
||||
|
||||
Definition of Done:
|
||||
|
||||
- Gap analysis model documented
|
||||
- Follow-up question model documented
|
||||
- Example gap-to-question mappings included
|
||||
- TASK-026 implementation task defined
|
||||
|
||||
Result
|
||||
|
||||
Created `context/gap-analysis-model.md` (320 lines) as a planning artifact documenting:
|
||||
|
||||
1. **Gap detection rules** — 4 rule types (GAP-01 through GAP-04): TBD placeholders, unmapped sections, partial content, near-empty files
|
||||
2. **Severity levels** — S1 (Critical), S2 (Important), S3 (Low) with assignment criteria per stage and mapping status
|
||||
3. **Question generation rules** — 6 rules (QG-01 through QG-06): one-gap-per-question, answerable format, section reference, table-column specificity, skip-filled sections, owner-answer alignment
|
||||
4. **Question prioritisation** — Ordered by severity → file completeness → dependency order → question scope
|
||||
5. **Gap-to-question mapping table** — 40+ section-level mappings across all 16 template context files (excludes CLI-managed project-level files)
|
||||
6. **End-to-end example flow** — Step-by-step walkthrough from detection through prioritisation to follow-up generation to regeneration
|
||||
7. **Edge cases** — Partial sections, acceptable TBDs, new templates, multi-question sections, heading-only files
|
||||
8. **TASK-026 implementation scope** — CLI command spec (`rdb gap`), module structure, test requirements (~45 tests across 6 categories)
|
||||
|
||||
## TASK-026 — Implement discovery gap analysis
|
||||
|
||||
Status: Todo
|
||||
|
||||
Role: Implementation Agent
|
||||
|
||||
Goal:
|
||||
Implement the approved discovery gap analysis model.
|
||||
|
||||
Acceptance Criteria:
|
||||
|
||||
- Analyse generated context files
|
||||
- Detect missing sections
|
||||
- Generate follow-up questions
|
||||
- Link questions to missing context sections
|
||||
- Add/update tests
|
||||
|
||||
## TASK-027 — Context-driven follow-up questions
|
||||
|
||||
Status: Todo
|
||||
|
||||
Role: Implementation Agent
|
||||
|
||||
Goal:
|
||||
Generate follow-up discovery questions based on previous answers.
|
||||
|
||||
Implementation Gap:
|
||||
Discovery questions are currently static and do not adapt based on project-specific answers.
|
||||
|
||||
Acceptance Criteria:
|
||||
|
||||
- Follow-up questions are generated from discovery answers
|
||||
- Different answers produce different follow-up questions
|
||||
- Rules are deterministic and testable
|
||||
- Add/update tests
|
||||
|
||||
Definition of Done:
|
||||
|
||||
- Discovery flow becomes adaptive
|
||||
- Follow-up questions are linked to discovery answers
|
||||
- Tests pass
|
||||
|
||||
## TASK-028 — Improve discovery answer reliability UX
|
||||
|
||||
Status: Todo
|
||||
|
||||
Role: Architecture Agent
|
||||
|
||||
Goal:
|
||||
Make answer reliability easier for users to understand during discovery.
|
||||
|
||||
Implementation Gap:
|
||||
Users are asked to provide a confidence level, but confidence is an internal implementation concept and may not reflect how users think about the reliability of information.
|
||||
|
||||
Acceptance Criteria:
|
||||
|
||||
- Review confidence collection workflow
|
||||
- Define a more intuitive reliability model
|
||||
- Maintain compatibility with existing confidence filtering
|
||||
- Update discovery-to-context documentation
|
||||
- Add/update tests if required
|
||||
|
||||
Definition of Done:
|
||||
|
||||
- Reliability terminology is documented
|
||||
- Mapping to internal confidence levels is defined
|
||||
- Future implementation work is clearly specified
|
||||
|
||||
## TASK-029 — Capture AI tool targets
|
||||
|
||||
Status: Todo
|
||||
|
||||
Role: Architecture Agent
|
||||
|
||||
Goal:
|
||||
Allow discovery to capture which AI development tools will consume generated project context.
|
||||
|
||||
Implementation Gap:
|
||||
Generated context is currently tool-agnostic, but different tools require different guidance files and configuration formats.
|
||||
|
||||
Acceptance Criteria:
|
||||
|
||||
- Define supported tool categories:
|
||||
- Claude Code
|
||||
- Cline
|
||||
- Cursor
|
||||
- ChatGPT
|
||||
- GitHub Copilot
|
||||
- Local LLMs
|
||||
- Other
|
||||
- Add discovery question for AI tool usage
|
||||
- Update discovery-to-context mapping
|
||||
- Define which generated files are generic
|
||||
- Define which generated files are tool-specific
|
||||
- Do not implement tool-specific generation yet
|
||||
|
||||
Definition of Done:
|
||||
|
||||
- Tool model documented
|
||||
- Discovery captures tool preferences
|
||||
- Future generation requirements are defined
|
||||
|
||||
## TASK-040 — Generate Claude Code permission profile
|
||||
|
||||
Status: Todo
|
||||
|
||||
Role: Documentation Agent
|
||||
|
||||
Goal:
|
||||
|
||||
Create a recommended Claude Code permissions configuration for RDB workflows.
|
||||
|
||||
Implementation Gap:
|
||||
|
||||
Claude Code permission settings are not currently documented, resulting in unnecessary approval prompts and inconsistent behaviour between projects.
|
||||
|
||||
Acceptance Criteria:
|
||||
|
||||
- Define recommended safe commands for auto-approval
|
||||
- Define commands that should require approval
|
||||
- Document the profile in CLAUDE.md
|
||||
- Include rationale for each category
|
||||
|
||||
Definition of Done:
|
||||
|
||||
- Permission profile documented
|
||||
- CLAUDE.md updated
|
||||
|
||||
## TASK-050 — AI-assisted discovery questioning
|
||||
|
||||
Status: Todo
|
||||
|
||||
Goal:
|
||||
Use captured discovery answers to generate initial context files.
|
||||
|
||||
Implementation Gap:
|
||||
|
||||
Discovery questions can collect information, but the answers are not yet transformed into structured project context documents.
|
||||
Use an LLM to propose the most valuable next discovery questions.
|
||||
|
||||
Acceptance Criteria:
|
||||
|
||||
- Add command or function to generate context files from discovery data
|
||||
- Create missing context files safely
|
||||
- Do not overwrite existing files without explicit behaviour
|
||||
- Add/update tests
|
||||
|
||||
## TASK-018 — Add context completeness report
|
||||
|
||||
Status: Todo
|
||||
|
||||
Goal:
|
||||
Report which expected context files exist and which sections still need information.
|
||||
|
||||
Implementation Gap:
|
||||
|
||||
There is no way to see whether a project has enough context for an AI agent to work effectively.
|
||||
|
||||
Acceptance Criteria:
|
||||
|
||||
- Add a context status/check command
|
||||
- Report present context files
|
||||
- Report missing context files
|
||||
- Report obvious placeholder sections still needing completion
|
||||
- Add/update tests
|
||||
- Review existing context
|
||||
- Review discovery history
|
||||
- Suggest follow-up questions
|
||||
- Keep human approval in the loop
|
||||
|
||||
@@ -23,6 +23,40 @@ Agents should favour many small successful changes over large risky changes.
|
||||
|
||||
---
|
||||
|
||||
# Objectivity
|
||||
|
||||
- Do not praise, flatter, or agree unnecessarily.
|
||||
- Do not repeat information the user already knows.
|
||||
- State problems directly without hedging or softening.
|
||||
- Provide factual assessments; separate observation from opinion.
|
||||
|
||||
---
|
||||
|
||||
# Context Discipline
|
||||
|
||||
- Read each file at most once unless its content changed between reads.
|
||||
- Do not re-read context files that were read earlier in the same session unless explicitly asked.
|
||||
- Track which files you have already read in your working notes rather than reloading them.
|
||||
|
||||
---
|
||||
|
||||
# Communication Efficiency
|
||||
|
||||
- Do not restate the task or requirements before acting.
|
||||
- Skip summarising instructions the user has already given.
|
||||
- Provide results, not process descriptions, unless asked for a rationale.
|
||||
- Use concise technical language; avoid filler phrases ("sure", "great", "I can help with that").
|
||||
|
||||
---
|
||||
|
||||
# Execution Efficiency
|
||||
|
||||
- Act on the task immediately after understanding it.
|
||||
- Avoid unnecessary planning or confirmation steps when the next action is clear.
|
||||
- Report only what changed, not everything you considered.
|
||||
|
||||
---
|
||||
|
||||
# Before Starting Work
|
||||
|
||||
Agents should first read:
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
# Discovery-to-Context Mapping
|
||||
|
||||
This document defines how answers to the 14 core discovery questions should be transformed into project context files.
|
||||
|
||||
## Reference: Core Discovery Questions
|
||||
|
||||
| ID | Question |
|
||||
|---|---|
|
||||
| Q-001 | What problem are we solving? |
|
||||
| Q-002 | Who is the user? |
|
||||
| Q-003 | What does success look like? |
|
||||
| Q-004 | What is the minimum useful version? |
|
||||
| Q-005 | What data do we need? |
|
||||
| Q-006 | What systems must it connect to? |
|
||||
| Q-007 | What are the risks? |
|
||||
| Q-008 | What must not happen? |
|
||||
| Q-009 | How will we test it? |
|
||||
| Q-010 | How will it be deployed? |
|
||||
| Q-011 | Who owns and directs the project, and what are its organisational goals? |
|
||||
| Q-012 | What is the technology stack, repository structure, and architecture approach? |
|
||||
| Q-013 | Where is it hosted, which environments exist, and what are the security requirements? |
|
||||
| Q-014 | What agent and developer workflow tools, constraints, and guidelines apply? |
|
||||
|
||||
## Reference: Target Context Files
|
||||
|
||||
All paths are relative to the project root.
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `context/discovery-log.md` | Source of discovery answers — not a target, but the input to mapping |
|
||||
| `context/product-brief.md` | Product vision, users, success criteria, MVP scope |
|
||||
| `context/project-brief.md` | Detailed project brief with problem statement and timeline |
|
||||
| `context/architecture.md` | System design, components, data flow, integrations, deployment |
|
||||
| `context/decisions.md` | Architecture and design decision records (ADR-style table) |
|
||||
| `context/risks.md` | Risk register with impact and mitigation columns |
|
||||
| `context/assumptions.md` | Project assumptions with confidence and validation status |
|
||||
| `context/open-questions.md` | Open questions needing further investigation |
|
||||
| `context/development-context.md` | Tech stack, coding standards, build/test instructions |
|
||||
| `context/infrastructure-context.md` | Hosting, environments, CI/CD, monitoring, security |
|
||||
| `context/agent-guidelines.md` | Agent operating rules for this project |
|
||||
| `context/company-context.md` | Company goals and constraints that shape the project |
|
||||
| `context/repository-context.md` | Repository structure, workflow, and contributing info |
|
||||
| `TEST_PLAN.md` | Manual and automated test plan |
|
||||
|
||||
## Mapping: Question → Context Files + Sections
|
||||
|
||||
### Q-001 — What problem are we solving?
|
||||
|
||||
Maps to:
|
||||
|
||||
| Context File | Section | Guidance |
|
||||
|---|---|---|
|
||||
| `context/product-brief.md` | `## Problem` | The raw answer fills in the product problem statement. If the answer is vague, split it into customer pain and desired outcome. |
|
||||
| `context/project-brief.md` | `## Problem Statement` | Restate in project-specific terms. Include scope boundaries if mentioned. |
|
||||
| `context/company-context.md` | (append to Goals) | Link back to company-level goals if the answer references them. |
|
||||
|
||||
### Q-002 — Who is the user?
|
||||
|
||||
Maps to:
|
||||
|
||||
| Context File | Section | Guidance |
|
||||
|---|---|---|
|
||||
| `context/product-brief.md` | `## Users` | Record user personas, roles, or segments. Use bullet points for each distinct user type. |
|
||||
| `context/company-context.md` | `## Customers / Users` | If the answer identifies external customers rather than internal users, append to this section. |
|
||||
| `context/development-context.md` | `## IDEs and Editors` | Note any tooling constraints specific to the target users (if they are developers). |
|
||||
|
||||
### Q-003 — What does success look like?
|
||||
|
||||
Maps to:
|
||||
|
||||
| Context File | Section | Guidance |
|
||||
|---|---|---|
|
||||
| `context/product-brief.md` | `## Success Criteria` | Record measurable success criteria as a numbered or bulleted list. Each criterion should be verifiable. |
|
||||
| `context/project-brief.md` | `## Success Metrics` | Restate with specific measurement approach (e.g., user count, latency threshold, coverage %). |
|
||||
|
||||
### Q-004 — What is the minimum useful version?
|
||||
|
||||
Maps to:
|
||||
|
||||
| Context File | Section | Guidance |
|
||||
|---|---|---|
|
||||
| `context/product-brief.md` | `## Minimum Useful Version` | List MVP features as bullet points. Each feature should be independently valuable. |
|
||||
| `context/project-brief.md` | `## Key Features (MVP)` | Expand with brief descriptions for each MVP feature. |
|
||||
| `TASKS.md` | New TASK entries | Consider generating initial task rows from MVP features. |
|
||||
|
||||
### Q-005 — What data do we need?
|
||||
|
||||
Maps to:
|
||||
|
||||
| Context File | Section | Guidance |
|
||||
|---|---|---|
|
||||
| `context/architecture.md` | `## Data Flow` | Describe data entities, storage locations, and movement between components. |
|
||||
| `context/development-context.md` | `## Dependencies` | Record database platforms, storage services, or data stores required. |
|
||||
| `context/infrastructure-context.md` | `## Environments` | Note any data requirements per environment (dev/staging/prod data parity). |
|
||||
|
||||
### Q-006 — What systems must it connect to?
|
||||
|
||||
Maps to:
|
||||
|
||||
| Context File | Section | Guidance |
|
||||
|---|---|---|
|
||||
| `context/architecture.md` | `## External Integrations` | List each external system with its role (API, database, auth provider, etc.). |
|
||||
| `context/infrastructure-context.md` | Append as section | If the integration requires infrastructure changes, document there. |
|
||||
| `context/decisions.md` | New row | If integration choices involved trade-offs, record as a decision. |
|
||||
|
||||
### Q-007 — What are the risks?
|
||||
|
||||
Maps to:
|
||||
|
||||
| Context File | Section | Guidance |
|
||||
|---|---|---|
|
||||
| `context/risks.md` | Data rows | Each risk becomes a row with columns: ID, Risk Description, Impact (High/Med/Low), Mitigation, Status. Assign sequential IDs like RISK-001. |
|
||||
| `context/open-questions.md` | New rows | Any unresolved aspects of a risk become open questions. |
|
||||
|
||||
### Q-008 — What must not happen?
|
||||
|
||||
Maps to:
|
||||
|
||||
| Context File | Section | Guidance |
|
||||
|---|---|---|
|
||||
| `context/assumptions.md` | Data rows | Constraints that are actually assumptions about what won't be done. Record confidence and validation need. |
|
||||
| `context/decisions.md` | New row | If a "must not" is the result of an active decision (e.g., "no third-party auth"), record as a decision with reasoning. |
|
||||
| `context/open-questions.md` | New rows | Any constraint that needs clarification becomes an open question. |
|
||||
|
||||
### Q-009 — How will we test it?
|
||||
|
||||
Maps to:
|
||||
|
||||
| Context File | Section | Guidance |
|
||||
|---|---|---|
|
||||
| `context/development-context.md` | `## Build & Test` | Document test framework, test execution commands, and local setup steps. |
|
||||
| `context/agent-guidelines.md` | `## Testing Preferences` (append) | Add a new section describing how tests should be written, run, and verified. Do **not** map testing answers into Timeline / Milestones sections in other files. |
|
||||
|
||||
### Q-010 — How will it be deployed?
|
||||
|
||||
Maps to:
|
||||
|
||||
| Context File | Section | Guidance |
|
||||
|---|---|---|
|
||||
| `context/architecture.md` | `## Deployment Architecture` | Describe the deployment layout, scaling strategy, and process. |
|
||||
| `context/infrastructure-context.md` | `## CI / CD Pipeline`, `## Hosting`, `## Environments` | Fill in hosting platform, environment details, and pipeline steps from the discovery answer. |
|
||||
|
||||
### Q-011 — Who owns and directs the project, and what are its organisational goals?
|
||||
|
||||
Maps to:
|
||||
|
||||
| Context File | Section | Guidance |
|
||||
|---|---|---|
|
||||
| `context/company-context.md` | `## Mission` | Record the organisation's core mission, vision, or strategic goals that relate to this project. If no company-level mission exists, record the team's purpose. |
|
||||
| `context/company-context.md` | `## Products & Services` (append) | Add a new section describing the company's products and services if the answer includes them. |
|
||||
| `context/project-brief.md` | `## Target Audience` | Restate in project-specific terms — who will use or benefit from the final product? |
|
||||
|
||||
### Q-012 — What is the technology stack, repository structure, and architecture approach?
|
||||
|
||||
Maps to:
|
||||
|
||||
| Context File | Section | Guidance |
|
||||
|---|---|---|
|
||||
| `context/development-context.md` | `## Tech Stack` | Extract languages, frameworks, libraries, tooling, and build/test tools. Record each as a distinct item. |
|
||||
| `context/development-context.md` | `## Coding Standards` | Note any style conventions, naming patterns, linters, formatters, or agreed-upon patterns. If none exist, note "TBD". |
|
||||
| `context/repository-context.md` | `## Dependencies` (append) | Add a new section describing project dependencies if not yet documented. |
|
||||
| `context/architecture.md` | `## Overview` | Record high-level design principles, architectural patterns (e.g., monolith, microservices), and key design decisions. |
|
||||
| `context/architecture.md` | `## Core Components` | List the main modules, services, or packages and their responsibilities. Use bullet points for each component. |
|
||||
|
||||
### Q-013 — Where is it hosted, which environments exist, and what are the security requirements?
|
||||
|
||||
Maps to:
|
||||
|
||||
| Context File | Section | Guidance |
|
||||
|---|---|---|
|
||||
| `context/infrastructure-context.md` | `## Hosting` | Record hosting platform (cloud provider, on-prem, container orchestration, etc.). |
|
||||
| `context/infrastructure-context.md` | `## Environments` | List all environments (dev/staging/prod), their purpose, and data management approach. |
|
||||
| `context/infrastructure-context.md` | `## Monitoring & Alerting` | Document monitoring tools, alerting channels, and observability requirements. |
|
||||
| `context/infrastructure-context.md` | `## Security` | Record authentication models, data protection requirements, compliance needs, and access control policies. |
|
||||
|
||||
### Q-014 — What agent and developer workflow tools, constraints, and guidelines apply?
|
||||
|
||||
Maps to:
|
||||
|
||||
| Context File | Section | Guidance |
|
||||
|---|---|---|
|
||||
| `context/agent-guidelines.md` | `## Purpose` | Record why agents need specific context for this project (constraints, conventions, expectations). |
|
||||
| `context/agent-guidelines.md` | `## Preferred Tools` | List recommended editors, debuggers, testing frameworks, CLI tools, and automation tools. |
|
||||
| `context/repository-context.md` | `## Purpose` | Record the project's overall purpose, scope boundaries, and what it is / is not responsible for. |
|
||||
| `context/repository-context.md` | `## Contributing` | Document contribution process, branch strategy, code review requirements, and testing expectations for contributors. |
|
||||
|
||||
## Summary: Question-to-File Matrix
|
||||
|
||||
This matrix provides a quick-reference view of all mappings. "Primary" means the answer should go there first; "Secondary" means it may also be updated if relevant.
|
||||
|
||||
| Q-ID | Question | Primary Context Files | Secondary Context Files |
|
||||
|---|---|---|---|
|
||||
| Q-001 | What problem are we solving? | product-brief.md, project-brief.md | company-context.md |
|
||||
| Q-002 | Who is the user? | product-brief.md, company-context.md | development-context.md |
|
||||
| Q-003 | What does success look like? | product-brief.md, project-brief.md | — |
|
||||
| Q-004 | Minimum useful version? | product-brief.md, project-brief.md | TASKS.md |
|
||||
| Q-005 | What data do we need? | architecture.md, development-context.md | infrastructure-context.md |
|
||||
| Q-006 | Systems to connect to? | architecture.md, infrastructure-context.md | decisions.md |
|
||||
| Q-007 | What are the risks? | risks.md, open-questions.md | — |
|
||||
| Q-008 | What must not happen? | assumptions.md, decisions.md | open-questions.md |
|
||||
| Q-009 | How will we test it? | development-context.md, agent-guidelines.md | — |
|
||||
| Q-010 | How will it be deployed? | architecture.md, infrastructure-context.md | — |
|
||||
| Q-011 | Who owns and directs the project? What are its organisational goals? | company-context.md, project-brief.md | — |
|
||||
| Q-012 | What is the technology stack, repository structure, architecture approach? | development-context.md, repository-context.md, architecture.md | — |
|
||||
| Q-013 | Where is it hosted, which environments exist, and what are the security requirements? | infrastructure-context.md | — |
|
||||
| Q-014 | What agent and developer workflow tools, constraints, and guidelines apply? | agent-guidelines.md, repository-context.md | — |
|
||||
|
||||
## Implementation Notes for Future Work
|
||||
|
||||
This document is a planning artifact. No code generation has been implemented yet.
|
||||
|
||||
### What future implementation should do
|
||||
|
||||
1. **Read** discovery-log.md (via `read_discovery_answers` in `discovery.py`)
|
||||
2. **Classify** each answer by its question ID
|
||||
3. **Transform** the answer text according to this mapping's section guidance
|
||||
4. **Write** or update target context files, creating rows in tables where applicable
|
||||
5. **Preserve** existing content — never overwrite sections without explicit user intent
|
||||
6. **Record** what was generated so the user can review
|
||||
|
||||
### Constraints for future implementation
|
||||
|
||||
- Only process answers with High or Medium confidence
|
||||
- Skip Low confidence answers (these should be handled by `ask-more` first)
|
||||
- Use template format from `templates.py` as the base for context files
|
||||
- Add new sections only if a discovery answer introduces entirely new content areas
|
||||
- Log each mapping action to `.rdb/session-log.jsonl` for traceability
|
||||
|
||||
### Test requirements for future implementation
|
||||
|
||||
When code is eventually written, tests should verify:
|
||||
|
||||
- Each question ID maps to exactly the files listed in this document
|
||||
- Empty/placeholder sections are populated correctly
|
||||
- Existing file content is preserved (not overwritten)
|
||||
- The matrix above passes programmatic validation against the mapping data
|
||||
@@ -0,0 +1,320 @@
|
||||
# Discovery Gap Analysis Model
|
||||
|
||||
This document defines how missing or weak project context should be detected and converted into targeted follow-up discovery questions.
|
||||
|
||||
It is a planning artifact for TASK-026 implementation. No code has been implemented.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
When `rdb generate` populates context files from discovery answers, some sections will remain empty or contain only placeholders because:
|
||||
|
||||
- No discovery question addressed them
|
||||
- The answer had Low confidence (filtered out)
|
||||
- The mapping did not route data to that section
|
||||
- The section does not exist in the template
|
||||
|
||||
Gap analysis detects these cases and generates targeted follow-up questions instead of leaving them as TBD.
|
||||
|
||||
---
|
||||
|
||||
## 1. Gap Detection Rules
|
||||
|
||||
A gap exists when one or more of the following conditions hold:
|
||||
|
||||
| Rule ID | Condition | Example |
|
||||
|---|---|---|
|
||||
| GAP-01 | A context file section contains only a `TBD` or `TDB` placeholder | `## Mission\n\nTBD — What is the organisation's core mission?` |
|
||||
| GAP-02 | A context file section exists in the template but no discovery answer mapped to it | `## Monitoring & Alerting` has no mapped source from any Q-id |
|
||||
| GAP-03 | A section contains partial content with unresolved sub-items (e.g., blank list entries, empty brackets) | Section has text but a required sub-list item is missing |
|
||||
| GAP-04 | An entire context file exists but has fewer than 50 characters of non-whitespace content beyond the heading | File is effectively empty |
|
||||
|
||||
Detection operates per **section** within each **context file**, not at the file level. A file with partial data may have gaps in some sections and none in others.
|
||||
|
||||
---
|
||||
|
||||
## 2. Gap Severity Levels
|
||||
|
||||
| Severity | Label | Criteria | Action Required |
|
||||
|---|---|---|---|
|
||||
| S1 | Critical | The gap is in a section required for the current BUILDING stage. Blocking on this gap would stop all downstream work. | Must be resolved before moving to REVIEW_READY |
|
||||
| S2 | Important | The gap is in a section that will affect future implementation decisions but does not block immediate work. | Resolve within 1 BUILDING cycle (next discovery round) |
|
||||
| S3 | Low | The gap adds useful detail but is not needed for any active or planned task. | Can be deferred to REVIEW_READY or later |
|
||||
|
||||
Severity assignment logic:
|
||||
|
||||
- A gap is **S1** if its section appears in the *current stage's expected files* and any open TASK references that section
|
||||
- A gap is **S2** if its section is referenced by a mapped discovery question (Q-001 to Q-014) but no High/Medium confidence answer was produced
|
||||
- A gap is **S3** if the section exists in templates.py, has no mapped discovery question, and does not appear in any active task
|
||||
|
||||
---
|
||||
|
||||
## 3. Question Generation Rules
|
||||
|
||||
Follow-up questions are generated according to these rules:
|
||||
|
||||
| Rule | Guidance | Example |
|
||||
|---|---|---|
|
||||
| QG-01 | One gap maps to exactly one follow-up question — do not bundle multiple gaps into a single question | Separate `## Mission` and `## Customers / Users` even if both are in company-context.md |
|
||||
| QG-02 | Questions must be answerable with the original discovery format (text answer + confidence) | Ask for specific facts, not open-ended essays |
|
||||
| QG-03 | The question text should reference the target section so the responder understands context | "What is the team's core mission statement?" rather than "Tell us about your company" |
|
||||
| QG-04 | If a gap maps to a table format section, specify the table columns needed | For risks.md: "What security risk affects data retention? Provide impact and mitigation." |
|
||||
| QG-05 | Do not generate questions for sections already filled with non-TBD content | Skip `## Mission` if it reads "We build developer tools" |
|
||||
| QG-06 | Prefer questions that can be answered by the project owner or a technical lead, not external experts | "Which cloud provider hosts production?" rather than "Describe your AWS architecture" unless already established |
|
||||
|
||||
---
|
||||
|
||||
## 4. Question Prioritisation Rules
|
||||
|
||||
Follow-up questions are prioritised using this ordered scheme:
|
||||
|
||||
1. **Severity first** — All S1 gaps before all S2 gaps; all S2 before all S3
|
||||
2. **File completeness within severity** — Files with more gaps are addressed earlier (reduce file-level churn)
|
||||
3. **Dependency order** — If answering gap A enables answers to gap B, place A first
|
||||
4. **Question scope** — Within equal priority, shorter/closer-to-answer questions come first
|
||||
|
||||
For example: `## Mission` (S1, company-context.md) and `## Hosting` (S1, infrastructure-context.md) are both S1; company-context.md goes first because it has 3 unresolved gaps versus infrastructure-context.md's 2.
|
||||
|
||||
---
|
||||
|
||||
## 5. Gap-to-Question Mapping
|
||||
|
||||
This mapping covers every template section that can contain TBD placeholders. For each section it defines: the gap trigger, the follow-up question to ask, and the target discovery question ID for the follow-up entry.
|
||||
|
||||
### context/company-context.md
|
||||
|
||||
| Section | Trigger | Follow-up Question | Discovery Q-ID |
|
||||
|---|---|---|---|
|
||||
| `## Mission` | Contains only TBD/placeholder text | "What is the organisation's core mission or purpose?" | New (Q-015-style) |
|
||||
| `## Products & Services` | Contains only TBD/placeholder text | "List the products or services the organisation offers. What does each one do?" | New |
|
||||
| `## Customers / Users` | Contains only TBD/placeholder text | "Who are the primary customers or users of your products? Use bullet points for each distinct user type." | New |
|
||||
| `## Brand & Positioning` | Contains only TBD/placeholder text | "How does the organisation position itself in the market?" | New |
|
||||
| `## Key Stakeholders` | Contains only TBD/placeholder text | "Who are the key stakeholders (names, roles, contact) for this project?" | New |
|
||||
|
||||
### context/product-brief.md
|
||||
|
||||
| Section | Trigger | Follow-up Question | Discovery Q-ID |
|
||||
|---|---|---|---|
|
||||
| `## Problem` | Contains only TBD/placeholder text | "What problem is this product solving? Describe the customer pain and desired outcome." | Mirrors Q-001 |
|
||||
| `## Users` | Contains only TBD/placeholder text | "Who are the users of this product? List personas, roles, or segments." | Mirrors Q-002 |
|
||||
| `## Success Criteria` | Contains only TBD/placeholder text | "How will you measure success for this product? Be specific and verifiable." | Mirrors Q-003 |
|
||||
| `## Minimum Useful Version` | Contains only TBD/placeholder text | "What is the minimum set of features that delivers real value to users?" | Mirrors Q-004 |
|
||||
|
||||
### context/project-brief.md
|
||||
|
||||
| Section | Trigger | Follow-up Question | Discovery Q-ID |
|
||||
|---|---|---|---|
|
||||
| `## Problem Statement` | Contains only TBD/placeholder text | "Restate the product problem in project-specific terms. Include scope boundaries." | Mirrors Q-001 |
|
||||
| `## Target Audience` | Contains only TBD/placeholder text | "Who is the intended audience for this project? Who benefits from the final product?" | Mirrors Q-011 |
|
||||
| `## Key Features (MVP)` | Contains only TBD/placeholder text | "List the minimum set of features for the first release. Each feature should be independently valuable." | Mirrors Q-004 |
|
||||
| `## Success Metrics` | Contains only TBD/placeholder text | "How will you measure success for this project? Include specific thresholds or targets." | Mirrors Q-003 |
|
||||
| `## Timeline & Milestones` | Contains only TBD/placeholder text | "What are the expected delivery dates and key milestones?" | New (Q-015-style) |
|
||||
|
||||
### context/architecture.md
|
||||
|
||||
| Section | Trigger | Follow-up Question | Discovery Q-ID |
|
||||
|---|---|---|---|
|
||||
| `## Overview` | Contains only TBD/placeholder text | "Describe the high-level system architecture. What design principles or patterns apply?" | Mirrors Q-012 |
|
||||
| `## Core Components` | Contains only TBD/placeholder text | "List the main modules, services, or packages and their responsibilities." | Mirrors Q-012 |
|
||||
| `## Data Flow` | Contains only TBD/placeholder text | "How does data move through the system? Describe entities, storage, and movement between components." | Mirrors Q-005 |
|
||||
| `## External Integrations` | Contains only TBD/placeholder text | "What external systems, APIs, or services must this connect to?" | Mirrors Q-006 |
|
||||
| `## Deployment Architecture` | Contains only TBD/placeholder text | "Describe the deployment layout, scaling strategy, and process." | Mirrors Q-010 |
|
||||
|
||||
### context/decisions.md
|
||||
|
||||
| Section | Trigger | Follow-up Question | Discovery Q-ID |
|
||||
|---|---|---|---|
|
||||
| Table body (no rows) | Has 0 data rows in the decision table | "What architecture or design decisions have been made? Record each with reason and date." | New |
|
||||
|
||||
### context/risks.md
|
||||
|
||||
| Section | Trigger | Follow-up Question | Discovery Q-ID |
|
||||
|---|---|---|---|
|
||||
| Table body (no rows) | Has 0 data rows in the risks table | "What are the project risks? For each, describe impact and mitigation strategy." | Mirrors Q-007 |
|
||||
|
||||
### context/assumptions.md
|
||||
|
||||
| Section | Trigger | Follow-up Question | Discovery Q-ID |
|
||||
|---|---|---|---|
|
||||
| Table body (no rows) | Has 0 data rows in the assumptions table | "What are you assuming about this project? For each, note confidence and whether validation is needed." | Mirrors Q-008 |
|
||||
|
||||
### context/development-context.md
|
||||
|
||||
| Section | Trigger | Follow-up Question | Discovery Q-ID |
|
||||
|---|---|---|---|
|
||||
| `## Tech Stack` | Contains only TBD/placeholder text | "What is the technology stack? List languages, frameworks, libraries, and tooling." | Mirrors Q-012 |
|
||||
| `## Coding Standards` | Contains only TBD/placeholder text | "Are there agreed coding standards? Describe style conventions, naming patterns, linters, and formatters." | New |
|
||||
| `## Repository Structure` | Contains only TBD/placeholder text | "Describe the repository directory layout and module organisation." | Mirrors Q-012 |
|
||||
| `## Build & Test` | Contains only TBD/placeholder text | "How do you build, test, and run this codebase locally? Include commands and setup steps." | Mirrors Q-009 |
|
||||
| `## Dependencies` | Contains only TBD/placeholder text | "What external services, databases, or third-party APIs are required?" | Mirrors Q-006 |
|
||||
|
||||
### context/infrastructure-context.md
|
||||
|
||||
| Section | Trigger | Follow-up Question | Discovery Q-ID |
|
||||
|---|---|---|---|
|
||||
| `## Hosting` | Contains only TBD/placeholder text | "Where is the application hosted? Specify cloud provider, on-prem details, or container orchestration." | Mirrors Q-013 |
|
||||
| `## Environments` | Contains only TBD/placeholder text | "What environments exist (dev/staging/prod)? Describe each and data management approach." | Mirrors Q-013 |
|
||||
| `## CI / CD Pipeline` | Contains only TBD/placeholder text | "Describe the build, test, and deployment pipeline configuration." | Mirrors Q-013 |
|
||||
| `## Monitoring & Alerting` | Contains only TBD/placeholder text | "What tools are used for monitoring, logging, and alerting?" | Mirrors Q-013 |
|
||||
| `## Security` | Contains only TBD/placeholder text | "What authentication models, data protection requirements, compliance needs, and access control policies apply?" | Mirrors Q-013 |
|
||||
|
||||
### context/agent-guidelines.md
|
||||
|
||||
| Section | Trigger | Follow-up Question | Discovery Q-ID |
|
||||
|---|---|---|---|
|
||||
| `## Purpose` | Contains only TBD/placeholder text | "What should agents or developers know about working on this project? Note constraints and conventions." | Mirrors Q-014 |
|
||||
| `## Preferred Tools` | Contains only TBD/placeholder text | "Which editors, debuggers, testing frameworks, and CLI tools are recommended?" | Mirrors Q-014 |
|
||||
| `## Testing Preferences` | Contains only TBD/placeholder text | "How should tests be written, run, and verified for this project?" | Mirrors Q-009 |
|
||||
| `## Common Tasks` | Contains only TBD/placeholder text | "What are the typical workflows for developers or AI agents on this project?" | New |
|
||||
| `## Known Gotchas` | Contains only TBD/placeholder text | "Are there known pitfalls, quirks, or important caveats to be aware of?" | New |
|
||||
|
||||
### context/repository-context.md
|
||||
|
||||
| Section | Trigger | Follow-up Question | Discovery Q-ID |
|
||||
|---|---|---|---|
|
||||
| `## Purpose` | Contains only TBD/placeholder text | "What is this repository's overall purpose and scope? What is it responsible for (and not responsible for)?" | Mirrors Q-014 |
|
||||
| `## Key Components` | Contains only TBD/placeholder text | "List the key components or modules in this repository." | Mirrors Q-012 |
|
||||
| `## Dependencies` | Contains only TBD/placeholder text | "What project dependencies exist? List external services, databases, and third-party APIs." | Mirrors Q-006 |
|
||||
| `## Contributing` | Contains only TBD/placeholder text | "Describe the contribution process: branch strategy, code review requirements, testing expectations." | Mirrors Q-014 |
|
||||
|
||||
### context/open-questions.md
|
||||
|
||||
| Section | Trigger | Follow-up Question | Discovery Q-ID |
|
||||
|---|---|---|---|
|
||||
| Table body (no rows) | Has 0 data rows | "Are there any open questions that need further investigation before implementation begins?" | New |
|
||||
|
||||
### project-level files (PROJECT_STATE.md, AGENT_HANDOFF.md, TASKS.md, TEST_PLAN.md, RUN_LOG.md)
|
||||
|
||||
These files are managed by CLI commands, not by discovery answers. They are **not** subject to gap analysis. Gaps in these files are handled by command logic, not by question generation.
|
||||
|
||||
---
|
||||
|
||||
## 6. Example Gap-to-Question Flow
|
||||
|
||||
Here is an end-to-end example of how the model operates:
|
||||
|
||||
### Step 1 — Gap Detection
|
||||
|
||||
`rdb context-status` or a dedicated gap analysis scan finds:
|
||||
|
||||
| File | Section | Gap Rule | Severity |
|
||||
|---|---|---|---|
|
||||
| `context/company-context.md` | `## Mission` | GAP-01 (TBD placeholder) | S1 |
|
||||
| `context/company-context.md` | `## Key Stakeholders` | GAP-01 (TBD placeholder) | S2 |
|
||||
| `context/infrastructure-context.md` | `## Hosting` | GAP-01 (TBD placeholder) | S1 |
|
||||
| `context/infrastructure-context.md` | `## Monitoring & Alerting` | GAP-01 (TBD placeholder) | S2 |
|
||||
| `context/development-context.md` | `## Coding Standards` | GAP-01 (TBD placeholder) | S3 |
|
||||
|
||||
### Step 2 — Prioritisation
|
||||
|
||||
Sorted by severity, then file completeness:
|
||||
|
||||
1. S1 — `company-context.md` / `## Mission` (company has 5 gaps vs infra's 4)
|
||||
2. S1 — `infrastructure-context.md` / `## Hosting`
|
||||
3. S2 — `company-context.md` / `## Key Stakeholders`
|
||||
4. S2 — `infrastructure-context.md` / `## Monitoring & Alerting`
|
||||
5. S3 — `development-context.md` / `## Coding Standards`
|
||||
|
||||
### Step 3 — Question Generation
|
||||
|
||||
Follow-up questions appended to `context/discovery-log.md`:
|
||||
|
||||
| New ID | Follow-up Question | Source Gap | Confidence Expected |
|
||||
|---|---|---|---|
|
||||
| Q-015 | "What is the organisation's core mission or purpose?" | company-context.md `## Mission` | High |
|
||||
| Q-016 | "Where is the application hosted? Specify cloud provider, on-prem details, or container orchestration." | infrastructure-context.md `## Hosting` | High |
|
||||
| Q-017 | "Who are the key stakeholders (names, roles, contact) for this project?" | company-context.md `## Key Stakeholders` | Medium |
|
||||
| Q-018 | "What tools are used for monitoring, logging, and alerting?" | infrastructure-context.md `## Monitoring & Alerting` | Medium |
|
||||
| Q-019 | "Are there agreed coding standards? Describe style conventions, naming patterns, linters, and formatters." | development-context.md `## Coding Standards` | Low |
|
||||
|
||||
### Step 4 — Regeneration
|
||||
|
||||
After answers to Q-015 through Q-019 are collected:
|
||||
- Run `rdb generate` (or re-run gap analysis)
|
||||
- Sections populated from new answers should resolve the S1 and S2 gaps
|
||||
- Remaining gaps re-prioritise for the next cycle
|
||||
|
||||
---
|
||||
|
||||
## 7. Edge Cases
|
||||
|
||||
| Case | Handling |
|
||||
|---|---|
|
||||
| Section contains partial text + TBD sub-items | Only generate a question for the missing sub-item, not the entire section |
|
||||
| Section text reads "TBD" but it is actually correct (intentional placeholder) | Provide a skip/confirm mechanism — let the user mark as "acceptable" to suppress the gap |
|
||||
| Template adds new sections in future versions | Sections with no mapping entry default to S3 severity with a generic question: "What information belongs in this section?" |
|
||||
| A section maps to multiple discovery questions | Generate separate follow-ups per unmapped question, each linked to its source Q-id |
|
||||
| File has fewer than 50 characters (GAP-04) but the heading is meaningful | Count content beyond the heading line only |
|
||||
|
||||
---
|
||||
|
||||
## 8. Implementation Task: TASK-026
|
||||
|
||||
### Scope for TASK-026 (Implementation)
|
||||
|
||||
TASK-026 will implement a CLI command `rdb gap` that:
|
||||
|
||||
1. Reads all 18 template-based context files listed in `templates.py CONTEXT_FILES`
|
||||
2. For each file, scans every section for GAP-01 (TBD), GAP-03 (partial content), or GAP-04 (near-empty file)
|
||||
3. For each gap found, applies severity rules from Section 2
|
||||
4. Generates follow-up questions using the mapping in Section 5
|
||||
5. Prioritises questions per Section 4
|
||||
6. Outputs the prioritised question list to stdout
|
||||
7. Optionally appends answers to `context/discovery-log.md` when a user provides them interactively
|
||||
|
||||
### What TASK-026 Does NOT Do
|
||||
|
||||
- Does not auto-populate context files — that remains `rdb generate`'s scope
|
||||
- Does not replace `rdb discover` — follow-up questions are new entries in discovery-log.md
|
||||
- Does not change the existing gap analysis model document
|
||||
|
||||
### Expected Module Structure (TASK-026)
|
||||
|
||||
```
|
||||
src/rdb_discovery/gap_analysis.py # Core detection and question generation logic
|
||||
tests/test_gap_analysis.py # Unit tests for each section mapping
|
||||
```
|
||||
|
||||
### Expected Test Requirements (TASK-026)
|
||||
|
||||
| Test Category | Count | Description |
|
||||
|---|---|---|
|
||||
| Gap detection per template section | ~25 | One per section in the mapping table |
|
||||
| Severity classification | 3+ | S1, S2, S3 on representative inputs |
|
||||
| Question generation format | 5+ | Follow-up questions match QG-01 through QG-06 rules |
|
||||
| Prioritisation ordering | 3+ | Sorted output matches severity → completeness → dependency → scope |
|
||||
| Edge cases (Section 6) | 5+ | Partial sections, acceptable TBDs, new template sections, multi-question sections |
|
||||
| CLI command `rdb gap` | 4+ | Help text, empty project handling, discovery-log append mode |
|
||||
|
||||
---
|
||||
|
||||
## 9. How This Model Relates to the Rest of RDB
|
||||
|
||||
```
|
||||
discovery-log.md (source of truth for all answers)
|
||||
│
|
||||
▼
|
||||
rdb discover / rdb gap (collects or generates questions)
|
||||
│
|
||||
▼
|
||||
rdb generate (maps answers → context files via discovery-context-mapping.md)
|
||||
│
|
||||
▼
|
||||
rdb context-status (reports health, can trigger gap analysis)
|
||||
│
|
||||
▼
|
||||
rdb gap (detects remaining gaps → new questions)
|
||||
│
|
||||
└───────────────────► loop back to rdb discover for follow-up answers
|
||||
```
|
||||
|
||||
This model connects to:
|
||||
|
||||
- `context/discovery-context-mapping.md` — the existing Q-001 to Q-014 mapping (this model extends it with follow-up question generation)
|
||||
- `src/rdb_discovery/templates.py` — the authoritative list of template sections used for detection
|
||||
- `rdb context-status` — health reporting can surface gap counts as an additional metric
|
||||
|
||||
---
|
||||
|
||||
*This document is a planning artifact. No code has been implemented. TASK-026 will implement the CLI command based on this model.*
|
||||
@@ -11,9 +11,11 @@ Requires-Dist: pytest>=8.0.0; extra == "dev"
|
||||
|
||||
# rdb-discovery
|
||||
|
||||
A small CLI-first tool for repeatable software project discovery and delivery.
|
||||
A small CLI-first tool that helps you generate and maintain useful project context files for AI-assisted development.
|
||||
|
||||
It creates markdown files that help humans and AI coding agents understand:
|
||||
## Purpose
|
||||
|
||||
rdb-discovery exists to make it easier for humans and AI coding agents to understand:
|
||||
|
||||
- what is being built
|
||||
- why it exists
|
||||
@@ -21,6 +23,20 @@ It creates markdown files that help humans and AI coding agents understand:
|
||||
- what task should be done next
|
||||
- what stage the project is currently in
|
||||
|
||||
The tool creates and manages a set of markdown files — your project's source-of-truth context.
|
||||
|
||||
## Core workflow
|
||||
|
||||
1. **Ask discovery questions** — `rdb discover` captures answers about the product, architecture, risks, and open questions.
|
||||
2. **Capture answers** — Structured responses are stored in `context/discovery-log.md` alongside confidence scores and follow-ups.
|
||||
3. **Generate context files** — `rdb init` creates standardised project files (`product-brief.md`, `architecture.md`, `decisions.md`, etc.).
|
||||
4. **Support agent implementation** — `rdb prompt` produces ready-to-paste implementation prompts for Claude Code or Cline, and `rdb start` / `rdb complete` manage task lifecycles.
|
||||
|
||||
## Support features (not the product)
|
||||
|
||||
- **Telemetry** records structured agent activity events in `.rdb/session-log.jsonl`. It enables future analysis but is not the main offering.
|
||||
- **Guardrails** detect agent stalls, repeated reads, and inconsistent state to keep projects on track. They support the workflow but are secondary.
|
||||
|
||||
## Install for local development
|
||||
|
||||
```bash
|
||||
@@ -32,23 +48,43 @@ pip install -e '.[dev]'
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
rdb init
|
||||
rdb discover
|
||||
rdb status
|
||||
rdb next
|
||||
rdb start TASK-001
|
||||
rdb complete TASK-001
|
||||
rdb handoff
|
||||
rdb init # Create standard project structure and context files
|
||||
rdb discover # Ask 10 core discovery questions, record answers
|
||||
rdb guardrails # Check for agent stalls and state inconsistencies
|
||||
rdb prompt # Generate implementation prompt for the next task
|
||||
rdb start TASK-001 # Mark a task as in progress
|
||||
rdb complete TASK-001# Mark a task as done
|
||||
rdb status # Show current stage and active task
|
||||
rdb next # Print the next task to work on
|
||||
rdb handoff # Generate agent handoff notes
|
||||
```
|
||||
|
||||
## Bootstrap workflow
|
||||
|
||||
1. Run `rdb init`
|
||||
2. Run `rdb discover`
|
||||
3. Run `rdb status`
|
||||
4. Run `rdb next`
|
||||
5. Give the next task to Claude Code or Cline
|
||||
6. Commit after each completed task
|
||||
3. Review `rdb status` and `rdb next`
|
||||
4. Give the next prompt to Claude Code or Cline via `rdb prompt`
|
||||
5. Commit after each completed task
|
||||
|
||||
## Project structure
|
||||
|
||||
`rdb init` creates the following files (skips existing ones):
|
||||
|
||||
- context/discovery-log.md
|
||||
- context/product-brief.md
|
||||
- context/architecture.md
|
||||
- context/decisions.md
|
||||
- context/risks.md
|
||||
- context/assumptions.md
|
||||
- context/open-questions.md
|
||||
- context/repository-context.md
|
||||
- TASKS.md
|
||||
- TEST_PLAN.md
|
||||
- RUN_LOG.md
|
||||
- PROJECT_STATE.md
|
||||
- AGENT_HANDOFF.md
|
||||
- .rdb/project.json
|
||||
|
||||
## Principle
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@ pyproject.toml
|
||||
src/rdb_discovery/__init__.py
|
||||
src/rdb_discovery/cli.py
|
||||
src/rdb_discovery/discovery.py
|
||||
src/rdb_discovery/guardrails.py
|
||||
src/rdb_discovery/handoff.py
|
||||
src/rdb_discovery/status.py
|
||||
src/rdb_discovery/tasks.py
|
||||
src/rdb_discovery/telemetry.py
|
||||
src/rdb_discovery/templates.py
|
||||
src/rdb_discovery.egg-info/PKG-INFO
|
||||
src/rdb_discovery.egg-info/SOURCES.txt
|
||||
@@ -13,5 +15,10 @@ src/rdb_discovery.egg-info/dependency_links.txt
|
||||
src/rdb_discovery.egg-info/entry_points.txt
|
||||
src/rdb_discovery.egg-info/requires.txt
|
||||
src/rdb_discovery.egg-info/top_level.txt
|
||||
tests/test_discovery.py
|
||||
tests/test_guardrails.py
|
||||
tests/test_prompt.py
|
||||
tests/test_status.py
|
||||
tests/test_tasks.py
|
||||
tests/test_telemetry.py
|
||||
tests/test_templates.py
|
||||
@@ -14,6 +14,8 @@ 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
|
||||
from .generate_context import generate_context_files
|
||||
from .context_status import context_status as get_context_status, compute_health_score
|
||||
|
||||
app = typer.Typer(help="RDB discovery and delivery workflow CLI.")
|
||||
console = Console()
|
||||
@@ -209,3 +211,85 @@ def guardrails() -> None:
|
||||
results = run_all_guardrails(root)
|
||||
report = format_report(results)
|
||||
console.print(report)
|
||||
|
||||
|
||||
@app.command()
|
||||
def generate(min_confidence: str = typer.Option("Medium", help="Minimum confidence to process (High or Medium).")) -> None:
|
||||
"""Generate context files from discovery answers using the mapping document."""
|
||||
root = root_path()
|
||||
record_event(root, "command", "rdb generate")
|
||||
|
||||
result = generate_context_files(root, min_confidence)
|
||||
|
||||
if result["generated"]:
|
||||
console.print("[bold green]Generated context content:[/bold green]")
|
||||
for f in result["generated"]:
|
||||
console.print(f"- {f}")
|
||||
else:
|
||||
console.print("[yellow]No context files generated.[/yellow]")
|
||||
|
||||
if result["skipped"]:
|
||||
console.print("\n[dim]Skipped:[/dim]")
|
||||
for s in result["skipped"]:
|
||||
console.print(f" - {s}")
|
||||
|
||||
|
||||
@app.command()
|
||||
def context_status() -> None:
|
||||
"""Report the health and completeness of project context."""
|
||||
root = root_path()
|
||||
record_event(root, "command", "rdb context-status")
|
||||
|
||||
report = get_context_status(root)
|
||||
score = report["score"]
|
||||
summary = report["summary"]
|
||||
|
||||
# Score bar
|
||||
if score >= 70:
|
||||
score_color = "green"
|
||||
elif score >= 40:
|
||||
score_color = "yellow"
|
||||
else:
|
||||
score_color = "red"
|
||||
|
||||
console.print(f"\n[bold]Context Health Report[/bold]")
|
||||
score_text = f"{score}/100"
|
||||
console.print(f"[bold][{score_color}]{score_text}[/{score_color}]")
|
||||
console.print("")
|
||||
|
||||
# File status table
|
||||
table = Table(title="Expected Files")
|
||||
table.add_column("File")
|
||||
table.add_column("Status")
|
||||
table.add_column("Size")
|
||||
for f in report["expected_files"]:
|
||||
color = "green" if f["status"] == "present" else "red"
|
||||
icon = "[green]✓[/green]" if f["status"] == "present" else "[red]✗[/red]"
|
||||
table.add_row(f["path"], f"[{color}]{icon}[/{color}]", f["size"])
|
||||
console.print(table)
|
||||
|
||||
# TBD placeholders
|
||||
if report["tbd_sections"]:
|
||||
console.print(f"\n[bold yellow]TBD Placeholders ({summary['tbd_sections']} still open):[/bold yellow]")
|
||||
for item in report["tbd_sections"]:
|
||||
console.print(f" [yellow]• {item['file']} — {item['section']} (line {item['line']})[/yellow]")
|
||||
else:
|
||||
console.print("[green]\nNo TBD placeholders found.[/green]")
|
||||
|
||||
# Low-confidence discovery answers
|
||||
if report["low_confidence"]:
|
||||
console.print(f"\n[bold yellow]Low-Confidence Discovery Answers ({summary['low_confidence_answers']}):[/bold yellow]")
|
||||
for item in report["low_confidence"]:
|
||||
console.print(f" [yellow]• {item['id']}: {item['question']} (confidence: {item['confidence']})[/yellow]")
|
||||
else:
|
||||
console.print("[green]\nNo low-confidence discovery answers.[/green]")
|
||||
|
||||
# Summary line
|
||||
missing = summary["missing"]
|
||||
if missing > 0:
|
||||
console.print(f"\n[dim]{summary['present']} of {summary['total_expected']} context files present. "
|
||||
f"{missing} missing, {summary['tbd_sections']} TBD sections, "
|
||||
f"{summary['low_confidence_answers']} low-confidence answers.[/dim]")
|
||||
else:
|
||||
console.print(f"\n[dim]{summary['present']} of {summary['total_expected']} context files present. "
|
||||
f"All expected files exist.{'' if summary['tbd_sections'] == 0 and summary['low_confidence_answers'] == 0 else ' Review TBD sections and low-confidence answers above.'}[/dim]")
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
"""Context health report — assess whether sufficient context exists for AI-assisted development."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
def _expected_files(root: Path) -> list[str]:
|
||||
"""Return the list of expected context files for this project."""
|
||||
return [
|
||||
"context/discovery-log.md",
|
||||
"context/product-brief.md",
|
||||
"context/architecture.md",
|
||||
"context/decisions.md",
|
||||
"context/risks.md",
|
||||
"context/assumptions.md",
|
||||
"context/open-questions.md",
|
||||
"context/repository-context.md",
|
||||
"context/company-context.md",
|
||||
"context/development-context.md",
|
||||
"context/infrastructure-context.md",
|
||||
"context/agent-guidelines.md",
|
||||
"context/project-brief.md",
|
||||
"TASKS.md",
|
||||
"TEST_PLAN.md",
|
||||
"RUN_LOG.md",
|
||||
"PROJECT_STATE.md",
|
||||
"AGENT_HANDOFF.md",
|
||||
]
|
||||
|
||||
|
||||
def _check_expected_files(root: Path) -> list[dict[str, str]]:
|
||||
"""Check which expected files exist and which are missing."""
|
||||
results: list[dict[str, str]] = []
|
||||
for path_str in _expected_files(root):
|
||||
full = root / path_str
|
||||
status = "missing" if not full.exists() else "present"
|
||||
size = full.stat().st_size if full.exists() else 0
|
||||
results.append({"path": path_str, "status": status, "size": str(size)})
|
||||
return results
|
||||
|
||||
|
||||
def _check_tbd_sections(root: Path) -> list[dict[str, Any]]:
|
||||
"""Find sections still containing TBD/TDB placeholders."""
|
||||
context_dir = root / "context"
|
||||
if not context_dir.exists():
|
||||
return []
|
||||
|
||||
findings: list[dict[str, Any]] = []
|
||||
for file_path in sorted(context_dir.iterdir()):
|
||||
if not file_path.is_file() or file_path.suffix != ".md":
|
||||
continue
|
||||
content = file_path.read_text(encoding="utf-8")
|
||||
lines = content.splitlines()
|
||||
for i, line in enumerate(lines):
|
||||
stripped = line.strip()
|
||||
if re.match(r"^## .+$", stripped):
|
||||
# Look past blank lines to find the first non-empty content line
|
||||
content_line = None
|
||||
for j in range(i + 1, len(lines)):
|
||||
candidate = lines[j].strip()
|
||||
if not candidate:
|
||||
continue
|
||||
if re.match(r"^## ", candidate) or candidate.startswith("# "):
|
||||
break
|
||||
content_line = candidate
|
||||
break
|
||||
if content_line and (content_line.startswith("TBD") or content_line.startswith("TDB")):
|
||||
placeholder_text = content_line.split("—")[0].split()[0] if content_line.split() else ""
|
||||
findings.append({
|
||||
"file": file_path.name,
|
||||
"section": stripped,
|
||||
"line": i + 2,
|
||||
"placeholder": placeholder_text,
|
||||
})
|
||||
return findings
|
||||
|
||||
|
||||
def _check_low_confidence(root: Path) -> list[dict[str, str]]:
|
||||
"""Find low-confidence discovery answers from discovery-log.md."""
|
||||
from .discovery import read_discovery_answers
|
||||
|
||||
answers = read_discovery_answers(root)
|
||||
low_conf = [a for a in answers if a["low_confidence"]]
|
||||
return [{"id": a["id"], "question": a["question"], "confidence": a["confidence"]} for a in low_conf]
|
||||
|
||||
|
||||
def compute_health_score(expected: list[dict[str, str]], tbd_sections: int, low_conf_count: int) -> float:
|
||||
"""Compute an overall health score (0-100).
|
||||
|
||||
Scoring breakdown:
|
||||
- 45 pts for file completeness (each file is equal share of 45)
|
||||
- 30 pts for no TBD placeholders in body sections (proportional decay from 30 down to 0 at 20+ TBDs)
|
||||
- 15 pts for no low-confidence answers (proportional decay from 15 down to 0 at 10+ low-conf)
|
||||
- 10 pts if discovery-log.md exists with data
|
||||
"""
|
||||
total_expected = len(expected)
|
||||
present_count = sum(1 for f in expected if f["status"] == "present")
|
||||
|
||||
# File completeness: up to 45 points
|
||||
file_score = (present_count / max(total_expected, 1)) * 45
|
||||
|
||||
# TBD penalty: start at 30, lose 1.5 per TBD (min 0)
|
||||
tbd_score = max(30 - (tbd_sections * 1.5), 0)
|
||||
|
||||
# Low-confidence penalty: start at 15, lose 1.5 per low-conf answer (min 0)
|
||||
lc_score = max(15 - (low_conf_count * 1.5), 0)
|
||||
|
||||
return round(min(file_score + tbd_score + lc_score, 100), 1)
|
||||
|
||||
|
||||
def context_status(root: Path) -> dict[str, Any]:
|
||||
"""Run all health checks and return a structured report."""
|
||||
expected = _check_expected_files(root)
|
||||
tbd_sections = _check_tbd_sections(root)
|
||||
low_confidence = _check_low_confidence(root)
|
||||
|
||||
missing_count = sum(1 for f in expected if f["status"] == "missing")
|
||||
present_count = len(expected) - missing_count
|
||||
total_expected = len(expected)
|
||||
|
||||
score = compute_health_score(expected, len(tbd_sections), len(low_confidence))
|
||||
|
||||
# discovery log bonus (up to 10 points)
|
||||
discovery_log = root / "context" / "discovery-log.md"
|
||||
discovery_score = 0
|
||||
if discovery_log.exists():
|
||||
content = discovery_log.read_text(encoding="utf-8")
|
||||
if any(line.startswith("| Q-") for line in content.splitlines()):
|
||||
discovery_score = 10
|
||||
else:
|
||||
discovery_score = 5
|
||||
|
||||
score += discovery_score
|
||||
|
||||
return {
|
||||
"score": score,
|
||||
"summary": {
|
||||
"total_expected": total_expected,
|
||||
"present": present_count,
|
||||
"missing": missing_count,
|
||||
"tbd_sections": len(tbd_sections),
|
||||
"low_confidence_answers": len(low_confidence),
|
||||
},
|
||||
"expected_files": expected,
|
||||
"tbd_sections": tbd_sections,
|
||||
"low_confidence": low_confidence,
|
||||
}
|
||||
@@ -58,7 +58,10 @@ def read_discovery_answers(root: Path) -> list[dict]:
|
||||
if not line.startswith("| Q-"):
|
||||
continue
|
||||
|
||||
cells = [c.strip() for c in line.split("|")[1:-1]]
|
||||
# Replace escaped pipes with a placeholder before splitting,
|
||||
# so they don't create extra columns. Restore after splitting.
|
||||
safe_line = line.replace("\\|", "\x00PIPE\x00")
|
||||
cells = [c.strip().replace("\x00PIPE\x00", "|") for c in safe_line.split("|")[1:-1]]
|
||||
if len(cells) < 5:
|
||||
continue
|
||||
|
||||
|
||||
@@ -0,0 +1,293 @@
|
||||
"""Generate context files from discovery answers.
|
||||
|
||||
Uses the mapping defined in context/discovery-context-mapping.md to transform
|
||||
discovery-log.md entries into the appropriate context files.
|
||||
|
||||
Only processes High/Medium confidence answers. Low-confidence answers are
|
||||
skipped and must be handled by `rdb ask-more` first.
|
||||
|
||||
Preserves existing content in context files - never overwrites.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from .discovery import read_discovery_answers
|
||||
|
||||
|
||||
# Mapping from question ID to list of (file_path, section_name, format_type)
|
||||
# format_type: "body" = fill under heading with body text
|
||||
# "table" = append row to table-based section
|
||||
# "append" = append as a new section at end of file
|
||||
CONTEXT_MAP = {
|
||||
"Q-001": [ # What problem are we solving?
|
||||
("context/product-brief.md", "## Problem", "body"),
|
||||
("context/project-brief.md", "## Problem Statement", "body"),
|
||||
],
|
||||
"Q-002": [ # Who is the user?
|
||||
("context/product-brief.md", "## Users", "body"),
|
||||
("context/company-context.md", "## Customers / Users", "body"),
|
||||
("context/company-context.md", "## Key Stakeholders", "body"),
|
||||
("context/development-context.md", "## IDEs and Editors", "body"),
|
||||
],
|
||||
"Q-003": [ # What does success look like?
|
||||
("context/product-brief.md", "## Success Criteria", "body"),
|
||||
("context/project-brief.md", "## Success Metrics", "body"),
|
||||
],
|
||||
"Q-004": [ # What is the minimum useful version?
|
||||
("context/product-brief.md", "## Minimum Useful Version", "body"),
|
||||
("context/project-brief.md", "## Key Features (MVP)", "body"),
|
||||
],
|
||||
"Q-005": [ # What data do we need?
|
||||
("context/architecture.md", "## Data Flow", "body"),
|
||||
("context/development-context.md", "## Dependencies", "body"),
|
||||
],
|
||||
"Q-006": [ # What systems must it connect to?
|
||||
("context/architecture.md", "## External Integrations", "body"),
|
||||
("context/infrastructure-context.md", None, "append"),
|
||||
],
|
||||
"Q-007": [ # What are the risks?
|
||||
("context/risks.md", "# Risks", "table"),
|
||||
],
|
||||
"Q-008": [ # What must not happen?
|
||||
("context/assumptions.md", "# Assumptions", "table"),
|
||||
],
|
||||
"Q-009": [ # How will we test it?
|
||||
("context/development-context.md", "## Build & Test", "body"),
|
||||
("context/agent-guidelines.md", "## Testing Preferences", "append"),
|
||||
],
|
||||
"Q-010": [ # How will it be deployed?
|
||||
("context/architecture.md", "## Deployment Architecture", "body"),
|
||||
("context/infrastructure-context.md", None, "append"),
|
||||
],
|
||||
"Q-011": [ # Who owns and directs the project, and what are its organisational goals?
|
||||
("context/company-context.md", "## Mission", "body"),
|
||||
("context/company-context.md", "## Products & Services", "body"),
|
||||
("context/project-brief.md", "## Target Audience", "body"),
|
||||
],
|
||||
"Q-012": [ # What is the technology stack, repository structure, and architecture approach?
|
||||
("context/development-context.md", "## Tech Stack", "body"),
|
||||
("context/development-context.md", "## Coding Standards", "body"),
|
||||
("context/repository-context.md", "## Dependencies", "body"),
|
||||
("context/architecture.md", "## Overview", "body"),
|
||||
("context/architecture.md", "## Core Components", "body"),
|
||||
],
|
||||
"Q-013": [ # Where is it hosted, which environments exist, and what are the security requirements?
|
||||
("context/infrastructure-context.md", "## Hosting", "body"),
|
||||
("context/infrastructure-context.md", "## Environments", "body"),
|
||||
("context/infrastructure-context.md", "## Monitoring & Alerting", "body"),
|
||||
("context/infrastructure-context.md", "## Security", "body"),
|
||||
],
|
||||
"Q-014": [ # What agent and developer workflow tools, constraints, and guidelines apply?
|
||||
("context/agent-guidelines.md", "## Purpose", "body"),
|
||||
("context/agent-guidelines.md", "## Preferred Tools", "body"),
|
||||
("context/repository-context.md", "## Purpose", "body"),
|
||||
("context/repository-context.md", "## Contributing", "body"),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def generate_context_files(
|
||||
root: Path, min_confidence: str = "Medium"
|
||||
) -> dict[str, list[str]]:
|
||||
"""Generate context files from discovery answers.
|
||||
|
||||
Args:
|
||||
root: Project root path.
|
||||
min_confidence: Minimum confidence to process (High, Medium).
|
||||
|
||||
Returns:
|
||||
Dict with keys: 'generated', 'skipped' each a list of strings.
|
||||
"""
|
||||
valid_confidences = {"High", "Medium"}
|
||||
answers = read_discovery_answers(root)
|
||||
|
||||
if not answers:
|
||||
return {"generated": [], "skipped": ["No discovery answers found in discovery-log.md"]}
|
||||
|
||||
filtered = [a for a in answers if a["confidence"] in valid_confidences]
|
||||
skipped = [a for a in answers if a["confidence"] not in valid_confidences]
|
||||
|
||||
generated_files: set[str] = set()
|
||||
|
||||
for answer in filtered:
|
||||
qid = answer["id"]
|
||||
rules = CONTEXT_MAP.get(qid)
|
||||
if not rules:
|
||||
skipped.append(f"{qid} (no mapping)")
|
||||
continue
|
||||
|
||||
for file_path, section_name, fmt in rules:
|
||||
_write_section(
|
||||
root=root,
|
||||
file_path=file_path,
|
||||
section_name=section_name,
|
||||
format_type=fmt,
|
||||
answer=answer,
|
||||
rules=rules,
|
||||
)
|
||||
generated_files.add(file_path)
|
||||
|
||||
skipped_ids = [a["id"] for a in skipped]
|
||||
if skipped_ids:
|
||||
skipped = ["Low-confidence or flagged answers (skipped): " + ", ".join(skipped_ids)]
|
||||
|
||||
return {"generated": sorted(generated_files), "skipped": skipped}
|
||||
|
||||
|
||||
def _write_section(root, file_path, section_name, format_type, answer, rules):
|
||||
"""Dispatch to the appropriate write strategy."""
|
||||
target = root / file_path
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if format_type == "table":
|
||||
_write_table_row(target, section_name, answer)
|
||||
elif format_type == "append":
|
||||
_append_new_section(target, answer, rules, file_path)
|
||||
else:
|
||||
# body text type
|
||||
existing = target.read_text(encoding="utf-8") if target.exists() else ""
|
||||
lines = existing.splitlines() if existing else []
|
||||
|
||||
section_idx = _find_section(lines, section_name) if section_name else -1
|
||||
|
||||
if section_idx >= 0:
|
||||
_fill_or_append_body(lines, target, section_idx, answer)
|
||||
else:
|
||||
_create_new_section(target, section_name, answer, format_type)
|
||||
|
||||
|
||||
def _find_section(lines, section_header):
|
||||
"""Find the index of a section header line in the markdown."""
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip() == section_header:
|
||||
return i
|
||||
return -1
|
||||
|
||||
|
||||
def _fill_or_append_body(lines, target, section_idx, answer):
|
||||
"""Fill or append body text under an existing section header.
|
||||
|
||||
Preserves the rest of the document - never drops subsequent sections.
|
||||
"""
|
||||
start = section_idx + 1
|
||||
|
||||
# Find another section header to know the boundary
|
||||
end = len(lines)
|
||||
for i in range(start, len(lines)):
|
||||
if lines[i].startswith("## "):
|
||||
end = i
|
||||
break
|
||||
|
||||
content_lines = [l.strip() for l in lines[start:end] if l.strip()]
|
||||
|
||||
# Check if section is empty or only has TBD placeholder
|
||||
is_tbd = (
|
||||
len(content_lines) == 1 and content_lines[0].lower().startswith("tbd")
|
||||
) or len(content_lines) == 0
|
||||
|
||||
answer_text = _format_answer_text(answer)
|
||||
|
||||
if is_tbd:
|
||||
# Replace TBD with discovery answer, keep rest of document
|
||||
new_lines = (
|
||||
lines[:section_idx + 1]
|
||||
+ [answer_text, ""]
|
||||
+ lines[end:]
|
||||
)
|
||||
else:
|
||||
# Append under existing content, after the last non-empty line in this section
|
||||
insert_at = start
|
||||
for i in range(start, end):
|
||||
if lines[i].strip():
|
||||
insert_at = i + 1
|
||||
new_lines = (
|
||||
lines[:insert_at]
|
||||
+ [answer_text, ""]
|
||||
+ lines[insert_at:]
|
||||
)
|
||||
|
||||
target.write_text("\n".join(new_lines), encoding="utf-8")
|
||||
|
||||
|
||||
def _write_table_row(target, section_header, answer):
|
||||
"""Append a row to a table-based context file."""
|
||||
existing = target.read_text(encoding="utf-8") if target.exists() else ""
|
||||
|
||||
q_num = answer["id"].split("-")[1]
|
||||
all_questions = [
|
||||
"What problem are we solving?",
|
||||
"Who is the user?",
|
||||
"What does success look like?",
|
||||
"What is the minimum useful version?",
|
||||
"What data do we need?",
|
||||
"What systems must it connect to?",
|
||||
"What are the risks?",
|
||||
"What must not happen?",
|
||||
"How will we test it?",
|
||||
"How will it be deployed?",
|
||||
"Who owns and directs the project, and what are its organisational goals?",
|
||||
"What is the technology stack, repository structure, and architecture approach?",
|
||||
"Where is it hosted, which environments exist, and what are the security requirements?",
|
||||
"What agent and developer workflow tools, constraints, and guidelines apply?",
|
||||
]
|
||||
question_text = all_questions[int(q_num) - 1] if int(q_num) <= len(all_questions) else answer["question"]
|
||||
|
||||
fname = target.name
|
||||
if fname == "risks.md":
|
||||
risk_id = f"RISK-{q_num}"
|
||||
clean = answer["answer"].replace("\\|", "|").strip()
|
||||
row = f"| {risk_id} | {question_text}: {clean} | Medium | Monitor and review | Open |\n"
|
||||
elif fname == "assumptions.md":
|
||||
assump_id = f"ASSUMPTION-{q_num}"
|
||||
clean = answer["answer"].replace("\\|", "|").strip()
|
||||
row = f"| {assump_id} | {question_text}: {clean} | Medium | Yes |\n"
|
||||
else:
|
||||
row = f"| {answer['id']} | {question_text} | {answer['answer']} | Open |\n"
|
||||
|
||||
target.write_text(existing + "\n" + row, encoding="utf-8")
|
||||
|
||||
|
||||
def _append_new_section(target, answer, rules, file_path):
|
||||
"""Append a new section to the end of an existing file."""
|
||||
existing = target.read_text(encoding="utf-8") if target.exists() else ""
|
||||
|
||||
display_name = None
|
||||
for fpath, sec, _ in rules:
|
||||
if fpath == file_path and sec and sec.startswith("## "):
|
||||
display_name = sec
|
||||
break
|
||||
if not display_name:
|
||||
q_num = answer["id"].split("-")[1]
|
||||
display_name = f"## Q-{q_num} Discovery Answer"
|
||||
|
||||
section_text = (
|
||||
f"\n{display_name}\n\n"
|
||||
f"**Discovery Question:** {answer['question']}\n\n"
|
||||
f"**Answer:** {answer['answer'].strip()}\n\n"
|
||||
f"**Confidence:** {answer['confidence']}\n"
|
||||
)
|
||||
target.write_text(existing + section_text, encoding="utf-8")
|
||||
|
||||
|
||||
def _create_new_section(target, header, answer, fmt):
|
||||
"""Create a new section in a context file when it doesn't exist yet."""
|
||||
existing = target.read_text(encoding="utf-8") if target.exists() else ""
|
||||
|
||||
if not header:
|
||||
q_num = answer["id"].split("-")[1]
|
||||
header = f"## Q-{q_num} Discovery Answer"
|
||||
|
||||
section_text = (
|
||||
f"\n{header}\n\n"
|
||||
f"**Discovery Question:** {answer['question']}\n\n"
|
||||
f"**Answer:** {answer['answer'].strip()}\n\n"
|
||||
f"**Confidence:** {answer['confidence']}\n"
|
||||
)
|
||||
target.write_text(existing + section_text, encoding="utf-8")
|
||||
|
||||
|
||||
def _format_answer_text(answer):
|
||||
"""Format a discovery answer into readable body text."""
|
||||
return answer["answer"].replace("\\|", "|").strip()
|
||||
+66
-11
@@ -7,12 +7,21 @@ import re
|
||||
TASK_HEADING_RE = re.compile(r"^##\s+(TASK-\d+)\s+[-–—]\s+(.+)$", re.MULTILINE)
|
||||
STATUS_RE = re.compile(r"^Status:\s*(.+)$", re.MULTILINE)
|
||||
|
||||
|
||||
GOAL_RE = re.compile(r"^Goal:\s*(.+)", re.MULTILINE)
|
||||
AC_RE = re.compile(r"^- (.+)$", re.MULTILINE)
|
||||
GAP_RE = re.compile(r"^Implementation Gap:\s*(.+)", re.MULTILINE)
|
||||
|
||||
|
||||
def _indefinite_article(word: str) -> str:
|
||||
"""Return 'a' or 'an' based on the first letter of word."""
|
||||
if not word:
|
||||
return "an"
|
||||
first = word[0].lower()
|
||||
if first in "aeiou":
|
||||
return "an"
|
||||
return "a"
|
||||
|
||||
|
||||
@dataclass
|
||||
class Task:
|
||||
task_id: str
|
||||
@@ -20,6 +29,15 @@ class Task:
|
||||
status: str
|
||||
body: str
|
||||
|
||||
def role(self, root: Path) -> tuple[str, str]:
|
||||
if "Role:" not in self.body:
|
||||
return ("Implementation Agent", _indefinite_article("Implementation Agent"))
|
||||
parts = self.body.split("Role:", 1)
|
||||
value = parts[1].splitlines()[0].strip()
|
||||
if value:
|
||||
return (value, _indefinite_article(value))
|
||||
return ("Implementation Agent", _indefinite_article("Implementation Agent"))
|
||||
|
||||
def goal(self, root: Path) -> str:
|
||||
match = GOAL_RE.search(self.body)
|
||||
if match:
|
||||
@@ -91,7 +109,7 @@ def update_task_status(root: Path, task_id: str, new_status: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
CONTEXT_FILES_TO_READ = [
|
||||
DEFAULT_CONTEXT_FILES = [
|
||||
"README.md",
|
||||
"TASKS.md",
|
||||
"PROJECT_STATE.md",
|
||||
@@ -99,6 +117,38 @@ CONTEXT_FILES_TO_READ = [
|
||||
"context/agent-guidelines.md",
|
||||
]
|
||||
|
||||
ROLE_CONTEXT_FILES: dict[str, list[str]] = {
|
||||
"Architecture Agent": [
|
||||
"README.md",
|
||||
"TASKS.md",
|
||||
"PROJECT_STATE.md",
|
||||
"context/architecture.md",
|
||||
"context/product-brief.md",
|
||||
"context/decisions.md",
|
||||
],
|
||||
"Implementation Agent": [
|
||||
"README.md",
|
||||
"TASKS.md",
|
||||
"PROJECT_STATE.md",
|
||||
"AGENT_HANDOFF.md",
|
||||
"context/agent-guidelines.md",
|
||||
"CLAUDE.md",
|
||||
],
|
||||
"Documentation Agent": [
|
||||
"README.md",
|
||||
"TASKS.md",
|
||||
"PROJECT_STATE.md",
|
||||
"AGENT_HANDOFF.md",
|
||||
"context/agent-guidelines.md",
|
||||
"TEST_PLAN.md",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _context_files_for_role(role: str) -> list[str]:
|
||||
"""Return the context file list for a given role, falling back to default."""
|
||||
return ROLE_CONTEXT_FILES.get(role, DEFAULT_CONTEXT_FILES)
|
||||
|
||||
TEST_CMD_SECTION_RE = re.compile(
|
||||
r"^##\s*Test Commands\s*\n([\s\S]*?)(?=^##|\Z)",
|
||||
re.MULTILINE,
|
||||
@@ -167,20 +217,25 @@ def generate_agent_prompt(root: Path) -> str:
|
||||
if not task:
|
||||
return "No Todo task found."
|
||||
|
||||
role_text, article = task.role(root)
|
||||
context_files = _context_files_for_role(role_text)
|
||||
|
||||
sections = [
|
||||
"You are an implementation agent.",
|
||||
f"You are {article} {role_text.lower()}.",
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"# Read these files first",
|
||||
"",
|
||||
f"""- README.md
|
||||
- TASKS.md
|
||||
- PROJECT_STATE.md
|
||||
- AGENT_HANDOFF.md""",
|
||||
"",
|
||||
"- context/agent-guidelines.md",
|
||||
"",
|
||||
]
|
||||
for i, f in enumerate(context_files):
|
||||
line = f"- {f}"
|
||||
if i == len(context_files) - 1:
|
||||
sections.append(line)
|
||||
else:
|
||||
sections.append(line)
|
||||
|
||||
sections.extend([
|
||||
"---",
|
||||
"",
|
||||
"# Task",
|
||||
@@ -190,7 +245,7 @@ def generate_agent_prompt(root: Path) -> str:
|
||||
f"Status: {task.status}",
|
||||
"",
|
||||
f"Goal:\n{task.goal(root)}",
|
||||
]
|
||||
])
|
||||
|
||||
gap = task.implementation_gap(root)
|
||||
if gap:
|
||||
|
||||
+136
-18
@@ -13,6 +13,10 @@ CORE_QUESTIONS = [
|
||||
"What must not happen?",
|
||||
"How will we test it?",
|
||||
"How will it be deployed?",
|
||||
"Who owns and directs the project, and what are its organisational goals?",
|
||||
"What is the technology stack, repository structure, and architecture approach?",
|
||||
"Where is it hosted, which environments exist, and what are the security requirements?",
|
||||
"What agent and developer workflow tools, constraints, and guidelines apply?",
|
||||
]
|
||||
|
||||
CONTEXT_FILES: dict[str, str] = {
|
||||
@@ -37,24 +41,6 @@ TBD
|
||||
|
||||
## Minimum Useful Version
|
||||
|
||||
TBD
|
||||
""",
|
||||
"context/architecture.md": """# Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
TBD
|
||||
|
||||
## Components
|
||||
|
||||
TBD
|
||||
|
||||
## Integrations
|
||||
|
||||
TBD
|
||||
|
||||
## Deployment
|
||||
|
||||
TBD
|
||||
""",
|
||||
"context/decisions.md": """# Decisions
|
||||
@@ -127,6 +113,138 @@ DISCOVERY
|
||||
|
||||
| ID | Question | Reason | Owner | Status |
|
||||
|---|---|---|---|---|
|
||||
""",
|
||||
"context/company-context.md": """# Company Context
|
||||
|
||||
## Mission
|
||||
|
||||
TBD — What is the organisation's core mission?
|
||||
|
||||
## Products & Services
|
||||
|
||||
TBD — List of products and services offered.
|
||||
|
||||
## Customers / Users
|
||||
|
||||
TBD — Who are the primary customers or users?
|
||||
|
||||
## Brand & Positioning
|
||||
|
||||
TBD — How does the company position itself in the market?
|
||||
|
||||
## Key Stakeholders
|
||||
|
||||
TBD — Names, roles, and contact information.
|
||||
""",
|
||||
"context/development-context.md": """# Development Context
|
||||
|
||||
## Tech Stack
|
||||
|
||||
TBD — Languages, frameworks, libraries, and tooling.
|
||||
|
||||
## Coding Standards
|
||||
|
||||
TBD — Style guide conventions, naming patterns, and linting rules.
|
||||
|
||||
## Repository Structure
|
||||
|
||||
TBD — Overview of directory layout and module organisation.
|
||||
|
||||
## Build & Test
|
||||
|
||||
TBD — How to build, test, and run the codebase locally.
|
||||
|
||||
## Dependencies
|
||||
|
||||
TBD — External services, databases, and third-party APIs required.
|
||||
""",
|
||||
"context/infrastructure-context.md": """# Infrastructure Context
|
||||
|
||||
## Hosting
|
||||
|
||||
TBD — Where is the application hosted (cloud provider, on-prem, etc.)?
|
||||
|
||||
## Environments
|
||||
|
||||
TBD — Development, staging, production environment details.
|
||||
|
||||
## CI / CD Pipeline
|
||||
|
||||
TBD — Build, test, and deployment pipeline configuration.
|
||||
|
||||
## Monitoring & Alerting
|
||||
|
||||
TBD — Tools used for monitoring, logging, and alerting.
|
||||
|
||||
## Security
|
||||
|
||||
TBD — Authentication, data protection, and access control measures.
|
||||
""",
|
||||
"context/agent-guidelines.md": """# Agent Guidelines
|
||||
|
||||
## Purpose
|
||||
|
||||
TBD — What should agents know about working with this project?
|
||||
|
||||
## Preferred Tools
|
||||
|
||||
TBD — Recommended editors, debuggers, testing frameworks, and CLI tools.
|
||||
|
||||
## Testing Preferences
|
||||
|
||||
TBD — How the team prefers tests to be written, run, and verified.
|
||||
|
||||
## Common Tasks
|
||||
|
||||
TBD — Typical workflows for developers and AI coding agents.
|
||||
|
||||
## Known Gotchas
|
||||
|
||||
TBD — Pitfalls, quirks, or important caveats to be aware of.
|
||||
""",
|
||||
"context/project-brief.md": """# Project Brief
|
||||
|
||||
## Problem Statement
|
||||
|
||||
TBD — What problem does this project solve?
|
||||
|
||||
## Target Audience
|
||||
|
||||
TBD — Who is the intended audience?
|
||||
|
||||
## Key Features (MVP)
|
||||
|
||||
TBD — Minimum set of features for the first release.
|
||||
|
||||
## Success Metrics
|
||||
|
||||
TBD — How will success be measured?
|
||||
|
||||
## Timeline & Milestones
|
||||
|
||||
TBD — Expected delivery dates and key milestones.
|
||||
""",
|
||||
"context/architecture.md": """# Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
TBD — High-level system description and design principles.
|
||||
|
||||
## Core Components
|
||||
|
||||
TBD — Key modules, services, and their responsibilities.
|
||||
|
||||
## Data Flow
|
||||
|
||||
TBD — How data moves through the system (APIs, queues, databases).
|
||||
|
||||
## External Integrations
|
||||
|
||||
TBD — Third-party services, APIs, and dependencies.
|
||||
|
||||
## Deployment Architecture
|
||||
|
||||
TBD — Infrastructure layout, scaling strategy, and deployment process.
|
||||
""",
|
||||
"TASKS.md": """# TASKS.md
|
||||
|
||||
|
||||
@@ -0,0 +1,431 @@
|
||||
"""Tests for context health report (TASK-021)."""
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
|
||||
|
||||
def _get_project_root() -> Path:
|
||||
current = Path(__file__).resolve().parent.parent
|
||||
while current != current.parent:
|
||||
if (current / "pyproject.toml").exists() or (current / ".git").exists():
|
||||
return current
|
||||
current = current.parent
|
||||
return Path.cwd()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def tmp_project(tmp_path):
|
||||
"""Create a minimal project root with context files and discovery answers."""
|
||||
root = tmp_path / "testproject"
|
||||
root.mkdir()
|
||||
(root / "context").mkdir()
|
||||
|
||||
# Write discovery-log.md with various confidence levels
|
||||
(root / "context" / "discovery-log.md").write_text(
|
||||
"# Discovery Log\n\n"
|
||||
"| ID | Question | Answer | Confidence | Follow-up needed | Linked decision | Linked task | Date |\n"
|
||||
"|---|---|---|---|---|---|---|---|\n"
|
||||
'| Q-001 | What problem are we solving? | A CLI tool for discovery workflow | High | No | | | 2026-06-03 |\n'
|
||||
'| Q-002 | Who is the user? | Internal developers and small dev teams | Medium | No | | | 2026-06-03 |\n'
|
||||
'| Q-008 | What must not happen? | No third-party SaaS dependencies | Low | No | | | 2026-06-03 |\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write product-brief.md with TBD placeholders
|
||||
(root / "context" / "product-brief.md").write_text(
|
||||
"# Product Brief\n\n## Problem\n\nTBD\n\n## Users\n\nTBD\n\n## Success Criteria\n\nDone\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write architecture.md with some TBDs
|
||||
(root / "context" / "architecture.md").write_text(
|
||||
"# Architecture\n\n## Overview\n\nTDB — Not yet defined.\n\n## Core Components\n\nTBD\n\n## Data Flow\n\nDone\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write decisions.md (present, non-empty)
|
||||
(root / "context" / "decisions.md").write_text(
|
||||
"# Decisions\n\n| ID | Decision | Reason | Date |\n|---|---|---|---|\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write risks.md and assumptions.md
|
||||
(root / "context" / "risks.md").write_text(
|
||||
"# Risks\n\n| ID | Risk | Impact | Mitigation | Status |\n|---|---|---|---|---|\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(root / "context" / "assumptions.md").write_text(
|
||||
"# Assumptions\n\n| ID | Assumption | Confidence | Validation Needed |\n|---|---|---|---|\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write open-questions.md (present)
|
||||
(root / "context" / "open-questions.md").write_text(
|
||||
"# Open Questions\n\n| ID | Question | Reason | Owner | Status |\n|---|---|---|---|---|\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write company-context.md, development-context.md, infrastructure-context.md with TBDs
|
||||
(root / "context" / "company-context.md").write_text(
|
||||
"# Company Context\n\n## Mission\n\nTBD\n\n## Products & Services\n\nDone.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(root / "context" / "development-context.md").write_text(
|
||||
"# Development Context\n\n## Tech Stack\n\nPython 3.12, Typer, Rich.\n\n## Coding Standards\n\nTBD\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
(root / "context" / "infrastructure-context.md").write_text(
|
||||
"# Infrastructure Context\n\n## Hosting\n\nTBD — Cloud provider details.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write agent-guidelines.md and repository-context.md (present)
|
||||
(root / "context" / "agent-guidelines.md").write_text("# Agent Guidelines\n\nDone.\n", encoding="utf-8")
|
||||
(root / "context" / "repository-context.md").write_text("# Repository Context\n\nPurpose: Test project.\n", encoding="utf-8")
|
||||
|
||||
# Write PROJECT_STATE.md, TASKS.md, TEST_PLAN.md, RUN_LOG.md, AGENT_HANDOFF.md
|
||||
(root / "PROJECT_STATE.md").write_text("# Project State\n\nCurrent Stage: BUILDING\n", encoding="utf-8")
|
||||
(root / "TASKS.md").write_text("# TASKS.md\n## TASK-001\nStatus: Done\n", encoding="utf-8")
|
||||
(root / "TEST_PLAN.md").write_text("# TEST_PLAN.md\n## Tests\nDone.\n", encoding="utf-8")
|
||||
(root / "RUN_LOG.md").write_text("# RUN_LOG.md\n| Date | Event | Task | Notes |\n|---|---|---|---|\n", encoding="utf-8")
|
||||
(root / "AGENT_HANDOFF.md").write_text("# Agent Handoff\n## Current Stage\nBUILDING\n", encoding="utf-8")
|
||||
|
||||
# Create .rdb directory
|
||||
(root / ".rdb").mkdir()
|
||||
|
||||
return root
|
||||
|
||||
|
||||
class TestExpectedFiles:
|
||||
"""Tests for expected file reporting."""
|
||||
|
||||
def test_all_expected_files_returned(self, tmp_project):
|
||||
"""Every standard context file should appear in the expected list."""
|
||||
from rdb_discovery.context_status import _expected_files
|
||||
|
||||
files = _expected_files(tmp_project)
|
||||
assert len(files) == 18
|
||||
|
||||
def test_missing_file_detected(self, tmp_project):
|
||||
"""Files that don't exist should be marked as missing."""
|
||||
# project-brief.md is not created by this fixture
|
||||
(tmp_project / "context" / "project-brief.md").unlink(missing_ok=True)
|
||||
|
||||
from rdb_discovery.context_status import _check_expected_files
|
||||
|
||||
results = _check_expected_files(tmp_project)
|
||||
paths_by_status = {r["path"]: r["status"] for r in results}
|
||||
assert paths_by_status.get("context/project-brief.md") == "missing"
|
||||
|
||||
def test_present_file_detected(self, tmp_project):
|
||||
"""Files that exist should be marked as present."""
|
||||
from rdb_discovery.context_status import _check_expected_files
|
||||
|
||||
results = _check_expected_files(tmp_project)
|
||||
paths_by_status = {r["path"]: r["status"] for r in results}
|
||||
assert paths_by_status["TASKS.md"] == "present"
|
||||
assert paths_by_status["PROJECT_STATE.md"] == "present"
|
||||
|
||||
|
||||
class TestTbdDetection:
|
||||
"""Tests for TBD placeholder detection."""
|
||||
|
||||
def test_tbd_sections_detected(self, tmp_project):
|
||||
"""Sections with TBD/TDB placeholders should be reported."""
|
||||
from rdb_discovery.context_status import _check_tbd_sections
|
||||
|
||||
findings = _check_tbd_sections(tmp_project)
|
||||
assert len(findings) > 0
|
||||
|
||||
paths = {f["file"] for f in findings}
|
||||
assert "product-brief.md" in paths # has TBD sections
|
||||
assert "architecture.md" in paths # has TDB section
|
||||
|
||||
def test_no_tbd_in_clean_files(self, tmp_project):
|
||||
"""Files without TBD placeholders should not appear."""
|
||||
from rdb_discovery.context_status import _check_tbd_sections
|
||||
|
||||
findings = _check_tbd_sections(tmp_project)
|
||||
# TASKS.md has "Status: Done" — no TBD
|
||||
for f in findings:
|
||||
assert f["file"] != "TASKS.md" or not any(
|
||||
"TBD" in str(getattr(f, "placeholder", "")) or "TDB" in str(getattr(f, "placeholder", ""))
|
||||
for _ in [1]
|
||||
)
|
||||
|
||||
def test_tbd_sections_returns_empty_for_no_context_dir(self):
|
||||
"""Should return empty list when no context dir exists."""
|
||||
from rdb_discovery.context_status import _check_tbd_sections
|
||||
|
||||
findings = _check_tbd_sections(_get_project_root() / "nonexistent")
|
||||
assert findings == []
|
||||
|
||||
|
||||
class TestLowConfidence:
|
||||
"""Tests for low-confidence discovery answer detection."""
|
||||
|
||||
def test_low_confidence_detected(self, tmp_project):
|
||||
"""Low-confidence answers should be detected."""
|
||||
from rdb_discovery.context_status import _check_low_confidence
|
||||
|
||||
low = _check_low_confidence(tmp_project)
|
||||
assert len(low) == 1
|
||||
assert low[0]["id"] == "Q-008"
|
||||
assert low[0]["confidence"] == "Low"
|
||||
|
||||
def test_high_confidence_not_flagged(self, tmp_project):
|
||||
"""High-confidence answers should not appear in low_confidence results."""
|
||||
from rdb_discovery.context_status import _check_low_confidence
|
||||
|
||||
low = _check_low_confidence(tmp_project)
|
||||
ids = {a["id"] for a in low}
|
||||
assert "Q-001" not in ids # Q-001 is High confidence
|
||||
|
||||
def test_medium_confidence_not_flagged(self, tmp_project):
|
||||
"""Medium-confidence answers should not appear in low_confidence results."""
|
||||
from rdb_discovery.context_status import _check_low_confidence
|
||||
|
||||
low = _check_low_confidence(tmp_project)
|
||||
ids = {a["id"] for a in low}
|
||||
assert "Q-002" not in ids # Q-002 is Medium confidence
|
||||
|
||||
|
||||
class TestHealthScore:
|
||||
"""Tests for health score computation."""
|
||||
|
||||
def test_full_score_when_everything_complete(self):
|
||||
"""Score should be near 100 when all files present, no TBDs, no low-conf."""
|
||||
from rdb_discovery.context_status import compute_health_score
|
||||
|
||||
expected = [{"path": "a", "status": "present", "size": "0"} for _ in range(18)]
|
||||
score = compute_health_score(expected, tbd_sections=0, low_conf_count=0)
|
||||
# 45 (files) + 30 (no TBD) + 15 (no low-conf) = 90, discovery bonus later adds to it
|
||||
assert score >= 90
|
||||
|
||||
def test_score_degrades_with_missing_files(self):
|
||||
"""Score should decrease as files are missing."""
|
||||
from rdb_discovery.context_status import compute_health_score
|
||||
|
||||
full = [
|
||||
{"path": f"file{i}", "status": "present", "size": "0"} for i in range(18)
|
||||
]
|
||||
score_full = compute_health_score(full, tbd_sections=0, low_conf_count=0)
|
||||
|
||||
partial = [
|
||||
{"path": "a", "status": "present", "size": "0"},
|
||||
{"path": "b", "status": "missing", "size": "0"},
|
||||
{"path": "c", "status": "missing", "size": "0"},
|
||||
] + [
|
||||
{"path": f"file{i}", "status": "present", "size": "0"} for i in range(15)
|
||||
]
|
||||
score_partial = compute_health_score(partial, tbd_sections=0, low_conf_count=0)
|
||||
|
||||
assert score_full > score_partial
|
||||
|
||||
def test_score_degrades_with_tbd_sections(self):
|
||||
"""Score should decrease as TBD sections increase."""
|
||||
from rdb_discovery.context_status import compute_health_score
|
||||
|
||||
expected = [{"path": "a", "status": "present", "size": "0"} for _ in range(18)]
|
||||
score_0 = compute_health_score(expected, tbd_sections=0, low_conf_count=0)
|
||||
score_5 = compute_health_score(expected, tbd_sections=5, low_conf_count=0)
|
||||
score_20 = compute_health_score(expected, tbd_sections=20, low_conf_count=0)
|
||||
|
||||
assert score_0 > score_5
|
||||
assert score_5 > score_20
|
||||
# At 20+ TBDs, TBD score component is 0
|
||||
assert score_20 <= score_5 - 15
|
||||
|
||||
|
||||
class TestContextStatus:
|
||||
"""Tests for the main context_status function."""
|
||||
|
||||
def test_returns_score(self, tmp_project):
|
||||
"""context_status should return a score in the report."""
|
||||
from rdb_discovery.context_status import context_status
|
||||
|
||||
report = context_status(tmp_project)
|
||||
assert "score" in report
|
||||
assert 0 <= report["score"] <= 110 # allows for discovery bonus
|
||||
|
||||
def test_returns_summary(self, tmp_project):
|
||||
"""Report should contain a summary dict."""
|
||||
from rdb_discovery.context_status import context_status
|
||||
|
||||
report = context_status(tmp_project)
|
||||
assert "summary" in report
|
||||
assert "total_expected" in report["summary"]
|
||||
assert "missing" in report["summary"]
|
||||
|
||||
def test_returns_expected_files(self, tmp_project):
|
||||
"""Report should list all expected files."""
|
||||
from rdb_discovery.context_status import context_status
|
||||
|
||||
report = context_status(tmp_project)
|
||||
assert len(report["expected_files"]) == 18
|
||||
|
||||
def test_returns_tbd_sections(self, tmp_project):
|
||||
"""Report should include TBD section findings."""
|
||||
from rdb_discovery.context_status import context_status
|
||||
|
||||
report = context_status(tmp_project)
|
||||
assert "tbd_sections" in report
|
||||
assert len(report["tbd_sections"]) > 0
|
||||
|
||||
def test_returns_low_confidence(self, tmp_project):
|
||||
"""Report should include low-confidence answers."""
|
||||
from rdb_discovery.context_status import context_status
|
||||
|
||||
report = context_status(tmp_project)
|
||||
assert "low_confidence" in report
|
||||
assert len(report["low_confidence"]) == 1
|
||||
|
||||
|
||||
class TestCLICommand:
|
||||
"""Tests that the CLI 'context-status' command is available and works."""
|
||||
|
||||
def test_context_status_command_exists(self):
|
||||
"""The 'rdb context-status' command should be registered."""
|
||||
from typer.testing import CliRunner
|
||||
from rdb_discovery.cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(app, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "context-status" in result.output.lower()
|
||||
|
||||
def test_context_status_runs_successfully(self):
|
||||
"""The CLI command should exit cleanly."""
|
||||
from typer.testing import CliRunner
|
||||
from rdb_discovery.cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(app, ["context-status"], catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
|
||||
def test_context_status_output_contains_score(self):
|
||||
"""The output should contain the health score."""
|
||||
from typer.testing import CliRunner
|
||||
from rdb_discovery.cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(app, ["context-status"], catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
# Score is in format like "92.5/100"
|
||||
assert "/100" in result.output
|
||||
|
||||
def test_context_status_reports_missing_files(self):
|
||||
"""Missing files should be detected by the core function."""
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
root = Path(td) / "proj"
|
||||
root.mkdir()
|
||||
(root / "context").mkdir()
|
||||
# No context files created — everything missing
|
||||
|
||||
from rdb_discovery.context_status import _check_expected_files
|
||||
|
||||
results = _check_expected_files(root)
|
||||
paths_by_status = {r["path"]: r["status"] for r in results}
|
||||
assert paths_by_status.get("TASKS.md") == "missing"
|
||||
|
||||
def test_context_status_reports_tbd_sections(self):
|
||||
"""TBD placeholders should be detected by the core function."""
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
root = Path(td) / "proj"
|
||||
root.mkdir()
|
||||
(root / "context").mkdir()
|
||||
# Write a file with TBD placeholder
|
||||
(root / "context" / "product-brief.md").write_text(
|
||||
"# Product Brief\n\n## Problem\n\nTBD\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
from rdb_discovery.context_status import _check_tbd_sections
|
||||
|
||||
findings = _check_tbd_sections(root)
|
||||
assert len(findings) > 0
|
||||
paths = {f["file"] for f in findings}
|
||||
assert "product-brief.md" in paths
|
||||
|
||||
def test_context_status_reports_low_confidence(self):
|
||||
"""Low-confidence answers should be detected by the core function."""
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
root = Path(td) / "proj"
|
||||
root.mkdir()
|
||||
(root / "context").mkdir()
|
||||
# Write discovery-log.md with a Low-confidence answer
|
||||
(root / "context" / "discovery-log.md").write_text(
|
||||
"# Discovery Log\n\n"
|
||||
"| ID | Question | Answer | Confidence | Follow-up needed | Linked decision | Linked task | Date |\n"
|
||||
"|---|---|---|---|---|---|---|---|\n"
|
||||
'| Q-003 | What does success look like? | Not sure yet | Low | Yes | | | 2026-06-03 |\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
from rdb_discovery.context_status import _check_low_confidence
|
||||
|
||||
low = _check_low_confidence(root)
|
||||
assert len(low) == 1
|
||||
assert low[0]["id"] == "Q-003"
|
||||
|
||||
|
||||
class TestHealthScoreColor:
|
||||
"""Tests for health score color coding."""
|
||||
|
||||
def test_full_score_is_high(self):
|
||||
"""All files present, no TBDs, no low-conf should produce a high score."""
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
root = Path(td) / "proj"
|
||||
root.mkdir()
|
||||
(root / "context").mkdir()
|
||||
for f in [
|
||||
"discovery-log.md", "product-brief.md", "architecture.md",
|
||||
"decisions.md", "risks.md", "assumptions.md", "open-questions.md",
|
||||
]:
|
||||
(root / "context" / f).write_text(f"# {f}\nDone\n", encoding="utf-8")
|
||||
for f in ["TASKS.md", "TEST_PLAN.md", "RUN_LOG.md", "PROJECT_STATE.md", "AGENT_HANDOFF.md"]:
|
||||
(root / f).write_text(f"# {f}\nDone\n", encoding="utf-8")
|
||||
|
||||
from rdb_discovery.context_status import context_status
|
||||
|
||||
report = context_status(root)
|
||||
assert report["score"] >= 70
|
||||
|
||||
def test_empty_project_score_is_below_full(self):
|
||||
"""A completely empty project should score well below a full project."""
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
root = Path(td) / "proj"
|
||||
root.mkdir()
|
||||
# Don't create any files
|
||||
|
||||
from rdb_discovery.context_status import context_status
|
||||
|
||||
report = context_status(root)
|
||||
assert report["score"] < 70
|
||||
|
||||
def test_cli_score_appears_in_output(self):
|
||||
"""The CLI should display the numeric score in output."""
|
||||
from typer.testing import CliRunner
|
||||
from rdb_discovery.cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(app, ["context-status"], catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
# Score format is like "92.5/100" — digits followed by /100
|
||||
assert "/100" in result.output
|
||||
@@ -3,9 +3,9 @@ from pathlib import Path
|
||||
import rdb_discovery.discovery as discovery_mod
|
||||
|
||||
|
||||
def test_core_questions_returns_10_items() -> None:
|
||||
def test_core_questions_returns_at_least_10_items() -> None:
|
||||
questions = discovery_mod.core_questions()
|
||||
assert len(questions) == 10
|
||||
assert len(questions) >= 10
|
||||
|
||||
|
||||
def test_append_creates_file_when_missing(tmp_path: Path) -> None:
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
"""Tests for the discovery-to-context mapping document."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# Path to the mapping document (relative to project root)
|
||||
MAPPING_FILE = "context/discovery-context-mapping.md"
|
||||
|
||||
|
||||
def _get_project_root() -> Path:
|
||||
"""Return the project root directory.
|
||||
|
||||
Walk up from this file's location to find the repo root.
|
||||
"""
|
||||
# Start from tests/ and walk up
|
||||
current = Path(__file__).resolve().parent.parent
|
||||
# Look for pyproject.toml or .git as markers
|
||||
while current != current.parent:
|
||||
if (current / "pyproject.toml").exists() or (current / ".git").exists():
|
||||
return current
|
||||
current = current.parent
|
||||
return Path.cwd()
|
||||
|
||||
|
||||
def test_mapping_file_exists() -> None:
|
||||
"""The discovery-to-context mapping file must exist."""
|
||||
root = _get_project_root()
|
||||
mapping_path = root / MAPPING_FILE
|
||||
assert mapping_path.exists(), f"Mapping file missing: {mapping_path}"
|
||||
|
||||
|
||||
def test_mapping_contains_all_questions() -> None:
|
||||
"""Every core discovery question must appear in the mapping."""
|
||||
from rdb_discovery.templates import CORE_QUESTIONS
|
||||
|
||||
root = _get_project_root()
|
||||
mapping_path = root / MAPPING_FILE
|
||||
content = mapping_path.read_text(encoding="utf-8")
|
||||
|
||||
for question in CORE_QUESTIONS:
|
||||
assert question in content, f"Question not mapped: {question}"
|
||||
|
||||
|
||||
def test_mapping_documentation_contains_table_format() -> None:
|
||||
"""The mapping must be documented in a table format (machine-readable)."""
|
||||
root = _get_project_root()
|
||||
mapping_path = root / MAPPING_FILE
|
||||
content = mapping_path.read_text(encoding="utf-8")
|
||||
|
||||
# Accept both the exact header and any variant that contains the key phrase
|
||||
has_section = ("Mapping: Question" in content or "Question → Context" in content)
|
||||
assert has_section, (
|
||||
"Missing 'Mapping: Question -> Context Files + Sections' section header"
|
||||
)
|
||||
|
||||
|
||||
def test_mapping_contains_summary_matrix() -> None:
|
||||
"""A summary question-to-file matrix table must exist."""
|
||||
root = _get_project_root()
|
||||
mapping_path = root / MAPPING_FILE
|
||||
content = mapping_path.read_text(encoding="utf-8")
|
||||
|
||||
assert "Question-to-File Matrix" in content, (
|
||||
"Missing 'Question-to-File Matrix' summary section"
|
||||
)
|
||||
|
||||
|
||||
def test_mapping_contains_future_implementation_notes() -> None:
|
||||
"""The mapping must define future implementation guidance."""
|
||||
root = _get_project_root()
|
||||
mapping_path = root / MAPPING_FILE
|
||||
content = mapping_path.read_text(encoding="utf-8")
|
||||
|
||||
assert "## Implementation Notes" in content or "## What future implementation should do" in content, (
|
||||
"Missing 'Implementation Notes' section"
|
||||
)
|
||||
# Verify it clearly states no code has been implemented yet
|
||||
assert "No code generation has been implemented" in content, (
|
||||
"Must clarify that mapping is a planning artifact with no code yet"
|
||||
)
|
||||
|
||||
|
||||
def test_mapping_covers_all_context_files() -> None:
|
||||
"""Every context file listed in templates.py must appear in the mapping."""
|
||||
from rdb_discovery.templates import CONTEXT_FILES
|
||||
|
||||
# Filter out control files (TASKS.md, PROJECT_STATE.md, etc.) and keep context files
|
||||
context_paths = {p for p in CONTEXT_FILES if p.startswith("context/")}
|
||||
|
||||
root = _get_project_root()
|
||||
mapping_path = root / MAPPING_FILE
|
||||
content = mapping_path.read_text(encoding="utf-8")
|
||||
|
||||
# Extract file paths from the mapping's Reference section
|
||||
found_files = set()
|
||||
for line in content.splitlines():
|
||||
for cp in context_paths:
|
||||
if f"`{cp}`" in line or cp in line:
|
||||
found_files.add(cp)
|
||||
|
||||
for cp in context_paths:
|
||||
assert any(cp in fp for fp in found_files), (
|
||||
f"Context file not referenced in mapping: {cp}"
|
||||
)
|
||||
@@ -0,0 +1,459 @@
|
||||
"""Tests for context file generation from discovery answers."""
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
|
||||
# Use a helper to find the project root
|
||||
def _get_project_root() -> Path:
|
||||
current = Path(__file__).resolve().parent.parent
|
||||
while current != current.parent:
|
||||
if (current / "pyproject.toml").exists() or (current / ".git").exists():
|
||||
return current
|
||||
current = current.parent
|
||||
return Path.cwd()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def tmp_project(tmp_path):
|
||||
"""Create a minimal project root with discovery-log.md and context files."""
|
||||
root = tmp_path / "testproject"
|
||||
root.mkdir()
|
||||
|
||||
# Create directory structure
|
||||
(root / "context").mkdir()
|
||||
|
||||
# Write a discovery log with various confidence levels
|
||||
(root / "context" / "discovery-log.md").write_text(
|
||||
"# Discovery Log\n\n"
|
||||
"| ID | Question | Answer | Confidence | Follow-up needed | Linked decision | Linked task | Date |\n"
|
||||
"|---|---|---|---|---|---|---|---|\n"
|
||||
'| Q-001 | What problem are we solving? | \\|A CLI tool for discovery workflow\\| | High | No | | | 2026-06-03 |\n'
|
||||
'| Q-002 | Who is the user? | Internal developers and small dev teams | Medium | No | | | 2026-06-03 |\n'
|
||||
'| Q-003 | What does success look like? | 80% reduction in onboarding time | High | No | | | 2026-06-03 |\n'
|
||||
'| Q-007 | What are the risks? | Scope creep if requirements change frequently | Medium | Yes | | | 2026-06-03 |\n'
|
||||
'| Q-008 | What must not happen? | No third-party SaaS dependencies | Low | No | | | 2026-06-03 |\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write product-brief.md with TBD placeholders
|
||||
(root / "context" / "product-brief.md").write_text(
|
||||
"# Product Brief\n\n## Problem\n\nTBD\n\n## Users\n\nTBD\n\n## Success Criteria\n\nTBD\n\n## Minimum Useful Version\n\nTBD\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write architecture.md with TBD placeholders
|
||||
(root / "context" / "architecture.md").write_text(
|
||||
"# Architecture\n\n## Overview\n\nTBD\n\n## Core Components\n\nTBD\n\n## Data Flow\n\nTDB\n\n## External Integrations\n\nTBD\n\n## Deployment Architecture\n\nTBD\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write risks.md with existing table header only
|
||||
(root / "context" / "risks.md").write_text(
|
||||
"# Risks\n\n| ID | Risk | Impact | Mitigation | Status |\n|---|---|---|---|---|\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write assumptions.md with existing table header only
|
||||
(root / "context" / "assumptions.md").write_text(
|
||||
"# Assumptions\n\n| ID | Assumption | Confidence | Validation Needed |\n|---|---|---|---|\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write infrastructure-context.md with existing content
|
||||
(root / "context" / "infrastructure-context.md").write_text(
|
||||
"# Infrastructure Context\n\n## Hosting\n\nTBD — Cloud provider details.\n\n## Environments\n\nTBD — Dev/staging/prod setup.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Write development-context.md with existing content (non-TBD)
|
||||
(root / "context" / "development-context.md").write_text(
|
||||
"# Development Context\n\n## Tech Stack\n\nPython 3.12, Typer, Rich.\n\n## IDEs and Editors\n\nVS Code and Cursor.\n\n## Dependencies\n\nPostgreSQL, Docker.\n\n## Build & Test\n\npytest for unit tests.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
return root
|
||||
|
||||
|
||||
class TestGenerateContextFiles:
|
||||
"""Tests for the generate_context_files function."""
|
||||
|
||||
def test_mapping_file_exists(self):
|
||||
"""The mapping document must exist."""
|
||||
root = _get_project_root()
|
||||
mapping_path = root / "context/discovery-context-mapping.md"
|
||||
assert mapping_path.exists()
|
||||
|
||||
def test_mapping_covers_all_questions(self):
|
||||
"""All core questions should have mappings."""
|
||||
from rdb_discovery.generate_context import CONTEXT_MAP
|
||||
from rdb_discovery.templates import CORE_QUESTIONS
|
||||
|
||||
expected_count = len(CORE_QUESTIONS)
|
||||
for i in range(1, expected_count + 1):
|
||||
qid = f"Q-{i:03d}"
|
||||
assert qid in CONTEXT_MAP, f"Missing mapping for {qid}"
|
||||
|
||||
def test_mapping_all_target_files_are_valid(self):
|
||||
"""All target files in the map should exist as templates."""
|
||||
from rdb_discovery.generate_context import CONTEXT_MAP
|
||||
from rdb_discovery.templates import CONTEXT_FILES
|
||||
|
||||
template_paths = set(CONTEXT_FILES.keys()) | {"TASKS.md", "TEST_PLAN.md"}
|
||||
|
||||
for qid, targets in CONTEXT_MAP.items():
|
||||
for file_path, _, _ in targets:
|
||||
# The file should either be a known template or be context/
|
||||
assert any(file_path.startswith(p.rstrip('/').split('/')[-1] if '/' not in p else '')
|
||||
for p in template_paths) or 'context/' in file_path, \
|
||||
f"{qid} maps to unknown file: {file_path}"
|
||||
|
||||
def test_no_discovery_answers_returns_skipped(self, tmp_project):
|
||||
"""When no discovery answers exist, return skipped message."""
|
||||
# Overwrite discovery log with empty content
|
||||
(tmp_project / "context" / "discovery-log.md").write_text(
|
||||
"# Discovery Log\n\n| ID | Question | Answer | Confidence | Follow-up needed | Linked decision | Linked task | Date |\n|---|---|---|---|---|---|---|---|\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
from rdb_discovery.generate_context import generate_context_files
|
||||
|
||||
result = generate_context_files(tmp_project)
|
||||
assert result["generated"] == []
|
||||
assert len(result["skipped"]) > 0
|
||||
assert "No discovery answers" in result["skipped"][0]
|
||||
|
||||
|
||||
class TestLowConfidenceFiltering:
|
||||
"""Tests for low-confidence answer filtering."""
|
||||
|
||||
def test_low_confidence_answers_are_skipped(self, tmp_project):
|
||||
"""Answers with Low confidence should not generate any context content."""
|
||||
from rdb_discovery.generate_context import generate_context_files
|
||||
|
||||
# Overwrite to have only Low confidence answers
|
||||
(tmp_project / "context" / "discovery-log.md").write_text(
|
||||
"# Discovery Log\n\n"
|
||||
"| ID | Question | Answer | Confidence | Follow-up needed | Linked decision | Linked task | Date |\n"
|
||||
"|---|---|---|---|---|---|---|---|\n"
|
||||
'| Q-001 | What problem are we solving? | Some answer | Low | No | | | 2026-06-03 |\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
result = generate_context_files(tmp_project)
|
||||
assert result["generated"] == []
|
||||
assert "skipped" in result
|
||||
assert len(result["skipped"]) > 0
|
||||
|
||||
|
||||
class TestBodyTextGeneration:
|
||||
"""Tests for body-text section filling."""
|
||||
|
||||
def test_tbd_placeholder_replaced(self, tmp_project):
|
||||
"""TBD placeholders should be replaced with discovery answer."""
|
||||
from rdb_discovery.generate_context import generate_context_files
|
||||
|
||||
result = generate_context_files(tmp_project)
|
||||
assert "context/product-brief.md" in result["generated"]
|
||||
|
||||
content = (tmp_project / "context/product-brief.md").read_text()
|
||||
# Q-001 answer should replace TBD under ## Problem
|
||||
assert "CLI tool for discovery workflow" in content
|
||||
assert "## Problem" in content
|
||||
# The rest of the file must be preserved
|
||||
assert "## Success Criteria" in content
|
||||
assert "## Minimum Useful Version" in content
|
||||
|
||||
|
||||
class TestTableGeneration:
|
||||
"""Tests for table-based section row generation."""
|
||||
|
||||
def test_risks_table_gets_row(self, tmp_project):
|
||||
"""Q-007 (risks) should add a row to risks.md table."""
|
||||
from rdb_discovery.generate_context import generate_context_files
|
||||
|
||||
result = generate_context_files(tmp_project)
|
||||
assert "context/risks.md" in result["generated"]
|
||||
|
||||
content = (tmp_project / "context/risks.md").read_text()
|
||||
assert "| RISK-007 |" in content
|
||||
assert "Scope creep" in content
|
||||
|
||||
|
||||
class TestContentPreservation:
|
||||
"""Tests that existing content is preserved."""
|
||||
|
||||
def test_existing_development_context_preserved(self, tmp_project):
|
||||
"""Existing non-TBD content should be preserved and new content appended."""
|
||||
from rdb_discovery.generate_context import generate_context_files
|
||||
|
||||
generate_context_files(tmp_project)
|
||||
|
||||
content = (tmp_project / "context/development-context.md").read_text()
|
||||
assert "Python 3.12, Typer, Rich." in content
|
||||
assert "VS Code and Cursor." in content
|
||||
|
||||
|
||||
class TestContextMapCompleteness:
|
||||
"""Tests for mapping document completeness."""
|
||||
|
||||
def test_all_questions_mapped(self):
|
||||
"""Every core question should have a mapping entry."""
|
||||
from rdb_discovery.generate_context import CONTEXT_MAP
|
||||
from rdb_discovery.templates import CORE_QUESTIONS
|
||||
|
||||
expected_count = len(CORE_QUESTIONS)
|
||||
expected_ids = {f"Q-{i:03d}" for i in range(1, expected_count + 1)}
|
||||
mapped_ids = set(CONTEXT_MAP.keys())
|
||||
assert expected_ids == mapped_ids, f"Missing mappings: {expected_ids - mapped_ids}"
|
||||
|
||||
def test_each_question_has_at_least_one_target(self):
|
||||
"""Every question mapping should target at least one file."""
|
||||
from rdb_discovery.generate_context import CONTEXT_MAP
|
||||
|
||||
for qid, targets in CONTEXT_MAP.items():
|
||||
assert len(targets) >= 1, f"{qid} has no target files"
|
||||
for file_path, section_name, fmt in targets:
|
||||
assert file_path, f"{qid}: empty file path"
|
||||
assert fmt in ("body", "table", "append"), f"{qid}: invalid format {fmt}"
|
||||
|
||||
|
||||
class TestCLICommand:
|
||||
"""Tests that the CLI 'generate' command is available."""
|
||||
|
||||
def test_generate_command_exists(self):
|
||||
"""The 'rdb generate' command should be registered in the CLI."""
|
||||
from typer.testing import CliRunner
|
||||
from rdb_discovery.cli import app
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(app, ["--help"])
|
||||
assert result.exit_code == 0
|
||||
assert "generate" in result.output.lower() or "-g" in result.output.lower()
|
||||
|
||||
def test_generate_command_with_empty_discovery(self):
|
||||
"""The CLI generate command handles empty discovery gracefully."""
|
||||
from typer.testing import CliRunner
|
||||
from rdb_discovery.cli import app
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
root = Path(td) / "proj"
|
||||
root.mkdir()
|
||||
(root / "context").mkdir()
|
||||
(root / "context" / "discovery-log.md").write_text(
|
||||
"# Discovery Log\n\n| ID | Question |\n|---|---|\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(app, ["generate"], catch_exceptions=False)
|
||||
assert result.exit_code == 0
|
||||
|
||||
def test_generate_creates_missing_files(self):
|
||||
"""Generate should create context files that don't yet exist."""
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
tmp_path = Path(tempfile.mkdtemp())
|
||||
root = tmp_path / "testproject"
|
||||
root.mkdir()
|
||||
(root / "context").mkdir()
|
||||
|
||||
# Write only discovery-log.md — no context files
|
||||
(root / "context" / "discovery-log.md").write_text(
|
||||
"# Discovery Log\n\n"
|
||||
"| ID | Question | Answer | Confidence | Follow-up needed | Linked decision | Linked task | Date |\n"
|
||||
"|---|---|---|---|---|---|---|---|\n"
|
||||
'| Q-007 | What are the risks? | Some risk description | High | No | | | 2026-06-03 |\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
from rdb_discovery.generate_context import generate_context_files
|
||||
|
||||
result = generate_context_files(root)
|
||||
assert "context/risks.md" in result["generated"]
|
||||
assert (root / "context" / "risks.md").exists()
|
||||
|
||||
shutil.rmtree(tmp_path)
|
||||
|
||||
|
||||
class TestIntegration:
|
||||
"""End-to-end integration tests for the full generation pipeline."""
|
||||
|
||||
def test_full_generation_flow(self, tmp_project):
|
||||
"""All mapped answers are written to correct files with correct confidence filtering."""
|
||||
from rdb_discovery.generate_context import generate_context_files
|
||||
|
||||
result = generate_context_files(tmp_project)
|
||||
|
||||
# High/Medium answers should be generated
|
||||
assert "context/product-brief.md" in result["generated"]
|
||||
assert "context/project-brief.md" in result["generated"]
|
||||
assert "context/risks.md" in result["generated"]
|
||||
assert "context/company-context.md" in result["generated"] # Q-002 → company-context
|
||||
assert "context/assumptions.md" not in result["generated"] # Q-008 is Low confidence
|
||||
|
||||
# Check that product-brief has filled sections
|
||||
content = (tmp_project / "context/product-brief.md").read_text()
|
||||
assert "CLI tool for discovery workflow" in content
|
||||
assert "Internal developers and small dev teams" in content
|
||||
|
||||
# Check company-context got users data from Q-002
|
||||
company = (tmp_project / "context/company-context.md").read_text()
|
||||
assert "## Customers / Users" in company
|
||||
assert "Internal developers and small dev teams" in company
|
||||
|
||||
# Check that risks.md got a table row
|
||||
risks = (tmp_project / "context/risks.md").read_text()
|
||||
assert "| RISK-007 |" in risks
|
||||
assert "Scope creep" in risks
|
||||
|
||||
def test_preserves_existing_infrastructure_content(self, tmp_project):
|
||||
"""Existing content in infrastructure-context.md is preserved."""
|
||||
from rdb_discovery.generate_context import generate_context_files
|
||||
|
||||
original = (tmp_project / "context/infrastructure-context.md").read_text()
|
||||
|
||||
generate_context_files(tmp_project)
|
||||
|
||||
result = (tmp_project / "context/infrastructure-context.md").read_text()
|
||||
assert "Cloud provider details" in result # Original content preserved
|
||||
|
||||
|
||||
class TestContextQuality:
|
||||
"""Tests for TASK-023: improved context generation quality."""
|
||||
|
||||
def test_company_context_populated_from_users(self):
|
||||
"""company-context.md should be populated when discovery answers include users/stakeholders."""
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
tmp_path = Path(tempfile.mkdtemp())
|
||||
root = tmp_path / "testproject"
|
||||
root.mkdir()
|
||||
(root / "context").mkdir()
|
||||
|
||||
# Only Q-002 (users) - no other discovery data
|
||||
(root / "context" / "discovery-log.md").write_text(
|
||||
"# Discovery Log\n\n"
|
||||
"| ID | Question | Answer | Confidence | Follow-up needed | Linked decision | Linked task | Date |\n"
|
||||
"|---|---|---|---|---|---|---|---|\n"
|
||||
'| Q-002 | Who is the user? | Internal dev teams and external customers | High | No | | | 2026-06-03 |\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
from rdb_discovery.generate_context import generate_context_files
|
||||
|
||||
result = generate_context_files(root)
|
||||
assert "context/company-context.md" in result["generated"]
|
||||
assert "context/product-brief.md" in result["generated"]
|
||||
|
||||
company = (root / "context/company-context.md").read_text()
|
||||
assert "## Customers / Users" in company
|
||||
assert "Internal dev teams and external customers" in company
|
||||
|
||||
shutil.rmtree(tmp_path)
|
||||
|
||||
def test_agent_guidelines_gets_testing_preferences(self):
|
||||
"""agent-guidelines.md should get Testing Preferences from testing discovery answers."""
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
tmp_path = Path(tempfile.mkdtemp())
|
||||
root = tmp_path / "testproject"
|
||||
root.mkdir()
|
||||
(root / "context").mkdir()
|
||||
|
||||
# Only Q-009 (testing) - no agent tooling questions
|
||||
(root / "context" / "discovery-log.md").write_text(
|
||||
"# Discovery Log\n\n"
|
||||
"| ID | Question | Answer | Confidence | Follow-up needed | Linked decision | Linked task | Date |\n"
|
||||
"|---|---|---|---|---|---|---|---|\n"
|
||||
'| Q-009 | How will we test it? | pytest with coverage, integration tests via docker-compose | Medium | No | | | 2026-06-03 |\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
from rdb_discovery.generate_context import generate_context_files
|
||||
|
||||
result = generate_context_files(root)
|
||||
assert "context/agent-guidelines.md" in result["generated"]
|
||||
|
||||
guidelines = (root / "context/agent-guidelines.md").read_text()
|
||||
assert "## Testing Preferences" in guidelines
|
||||
assert "pytest with coverage" in guidelines
|
||||
|
||||
shutil.rmtree(tmp_path)
|
||||
|
||||
def test_no_testing_answers_in_timeline(self):
|
||||
"""Testing answers must not be mapped to Timeline & Milestones sections."""
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
tmp_path = Path(tempfile.mkdtemp())
|
||||
root = tmp_path / "testproject"
|
||||
root.mkdir()
|
||||
(root / "context").mkdir()
|
||||
|
||||
# Only Q-009 (testing) - verify no timeline data appears
|
||||
(root / "context" / "discovery-log.md").write_text(
|
||||
"# Discovery Log\n\n"
|
||||
"| ID | Question | Answer | Confidence | Follow-up needed | Linked decision | Linked task | Date |\n"
|
||||
"|---|---|---|---|---|---|---|\n"
|
||||
'| Q-009 | How will we test it? | pytest with coverage | High | No | | | 2026-06-03 |\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
# Create project-brief.md that has a Timeline section already
|
||||
(root / "context" / "project-brief.md").write_text(
|
||||
"# Project Brief\n\n## Timeline & Milestones\n\nTBD\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
from rdb_discovery.generate_context import generate_context_files
|
||||
|
||||
result = generate_context_files(root)
|
||||
assert "context/project-brief.md" not in result["generated"]
|
||||
|
||||
timeline_content = (root / "context/project-brief.md").read_text()
|
||||
# The Timeline section should remain as TBD (not filled with testing data)
|
||||
assert "pytest with coverage" not in timeline_content
|
||||
# Original TBD placeholder preserved
|
||||
assert "TBD" in timeline_content
|
||||
|
||||
shutil.rmtree(tmp_path)
|
||||
|
||||
def test_repository_context_populated_from_dependencies(self):
|
||||
"""repository-context.md should be populated when answers include dependencies."""
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
tmp_path = Path(tempfile.mkdtemp())
|
||||
root = tmp_path / "testproject"
|
||||
root.mkdir()
|
||||
(root / "context").mkdir()
|
||||
|
||||
# Q-012 includes technology stack info that maps to repository dependencies
|
||||
(root / "context" / "discovery-log.md").write_text(
|
||||
"# Discovery Log\n\n"
|
||||
"| ID | Question | Answer | Confidence | Follow-up needed | Linked decision | Linked task | Date |\n"
|
||||
"|---|---|---|---|---|---|---|---|\n"
|
||||
'| Q-012 | What is the technology stack, repository structure, and architecture approach? | Python 3.12, Typer, Rich; Docker; PostgreSQL | High | No | | | 2026-06-03 |\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
from rdb_discovery.generate_context import generate_context_files
|
||||
|
||||
result = generate_context_files(root)
|
||||
assert "context/repository-context.md" in result["generated"]
|
||||
|
||||
repo_ctx = (root / "context/repository-context.md").read_text()
|
||||
assert "## Dependencies" in repo_ctx
|
||||
assert "Python 3.12, Typer, Rich" in repo_ctx
|
||||
|
||||
shutil.rmtree(tmp_path)
|
||||
@@ -259,3 +259,157 @@ def test_extract_test_commands_no_section(tmp_path: Path) -> None:
|
||||
|
||||
commands = _extract_test_commands_from_claude(tmp_path)
|
||||
assert commands == []
|
||||
|
||||
|
||||
def test_generate_agent_prompt_with_role(tmp_path: Path) -> None:
|
||||
(tmp_path / "TASKS.md").write_text(
|
||||
"# TASKS\n\n"
|
||||
"## TASK-001 — First\nStatus: Done\n\n"
|
||||
"Goal: x.\n\n"
|
||||
"## TASK-002 — Second\nStatus: Todo\n\n"
|
||||
"Role: Architecture Agent\n"
|
||||
"Goal: y.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
prompt = generate_agent_prompt(tmp_path)
|
||||
|
||||
assert "You are an architecture agent." in prompt
|
||||
|
||||
|
||||
def test_generate_agent_prompt_default_role_when_missing(tmp_path: Path) -> None:
|
||||
(tmp_path / "TASKS.md").write_text(
|
||||
"# TASKS\n\n"
|
||||
"## TASK-001 — First\nStatus: Done\n\n"
|
||||
"Goal: x.\n\n"
|
||||
"## TASK-002 — Second\nStatus: Todo\n\n"
|
||||
"Goal: y.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
prompt = generate_agent_prompt(tmp_path)
|
||||
|
||||
assert "You are an implementation agent." in prompt
|
||||
|
||||
|
||||
def test_generate_agent_prompt_with_empty_role_defaults(tmp_path: Path) -> None:
|
||||
(tmp_path / "TASKS.md").write_text(
|
||||
"# TASKS\n\n"
|
||||
"## TASK-001 — First\nStatus: Done\n\n"
|
||||
"Goal: x.\n\n"
|
||||
"## TASK-002 — Second\nStatus: Todo\n\n"
|
||||
"Role:\n"
|
||||
"Goal: y.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
prompt = generate_agent_prompt(tmp_path)
|
||||
|
||||
assert "You are an implementation agent." in prompt
|
||||
|
||||
|
||||
def test_generate_agent_prompt_with_custom_role(tmp_path: Path) -> None:
|
||||
(tmp_path / "TASKS.md").write_text(
|
||||
"# TASKS\n\n"
|
||||
"## TASK-001 — First\nStatus: Done\n\n"
|
||||
"Goal: x.\n\n"
|
||||
"## TASK-002 — Second\nStatus: Todo\n\n"
|
||||
"Role: DevOps Engineer\n"
|
||||
"Goal: y.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
prompt = generate_agent_prompt(tmp_path)
|
||||
|
||||
assert "You are a devops engineer." in prompt
|
||||
|
||||
|
||||
def test_architecture_agent_receives_architecture_context(tmp_path: Path) -> None:
|
||||
(tmp_path / "TASKS.md").write_text(
|
||||
"# TASKS\n\n"
|
||||
"## TASK-001 — First\nStatus: Done\n\n"
|
||||
"Goal: x.\n\n"
|
||||
"## TASK-002 — Second\nStatus: Todo\n\n"
|
||||
"Role: Architecture Agent\n"
|
||||
"Goal: y.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
prompt = generate_agent_prompt(tmp_path)
|
||||
|
||||
assert "context/architecture.md" in prompt
|
||||
assert "context/product-brief.md" in prompt
|
||||
assert "context/decisions.md" in prompt
|
||||
|
||||
|
||||
def test_implementation_agent_receives_implementation_context(tmp_path: Path) -> None:
|
||||
(tmp_path / "TASKS.md").write_text(
|
||||
"# TASKS\n\n"
|
||||
"## TASK-001 — First\nStatus: Done\n\n"
|
||||
"Goal: x.\n\n"
|
||||
"## TASK-002 — Second\nStatus: Todo\n\n"
|
||||
"Role: Implementation Agent\n"
|
||||
"Goal: y.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
prompt = generate_agent_prompt(tmp_path)
|
||||
|
||||
assert "AGENT_HANDOFF.md" in prompt
|
||||
assert "context/agent-guidelines.md" in prompt
|
||||
assert "CLAUDE.md" in prompt
|
||||
|
||||
|
||||
def test_documentation_agent_receives_documentation_context(tmp_path: Path) -> None:
|
||||
(tmp_path / "TASKS.md").write_text(
|
||||
"# TASKS\n\n"
|
||||
"## TASK-001 — First\nStatus: Done\n\n"
|
||||
"Goal: x.\n\n"
|
||||
"## TASK-002 — Second\nStatus: Todo\n\n"
|
||||
"Role: Documentation Agent\n"
|
||||
"Goal: y.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
prompt = generate_agent_prompt(tmp_path)
|
||||
|
||||
assert "TEST_PLAN.md" in prompt
|
||||
assert "AGENT_HANDOFF.md" in prompt
|
||||
|
||||
|
||||
def test_context_files_differ_by_role(tmp_path: Path) -> None:
|
||||
(tmp_path / "TASKS.md").write_text(
|
||||
"# TASKS\n\n"
|
||||
"## TASK-001 — First\nStatus: Done\n\n"
|
||||
"Goal: x.\n\n"
|
||||
"## TASK-002 — Architect\nStatus: Todo\n\n"
|
||||
"Role: Architecture Agent\n"
|
||||
"Goal: y.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
prompt = generate_agent_prompt(tmp_path)
|
||||
|
||||
# Architecture role should have architecture-specific files
|
||||
assert "context/architecture.md" in prompt
|
||||
|
||||
|
||||
def test_no_role_uses_default_context(tmp_path: Path) -> None:
|
||||
"""When no Role is set, the default file list is used (backward compatible)."""
|
||||
(tmp_path / "TASKS.md").write_text(
|
||||
"# TASKS\n\n"
|
||||
"## TASK-001 — First\nStatus: Done\n\n"
|
||||
"Goal: x.\n\n"
|
||||
"## TASK-002 — Second\nStatus: Todo\n\n"
|
||||
"Goal: y.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
prompt = generate_agent_prompt(tmp_path)
|
||||
|
||||
# Default files must still be present
|
||||
assert "README.md" in prompt
|
||||
assert "TASKS.md" in prompt
|
||||
assert "PROJECT_STATE.md" in prompt
|
||||
assert "AGENT_HANDOFF.md" in prompt
|
||||
assert "context/agent-guidelines.md" in prompt
|
||||
|
||||
@@ -36,3 +36,33 @@ def test_init_skips_existing_files(tmp_path: Path) -> None:
|
||||
|
||||
assert skipped == list(CONTEXT_FILES)
|
||||
assert (tmp_path / "TASKS.md").read_text(encoding="utf-8") == "existing"
|
||||
|
||||
|
||||
def test_required_context_templates_exist() -> None:
|
||||
"""Ensure all six required standard context files are defined."""
|
||||
required = [
|
||||
"context/company-context.md",
|
||||
"context/development-context.md",
|
||||
"context/infrastructure-context.md",
|
||||
"context/agent-guidelines.md",
|
||||
"context/project-brief.md",
|
||||
"context/architecture.md",
|
||||
]
|
||||
for rel_path in required:
|
||||
assert rel_path in CONTEXT_FILES, f"Missing template: {rel_path}"
|
||||
|
||||
|
||||
def test_required_templates_have_headings_and_guidance() -> None:
|
||||
"""Each required context file must contain headings and placeholder guidance."""
|
||||
required = [
|
||||
"context/company-context.md",
|
||||
"context/development-context.md",
|
||||
"context/infrastructure-context.md",
|
||||
"context/agent-guidelines.md",
|
||||
"context/project-brief.md",
|
||||
"context/architecture.md",
|
||||
]
|
||||
for rel_path in required:
|
||||
content = CONTEXT_FILES[rel_path]
|
||||
assert "# " in content, f"{rel_path} missing H1 heading"
|
||||
assert "## " in content, f"{rel_path} missing H2 headings"
|
||||
|
||||
Reference in New Issue
Block a user