@linimin/pi-letscook 0.1.66 → 0.1.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -97,14 +97,15 @@ const STARTUP_ANALYST_ROLE = "cook-proposal-analyst";
97
97
  const ANALYST_HEARTBEAT_MS = 5_000;
98
98
 
99
99
  const PRIMARY_AGENT_HANDOFF_SYSTEM_PROMPT = [
100
- "You are the primary agent preparing an explicit /cook handoff after the user already chose workflow mode.",
101
- "Return either exactly one fenced ```cook_handoff JSON block or one brief plain sentence explaining why no concrete handoff can be prepared.",
102
- "If you can prepare a handoff, the JSON must use kind cook_handoff, source primary_agent, and handoff_kind implementation_workflow_handoff.",
103
- "When the user has clearly accepted a concrete assistant-proposed slice, carry that slice forward into the handoff instead of broadening or re-guessing the mission.",
104
- "Do not make /cook infer or rediscover the mission from recent discussion later; author the handoff now from the primary-agent view of the task.",
100
+ "You are the primary agent preparing an explicit /cook startup plan after the user already chose workflow mode.",
101
+ "Return either exactly one fenced ```cook_handoff JSON block or one brief plain sentence explaining why no concrete startup plan can be prepared.",
102
+ "If you can prepare a plan, the JSON must use kind cook_handoff, source primary_agent, and handoff_kind implementation_workflow_handoff.",
103
+ "Author the approved workflow startup plan now from the primary-agent view of the task so /cook can persist it under .agent before completion-regrounder derives canonical slices.",
104
+ "Capture the agreed mission, scope, constraints or non_goals, acceptance, risks, notes, and any concrete planning hints that will help completion-regrounder split slices later.",
105
+ "If a bounded first slice, likely implementation surfaces, or likely verification commands are already obvious, include first_slice_goal, first_slice_non_goals, implementation_surfaces, verification_commands, and why_this_slice_first as optional hints only. They are not required when the overall startup plan is already concrete enough to begin workflow planning.",
106
+ "Do not make /cook infer or rediscover the mission from recent discussion later; author the startup plan now from the primary-agent view of the task.",
105
107
  "Do not emit markdown commentary before or after the capsule.",
106
- "If the task is not concrete enough for implementation workflow, do not invent the slice.",
107
- "A valid implementation-ready handoff must include mission, scope, constraints or non_goals, acceptance, risks, notes, first_slice_goal, first_slice_non_goals, implementation_surfaces, verification_commands, and why_this_slice_first.",
108
+ "If the task is not concrete enough for workflow startup, do not invent missing detail.",
108
109
  ].join(" ");
109
110
  const PRIMARY_AGENT_HANDOFF_ROLE = "cook-primary-agent-handoff";
110
111
 
@@ -342,7 +343,7 @@ function buildPrimaryAgentHandoffPrompt(projectName: string, recentEntries: Rece
342
343
  lines.push(
343
344
  "",
344
345
  "Task:",
345
- "The user explicitly invoked /cook. Prepare the primary-agent handoff that /cook should consume immediately for Start/Cancel confirmation.",
346
+ "The user explicitly invoked /cook. Prepare the primary-agent startup plan that /cook should consume immediately for Start/Cancel confirmation, persistence under .agent, and later slice derivation by completion-regrounder.",
346
347
  );
347
348
  return lines.join("\n");
348
349
  }
