From 34f7364c86a0ad8a24ee0d62cb45f850af0807b6 Mon Sep 17 00:00:00 2001 From: robbond Date: Tue, 2 Jun 2026 17:56:38 +0100 Subject: [PATCH] feat: support role-based agent prompts --- .rdb/session-log.jsonl | 16 +++++ TASKS.md | 134 ++++++++++++++++++++++++++++++++++--- src/rdb_discovery/tasks.py | 24 ++++++- tests/test_prompt.py | 63 +++++++++++++++++ 4 files changed, 225 insertions(+), 12 deletions(-) diff --git a/.rdb/session-log.jsonl b/.rdb/session-log.jsonl index 2cbb35c..3457bc0 100644 --- a/.rdb/session-log.jsonl +++ b/.rdb/session-log.jsonl @@ -20,3 +20,19 @@ {"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": {}} diff --git a/TASKS.md b/TASKS.md index a58b143..2dfeded 100644 --- a/TASKS.md +++ b/TASKS.md @@ -282,34 +282,129 @@ 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: Todo +Status: Done Goal: -Use captured discovery answers to generate initial context files. +Allow tasks to define the agent role used by `rdb prompt`. Implementation Gap: -Discovery questions can collect information, but the answers are not yet transformed into structured project context documents. +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: Todo + +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: Todo + +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 + +## TASK-020 — Generate context files from discovery answers + +Status: Todo + +Role: Implementation Agent + +Goal: + +Generate initial context files using the approved discovery-to-context mapping. + +Implementation Gap: + +Discovery answers can be collected, but they are not yet transformed into structured project documentation. Acceptance Criteria: - Add command or function to generate context files from discovery data +- Use the approved mapping document - Create missing context files safely - Do not overwrite existing files without explicit behaviour - Add/update tests -## TASK-018 — Add context completeness report +Definition of Done: + +- Context files can be generated from discovery data +- Existing files are preserved +- Tests pass + +## TASK-021 — Add context completeness report Status: Todo +Role: Implementation Agent + 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. +There is no way to determine whether a project has sufficient context for effective AI-assisted development. Acceptance Criteria: @@ -319,15 +414,34 @@ Acceptance Criteria: - Report obvious placeholder sections still needing completion - Add/update tests -## TASK-019 — Generate Claude Code permission profile +Definition of Done: + +- Context report command exists +- Missing context is reported clearly +- Tests pass + +## TASK-022 — 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: -- Safe commands auto-approved -- Dangerous commands require approval -- Profile documented in CLAUDE.md +- 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 diff --git a/src/rdb_discovery/tasks.py b/src/rdb_discovery/tasks.py index 438bef8..3034272 100644 --- a/src/rdb_discovery/tasks.py +++ b/src/rdb_discovery/tasks.py @@ -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: @@ -167,8 +185,10 @@ def generate_agent_prompt(root: Path) -> str: if not task: return "No Todo task found." + role_text, article = task.role(root) + sections = [ - "You are an implementation agent.", + f"You are {article} {role_text.lower()}.", "", "---", "", diff --git a/tests/test_prompt.py b/tests/test_prompt.py index 70c9084..c0f4e70 100644 --- a/tests/test_prompt.py +++ b/tests/test_prompt.py @@ -259,3 +259,66 @@ 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