From 1c2faa31620bbd8f833da375189278d736ef0417 Mon Sep 17 00:00:00 2001 From: robbond Date: Tue, 2 Jun 2026 18:09:11 +0100 Subject: [PATCH] feat(task-018): add role-based context selection --- .rdb/session-log.jsonl | 5 ++ TASKS.md | 2 +- src/rdb_discovery.egg-info/PKG-INFO | 62 ++++++++++++++---- src/rdb_discovery.egg-info/SOURCES.txt | 7 ++ src/rdb_discovery/tasks.py | 53 ++++++++++++--- tests/test_prompt.py | 91 ++++++++++++++++++++++++++ 6 files changed, 197 insertions(+), 23 deletions(-) diff --git a/.rdb/session-log.jsonl b/.rdb/session-log.jsonl index 3457bc0..2858fa3 100644 --- a/.rdb/session-log.jsonl +++ b/.rdb/session-log.jsonl @@ -36,3 +36,8 @@ {"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": {}} diff --git a/TASKS.md b/TASKS.md index 2dfeded..f2390ff 100644 --- a/TASKS.md +++ b/TASKS.md @@ -309,7 +309,7 @@ Definition of Done: ## TASK-018 — Role-based context selection -Status: Todo +Status: Done Role: Implementation Agent diff --git a/src/rdb_discovery.egg-info/PKG-INFO b/src/rdb_discovery.egg-info/PKG-INFO index 250f67b..1a096f8 100644 --- a/src/rdb_discovery.egg-info/PKG-INFO +++ b/src/rdb_discovery.egg-info/PKG-INFO @@ -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 diff --git a/src/rdb_discovery.egg-info/SOURCES.txt b/src/rdb_discovery.egg-info/SOURCES.txt index fda3f8b..6bc008f 100644 --- a/src/rdb_discovery.egg-info/SOURCES.txt +++ b/src/rdb_discovery.egg-info/SOURCES.txt @@ -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 \ No newline at end of file diff --git a/src/rdb_discovery/tasks.py b/src/rdb_discovery/tasks.py index 3034272..d6325f9 100644 --- a/src/rdb_discovery/tasks.py +++ b/src/rdb_discovery/tasks.py @@ -109,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", @@ -117,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, @@ -186,6 +218,7 @@ def generate_agent_prompt(root: Path) -> str: return "No Todo task found." role_text, article = task.role(root) + context_files = _context_files_for_role(role_text) sections = [ f"You are {article} {role_text.lower()}.", @@ -194,13 +227,15 @@ def generate_agent_prompt(root: Path) -> str: "", "# 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", @@ -210,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: diff --git a/tests/test_prompt.py b/tests/test_prompt.py index c0f4e70..4e52b50 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -322,3 +322,94 @@ def test_generate_agent_prompt_with_custom_role(tmp_path: Path) -> None: 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