feat(task-018): add role-based context selection
This commit is contained in:
@@ -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: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: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: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": {}}
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ Definition of Done:
|
|||||||
|
|
||||||
## TASK-018 — Role-based context selection
|
## TASK-018 — Role-based context selection
|
||||||
|
|
||||||
Status: Todo
|
Status: Done
|
||||||
|
|
||||||
Role: Implementation Agent
|
Role: Implementation Agent
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|||||||
|
|
||||||
# rdb-discovery
|
# 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
|
- what is being built
|
||||||
- why it exists
|
- 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 task should be done next
|
||||||
- what stage the project is currently in
|
- 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
|
## Install for local development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -32,23 +48,43 @@ pip install -e '.[dev]'
|
|||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rdb init
|
rdb init # Create standard project structure and context files
|
||||||
rdb discover
|
rdb discover # Ask 10 core discovery questions, record answers
|
||||||
rdb status
|
rdb guardrails # Check for agent stalls and state inconsistencies
|
||||||
rdb next
|
rdb prompt # Generate implementation prompt for the next task
|
||||||
rdb start TASK-001
|
rdb start TASK-001 # Mark a task as in progress
|
||||||
rdb complete TASK-001
|
rdb complete TASK-001# Mark a task as done
|
||||||
rdb handoff
|
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
|
## Bootstrap workflow
|
||||||
|
|
||||||
1. Run `rdb init`
|
1. Run `rdb init`
|
||||||
2. Run `rdb discover`
|
2. Run `rdb discover`
|
||||||
3. Run `rdb status`
|
3. Review `rdb status` and `rdb next`
|
||||||
4. Run `rdb next`
|
4. Give the next prompt to Claude Code or Cline via `rdb prompt`
|
||||||
5. Give the next task to Claude Code or Cline
|
5. Commit after each completed task
|
||||||
6. 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
|
## Principle
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ pyproject.toml
|
|||||||
src/rdb_discovery/__init__.py
|
src/rdb_discovery/__init__.py
|
||||||
src/rdb_discovery/cli.py
|
src/rdb_discovery/cli.py
|
||||||
src/rdb_discovery/discovery.py
|
src/rdb_discovery/discovery.py
|
||||||
|
src/rdb_discovery/guardrails.py
|
||||||
src/rdb_discovery/handoff.py
|
src/rdb_discovery/handoff.py
|
||||||
src/rdb_discovery/status.py
|
src/rdb_discovery/status.py
|
||||||
src/rdb_discovery/tasks.py
|
src/rdb_discovery/tasks.py
|
||||||
|
src/rdb_discovery/telemetry.py
|
||||||
src/rdb_discovery/templates.py
|
src/rdb_discovery/templates.py
|
||||||
src/rdb_discovery.egg-info/PKG-INFO
|
src/rdb_discovery.egg-info/PKG-INFO
|
||||||
src/rdb_discovery.egg-info/SOURCES.txt
|
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/entry_points.txt
|
||||||
src/rdb_discovery.egg-info/requires.txt
|
src/rdb_discovery.egg-info/requires.txt
|
||||||
src/rdb_discovery.egg-info/top_level.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_tasks.py
|
||||||
|
tests/test_telemetry.py
|
||||||
tests/test_templates.py
|
tests/test_templates.py
|
||||||
@@ -109,7 +109,7 @@ def update_task_status(root: Path, task_id: str, new_status: str) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
CONTEXT_FILES_TO_READ = [
|
DEFAULT_CONTEXT_FILES = [
|
||||||
"README.md",
|
"README.md",
|
||||||
"TASKS.md",
|
"TASKS.md",
|
||||||
"PROJECT_STATE.md",
|
"PROJECT_STATE.md",
|
||||||
@@ -117,6 +117,38 @@ CONTEXT_FILES_TO_READ = [
|
|||||||
"context/agent-guidelines.md",
|
"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(
|
TEST_CMD_SECTION_RE = re.compile(
|
||||||
r"^##\s*Test Commands\s*\n([\s\S]*?)(?=^##|\Z)",
|
r"^##\s*Test Commands\s*\n([\s\S]*?)(?=^##|\Z)",
|
||||||
re.MULTILINE,
|
re.MULTILINE,
|
||||||
@@ -186,6 +218,7 @@ def generate_agent_prompt(root: Path) -> str:
|
|||||||
return "No Todo task found."
|
return "No Todo task found."
|
||||||
|
|
||||||
role_text, article = task.role(root)
|
role_text, article = task.role(root)
|
||||||
|
context_files = _context_files_for_role(role_text)
|
||||||
|
|
||||||
sections = [
|
sections = [
|
||||||
f"You are {article} {role_text.lower()}.",
|
f"You are {article} {role_text.lower()}.",
|
||||||
@@ -194,13 +227,15 @@ def generate_agent_prompt(root: Path) -> str:
|
|||||||
"",
|
"",
|
||||||
"# Read these files first",
|
"# Read these files first",
|
||||||
"",
|
"",
|
||||||
f"""- README.md
|
]
|
||||||
- TASKS.md
|
for i, f in enumerate(context_files):
|
||||||
- PROJECT_STATE.md
|
line = f"- {f}"
|
||||||
- AGENT_HANDOFF.md""",
|
if i == len(context_files) - 1:
|
||||||
"",
|
sections.append(line)
|
||||||
"- context/agent-guidelines.md",
|
else:
|
||||||
"",
|
sections.append(line)
|
||||||
|
|
||||||
|
sections.extend([
|
||||||
"---",
|
"---",
|
||||||
"",
|
"",
|
||||||
"# Task",
|
"# Task",
|
||||||
@@ -210,7 +245,7 @@ def generate_agent_prompt(root: Path) -> str:
|
|||||||
f"Status: {task.status}",
|
f"Status: {task.status}",
|
||||||
"",
|
"",
|
||||||
f"Goal:\n{task.goal(root)}",
|
f"Goal:\n{task.goal(root)}",
|
||||||
]
|
])
|
||||||
|
|
||||||
gap = task.implementation_gap(root)
|
gap = task.implementation_gap(root)
|
||||||
if gap:
|
if gap:
|
||||||
|
|||||||
@@ -322,3 +322,94 @@ def test_generate_agent_prompt_with_custom_role(tmp_path: Path) -> None:
|
|||||||
prompt = generate_agent_prompt(tmp_path)
|
prompt = generate_agent_prompt(tmp_path)
|
||||||
|
|
||||||
assert "You are a devops engineer." in prompt
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user