diff --git a/README.md b/README.md index 6ddff79..b08dbd6 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -# pi-plan +# pi-goals A [pi](https://github.com/badlogic/pi-mono) extension for plan-driven, goal-tracked work in one -`plan.md`. Set up goals (with evidence and failure modes) in plan mode, work them, and sign a goal +`goals.md`. Set up goals (with evidence and failure modes) in plan mode, work them, and sign a goal off only when a read-only subagent has checked the evidence. Successor to [pi-lgtm](https://github.com/wassname/pi-lgtm), kept deliberately small: about [burneikis/pi-plan](https://github.com/burneikis/pi-plan) plus the additions, goals with evidence, a sign-off check, a widget, and a reminder. -The form guides; it does not gate. The agent edits `plan.md` with its normal Edit tool. The one +The form guides; it does not gate. The agent edits `goals.md` with its normal Edit tool. The one blessed tool is `CompleteGoal`, which runs the sign-off check and records the result. The reminder, the injected plan summary, and git/widget visibility carry the process. It trusts the agent's judgement rather than guarding it. @@ -16,39 +16,39 @@ judgement rather than guarding it. ## Install ```bash -pi install npm:@wassname2/pi-plan +pi install npm:@wassname2/pi-goals ``` Or run without installing: ```bash -pi -e npm:@wassname2/pi-plan +pi -e npm:@wassname2/pi-goals ``` ## Use ``` -/plan add CSV export to the report view +/goals add CSV export to the report view ``` -1. Plan. The agent explores read-only and writes goals into `plan.md` (see format below). +1. Plan. The agent explores read-only and writes goals into `goals.md` (see format below). 2. Review. You get a menu: Ready, Edit (ask the agent to revise), Open in `$EDITOR`, or Cancel. On Ready you choose whether to keep the current context or start fresh and compacted. 3. Work. Each turn the active goal is injected (so it survives compaction) and a reminder nudges - the agent to keep `plan.md` current and work autonomously. When a goal's `done_when` is met the + the agent to keep `goals.md` current and work autonomously. When a goal's `done_when` is met the agent calls `CompleteGoal`, which runs `verify` and a read-only judge and, on accept, marks it done and logs it. -Other commands: `/plan` (print the goals), `/plan clear` (empty `plan.md`, history kept in git), -`/plan judge ` (use a specific model for the sign-off judge; default is your current +Other commands: `/goals` (print the goals), `/goals clear` (empty `goals.md`, history kept in git), +`/goals judge ` (use a specific model for the sign-off judge; default is your current model). -## plan.md format +## goals.md format One file holds the objective, the goals, and a short append-only log. ```markdown -# Plan: ship the cache layer +# Goals: ship the cache layer ## Goal: [/] Implement cache layer @@ -81,7 +81,7 @@ evidence: ## The sign-off check (`CompleteGoal`) `CompleteGoal(goal_id)` is the one blessed completion path. It reads the goal's `evidence:` block -from plan.md (so the proof is git-tracked and human-reviewable before sign-off, not buried in a tool +from goals.md (so the proof is git-tracked and human-reviewable before sign-off, not buried in a tool call): 1. If the goal has a `verify:` command, it is run. A non-zero exit rejects immediately, with no model @@ -94,7 +94,7 @@ call): the goal stays open and the agent is told what is missing. The judge defaults to your current model (guaranteed authorized and capable). Set a different one -with `/plan judge ` for an independent cross-family check. +with `/goals judge ` for an independent cross-family check. ## Prompts diff --git a/package.json b/package.json index 0bdcfa9..3d5aab2 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,13 @@ { - "name": "@wassname2/pi-plan", + "name": "@wassname2/pi-goals", "version": "0.0.1", - "description": "One plan.md: set goals via plan mode, work them, sign off only when a read-only check passes. Successor to pi-lgtm.", + "description": "One goals.md: set goals in plan mode, work them, sign off only when a read-only check passes. Successor to pi-lgtm.", "author": "wassname", "license": "MIT", "type": "module", "repository": { "type": "git", - "url": "https://github.com/wassname/pi-plan.git" + "url": "https://github.com/wassname/pi-goals.git" }, "keywords": [ "pi-package", diff --git a/src/index.ts b/src/index.ts index 515cb32..90de066 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,15 @@ /** - * pi-plan — plan mode that sets up goals with evidence, tracked in one plan.md, signed off by a + * pi-goals — plan mode that sets up goals with evidence, tracked in one goals.md, signed off by a * read-only subagent check. A successor to pi-lgtm, kept deliberately small (≈ burneikis/pi-plan * plus the additions: goals + failure_modes + subtasks, a sign-off check, a widget, a reminder). * - * Philosophy (spec D3): the form guides, it does not gate. The agent edits plan.md with its normal + * Philosophy (spec D3): the form guides, it does not gate. The agent edits goals.md with its normal * Edit tool. The one blessed tool is CompleteGoal, which runs the sign-off check and records it. The * reminder + the injected plan + git/widget visibility carry the process; we trust the agent's * judgement rather than guarding it. * * Flow: - * /plan -> plan mode: agent explores, drafts goals into plan.md (planDrafting guides) + * /goals -> plan mode: agent explores, drafts goals into goals.md (planDrafting guides) * agent_end -> review menu (Ready / Edit / $EDITOR / Cancel); Ready offers compaction * execution -> each turn, inject the plan summary (survives compaction) + a reminder; * agent works goals, ticks subtasks, appends ## Log, calls CompleteGoal @@ -27,10 +27,10 @@ import { Type } from "@sinclair/typebox"; import { counts, findGoal, type Goal, type PlanDoc, parse, recordSignOff, type SignOff } from "./plan-file.js"; import { evidenceJudgeSystem, evidenceJudgeUser, planDrafting, planInjection, reminder } from "./prompts.js"; -const STATE = "pi-plan-state"; -const PLAN_CONTEXT = "pi-plan-context"; // injected plan-mode guidance, stripped from history later -const STATUS_KEY = "pi-plan"; -const WIDGET_KEY = "pi-plan-widget"; +const STATE = "pi-goals-state"; +const PLAN_CONTEXT = "pi-goals-context"; // injected plan-mode guidance, stripped from history later +const STATUS_KEY = "pi-goals"; +const WIDGET_KEY = "pi-goals-widget"; const READ_ONLY_TOOLS = ["read", "grep", "find", "ls", "bash"]; interface PlanState { @@ -42,12 +42,12 @@ interface PlanState { export default function piPlanExtension(pi: ExtensionAPI): void { let state: PlanState = { isPlanMode: false, objective: null, judgeModel: null }; - // Reminder cadence: fire when an active goal exists but plan.md was not touched since last turn. + // Reminder cadence: fire when an active goal exists but goals.md was not touched since last turn. let lastInjectedPlan = ""; - // newSession is only on the command-handler context; agent_end's ctx lacks it. Save it from /plan. + // newSession is only on the command-handler context; agent_end's ctx lacks it. Save it from /goals. let savedCmdCtx: ExtensionCommandContext | null = null; - const planPath = (ctx: ExtensionContext) => join(ctx.cwd, "plan.md"); + const planPath = (ctx: ExtensionContext) => join(ctx.cwd, "goals.md"); const readPlan = (ctx: ExtensionContext): string => (existsSync(planPath(ctx)) ? readFileSync(planPath(ctx), "utf-8") : ""); function persist(): void { @@ -57,7 +57,7 @@ export default function piPlanExtension(pi: ExtensionAPI): void { function updateWidget(ctx: ExtensionContext): void { if (state.isPlanMode) { ctx.ui.setStatus(STATUS_KEY, ctx.ui.theme.fg("warning", "planning")); - ctx.ui.setWidget(WIDGET_KEY, ["pi-plan: drafting goals", "Write goals to plan.md, then review."]); + ctx.ui.setWidget(WIDGET_KEY, ["pi-goals: drafting goals", "Write goals to goals.md, then review."]); return; } const doc = parse(readPlan(ctx)); @@ -73,7 +73,7 @@ export default function piPlanExtension(pi: ExtensionAPI): void { function goalWidgetLines(doc: PlanDoc): string[] { const mark: Record = { done: "✔", active: "▸", open: "◻", cancelled: "✗" }; - const lines = [`Plan: ${doc.objective || "(untitled)"}`]; + const lines = [`Goals: ${doc.objective || "(untitled)"}`]; for (const g of doc.goals) { // Show every goal with its status glyph (✔ done, ▸ active, ◻ open, ✗ cancelled) so finished // goals read as checked off rather than vanishing. Plans are small, so this stays readable. @@ -86,8 +86,8 @@ export default function piPlanExtension(pi: ExtensionAPI): void { // --- plan mode: setup ------------------------------------------------------------------------- - pi.registerCommand("plan", { - description: "Plan mode: set up goals (with evidence) in plan.md, then work them. /plan ", + pi.registerCommand("goals", { + description: "Plan mode: set up goals (with evidence) in goals.md, then work them. /goals ", handler: async (args, ctx) => { savedCmdCtx = ctx; // ctx here is an ExtensionCommandContext (has newSession); keep it for later const arg = args.trim(); @@ -99,7 +99,7 @@ export default function piPlanExtension(pi: ExtensionAPI): void { setJudge(arg.slice("judge".length).trim(), ctx); return; } - // Bare `/plan` enters plan mode by prompting for the objective (the common expectation). + // Bare `/goals` enters plan mode by prompting for the objective (the common expectation). // If the user cancels with no objective, fall back to showing the current plan. let objective = arg; if (!objective) { @@ -128,24 +128,24 @@ export default function piPlanExtension(pi: ExtensionAPI): void { async function clearPlan(ctx: ExtensionContext): Promise { if (!existsSync(planPath(ctx))) { - ctx.ui.notify("No plan.md to clear.", "info"); + ctx.ui.notify("No goals.md to clear.", "info"); return; } if (ctx.hasUI) { - const ok = await ctx.ui.select("Clear plan.md? (it stays in git history)", ["Cancel", "Clear plan.md"]); - if (ok !== "Clear plan.md") return; + const ok = await ctx.ui.select("Clear goals.md? (it stays in git history)", ["Cancel", "Clear goals.md"]); + if (ok !== "Clear goals.md") return; } writeFileSync(planPath(ctx), ""); state = { ...state, isPlanMode: false, objective: null }; persist(); updateWidget(ctx); - ctx.ui.notify("Cleared plan.md.", "info"); + ctx.ui.notify("Cleared goals.md.", "info"); } function showPlan(ctx: ExtensionContext): void { const content = readPlan(ctx); if (!content.trim()) { - ctx.ui.notify("No plan yet. Use /plan to start.", "info"); + ctx.ui.notify("No goals yet. Use /goals to start.", "info"); return; } ctx.ui.notify(content, "info"); @@ -156,7 +156,7 @@ export default function piPlanExtension(pi: ExtensionAPI): void { async function reviewLoop(ctx: ExtensionContext): Promise { while (true) { const doc = parse(readPlan(ctx)); - const choice = await ctx.ui.select(`Plan: ${doc.goals.length} goal(s). What next?`, [ + const choice = await ctx.ui.select(`Goals: ${doc.goals.length} goal(s). What next?`, [ "Ready — start working the plan", "Edit — ask the agent to revise", "Open in $EDITOR", @@ -164,7 +164,7 @@ export default function piPlanExtension(pi: ExtensionAPI): void { ]); if (!choice || choice.startsWith("Cancel")) { exitPlanMode(ctx); - ctx.ui.notify("Left plan mode. plan.md kept.", "info"); + ctx.ui.notify("Left plan mode. goals.md kept.", "info"); return; } if (choice.startsWith("Ready")) return startExecution(ctx); @@ -203,7 +203,7 @@ export default function piPlanExtension(pi: ExtensionAPI): void { const planFile = planPath(ctx); const planContent = readPlan(ctx); // captured now: ctx is stale after newSession below const parentSession = ctx.sessionManager.getSessionFile(); - const startMsg = `Work the plan in ${planFile}. Pick an open goal, mark it active (set its header to [/]), work its subtasks, and when its done_when is met fill the goal's evidence: block then call CompleteGoal with the goal_id. Keep plan.md current as you go.`; + const startMsg = `Work the goals in ${planFile}. Pick an open goal, mark it active (set its header to [/]), work its subtasks, and when its done_when is met fill the goal's evidence: block then call CompleteGoal with the goal_id. Keep goals.md current as you go.`; exitPlanMode(ctx); if (fresh && savedCmdCtx) { @@ -223,7 +223,7 @@ export default function piPlanExtension(pi: ExtensionAPI): void { } return; } - if (doc.objective) pi.setSessionName(`Plan: ${doc.objective}`); + if (doc.objective) pi.setSessionName(`Goals: ${doc.objective}`); ctx.ui.notify(planContent, "info"); pi.sendUserMessage(startMsg, { deliverAs: "followUp" }); } @@ -234,20 +234,20 @@ export default function piPlanExtension(pi: ExtensionAPI): void { name: "CompleteGoal", label: "Complete goal", description: - "Sign off a goal once its done_when is met. First fill the goal's evidence: block in plan.md " + + "Sign off a goal once its done_when is met. First fill the goal's evidence: block in goals.md " + "(a '- ' list pointing at durable artifacts: saved logs, committed diffs, files, not claims), then " + "call this with the goal_id. Runs the goal's verify command (if any) then a read-only subagent that " + "inspects that evidence against the repo. On accept, the goal is marked done and logged; on reject, " + "it stays open and you get what is missing.", parameters: Type.Object({ - goal_id: Type.String({ description: "The goal's from plan.md" }), + goal_id: Type.String({ description: "The goal's from goals.md" }), }), async execute(_id, params, signal, _onUpdate, ctx) { const content = readPlan(ctx); const goal = findGoal(parse(content), params.goal_id); - if (!goal) return text(`No goal #${params.goal_id} in plan.md.`, true); + if (!goal) return text(`No goal #${params.goal_id} in goals.md.`, true); if (goal.evidence.length === 0) { - return text(`Goal #${goal.id} has no evidence: block. Add a "- " evidence list to the goal in plan.md (what shows done_when is met, and where to verify it), then call CompleteGoal.`, true); + return text(`Goal #${goal.id} has no evidence: block. Add a "- " evidence list to the goal in goals.md (what shows done_when is met, and where to verify it), then call CompleteGoal.`, true); } // Decide the outcome (the I/O); recordSignOff applies it to the file (the pure write). @@ -279,7 +279,7 @@ export default function piPlanExtension(pi: ExtensionAPI): void { lastLogLine: doc.log.at(-1) ?? null, counts: { done: c.done, open: c.open + c.active }, }); - // Reminder fires when there is an active goal but plan.md was untouched since the last turn. + // Reminder fires when there is an active goal but goals.md was untouched since the last turn. const planNow = readPlan(ctx); if (active && planNow === lastInjectedPlan) body += `\n\n${reminder}`; lastInjectedPlan = planNow; @@ -290,7 +290,7 @@ export default function piPlanExtension(pi: ExtensionAPI): void { if (!state.isPlanMode || !ctx.hasUI) return; const doc = parse(readPlan(ctx)); if (doc.goals.length === 0) { - ctx.ui.notify("No goals found in plan.md yet — ask the agent to draft them.", "warning"); + ctx.ui.notify("No goals found in goals.md yet — ask the agent to draft them.", "warning"); return; } await reviewLoop(ctx); diff --git a/src/plan-file.ts b/src/plan-file.ts index 05b0eb7..8182d33 100644 --- a/src/plan-file.ts +++ b/src/plan-file.ts @@ -1,5 +1,5 @@ /** - * plan-file.ts — read plan.md, and the two writes CompleteGoal needs. That is all. + * plan-file.ts — read goals.md, and the two writes CompleteGoal needs. That is all. * * Pure module, no pi deps, so it unit-tests without a runtime. The file is the canonical store and * the agent edits it with its normal Edit tool (create goals, tick subtasks, append log), guided by @@ -14,7 +14,7 @@ * * Format: * - * # Plan: + * # Goals: * * ## Goal: [ ] * @@ -87,7 +87,7 @@ export function parse(text: string): PlanDoc { }; for (const line of lines) { - const objMatch = /^#\s+Plan:\s*(.*)$/.exec(line); + const objMatch = /^#\s+Goals:\s*(.*)$/.exec(line); if (objMatch) { objective = objMatch[1].trim(); continue; @@ -201,7 +201,7 @@ export type SignOff = | { kind: "rejected"; missing: string } | { kind: "accepted" }; -/** Apply a sign-off outcome to plan.md text: accept flips the header checkbox to [x] + logs; reject only logs. Pure. */ +/** Apply a sign-off outcome to goals.md text: accept flips the header checkbox to [x] + logs; reject only logs. Pure. */ export function recordSignOff( text: string, goalId: string, @@ -209,7 +209,7 @@ export function recordSignOff( outcome: SignOff, ): { content: string; message: string; isError: boolean } { const goal = findGoal(parse(text), goalId); - if (!goal) return { content: text, message: `No goal #${goalId} in plan.md.`, isError: true }; + if (!goal) return { content: text, message: `No goal #${goalId} in goals.md.`, isError: true }; if (outcome.kind === "verify_failed") { const content = appendLog(text, `${when} reject #${goalId}: verify exit ${outcome.exitCode}`); @@ -222,7 +222,7 @@ export function recordSignOff( } const flipped = setGoalStatus(text, goalId, "done"); const content = appendLog(flipped, `${when} signed off #${goalId}: ${goal.subject} (oracle accept)`); - return { content, message: `Signed off #${goalId}: ${goal.subject}. Marked done in plan.md.`, isError: false }; + return { content, message: `Signed off #${goalId}: ${goal.subject}. Marked done in goals.md.`, isError: false }; } /** Append one verbatim line to ## Log (creating the section if absent). The other CompleteGoal write. */ diff --git a/src/prompts.ts b/src/prompts.ts index 8ff6177..075ff3a 100644 --- a/src/prompts.ts +++ b/src/prompts.ts @@ -1,8 +1,8 @@ /** - * pi-plan — all model-facing text, in flow order. + * pi-goals — all model-facing text, in flow order. * * Philosophy: the form guides a process; it does not police one. The agent can - * edit plan.md freely. These prompts + the plan.md structure make the right path + * edit goals.md freely. These prompts + the goals.md structure make the right path * the easy path. The only step that is genuinely rigorous is the evidence judge * (6), and even that is reached by guiding the agent to call CompleteGoal, not by * trapping it. Bypasses stay visible in the git diff and the widget. @@ -29,12 +29,12 @@ * * System guidance for the plan-phase agent. Runs on the plan model (may differ * from the execution model; the choice is sticky — see oracle.json-style config). - * This phase is read-only: explore, then draft goals into plan.md. No code yet. + * This phase is read-only: explore, then draft goals into goals.md. No code yet. * The field requirements here are the whole "elicitation" — get them agreed up * front, because the human reviews this output before any execution. * ──────────────────────────────────────────────────────────────────────── */ export const planDrafting = `\ -You are in plan mode. Explore the repository read-only, then draft goals into plan.md. +You are in plan mode. Explore the repository read-only, then draft goals into goals.md. Do not write or run code in this phase. Produce a plan the human will review and approve. Right-size it, don't force structure that isn't there: @@ -47,7 +47,7 @@ Right-size it, don't force structure that isn't there: Write the whole file in this shape: -# Plan: +# Goals: ## Goal: [ ] @@ -71,7 +71,7 @@ Keep it lean: - done_when is ONE concrete, checkable condition, not a paragraph, no "if wrong" clause. The symptom of failure goes in failure_modes, not here. - done_when names a real artifact: a file, a test result, a committed diff, a program's output. - Never write it about plan.md's own checkbox or ## Log: CompleteGoal writes those when it accepts, + Never write it about goals.md's own checkbox or ## Log: CompleteGoal writes those when it accepts, so a done_when about them is circular and the sign-off can never pass. - failure_modes: 0-2 terse items, only the non-obvious ways a "done" could be wrong (a pre-mortem). If you add a verify command, one mode can be "verify passes on a gamed file". @@ -95,13 +95,13 @@ export function planInjection(p: { counts: { done: number; open: number }; }): string { if (!p.activeGoal) { - return `Plan (plan.md): ${p.objective}\nNo active goal. ${p.counts.open} open, ${p.counts.done} done. Pick the next goal (set its header to [/]) or run /plan.`; + return `Goals (goals.md): ${p.objective}\nNo active goal. ${p.counts.open} open, ${p.counts.done} done. Pick the next goal (set its header to [/]) or run /goals.`; } const subtasks = p.activeGoal.openSubtasks.length ? p.activeGoal.openSubtasks.map((s) => ` - [ ] ${s}`).join("\n") : " (no open subtasks)"; return `\ -Plan (plan.md): ${p.objective} +Goals (goals.md): ${p.objective} Active goal: ${p.activeGoal.subject} done_when: ${p.activeGoal.done_when} Open subtasks: @@ -115,15 +115,15 @@ Progress: ${p.counts.done} done, ${p.counts.open} open.`; * * The typed nudge. This is both the housekeeping and the autonomy engine — it is * what makes the process get followed without a hard gate. Fires after N - * file-modifying turns since the last plan.md update while a goal is active. + * file-modifying turns since the last goals.md update while a goal is active. * Keep the wording stable so it doesn't thrash the cache. * ──────────────────────────────────────────────────────────────────────── */ export const reminder = `\ -Keep plan.md current as you work: +Keep goals.md current as you work: - tasks: tick the subtasks you've finished; add any new ones you've discovered. - log: append ONE short line to ## Log (append, don't rewrite earlier lines). -- goal: when the active goal's done_when is met, fill its evidence: block in plan.md (a "- " list +- goal: when the active goal's done_when is met, fill its evidence: block in goals.md (a "- " list pointing at durable artifacts), then call CompleteGoal with the goal_id. Don't tick the goal's header [x] by hand; CompleteGoal reads the evidence, runs the check, and writes [x]. - otherwise: keep working toward the active goal. Don't stop to ask unless you're genuinely @@ -137,7 +137,7 @@ Keep plan.md current as you work: * continue. Does not mutate the system prompt, so the cache holds. * ──────────────────────────────────────────────────────────────────────── */ export const continuation = `\ -Continue toward the active goal in plan.md. If it now meets its done_when, fill the goal's +Continue toward the active goal in goals.md. If it now meets its done_when, fill the goal's evidence: block (durable artifacts: saved logs, committed diffs, files, not just claims) and then call CompleteGoal with the goal_id. If you're blocked, state what's blocking it.`; diff --git a/test/plan-file.test.ts b/test/plan-file.test.ts index 007e9a6..d9c1daf 100644 --- a/test/plan-file.test.ts +++ b/test/plan-file.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { appendLog, counts, findGoal, parse, recordSignOff, setGoalStatus } from "../src/plan-file.js"; -const SAMPLE = `# Plan: ship the cache layer +const SAMPLE = `# Goals: ship the cache layer ## Goal: [/] Implement cache layer @@ -91,7 +91,7 @@ describe("parse", () => { describe("failure_modes vs subtask disambiguation", () => { it("a column-0 checkbox right after failure_modes: is a SUBTASK", () => { const doc = parse( - `# Plan: x\n\n## Goal: [ ] G\n\ndone_when: z\nfailure_modes:\n- [ ] first subtask\n- [x] second subtask\n`, + `# Goals: x\n\n## Goal: [ ] G\n\ndone_when: z\nfailure_modes:\n- [ ] first subtask\n- [x] second subtask\n`, ); const g = findGoal(doc, "g-1"); expect(g?.failure_modes).toEqual([]); @@ -103,7 +103,7 @@ describe("failure_modes vs subtask disambiguation", () => { it("an indented checkbox-shaped item inside failure_modes is a FAILURE MODE", () => { const doc = parse( - `# Plan: x\n\n## Goal: [ ] G\n\ndone_when: z\nfailure_modes:\n - [ ] prose that looks like a checkbox\n- [ ] real subtask\n`, + `# Goals: x\n\n## Goal: [ ] G\n\ndone_when: z\nfailure_modes:\n - [ ] prose that looks like a checkbox\n- [ ] real subtask\n`, ); const g = findGoal(doc, "g-2"); expect(g?.failure_modes).toEqual(["[ ] prose that looks like a checkbox"]); @@ -111,7 +111,7 @@ describe("failure_modes vs subtask disambiguation", () => { }); it("a goal with no failure_modes keeps its subtasks", () => { - const doc = parse(`# Plan: x\n\n## Goal: [ ] G\n\ndone_when: z\n- [ ] only subtask\n`); + const doc = parse(`# Goals: x\n\n## Goal: [ ] G\n\ndone_when: z\n- [ ] only subtask\n`); const g = findGoal(doc, "g-3"); expect(g?.failure_modes).toEqual([]); expect(g?.subtasks).toEqual([{ text: "only subtask", done: false }]); @@ -147,7 +147,7 @@ describe("the two CompleteGoal writes (minimal diff)", () => { }); it("appendLog creates the section when absent", () => { - const noLog = "# Plan: x\n\n## Goal: [ ] y\n\ndone_when: z\n"; + const noLog = "# Goals: x\n\n## Goal: [ ] y\n\ndone_when: z\n"; expect(parse(appendLog(noLog, "first entry")).log).toEqual(["- first entry"]); }); });