@@ -359,7 +360,7 @@ async function runPrimaryAgentHandoffSubprocess(params: GenerateCookHandoffWithA
359
360
  const invocation = getPiInvocation(args);
360
361
  const liveActivity = createLiveRoleActivity(PRIMARY_AGENT_HANDOFF_ROLE);
361
362
  liveActivity.progress = "Preparing primary-agent /cook handoff";
362
- liveActivity.currentAction = "Authoring explicit startup handoff from current task context";
363
+ liveActivity.currentAction = "Authoring explicit startup plan from current task context";
363
364
  liveActivity.assistantSummary = liveActivity.progress;
364
365
  try {
365
366
  const output = await new Promise<string | undefined>((resolve) => {
@@ -409,7 +410,7 @@ export async function generateCookHandoffWithAgent(params: GenerateCookHandoffWi
409
410
  try {
410
411
  return await runPrimaryAgentHandoffSubprocess(params);
411
412
  } catch (error) {
412
- console.warn("[completion] primary-agent handoff generation failed", error);
413
+ console.warn("[completion] primary-agent startup-plan generation failed", error);
413
414
  return undefined;
414
415
  }
415
416
  }
@@ -3,6 +3,7 @@ import { spawnSync } from "node:child_process";
3
3
  import { promises as fsp } from "node:fs";
4
4
  import * as os from "node:os";
5
5
  import * as path from "node:path";
6
+ import { buildApprovedStartupPlanMarkdown } from "./prompt-surfaces";
6
7
  import type { CompletionStateSnapshot, JsonRecord } from "./types";
7
8
 
8
9
  const PROTOCOL_ID = "completion";
@@ -45,6 +46,8 @@ export function resolveFiles(root: string) {
45
46
  statePath: path.join(agentDir, "state.json"),
46
47
  planPath: path.join(agentDir, "plan.json"),
47
48
  activePath: path.join(agentDir, "active-slice.json"),
49
+ startupPlanPath: path.join(agentDir, "startup-plan.json"),
50
+ startupPlanMarkdownPath: path.join(agentDir, "startup-plan.md"),
48
51
  sliceHistoryPath: path.join(agentDir, "slice-history.jsonl"),
49
52
  stopHistoryPath: path.join(agentDir, "stop-check-history.jsonl"),
50
53
  verificationEvidencePath: path.join(agentDir, "verification-evidence.json"),
@@ -141,6 +144,7 @@ export async function loadCompletionSnapshot(startCwd: string): Promise<Completi
141
144
  const state = await readJson(files.statePath);
142
145
  const plan = await readJson(files.planPath);
143
146
  const active = await readJson(files.activePath);
147
+ const startupPlan = await readJson(files.startupPlanPath);
144
148
  const verificationEvidence = await readJson(files.verificationEvidencePath);
145
149
  return {
146
150
  files,
@@ -148,6 +152,7 @@ export async function loadCompletionSnapshot(startCwd: string): Promise<Completi
148
152
  state,
149
153
  plan,
150
154
  active,
155
+ startupPlan,
151
156
  verificationEvidence,
152
157
  activeSlice: findActiveSlice(plan, active),
153
158
  };
@@ -275,6 +280,32 @@ export function defaultPlan(
275
280
  };
276
281
  }
277
282
 
283
+ export function defaultStartupPlan(
284
+ missionAnchor: string,
285
+ routing?: { taskType?: string; evaluationProfile?: string },
286
+ approvedStartupPlan?: JsonRecord,
287
+ ): JsonRecord {
288
+ return approvedStartupPlan ?? {
289
+ schema_version: 1,
290
+ artifact_type: "completion-startup-plan",
291
+ status: "approved",
292
+ source: "recent_discussion",
293
+ captured_at: null,
294
+ mission_anchor: missionAnchor,
295
+ goal_text: `Mission: ${missionAnchor}`,
296
+ task_type: routing?.taskType ?? DEFAULT_TASK_TYPE,
297
+ evaluation_profile: routing?.evaluationProfile ?? DEFAULT_EVALUATION_PROFILE,
298
+ scope: [],
299
+ constraints: [],
300
+ acceptance: [],
301
+ risks: [],
302
+ notes: ["No approved startup plan has been recorded yet."],
303
+ planned_surfaces: [],
304
+ verification_intent: [],
305
+ sequencing_hints: [],
306
+ };
307
+ }
308
+
278
309
  export function defaultActiveSlice(
279
310
  missionAnchor: string,
280
311
  routing?: { taskType?: string; evaluationProfile?: string },
@@ -321,7 +352,7 @@ export function defaultVerificationEvidence(): JsonRecord {
321
352
  }
322
353
 
323
354
  export function buildAgentReadme(projectName: string): string {
324
- return `# Completion Control Plane\n\nThis repository uses the \`completion\` workflow for long-running coding tasks.\n\n## Canonical tracked contract files\n\n- \`.agent/README.md\`\n- \`.agent/mission.md\`\n- \`.agent/profile.json\`\n- \`.agent/verify_completion_stop.sh\`\n- \`.agent/verify_completion_control_plane.sh\`\n\n## Ignored canonical execution state\n\n- \`.agent/state.json\`\n- \`.agent/plan.json\`\n- \`.agent/active-slice.json\`\n- \`.agent/slice-history.jsonl\`\n- \`.agent/stop-check-history.jsonl\`\n- \`.agent/verification-evidence.json\`\n- \`.agent/*.log\`\n- \`.agent/tmp/\`\n\n\`.agent/verification-evidence.json\` is the durable canonical record of deterministic verification for the selected slice or current HEAD. Recovery, review, audit, and stop-check reminder surfaces consume it instead of temp-only artifacts or conversational summaries when it is populated.\n\nThe source of truth for long-running completion work is canonical \`.agent/**\` state plus current repo truth.\n\nProject: ${projectName}\n`;
355
+ return `# Completion Control Plane\n\nThis repository uses the \`completion\` workflow for long-running coding tasks.\n\n## Canonical tracked contract files\n\n- \`.agent/README.md\`\n- \`.agent/mission.md\`\n- \`.agent/profile.json\`\n- \`.agent/verify_completion_stop.sh\`\n- \`.agent/verify_completion_control_plane.sh\`\n\n## Ignored canonical execution state\n\n- \`.agent/state.json\`\n- \`.agent/startup-plan.json\`\n- \`.agent/startup-plan.md\`\n- \`.agent/plan.json\`\n- \`.agent/active-slice.json\`\n- \`.agent/slice-history.jsonl\`\n- \`.agent/stop-check-history.jsonl\`\n- \`.agent/verification-evidence.json\`\n- \`.agent/*.log\`\n- \`.agent/tmp/\`\n\n\`.agent/startup-plan.json\` plus \`.agent/startup-plan.md\` preserve the approved workflow startup plan captured at \`/cook\`. \`completion-regrounder\` consumes that plan as planning input, then derives canonical slices in \`.agent/plan.json\` from current repo truth.\n\n\`.agent/verification-evidence.json\` is the durable canonical record of deterministic verification for the selected slice or current HEAD. Recovery, review, audit, and stop-check reminder surfaces consume it instead of temp-only artifacts or conversational summaries when it is populated.\n\nThe source of truth for long-running completion work is canonical \`.agent/**\` state plus current repo truth.\n\nProject: ${projectName}\n`;
325
356
  }
326
357
 
327
358
  export function buildMission(projectName: string, missionAnchor: string): string {
@@ -424,15 +455,23 @@ function trackedDiffFiles(fromCommit, toCommit) {
424
455
 
425
456
  const profile = readJson('.agent/profile.json');
426
457
  const state = readJson('.agent/state.json');
458
+ const startupPlan = readJson('.agent/startup-plan.json');
427
459
  const plan = readJson('.agent/plan.json');
428
460
  const active = readJson('.agent/active-slice.json');
429
461
  const evidence = readJson('.agent/verification-evidence.json');
462
+ let startupPlanMarkdown = '';
463
+ try {
464
+ startupPlanMarkdown = fs.readFileSync('.agent/startup-plan.md', 'utf8');
465
+ } catch (error) {
466
+ fail('.agent/startup-plan.md must be present and readable: ' + error.message);
467
+ }
430
468
 
431
469
  ensureTrackedContractFiles();
432
470
 
433
471
  for (const [file, record] of [
434
472
  ['.agent/profile.json', profile],
435
473
  ['.agent/state.json', state],
474
+ ['.agent/startup-plan.json', startupPlan],
436
475
  ['.agent/plan.json', plan],
437
476
  ['.agent/active-slice.json', active],
438
477
  ]) {
@@ -443,12 +482,38 @@ for (const [file, record] of [
443
482
  const taskType = asString(profile.task_type);
444
483
  const evaluationProfile = asString(profile.evaluation_profile);
445
484
  if (asString(state.task_type) !== taskType) fail('.agent/state.json task_type must match .agent/profile.json task_type');
485
+ if (asString(startupPlan.task_type) !== taskType) fail('.agent/startup-plan.json task_type must match .agent/profile.json task_type');
446
486
  if (asString(plan.task_type) !== taskType) fail('.agent/plan.json task_type must match .agent/profile.json task_type');
447
487
  if (asString(active.task_type) !== taskType) fail('.agent/active-slice.json task_type must match .agent/profile.json task_type');
448
488
  if (asString(state.evaluation_profile) !== evaluationProfile) fail('.agent/state.json evaluation_profile must match .agent/profile.json evaluation_profile');
489
+ if (asString(startupPlan.evaluation_profile) !== evaluationProfile) fail('.agent/startup-plan.json evaluation_profile must match .agent/profile.json evaluation_profile');
449
490
  if (asString(plan.evaluation_profile) !== evaluationProfile) fail('.agent/plan.json evaluation_profile must match .agent/profile.json evaluation_profile');
450
491
  if (asString(active.evaluation_profile) !== evaluationProfile) fail('.agent/active-slice.json evaluation_profile must match .agent/profile.json evaluation_profile');
451
492
 
493
+ if (asString(startupPlan.artifact_type) !== 'completion-startup-plan') {
494
+ fail('.agent/startup-plan.json artifact_type must be completion-startup-plan');
495
+ }
496
+ if (asString(startupPlan.status) !== 'approved') {
497
+ fail('.agent/startup-plan.json status must be approved');
498
+ }
499
+ const startupPlanMissionAnchor = asString(startupPlan.mission_anchor);
500
+ if (!startupPlanMissionAnchor) fail('.agent/startup-plan.json mission_anchor must be present');
501
+ if (startupPlanMissionAnchor !== asString(state.mission_anchor)) fail('.agent/startup-plan.json mission_anchor must match .agent/state.json mission_anchor');
502
+ if (startupPlanMissionAnchor !== asString(plan.mission_anchor)) fail('.agent/startup-plan.json mission_anchor must match .agent/plan.json mission_anchor');
503
+ if (startupPlanMissionAnchor !== asString(active.mission_anchor)) fail('.agent/startup-plan.json mission_anchor must match .agent/active-slice.json mission_anchor');
504
+ if (!asString(startupPlan.goal_text)) fail('.agent/startup-plan.json goal_text must be present');
505
+ for (const field of ['scope', 'constraints', 'acceptance', 'risks', 'notes', 'planned_surfaces', 'verification_intent', 'sequencing_hints']) {
506
+ if (!Array.isArray(startupPlan[field])) fail('.agent/startup-plan.json is missing ' + field);
507
+ }
508
+ if (startupPlanMarkdown.trim().length === 0) fail('.agent/startup-plan.md must not be empty');
509
+ if (startupPlanMissionAnchor && !startupPlanMarkdown.includes(startupPlanMissionAnchor)) {
510
+ fail('.agent/startup-plan.md must mention the startup-plan mission_anchor');
511
+ }
512
+ const startupPlanGoalText = asString(startupPlan.goal_text);
513
+ if (startupPlanGoalText && !startupPlanMarkdown.includes(startupPlanGoalText)) {
514
+ fail('.agent/startup-plan.md must render the startup-plan goal_text');
515
+ }
516
+
452
517
  if (asString(evidence.artifact_type) !== 'completion-verification-evidence') {
453
518
  fail('.agent/verification-evidence.json artifact_type must be completion-verification-evidence');
454
519
  }
@@ -595,7 +660,12 @@ export type ScaffoldResult = {
595
660
  export async function scaffoldCompletionFiles(
596
661
  root: string,
597
662
  missionAnchor: string,
598
- options?: { analysis?: { taskType?: string; evaluationProfile?: string }; continuationReason?: string; advisoryStartupBrief?: JsonRecord },
663
+ options?: {
664
+ analysis?: { taskType?: string; evaluationProfile?: string };
665
+ continuationReason?: string;
666
+ advisoryStartupBrief?: JsonRecord;
667
+ approvedStartupPlan?: JsonRecord;
668
+ },
599
669
  ): Promise<ScaffoldResult> {
600
670
  const files = resolveFiles(root);
601
671
  const created: string[] = [];
@@ -605,6 +675,10 @@ export async function scaffoldCompletionFiles(
605
675
  const projectName = path.basename(root);
606
676
  const docsSurfaces = await detectDocsSurfaces(root);
607
677
  const verifierCommand = await detectVerifierCommand(root);
678
+ const startupPlanRecord =
679
+ options?.approvedStartupPlan ??
680
+ (await readJson(files.startupPlanPath)) ??
681
+ defaultStartupPlan(missionAnchor, { taskType: options?.analysis?.taskType, evaluationProfile: options?.analysis?.evaluationProfile });
608
682
  const trackedFiles: Array<{ path: string; content: string; executable?: boolean }> = [
609
683
  { path: path.join(files.agentDir, "README.md"), content: buildAgentReadme(projectName) },
610
684
  { path: path.join(files.agentDir, "mission.md"), content: buildMission(projectName, missionAnchor) },
@@ -618,6 +692,14 @@ export async function scaffoldCompletionFiles(
618
692
  path: files.statePath,
619
693
  content: `${JSON.stringify(defaultState(missionAnchor, { taskType: options?.analysis?.taskType, evaluationProfile: options?.analysis?.evaluationProfile, continuationReason: options?.continuationReason }, options?.advisoryStartupBrief), null, 2)}\n`,
620
694
  },
695
+ {
696
+ path: files.startupPlanPath,
697
+ content: `${JSON.stringify(startupPlanRecord, null, 2)}\n`,
698
+ },
699
+ {
700
+ path: files.startupPlanMarkdownPath,
701
+ content: buildApprovedStartupPlanMarkdown(startupPlanRecord as any),
702
+ },
621
703
  { path: files.planPath, content: `${JSON.stringify(defaultPlan(missionAnchor, { taskType: options?.analysis?.taskType, evaluationProfile: options?.analysis?.evaluationProfile }), null, 2)}\n` },
622
704
  { path: files.activePath, content: `${JSON.stringify(defaultActiveSlice(missionAnchor, { taskType: options?.analysis?.taskType, evaluationProfile: options?.analysis?.evaluationProfile }), null, 2)}\n` },
623
705
  { path: files.verificationEvidencePath, content: `${JSON.stringify(defaultVerificationEvidence(), null, 2)}\n` },
@@ -639,6 +721,7 @@ export function currentTaskType(snapshot: CompletionStateSnapshot): string | und
639
721
  return (
640
722
  asString(snapshot.active?.task_type) ??
641
723
  asString(snapshot.state?.task_type) ??
724
+ asString(snapshot.startupPlan?.task_type) ??
642
725
  asString(snapshot.plan?.task_type) ??
643
726
  asString(snapshot.profile?.task_type)
644
727
  );
@@ -648,6 +731,7 @@ export function currentEvaluationProfile(snapshot: CompletionStateSnapshot): str
648
731
  return (
649
732
  asString(snapshot.active?.evaluation_profile) ??
650
733
  asString(snapshot.state?.evaluation_profile) ??
734
+ asString(snapshot.startupPlan?.evaluation_profile) ??
651
735
  asString(snapshot.plan?.evaluation_profile) ??
652
736
  asString(snapshot.profile?.evaluation_profile)
653
737
  );
@@ -656,6 +740,7 @@ export function currentEvaluationProfile(snapshot: CompletionStateSnapshot): str
656
740
  export function currentMissionAnchor(snapshot: CompletionStateSnapshot): string {
657
741
  return (
658
742
  asString(snapshot.state?.mission_anchor) ??
743
+ asString(snapshot.startupPlan?.mission_anchor) ??
659
744
  asString(snapshot.plan?.mission_anchor) ??
660
745
  asString(snapshot.active?.mission_anchor) ??
661
746
  path.basename(snapshot.files.root)
@@ -18,6 +18,8 @@ export type CompletionFiles = {
18
18
  statePath: string;
19
19
  planPath: string;
20
20
  activePath: string;
21
+ startupPlanPath: string;
22
+ startupPlanMarkdownPath: string;
21
23
  sliceHistoryPath: string;
22
24
  stopHistoryPath: string;
23
25
  verificationEvidencePath: string;
@@ -30,6 +32,7 @@ export type CompletionStateSnapshot = {
30
32
  state?: JsonRecord;
31
33
  plan?: JsonRecord;
32
34
  active?: JsonRecord;
35
+ startupPlan?: JsonRecord;
33
36
  verificationEvidence?: JsonRecord;
34
37
  activeSlice?: JsonRecord;
35
38
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linimin/pi-letscook",
3
- "version": "0.1.66",
3
+ "version": "0.1.67",
4
4
  "description": "Pi package for long-running completion workflows with canonical .agent state, role-based subagents, continuity, and verification helpers.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -157,7 +157,7 @@ capsule = {
157
157
  "Keep scripts/active-slice-contract-test.sh aligned with the packaged startup contract."
158
158
  ],
159
159
  "risks": [
160
- "Active-slice fixture bootstrap must stay anchored to the fresh explicit handoff."
160
+ "Active-slice fixture bootstrap must stay anchored to the fresh explicit startup-plan preview."
161
161
  ],
162
162
  "notes": [
163
163
  "This handoff exists only to scaffold canonical files before the fixture rewrites them for contract parity coverage."
@@ -134,11 +134,11 @@ assertIncludes('.agent/README.md', 'durable canonical record of deterministic ve
134
134
  assertSectionIncludes('skills/completion-protocol/SKILL.md', '## Canonical Files', '- `.agent/verification-evidence.json`');
135
135
  assertSectionIncludes('skills/completion-protocol/SKILL.md', '## Canonical Inputs', '- `.agent/verification-evidence.json`');
136
136
  assertSectionIncludes('skills/completion-protocol/SKILL.md', '## Compaction And Recovery', '- `.agent/verification-evidence.json`');
137
- assertSectionIncludes('skills/completion-protocol/SKILL.md', '## Compaction And Recovery', '`completion-implementer` must also re-read canonical `.agent/state.json`, `.agent/plan.json`, `.agent/active-slice.json`, and `.agent/verification-evidence.json` before resuming work.');
137
+ assertSectionIncludes('skills/completion-protocol/SKILL.md', '## Compaction And Recovery', '`completion-implementer` must also re-read canonical `.agent/state.json`, `.agent/startup-plan.json`, `.agent/plan.json`, `.agent/active-slice.json`, and `.agent/verification-evidence.json` before resuming work.');
138
138
  assertSectionIncludes('skills/completion-protocol/references/completion.md', '## Ignored Canonical Execution State', '- `.agent/verification-evidence.json`');
139
139
  assertSectionIncludes('skills/completion-protocol/references/completion.md', '## Canonical Inputs', '- `.agent/verification-evidence.json`');
140
140
  assertSectionIncludes('skills/completion-protocol/references/completion.md', '## Compaction And Recovery', '- `.agent/verification-evidence.json`');
141
- assertSectionIncludes('skills/completion-protocol/references/completion.md', '## Compaction And Recovery', '`completion-implementer` must also re-read canonical `.agent/state.json`, `.agent/plan.json`, `.agent/active-slice.json`, and `.agent/verification-evidence.json` before resuming work.');
141
+ assertSectionIncludes('skills/completion-protocol/references/completion.md', '## Compaction And Recovery', '`completion-implementer` must also re-read canonical `.agent/state.json`, `.agent/startup-plan.json`, `.agent/plan.json`, `.agent/active-slice.json`, and `.agent/verification-evidence.json` before resuming work.');
142
142
  assertIncludes('extensions/completion/prompt-surfaces.ts', 'Verification evidence artifact: ${args.evidence.path} (${args.evidence.status})');
143
143
  assertIncludes('extensions/completion/prompt-surfaces.ts', 'Verification evidence summary: ${args.evidence.summary}');
144
144
  assertIncludes('extensions/completion/index.ts', 'Canonical verification evidence artifact is currently: ${evidence.path} (${evidence.status})');
@@ -202,7 +202,7 @@ capsule = {
202
202
  "Keep scripts/canonical-evidence-artifact-test.sh aligned with packaged bootstrap behavior."
203
203
  ],
204
204
  "risks": [
205
- "Evidence-artifact bootstrap must stay anchored to the fresh explicit handoff."
205
+ "Evidence-artifact bootstrap must stay anchored to the fresh explicit startup-plan preview."
206
206
  ],
207
207
  "notes": [
208
208
  "This fixture exists only to scaffold canonical files before rewriting them for evidence parity coverage."
@@ -242,7 +242,7 @@ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
242
242
  pi --session "$BOOTSTRAP_SESSION" -e "$PKG_ROOT" -p "/cook" \
243
243
  >"$TMPDIR/pi-canonical-evidence-bootstrap.out" 2>"$TMPDIR/pi-canonical-evidence-bootstrap.err"
244
244
 
245
- for file in .agent/profile.json .agent/state.json .agent/plan.json .agent/active-slice.json .agent/verification-evidence.json; do
245
+ for file in .agent/profile.json .agent/state.json .agent/startup-plan.json .agent/startup-plan.md .agent/plan.json .agent/active-slice.json .agent/verification-evidence.json; do
246
246
  [[ -f "$file" ]] || { echo "missing canonical bootstrap file: $file" >&2; exit 1; }
247
247
  done
248
248
 
@@ -288,6 +288,36 @@ acceptance = [
288
288
  'Canonical verification evidence is recorded for the selected slice.',
289
289
  'Fail-closed verification rejects missing or stale evidence.',
290
290
  ]
291
+ startup_plan = {
292
+ 'schema_version': 1,
293
+ 'artifact_type': 'completion-startup-plan',
294
+ 'status': 'approved',
295
+ 'source': 'primary_agent_handoff',
296
+ 'captured_at': '2026-05-03T00:00:00Z',
297
+ 'mission_anchor': mission,
298
+ 'goal_text': 'Mission: Exercise canonical verification evidence parity.\n\nScope:\n- Persist canonical verification evidence for the selected slice.\n- Keep the verifier fail-closed on stale or missing evidence.\n\nAcceptance:\n- Canonical verification evidence is recorded for the selected slice.\n- Fail-closed verification rejects missing or stale evidence.',
299
+ 'task_type': task_type,
300
+ 'evaluation_profile': evaluation_profile,
301
+ 'scope': [
302
+ 'Persist canonical verification evidence for the selected slice.',
303
+ 'Keep the verifier fail-closed on stale or missing evidence.',
304
+ ],
305
+ 'constraints': [
306
+ 'Keep the fixture scoped to canonical verification evidence parity.',
307
+ ],
308
+ 'acceptance': acceptance,
309
+ 'risks': [
310
+ 'Stale startup-plan parity could mask canonical evidence regressions.',
311
+ ],
312
+ 'notes': [
313
+ 'Use startup-plan parity to prove the verifier reads the approved startup plan alongside other canonical state.',
314
+ ],
315
+ 'planned_surfaces': implementation_surfaces,
316
+ 'verification_intent': verification_commands,
317
+ 'sequencing_hints': [
318
+ 'First slice goal: Persist canonical verification evidence for the selected slice.',
319
+ ],
320
+ }
291
321
  state = {
292
322
  'schema_version': 1,
293
323
  'mission_anchor': mission,
@@ -365,6 +395,21 @@ active = {
365
395
  }
366
396
 
367
397
  Path('.agent/state.json').write_text(json.dumps(state, indent=2) + '\n')
398
+ Path('.agent/startup-plan.json').write_text(json.dumps(startup_plan, indent=2) + '\n')
399
+ Path('.agent/startup-plan.md').write_text(
400
+ '# Approved Startup Plan\n\n'
401
+ f'Mission anchor: {mission}\n'
402
+ 'Source: primary_agent_handoff\n'
403
+ 'Captured at: 2026-05-03T00:00:00Z\n'
404
+ f'Task type: {task_type}\n'
405
+ f'Evaluation profile: {evaluation_profile}\n\n'
406
+ '## Goal\n\n'
407
+ f"{startup_plan['goal_text']}\n\n"
408
+ '## Planned surfaces\n\n'
409
+ + ''.join(f'- {item}\n' for item in startup_plan['planned_surfaces'])
410
+ + '\n## Verification intent\n\n'
411
+ + ''.join(f'- {item}\n' for item in startup_plan['verification_intent'])
412
+ )
368
413
  Path('.agent/plan.json').write_text(json.dumps(plan, indent=2) + '\n')
369
414
  Path('.agent/active-slice.json').write_text(json.dumps(active, indent=2) + '\n')
370
415
  PY