Files
Aperant/auto-claude/prompts.py
T
2025-12-15 21:10:27 +01:00

264 lines
7.8 KiB
Python

"""
Prompt Loading Utilities
========================
Functions for loading agent prompts from markdown files.
"""
import json
from pathlib import Path
# Directory containing prompt files
PROMPTS_DIR = Path(__file__).parent / "prompts"
def get_planner_prompt(spec_dir: Path) -> str:
"""
Load the planner agent prompt with spec path injected.
The planner creates subtask-based implementation plans.
Args:
spec_dir: Directory containing the spec.md file
Returns:
The planner prompt content with spec path
"""
prompt_file = PROMPTS_DIR / "planner.md"
if not prompt_file.exists():
raise FileNotFoundError(
f"Planner prompt not found at {prompt_file}\n"
"Make sure the auto-claude/prompts/planner.md file exists."
)
prompt = prompt_file.read_text()
# Inject spec directory information at the beginning
spec_context = f"""## SPEC LOCATION
Your spec file is located at: `{spec_dir}/spec.md`
Store all build artifacts in this spec directory:
- `{spec_dir}/implementation_plan.json` - Subtask-based implementation plan
- `{spec_dir}/build-progress.txt` - Progress notes
- `{spec_dir}/init.sh` - Environment setup script
The project root is the parent of auto-claude/. Implement code in the project root, not in the spec directory.
---
"""
return spec_context + prompt
def get_coding_prompt(spec_dir: Path) -> str:
"""
Load the coding agent prompt with spec path injected.
Args:
spec_dir: Directory containing the spec.md and implementation_plan.json
Returns:
The coding agent prompt content with spec path
"""
prompt_file = PROMPTS_DIR / "coder.md"
if not prompt_file.exists():
raise FileNotFoundError(
f"Coding prompt not found at {prompt_file}\n"
"Make sure the auto-claude/prompts/coder.md file exists."
)
prompt = prompt_file.read_text()
spec_context = f"""## SPEC LOCATION
Your spec and progress files are located at:
- Spec: `{spec_dir}/spec.md`
- Implementation plan: `{spec_dir}/implementation_plan.json`
- Progress notes: `{spec_dir}/build-progress.txt`
- Recovery context: `{spec_dir}/memory/attempt_history.json`
The project root is the parent of auto-claude/. All code goes in the project root, not in the spec directory.
---
"""
# Check for recovery context (stuck subtasks, retry hints)
recovery_context = _get_recovery_context(spec_dir)
if recovery_context:
spec_context += recovery_context
# Check for human input file
human_input_file = spec_dir / "HUMAN_INPUT.md"
if human_input_file.exists():
human_input = human_input_file.read_text().strip()
if human_input:
spec_context += f"""## HUMAN INPUT (READ THIS FIRST!)
The human has left you instructions. READ AND FOLLOW THESE CAREFULLY:
{human_input}
After addressing this input, you may delete or clear the HUMAN_INPUT.md file.
---
"""
return spec_context + prompt
def _get_recovery_context(spec_dir: Path) -> str:
"""
Get recovery context if there are failed attempts or stuck subtasks.
Args:
spec_dir: Spec directory containing memory/
Returns:
Recovery context string or empty string
"""
import json
attempt_history_file = spec_dir / "memory" / "attempt_history.json"
if not attempt_history_file.exists():
return ""
try:
with open(attempt_history_file) as f:
history = json.load(f)
# Check for stuck subtasks
stuck_subtasks = history.get("stuck_subtasks", [])
if stuck_subtasks:
context = """## ⚠️ RECOVERY ALERT - STUCK SUBTASKS DETECTED
Some subtasks have been attempted multiple times without success. These subtasks need:
- A COMPLETELY DIFFERENT approach
- Possibly simpler implementation
- Or escalation to human if infeasible
Stuck subtasks:
"""
for stuck in stuck_subtasks:
context += f"- {stuck['subtask_id']}: {stuck['reason']} ({stuck['attempt_count']} attempts)\n"
context += "\nBefore working on any subtask, check memory/attempt_history.json for previous attempts!\n\n---\n\n"
return context
# Check for subtasks with multiple attempts
subtasks_with_retries = []
for subtask_id, subtask_data in history.get("subtasks", {}).items():
attempts = subtask_data.get("attempts", [])
if len(attempts) > 1 and subtask_data.get("status") != "completed":
subtasks_with_retries.append((subtask_id, len(attempts)))
if subtasks_with_retries:
context = """## ⚠️ RECOVERY CONTEXT - RETRY AWARENESS
Some subtasks have been attempted before. When working on these:
1. READ memory/attempt_history.json for the specific subtask
2. See what approaches were tried
3. Use a DIFFERENT approach
Subtasks with previous attempts:
"""
for subtask_id, attempt_count in subtasks_with_retries:
context += f"- {subtask_id}: {attempt_count} attempts\n"
context += "\n---\n\n"
return context
return ""
except (OSError, json.JSONDecodeError):
return ""
def get_followup_planner_prompt(spec_dir: Path) -> str:
"""
Load the follow-up planner agent prompt with spec path and key files injected.
The follow-up planner adds new subtasks to an existing completed implementation plan.
Args:
spec_dir: Directory containing the completed spec and implementation_plan.json
Returns:
The follow-up planner prompt content with paths injected
"""
prompt_file = PROMPTS_DIR / "followup_planner.md"
if not prompt_file.exists():
raise FileNotFoundError(
f"Follow-up planner prompt not found at {prompt_file}\n"
"Make sure the auto-claude/prompts/followup_planner.md file exists."
)
prompt = prompt_file.read_text()
# Inject spec directory information at the beginning
spec_context = f"""## SPEC LOCATION (FOLLOW-UP MODE)
You are adding follow-up work to a **completed** spec.
**Key files in this spec directory:**
- Spec: `{spec_dir}/spec.md`
- Follow-up request: `{spec_dir}/FOLLOWUP_REQUEST.md` (READ THIS FIRST!)
- Implementation plan: `{spec_dir}/implementation_plan.json` (APPEND to this, don't replace)
- Progress notes: `{spec_dir}/build-progress.txt`
- Context: `{spec_dir}/context.json`
- Memory: `{spec_dir}/memory/`
**Important paths:**
- Spec directory: `{spec_dir}`
- Project root: Parent of auto-claude/ (where code should be implemented)
**Your task:**
1. Read `{spec_dir}/FOLLOWUP_REQUEST.md` to understand what to add
2. Read `{spec_dir}/implementation_plan.json` to see existing phases/subtasks
3. ADD new phase(s) with pending subtasks to the existing plan
4. PRESERVE all existing subtasks and their statuses
---
"""
return spec_context + prompt
def is_first_run(spec_dir: Path) -> bool:
"""
Check if this is the first run (no valid implementation plan with subtasks exists yet).
The spec runner may create a skeleton implementation_plan.json with empty phases.
This function checks for actual phases with subtasks, not just file existence.
Args:
spec_dir: Directory containing spec files
Returns:
True if implementation_plan.json doesn't exist or has no subtasks
"""
plan_file = spec_dir / "implementation_plan.json"
if not plan_file.exists():
return True
try:
with open(plan_file) as f:
plan = json.load(f)
# Check if there are any phases with subtasks
phases = plan.get("phases", [])
if not phases:
return True
# Check if any phase has subtasks
total_subtasks = sum(len(phase.get("subtasks", [])) for phase in phases)
return total_subtasks == 0
except (OSError, json.JSONDecodeError):
# If we can't read the file, treat as first run
return True