docs: add task for agent prompt generation
This commit is contained in:
+1
-1
@@ -7,4 +7,4 @@ Next Stage: REVIEW_READY
|
|||||||
Current Task: TASK-002
|
Current Task: TASK-002
|
||||||
Active Branch: main
|
Active Branch: main
|
||||||
|
|
||||||
Last Updated: 2026-06-01
|
Last Updated: 2026-06-02
|
||||||
|
|||||||
@@ -5,3 +5,5 @@ Bootstrap project structure created.
|
|||||||
Fixed TASKS.md parser to match em/en dashes in task headings.
|
Fixed TASKS.md parser to match em/en dashes in task headings.
|
||||||
TASK-000 marked Done — bootstrap standards complete.
|
TASK-000 marked Done — bootstrap standards complete.
|
||||||
TASK-001 marked Done — rdb init creates all project control files.
|
TASK-001 marked Done — rdb init creates all project control files.
|
||||||
|
2026-06-02
|
||||||
|
Added TASK-002 for agent prompt generation.
|
||||||
|
|||||||
@@ -21,3 +21,18 @@ Acceptance Criteria:
|
|||||||
Status: Done
|
Status: Done
|
||||||
|
|
||||||
Goal: Create the command that initialises the standard RDB project structure.
|
Goal: Create the command that initialises the standard RDB project structure.
|
||||||
|
|
||||||
|
## TASK-002 — Generate agent prompts
|
||||||
|
|
||||||
|
Status: Todo
|
||||||
|
|
||||||
|
Goal: Add a command that generates a ready-to-paste implementation prompt for the next Todo task.
|
||||||
|
|
||||||
|
Acceptance Criteria:
|
||||||
|
- `rdb prompt` reads the next Todo task from TASKS.md
|
||||||
|
- The prompt tells the agent to read README.md, TASKS.md, PROJECT_STATE.md, AGENT_HANDOFF.md, and context/agent-guidelines.md
|
||||||
|
- The prompt says to implement one task only
|
||||||
|
- The prompt includes the task title, status, goal, and acceptance criteria
|
||||||
|
- The prompt includes validation instructions
|
||||||
|
- The prompt includes reporting instructions
|
||||||
|
- Add or update tests
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from rich.table import Table
|
|||||||
from .discovery import append_discovery_answer, core_questions
|
from .discovery import append_discovery_answer, core_questions
|
||||||
from .handoff import build_handoff
|
from .handoff import build_handoff
|
||||||
from .status import project_stage, task_counts
|
from .status import project_stage, task_counts
|
||||||
from .tasks import get_next_task, update_task_status
|
from .tasks import generate_agent_prompt, get_next_task, update_task_status
|
||||||
from .templates import CONTEXT_FILES, write_file_if_missing
|
from .templates import CONTEXT_FILES, write_file_if_missing
|
||||||
|
|
||||||
app = typer.Typer(help="RDB discovery and delivery workflow CLI.")
|
app = typer.Typer(help="RDB discovery and delivery workflow CLI.")
|
||||||
@@ -131,3 +131,11 @@ def complete(task_id: str) -> None:
|
|||||||
def handoff() -> None:
|
def handoff() -> None:
|
||||||
"""Print an AI-agent handoff summary."""
|
"""Print an AI-agent handoff summary."""
|
||||||
console.print(build_handoff(root_path()))
|
console.print(build_handoff(root_path()))
|
||||||
|
|
||||||
|
|
||||||
|
@app.command()
|
||||||
|
def prompt() -> None:
|
||||||
|
"""Generate a ready-to-paste implementation prompt for the next Todo task."""
|
||||||
|
root = root_path()
|
||||||
|
result = generate_agent_prompt(root)
|
||||||
|
console.print(result)
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ TASK_HEADING_RE = re.compile(r"^##\s+(TASK-\d+)\s+[-–—]\s+(.+)$", re.MULTILI
|
|||||||
STATUS_RE = re.compile(r"^Status:\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)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Task:
|
class Task:
|
||||||
task_id: str
|
task_id: str
|
||||||
@@ -15,6 +19,15 @@ class Task:
|
|||||||
status: str
|
status: str
|
||||||
body: str
|
body: str
|
||||||
|
|
||||||
|
def goal(self, root: Path) -> str:
|
||||||
|
match = GOAL_RE.search(self.body)
|
||||||
|
if match:
|
||||||
|
return match.group(1).strip()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def ac_lines(self) -> list[str]:
|
||||||
|
return AC_RE.findall(self.body)
|
||||||
|
|
||||||
|
|
||||||
def read_tasks(root: Path) -> list[Task]:
|
def read_tasks(root: Path) -> list[Task]:
|
||||||
tasks_path = root / "TASKS.md"
|
tasks_path = root / "TASKS.md"
|
||||||
@@ -69,3 +82,87 @@ def update_task_status(root: Path, task_id: str, new_status: str) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
CONTEXT_FILES_TO_READ = [
|
||||||
|
"README.md",
|
||||||
|
"TASKS.md",
|
||||||
|
"PROJECT_STATE.md",
|
||||||
|
"AGENT_HANDOFF.md",
|
||||||
|
"context/agent-guidelines.md",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _read_file_safe(root: Path, relative: str) -> str:
|
||||||
|
path = root / relative
|
||||||
|
if path.exists():
|
||||||
|
return path.read_text(encoding="utf-8")
|
||||||
|
return f"# {relative} — not found"
|
||||||
|
|
||||||
|
|
||||||
|
def generate_agent_prompt(root: Path) -> str:
|
||||||
|
task = get_next_task(root)
|
||||||
|
if not task:
|
||||||
|
return "No Todo task found."
|
||||||
|
|
||||||
|
sections = [
|
||||||
|
"You are an implementation agent.",
|
||||||
|
"",
|
||||||
|
"---",
|
||||||
|
"",
|
||||||
|
"# Read these files first",
|
||||||
|
"",
|
||||||
|
f"""- README.md
|
||||||
|
- TASKS.md
|
||||||
|
- PROJECT_STATE.md
|
||||||
|
- AGENT_HANDOFF.md""",
|
||||||
|
"",
|
||||||
|
"- context/agent-guidelines.md",
|
||||||
|
"",
|
||||||
|
"---",
|
||||||
|
"",
|
||||||
|
"# Task",
|
||||||
|
"",
|
||||||
|
f"## {task.task_id} — {task.title}",
|
||||||
|
"",
|
||||||
|
f"Status: {task.status}",
|
||||||
|
"",
|
||||||
|
f"Goal:\n{task.goal(root)}",
|
||||||
|
"",
|
||||||
|
"Acceptance Criteria:",
|
||||||
|
]
|
||||||
|
|
||||||
|
for line in task.ac_lines():
|
||||||
|
sections.append(f"- {line}")
|
||||||
|
|
||||||
|
sections.extend([
|
||||||
|
"",
|
||||||
|
"---",
|
||||||
|
"",
|
||||||
|
"# Constraints",
|
||||||
|
"",
|
||||||
|
"- Implement ONE task only. Do not combine with other tasks.",
|
||||||
|
"- Make the smallest useful change possible.",
|
||||||
|
"",
|
||||||
|
"---",
|
||||||
|
"",
|
||||||
|
"# Validation",
|
||||||
|
"",
|
||||||
|
"- Run `rdb status` to confirm stage and task state.",
|
||||||
|
"- Run tests: `python -m pytest`.",
|
||||||
|
"- Verify acceptance criteria are met.",
|
||||||
|
"",
|
||||||
|
"---",
|
||||||
|
"",
|
||||||
|
"# Reporting",
|
||||||
|
"",
|
||||||
|
"When complete, provide:",
|
||||||
|
"- Summary of changes",
|
||||||
|
"- Files modified",
|
||||||
|
"- Test results",
|
||||||
|
"- Validation notes",
|
||||||
|
"- Update TASKS.md status to Done if all criteria pass",
|
||||||
|
"",
|
||||||
|
])
|
||||||
|
|
||||||
|
return "\n".join(sections)
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from rdb_discovery.tasks import generate_agent_prompt, get_next_task
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_agent_prompt_includes_task_info(tmp_path: Path) -> None:
|
||||||
|
(tmp_path / "TASKS.md").write_text(
|
||||||
|
"# TASKS\n\n"
|
||||||
|
"## TASK-001 — First\nStatus: Done\n\n"
|
||||||
|
"Goal: First goal.\n\n"
|
||||||
|
"Acceptance Criteria:\n"
|
||||||
|
"- a\n\n"
|
||||||
|
"## TASK-002 — Second\nStatus: Todo\n\n"
|
||||||
|
"Goal: Second goal.\n\n"
|
||||||
|
"Acceptance Criteria:\n"
|
||||||
|
"- b\n"
|
||||||
|
"- c\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
prompt = generate_agent_prompt(tmp_path)
|
||||||
|
|
||||||
|
assert "TASK-002" in prompt
|
||||||
|
assert "Second" in prompt
|
||||||
|
assert "Todo" in prompt
|
||||||
|
assert "Second goal." in prompt
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_agent_prompt_includes_acceptance_criteria(tmp_path: Path) -> None:
|
||||||
|
(tmp_path / "TASKS.md").write_text(
|
||||||
|
"# TASKS\n\n"
|
||||||
|
"## TASK-001 — First\nStatus: Done\n\n"
|
||||||
|
"Goal: x.\n\n"
|
||||||
|
"Acceptance Criteria:\n"
|
||||||
|
"- alpha\n\n"
|
||||||
|
"## TASK-002 — Second\nStatus: Todo\n\n"
|
||||||
|
"Goal: y.\n\n"
|
||||||
|
"Acceptance Criteria:\n"
|
||||||
|
"- bravo\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
prompt = generate_agent_prompt(tmp_path)
|
||||||
|
|
||||||
|
assert "- alpha" not in prompt
|
||||||
|
assert "- bravo" in prompt
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_agent_prompt_includes_read_instructions(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 "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
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_agent_prompt_includes_one_task_constraint(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 "ONE task only" in prompt or "one task only" in prompt
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_agent_prompt_includes_validation(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 "Validation" in prompt
|
||||||
|
assert "pytest" in prompt
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_agent_prompt_includes_reporting(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 "Reporting" in prompt
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_agent_prompt_no_todo_task(tmp_path: Path) -> None:
|
||||||
|
(tmp_path / "TASKS.md").write_text(
|
||||||
|
"# TASKS\n\n"
|
||||||
|
"## TASK-001 — First\nStatus: Done\n\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
prompt = generate_agent_prompt(tmp_path)
|
||||||
|
|
||||||
|
assert prompt == "No Todo task found."
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_agent_prompt_no_tasks_file(tmp_path: Path) -> None:
|
||||||
|
prompt = generate_agent_prompt(tmp_path)
|
||||||
|
|
||||||
|
assert prompt == "No Todo task found."
|
||||||
Reference in New Issue
Block a user