feat: improve generated agent prompts
This commit is contained in:
@@ -10,6 +10,7 @@ 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)
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -28,6 +29,12 @@ class Task:
|
||||
def ac_lines(self) -> list[str]:
|
||||
return AC_RE.findall(self.body)
|
||||
|
||||
def implementation_gap(self, root: Path) -> str | None:
|
||||
match = GAP_RE.search(self.body)
|
||||
if match and match.group(1).strip():
|
||||
return match.group(1).strip()
|
||||
return None
|
||||
|
||||
|
||||
def read_tasks(root: Path) -> list[Task]:
|
||||
tasks_path = root / "TASKS.md"
|
||||
@@ -92,6 +99,12 @@ CONTEXT_FILES_TO_READ = [
|
||||
"context/agent-guidelines.md",
|
||||
]
|
||||
|
||||
TEST_CMD_SECTION_RE = re.compile(
|
||||
r"^##\s*Test Commands\s*\n([\s\S]*?)(?=^##|\Z)",
|
||||
re.MULTILINE,
|
||||
)
|
||||
BASH_BLOCK_RE = re.compile(r"```bash\n(.*?)```", re.DOTALL)
|
||||
|
||||
|
||||
def _read_file_safe(root: Path, relative: str) -> str:
|
||||
path = root / relative
|
||||
@@ -100,6 +113,55 @@ def _read_file_safe(root: Path, relative: str) -> str:
|
||||
return f"# {relative} — not found"
|
||||
|
||||
|
||||
def _extract_test_commands_from_claude(root: Path) -> list[str]:
|
||||
"""Extract bash test commands from CLAUDE.md Test Commands section."""
|
||||
|
||||
def _looks_like_command(line: str) -> bool:
|
||||
"""Heuristic: keep lines that look like shell commands."""
|
||||
if not line.strip():
|
||||
return False
|
||||
lower = line.lower()
|
||||
# Skip markdown, prose, headings
|
||||
if any(lower.startswith(p) for p in ("# ", "- ", "if ", "do not", "for ", "use ")) or lower in (
|
||||
"validation commands",
|
||||
):
|
||||
return False
|
||||
# Skip lines that look like sentences (contain spaces followed by lowercase words)
|
||||
# but keep things like `source .venv/bin/activate` and `pip install ...`
|
||||
parts = line.split()
|
||||
if len(parts) <= 1:
|
||||
return True
|
||||
# If the first word is a known shell builtin / command prefix, accept it
|
||||
known_prefixes = ("source", "cd", "ls", "cp", "mv", "rm", "mkdir", "echo", "grep",
|
||||
"git", "pip", "python", "pytest", "rdb", "cat", "head", "tail",
|
||||
"sed", "awk", "find", "install")
|
||||
return parts[0].lower() in known_prefixes
|
||||
|
||||
claude_path = root / "CLAUDE.md"
|
||||
if not claude_path.exists():
|
||||
return []
|
||||
|
||||
text = claude_path.read_text(encoding="utf-8")
|
||||
section_match = TEST_CMD_SECTION_RE.search(text)
|
||||
if not section_match:
|
||||
return []
|
||||
|
||||
section_text = section_match.group(1)
|
||||
blocks = BASH_BLOCK_RE.findall(section_text)
|
||||
seen: set[str] = set()
|
||||
commands: list[str] = []
|
||||
for block in blocks:
|
||||
for line in block.strip().splitlines():
|
||||
stripped = line.strip()
|
||||
if not stripped:
|
||||
continue
|
||||
if _looks_like_command(stripped):
|
||||
if stripped not in seen:
|
||||
seen.add(stripped)
|
||||
commands.append(stripped)
|
||||
return commands
|
||||
|
||||
|
||||
def generate_agent_prompt(root: Path) -> str:
|
||||
task = get_next_task(root)
|
||||
if not task:
|
||||
@@ -128,9 +190,20 @@ def generate_agent_prompt(root: Path) -> str:
|
||||
f"Status: {task.status}",
|
||||
"",
|
||||
f"Goal:\n{task.goal(root)}",
|
||||
]
|
||||
|
||||
gap = task.implementation_gap(root)
|
||||
if gap:
|
||||
sections.extend([
|
||||
"",
|
||||
"Implementation Gap:",
|
||||
gap,
|
||||
])
|
||||
|
||||
sections.extend([
|
||||
"",
|
||||
"Acceptance Criteria:",
|
||||
]
|
||||
])
|
||||
|
||||
for line in task.ac_lines():
|
||||
sections.append(f"- {line}")
|
||||
@@ -141,8 +214,25 @@ def generate_agent_prompt(root: Path) -> str:
|
||||
"",
|
||||
"# Constraints",
|
||||
"",
|
||||
"- Implement ONE task only. Do not combine with other tasks.",
|
||||
"- Inspect the codebase first, then edit.",
|
||||
"- Do not repeatedly reread unchanged files.",
|
||||
"- Limit your work to ONE small implementation step only. Do not combine tasks or features.",
|
||||
"- Make the smallest useful change possible.",
|
||||
])
|
||||
|
||||
test_cmds = _extract_test_commands_from_claude(root)
|
||||
if test_cmds:
|
||||
sections.extend([
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
"# Test Commands (from CLAUDE.md)",
|
||||
"",
|
||||
"Use these existing test commands:",
|
||||
("```bash\n" + "\n".join(test_cmds) + "\n```\n"),
|
||||
])
|
||||
|
||||
sections.extend([
|
||||
"",
|
||||
"---",
|
||||
"",
|
||||
|
||||
Reference in New Issue
Block a user