mirror of
https://github.com/wassname/pi-plan.git
synced 2026-06-27 16:31:07 +08:00
pi-plan -> pi-goals: rename package, command, and file to goals.md
Distinguishes this from the other pi-plan extensions by foregrounding what's different (goals tracked to verified completion). Mechanical rename only, no behavior change: - package @wassname2/pi-plan -> @wassname2/pi-goals (+ repo url) - plan.md -> goals.md (the canonical file) - command /plan -> /goals - file H1 marker "# Plan:" -> "# Goals:", widget/session labels likewise - internal state keys pi-plan-* -> pi-goals-* Internal source filename (plan-file.ts) and identifiers (planDrafting, PlanDoc, setGoalStatus) keep "plan"; they're not user-visible. External burneikis/pi-plan references are left intact. Co-Authored-By: Claudypoo <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
@@ -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 <model-ref>` (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 <model-ref>` (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
|
||||
<!-- id: cache-layer-1 -->
|
||||
@@ -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 <provider/model>` for an independent cross-family check.
|
||||
with `/goals judge <provider/model>` for an independent cross-family check.
|
||||
|
||||
## Prompts
|
||||
|
||||
|
||||
+3
-3
@@ -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",
|
||||
|
||||
+30
-30
@@ -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 <objective> -> plan mode: agent explores, drafts goals into plan.md (planDrafting guides)
|
||||
* /goals <objective> -> 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<Goal["status"], string> = { 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 <objective>",
|
||||
pi.registerCommand("goals", {
|
||||
description: "Plan mode: set up goals (with evidence) in goals.md, then work them. /goals <objective>",
|
||||
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<void> {
|
||||
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 <objective> to start.", "info");
|
||||
ctx.ui.notify("No goals yet. Use /goals <objective> 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<void> {
|
||||
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 <!-- id --> from plan.md" }),
|
||||
goal_id: Type.String({ description: "The goal's <!-- id --> 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);
|
||||
|
||||
+6
-6
@@ -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: <objective>
|
||||
* # Goals: <objective>
|
||||
*
|
||||
* ## Goal: [ ] <subject>
|
||||
* <!-- id: <slug> -->
|
||||
@@ -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. */
|
||||
|
||||
+12
-12
@@ -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: <the objective>
|
||||
# Goals: <the objective>
|
||||
|
||||
## Goal: [ ] <one short imperative line>
|
||||
<!-- id: <kebab-case-slug, unique> -->
|
||||
@@ -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 = `\
|
||||
<system-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.`;
|
||||
|
||||
|
||||
@@ -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
|
||||
<!-- id: cache-layer-1 -->
|
||||
@@ -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<!-- id: g-1 -->\ndone_when: z\nfailure_modes:\n- [ ] first subtask\n- [x] second subtask\n`,
|
||||
`# Goals: x\n\n## Goal: [ ] G\n<!-- id: g-1 -->\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<!-- id: g-2 -->\ndone_when: z\nfailure_modes:\n - [ ] prose that looks like a checkbox\n- [ ] real subtask\n`,
|
||||
`# Goals: x\n\n## Goal: [ ] G\n<!-- id: g-2 -->\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<!-- id: g-3 -->\ndone_when: z\n- [ ] only subtask\n`);
|
||||
const doc = parse(`# Goals: x\n\n## Goal: [ ] G\n<!-- id: g-3 -->\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<!-- id: y-1 -->\ndone_when: z\n";
|
||||
const noLog = "# Goals: x\n\n## Goal: [ ] y\n<!-- id: y-1 -->\ndone_when: z\n";
|
||||
expect(parse(appendLog(noLog, "first entry")).log).toEqual(["- first entry"]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user