/** * Planner role prompts and degraded (no-LLM) plan generation. * * Production parent-session injects use slim messages only: * - `buildGoalSetContextMessage` while planning * - `buildExecuteKickoffPrompt` after plan is on disk * Do not reintroduce full draft-plan pastes into sendMessage content. */ import { expandToPlan } from "../plan/expand.ts"; import { renderPlan } from "../plan/template.ts"; /** * Prompt for an LLM planner subagent (optional path). * Criteria guidance draws on Critical Fallibilism / IGC: binary goals with * discrete breakpoints, not vague maximization. */ export function buildPlannerPrompt(objective: string): string { return [ "You are the Goal Plan Writer for the pi-goal-expander harness.", "Research the codebase first (read/search), then write a structured plan.", "", "Write a Markdown plan with ALL of these sections, in order:", "1. `# Plan: `", "2. `## Goal kind` — exactly one of: `code-change` | `analysis` | `research`", "3. `## Acceptance criteria` — 3–5 gating criteria (see rules below)", "4. `## Verification plan` — steps tagged `gating` or `evidence`", "5. `## Non-goals`", "6. `## Assumed scope`", "7. `## Subgoals` — optional nested outcomes", "8. `## Implementation approach` — guidance only (not judged)", "9. `## Task checklist` — `- [ ]` items for the implementer", "10. `## Risks / Contradictions` — optional", "11. `## Deviations` — leave empty; append-only during execution", "", "## Criteria quality (Critical Fallibilism / IGC-style)", "Prefer **objectively measurable** outcomes with **discrete pass/fail breakpoints**:", "- Each criterion must have an unambiguous fact of the matter — pass or fail, not a vibe.", "- Convert maximization/soft goals into breakpoints of *enough* (with margin), e.g.", " `npm run check` exits 0 — not \"tests feel good\"; latency ≤ N ms — not \"faster\".", "- Prefer discrete artifacts: exit codes, file contents, config keys, command output,", " presence of paths, API responses — things a harness or human can re-check without debate.", "- Drop non-decisive factors that already have excess capacity; keep only goals that", " can actually refute incomplete work.", "- Criteria are WHAT (outcomes), not HOW (file/function names) — unless the OBJECTIVE", " names a specific artifact.", "", "Rules:", "- Keep it short, concrete, and unambiguous (aim 3–5 gating criteria).", "- Do NOT modify the workspace except writing the plan.", "- Do not invent unrequested scope; put extras under Non-goals.", "", `OBJECTIVE: ${objective}`, "", "Output ONLY the plan markdown (no preamble).", ].join("\n"); } /** Immediate context inject when a goal is set and planner may still be running. */ export function buildGoalSetContextMessage(input: { objective: string; goalId: string; planPath: string; evidencePath: string; plannerPending: boolean; }): string { const plannerLine = input.plannerPending ? [ "**Planner status:** a planner subagent is running (or about to run) to research the", "codebase and write the plan file. Do **not** treat the goal as ready to execute yet", "and do **not** invent a parallel plan — wait for the execute kickoff after planning", "completes. You may continue other user work if asked, but the active goal is set.", ].join(" ") : "**Planner status:** planning finished (see execute kickoff if present)."; return [ "# Goal set", "", `**Goal id:** \`${input.goalId}\``, `**Objective:** ${input.objective}`, `**Plan path:** \`${input.planPath}\``, `**Evidence path:** \`${input.evidencePath}\``, "", plannerLine, "", "Goal state lives under `.pi/goal/` (cwd / `PI_GOAL_ROOT` / `PI_GOAL_CWD`);", "the objective may require edits **outside** that directory — edit scope follows the objective, not the goal store.", "", "When execute begins: read the plan **file on disk** (do not expect a pasted draft),", "fill the evidence index, and use `update_goal` for progress / completion / blocked.", "Required progress milestones: `researched`, `implementing`, `pre-complete` via `update_goal({ message })`.", "The harness verifies completion — you cannot self-certify.", ].join("\n"); } /** * Degraded planner: structural expand + render without an LLM call. * Always produces a non-empty plan markdown string (draft skeleton). */ export function planFromExpansion(objective: string): string { const plan = expandToPlan(objective); return renderPlan(plan); }