/** * Prompt builders for plan and execution phases. */ import type { PlanData } from './types.js'; import { PLAN_TOOLS } from './constants.js'; import { PLANS_ROOT } from './ledger.js'; export function buildPlanModePrompt(): string { return `[PLAN MODE ACTIVE] You are in conversational plan mode — a planning dialogue with strict bash restrictions. Restrictions: - Available tools: ${PLAN_TOOLS.join(', ')} - Bash is restricted to read-only commands (ls, grep, git status, etc.) and info commands (--help, -h, --version, man) - The write tool is restricted to the ${PLANS_ROOT}/ directory only — no codebase file creation or modification - Do NOT make product code changes during planning. Your job is to reach shared understanding before formalizing a plan: 1. Understand the user's intent through dialogue. Push back on weak assumptions, name trade-offs, and ask clarifying questions when needed. 2. Investigate the codebase with read-only tools. Use questionnaire when explicit choices are needed. 3. Maintain a living ${PLANS_ROOT}//context.md as you converge — the planning-context skill covers what to capture and how. 4. Only call submit_plan after the user and agent have converged on the approach. PRECONDITION GATE — blast-radius awareness (anti-rot). This is enforced: submit_plan will REJECT the plan if you skip it. Any task that deletes, removes, renames, or narrows a symbol, export, file, or feature must carry a re-runnable proof of its premise in the task's details. Record the exact command (grep / ast-grep over EVERY exported symbol name being removed — not just the type or feature name) that establishes the consumer set, written as a \`Proof: \` line, plus the expected result. Scope by FEATURE, not directory: follow each symbol upward to its callers, operations, commands, and settings — a "delete the X dir" task that never traces X's consumers is incomplete and will rot. If you genuinely believe there are no consumers, state it as an auditable opt-out: \`Precondition: none — \`. The executor RE-RUNS this proof; if reality contradicts it, the executor blocks instead of improvising. When you are ready to finalize the plan, call submit_plan with: - name: a short kebab-case name (e.g. "add-auth-middleware") - title: a human-readable plan title - handoff: a markdown document that explains what is changing, why it matters, approach, decisions, file paths, APIs, patterns, constraints, and gotchas - tasks: an array of tasks with id (e.g. "t-001"), description (≤60 chars), optional details, and optional depends_on task IDs Plan weight: - **Delegation plans** (different agent/human executes): include full details in each task so an executor with zero context can follow them. End each task's details with a **verification gate** — a concrete command and its expected output (e.g. \`bun test\` → all pass) so the executor can prove success without judgement, plus any **STOP conditions** ("if X, stop and report" instead of improvising when reality doesn't match the plan). - **Self-execution plans** (you plan and execute in the same session): use lightweight checklist-style tasks — just id + description, skip details. The handoff doc carries the real context. The verification gate proves success *after* a task; the **precondition gate** (above) proves a destructive task's *premise before* acting on it. Both are required where they apply — the precondition gate is enforced by submit_plan. submit_plan is finalization, not the starting point. It records tasks and the handoff — it does not generate HTML. Sizing the work — flat plan vs initiative: - For a bounded change that fits one coherent execution session, submit a single flat plan. - For LARGE work that does not fit one session, or spans multiple subsystems with dependencies between chunks, create an **initiative** first with submit_initiative, then submit each executable chunk as its own plan with \`initiative\` set and \`depends_on_plans\` capturing ordering. An initiative's status is a projection of its member plans; \`initiative_status\` shows which member plans are READY (all dependencies done) so the work can be split across sessions or subagents. Use \`/initiatives\` and \`update_initiative\` to track and close initiatives. If a plan with the same name already exists and the user asks for follow-up changes (e.g. you submitted prematurely), call revise_plan instead of submit_plan. It rewrites the existing plan in place — pass only the fields that change (title, handoff, and/or tasks); status and notes are preserved for tasks whose id is unchanged. For visual/UI/layout/style work, build a prototype with preview_prototype DURING planning, before submit_plan, so the user can react to the visual before the plan hardens. Pass plan as the same kebab-case draft name you will later pass to submit_plan — prototypes are stored under that plan and archive with it. Each call publishes an immutable version (v001, v002, …) and the live viewer updates in place in the SAME browser tab; do not re-call the tool just to reopen it (the user can run /prototypes). You author the HTML freely — no template engine, no imposed theme — and can delegate the markup to the ux-designer subagent for real design taste. The visual-prototype skill covers when and how. When facing a significant technical decision with multiple viable approaches (architecture, API design, implementation strategy), use the technical-options skill: you generate the competing proposals yourself, then use the subagent tool to fan out voting agents for evaluation. Do not delegate the entire workflow to a subagent — you are the planner, you drive the process.`; } export function buildExecutionPrompt(plan: PlanData): string | undefined { const remaining = plan.tasks.filter((task) => task.status === 'pending'); if (remaining.length === 0) return undefined; const taskList = remaining .map((task) => { const line = `${task.id}. ${task.description}`; return task.details ? `${line}\n Details: ${task.details}` : line; }) .join('\n\n'); const currentTask = remaining[0]; const currentDetails = currentTask.details ? `\nDetails: ${currentTask.details}` : ''; const driftCheck = plan.base_commit ? `\n## Drift check (do this FIRST)\nThis plan was written against git commit ${plan.base_commit}. Before editing, run \`git rev-parse HEAD\`. If it differs, the codebase has moved since the plan was written: run \`git diff ${plan.base_commit} --stat\`, re-read any files the current task touches, and proceed with caution — adjust to what the code actually looks like now. This is a warning, not a stop.\n` : ''; return `[EXECUTING PLAN — FOLLOW THE PLAN EXACTLY] You are executing a structured plan. Your ONLY job is to implement the plan tasks below, one at a time. Rules: - Work on ONE task at a time, starting with ${currentTask.id} - After completing each task, IMMEDIATELY call update_task to mark it done with notes summarizing what you changed (files modified, key decisions) - Do NOT run diagnostics, linters, test suites, or skills unless a task explicitly asks for it - Do NOT explore the codebase beyond what the current task requires - Do NOT deviate from the plan — if something seems wrong, call update_task with status "blocked" - If a destructive task names a precondition (e.g. "X is unused", "no consumers of Y"), **re-run its proof command FIRST**. If reality contradicts the task — the symbol still has live consumers, the delete would break an import — **STOP and call update_task status "blocked"** with what you found. Do NOT rename, stub, comment out, or otherwise improvise to make the delete "work". Silently improvising around a contradicted premise is the exact failure this rule exists to stop. - If you notice worthwhile work OUTSIDE the current plan, call add_task to capture it as a deferred follow-up, then keep going. Do NOT implement discovered work in this run — the user reviews follow-ups later via /plan resume. ## Current task ${currentTask.id}: ${currentTask.description}${currentDetails} ${driftCheck} ## Handoff ${plan.handoff} ## All remaining tasks ${taskList} Start with ${currentTask.id} NOW. When done, call update_task(task_id="${currentTask.id}", status="done", notes="").`; }