@linimin/pi-letscook 0.1.32 → 0.1.35

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.
@@ -50,6 +50,7 @@ type CompletionFiles = {
50
50
  activePath: string;
51
51
  sliceHistoryPath: string;
52
52
  stopHistoryPath: string;
53
+ verificationEvidencePath: string;
53
54
  compactionMarkerPath: string;
54
55
  };
55
56
 
@@ -59,6 +60,7 @@ type CompletionStateSnapshot = {
59
60
  state?: JsonRecord;
60
61
  plan?: JsonRecord;
61
62
  active?: JsonRecord;
63
+ verificationEvidence?: JsonRecord;
62
64
  activeSlice?: JsonRecord;
63
65
  };
64
66
 
@@ -146,7 +148,7 @@ type ContextProposalDecision = {
146
148
  analysis: ContextProposalAnalysis;
147
149
  };
148
150
 
149
- type ContextProposalConfirmAction = "start" | "edit" | "cancel";
151
+ type ContextProposalConfirmAction = "start" | "cancel";
150
152
 
151
153
  type ContextProposalConfirmationActionItem = {
152
154
  id: ContextProposalConfirmAction;
@@ -171,7 +173,6 @@ type ContextProposalConfirmationLayout = {
171
173
  type ContextProposalConfirmOptions = {
172
174
  title: string;
173
175
  nonInteractiveBehavior?: "accept" | "cancel";
174
- editorPrompt?: string;
175
176
  };
176
177
 
177
178
  class StartupAnalystOverlay extends Container {
@@ -203,7 +204,7 @@ class StartupAnalystOverlay extends Container {
203
204
 
204
205
  private updateDisplay(): void {
205
206
  this.title.setText(this.theme.fg("accent", this.theme.bold("/cook proposal analyst")));
206
- this.body.setText(this.theme.fg("dim", this.lines.join("\n")));
207
+ this.body.setText(formatInlineRunningText(this.theme, this.lines, { primaryAssistant: true }));
207
208
  this.footer.setText(this.theme.fg("muted", "Esc cancel • This analysis runs before /cook writes canonical workflow state"));
208
209
  }
209
210
 
@@ -275,6 +276,7 @@ function resolveFiles(root: string): CompletionFiles {
275
276
  activePath: path.join(agentDir, "active-slice.json"),
276
277
  sliceHistoryPath: path.join(agentDir, "slice-history.jsonl"),
277
278
  stopHistoryPath: path.join(agentDir, "stop-check-history.jsonl"),
279
+ verificationEvidencePath: path.join(agentDir, "verification-evidence.json"),
278
280
  compactionMarkerPath: path.join(tmpDir, "post-compaction-recovery.json"),
279
281
  };
280
282
  }
@@ -290,14 +292,24 @@ function walkUpForDir(startCwd: string, segments: string[]): string | undefined
290
292
  }
291
293
  }
292
294
 
295
+ function completionSearchRoots(startCwd: string): string[] {
296
+ return [...new Set([path.resolve(startCwd), path.resolve(process.cwd())])];
297
+ }
298
+
293
299
  function findCompletionRoot(startCwd: string): string | undefined {
294
- const profilePath = walkUpForDir(startCwd, [".agent", "profile.json"]);
295
- return profilePath ? path.dirname(path.dirname(profilePath)) : undefined;
300
+ for (const candidateRoot of completionSearchRoots(startCwd)) {
301
+ const profilePath = walkUpForDir(candidateRoot, [".agent", "profile.json"]);
302
+ if (profilePath) return path.dirname(path.dirname(profilePath));
303
+ }
304
+ return undefined;
296
305
  }
297
306
 
298
307
  function findRepoRoot(startCwd: string): string | undefined {
299
- const gitPath = walkUpForDir(startCwd, [".git"]);
300
- return gitPath ? path.dirname(gitPath) : undefined;
308
+ for (const candidateRoot of completionSearchRoots(startCwd)) {
309
+ const gitPath = walkUpForDir(candidateRoot, [".git"]);
310
+ if (gitPath) return path.dirname(gitPath);
311
+ }
312
+ return undefined;
301
313
  }
302
314
 
303
315
  async function readJson(filePath: string): Promise<JsonRecord | undefined> {
@@ -354,12 +366,14 @@ async function loadCompletionSnapshot(startCwd: string): Promise<CompletionState
354
366
  const state = await readJson(files.statePath);
355
367
  const plan = await readJson(files.planPath);
356
368
  const active = await readJson(files.activePath);
369
+ const verificationEvidence = await readJson(files.verificationEvidencePath);
357
370
  return {
358
371
  files,
359
372
  profile,
360
373
  state,
361
374
  plan,
362
375
  active,
376
+ verificationEvidence,
363
377
  activeSlice: findActiveSlice(plan, active),
364
378
  };
365
379
  }
@@ -439,92 +453,37 @@ function isWeakMissionAnchor(value: string): boolean {
439
453
 
440
454
  type MissionAnchorAssessment = {
441
455
  derived: string;
442
- needsConfirmation: boolean;
443
- reason?: string;
444
456
  };
445
457
 
446
458
  function assessMissionAnchor(rawGoal: string, projectName: string): MissionAnchorAssessment {
447
- const normalized = normalizeMissionAnchorText(rawGoal);
448
- const derived = deriveMissionAnchor(rawGoal, projectName);
449
- if (!normalized) {
450
- return {
451
- derived,
452
- needsConfirmation: true,
453
- reason: "No meaningful goal text was provided.",
454
- };
455
- }
456
- if (isWeakMissionAnchor(normalized)) {
457
- return {
458
- derived,
459
- needsConfirmation: true,
460
- reason: "The goal is too short or vague for stable canonical workflow state.",
461
- };
462
- }
463
- const vaguePronouns = /\b(this|that|it|things|stuff|something)\b/i.test(normalized);
464
- const fallback = derived === `Drive ${projectName} to truthful, verifiable completion.`;
465
- if (fallback || vaguePronouns) {
466
- return {
467
- derived,
468
- needsConfirmation: true,
469
- reason: fallback
470
- ? "The initial goal was too ambiguous, so the workflow fell back to a generic repo-based mission."
471
- : "The goal still contains ambiguous references that are better confirmed before writing canonical state.",
472
- };
473
- }
474
- return { derived, needsConfirmation: false };
475
- }
476
-
477
- async function confirmMissionAnchor(
478
- ctx: { hasUI: boolean; ui: any },
479
- assessment: MissionAnchorAssessment,
480
- ): Promise<string | undefined> {
481
- if (!getCtxHasUI(ctx)) return assessment.derived;
482
- const ui = getCtxUi(ctx);
483
- if (!ui) return assessment.derived;
484
- if (!assessment.needsConfirmation) return assessment.derived;
485
- const title = "Confirm mission anchor";
486
- const reason = assessment.reason ? `${assessment.reason}\n\n` : "";
487
- const choice = await ui.select(
488
- title,
489
- [
490
- `${reason}Proposed mission anchor:\n${assessment.derived}\n\nUse proposed mission anchor`,
491
- "Edit mission anchor",
492
- "Cancel",
493
- ],
494
- );
495
- if (!choice || choice === "Cancel") return undefined;
496
- if (choice === "Edit mission anchor") {
497
- const edited = await ui.editor(title, assessment.derived);
498
- return edited?.trim() ? edited.trim() : undefined;
499
- }
500
- return assessment.derived;
459
+ return { derived: deriveMissionAnchor(rawGoal, projectName) };
501
460
  }
502
461
 
503
462
  type ExistingWorkflowDecision =
504
463
  | { action: "continue"; currentMissionAnchor: string }
505
464
  | { action: "refocus"; currentMissionAnchor: string; missionAnchor: string };
506
465
 
507
- function completionTestWorkflowActionOverride(): "continue" | "refocus" | undefined {
466
+ function completionTestWorkflowActionOverride(): "continue" | "refocus" | "cancel" | undefined {
508
467
  const raw = process.env.PI_COMPLETION_EXISTING_WORKFLOW_ACTION?.trim().toLowerCase();
509
- return raw === "continue" || raw === "refocus" ? raw : undefined;
468
+ return raw === "continue" || raw === "refocus" || raw === "cancel" ? raw : undefined;
510
469
  }
511
470
 
512
471
  function shouldSkipDriverKickoffForTests(): boolean {
513
472
  return process.env.PI_COMPLETION_SKIP_DRIVER_KICKOFF === "1";
514
473
  }
515
474
 
516
- function completionTestContextProposalActionOverride(): "accept" | "edit" | "cancel" | undefined {
475
+ function completionTestContextProposalActionOverride(): "accept" | "cancel" | undefined {
517
476
  const raw = process.env.PI_COMPLETION_CONTEXT_PROPOSAL_ACTION?.trim().toLowerCase();
518
- return raw === "accept" || raw === "edit" || raw === "cancel" ? raw : undefined;
519
- }
520
-
521
- function completionTestContextProposalEditText(): string | undefined {
522
- return asString(process.env.PI_COMPLETION_CONTEXT_PROPOSAL_EDIT_TEXT);
477
+ return raw === "accept" || raw === "cancel" ? raw : undefined;
523
478
  }
524
479
 
525
480
  function completionTestContextProposalUiActionOverride(): ContextProposalConfirmAction | undefined {
526
481
  const raw = process.env.PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_ACTION?.trim().toLowerCase();
527
- return raw === "start" || raw === "edit" || raw === "cancel" ? raw : undefined;
482
+ return raw === "start" || raw === "cancel" ? raw : undefined;
483
+ }
484
+
485
+ function completionTestExistingWorkflowChooserSnapshotPath(): string | undefined {
486
+ return asString(process.env.PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH);
528
487
  }
529
488
 
530
489
  function completionTestContextProposalUiSnapshotPath(): string | undefined {
@@ -561,6 +520,12 @@ function maybeWriteTestSnapshot(targetPath: string | undefined, content: string)
561
520
  }
562
521
  }
563
522
 
523
+ const COOK_MAIN_CHAT_RERUN_GUIDANCE = "Discuss changes in the main chat and rerun /cook.";
524
+
525
+ function buildCookCancellationMessage(prefix: string): string {
526
+ return `${prefix}. ${COOK_MAIN_CHAT_RERUN_GUIDANCE}`;
527
+ }
528
+
564
529
  function shouldDisableContextProposalAnalyst(): boolean {
565
530
  return process.env.PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST === "1";
566
531
  }
@@ -1210,15 +1175,10 @@ function buildContextProposalConfirmationActions(): ContextProposalConfirmationA
1210
1175
  label: "Start",
1211
1176
  description: "Accept this proposal and let /cook write or refocus canonical workflow state.",
1212
1177
  },
1213
- {
1214
- id: "edit",
1215
- label: "Edit",
1216
- description: "Open the existing proposal editor before starting the workflow.",
1217
- },
1218
1178
  {
1219
1179
  id: "cancel",
1220
1180
  label: "Cancel",
1221
- description: "Exit without changing canonical workflow state.",
1181
+ description: `Stop here without changing canonical workflow state. ${COOK_MAIN_CHAT_RERUN_GUIDANCE}`,
1222
1182
  },
1223
1183
  ];
1224
1184
  }
@@ -1230,7 +1190,7 @@ function buildContextProposalConfirmationLayout(
1230
1190
  const analysis = finalizeContextProposalAnalysis(proposal.analysis, [proposal.goalText, proposal.mission]);
1231
1191
  return {
1232
1192
  title,
1233
- intro: "Review the proposed mission, scope, constraints, acceptance, critique, and routing details before /cook writes canonical workflow state.",
1193
+ intro: "Review the proposed mission, scope, constraints, acceptance, critique, and routing details before /cook writes canonical workflow state. This gate is approval-only: either Start it as-is or Cancel, discuss changes in the main chat, and rerun /cook.",
1234
1194
  proposalHeading: "Proposed workflow",
1235
1195
  proposalBody: buildContextProposalDisplayText(proposal),
1236
1196
  critiqueHeading: "Critique and risks",
@@ -1282,7 +1242,7 @@ async function promptContextProposalConfirmationAction(
1282
1242
  const container = new Container();
1283
1243
  container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
1284
1244
  container.addChild(new Text(theme.fg("accent", theme.bold(layout.title)), 1, 0));
1285
- container.addChild(new Text(theme.fg("dim", layout.intro), 1, 0));
1245
+ container.addChild(new Text(layout.intro, 1, 0));
1286
1246
  container.addChild(new Text("", 0, 0));
1287
1247
  container.addChild(new Text(theme.fg("accent", theme.bold(layout.proposalHeading)), 1, 0));
1288
1248
  container.addChild(new Text(layout.proposalBody, 1, 0));
@@ -1302,13 +1262,13 @@ async function promptContextProposalConfirmationAction(
1302
1262
  selectedPrefix: (text) => theme.fg("accent", text),
1303
1263
  selectedText: (text) => theme.fg("accent", text),
1304
1264
  description: (text) => theme.fg("muted", text),
1305
- scrollInfo: (text) => theme.fg("dim", text),
1265
+ scrollInfo: (text) => text,
1306
1266
  noMatch: (text) => theme.fg("warning", text),
1307
1267
  });
1308
1268
  selectList.onSelect = (item) => done(item.value as ContextProposalConfirmAction);
1309
1269
  selectList.onCancel = () => done(undefined);
1310
1270
  container.addChild(selectList);
1311
- container.addChild(new Text(theme.fg("dim", layout.footer), 1, 0));
1271
+ container.addChild(new Text(layout.footer, 1, 0));
1312
1272
  container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
1313
1273
 
1314
1274
  return {
@@ -1326,60 +1286,16 @@ async function promptContextProposalConfirmationAction(
1326
1286
  });
1327
1287
  }
1328
1288
 
1329
- async function resolveEditedContextProposalDecision(
1330
- ctx: { hasUI: boolean; ui: any },
1331
- projectName: string,
1332
- editedText: string,
1333
- confirmMissionWhenNeeded: boolean,
1334
- fallbackAnalysis?: ContextProposalAnalysis,
1335
- ): Promise<ContextProposalDecision | undefined> {
1336
- if (!editedText.trim()) return undefined;
1337
- const editedProposal = parseContextProposal(editedText, projectName);
1338
- if (editedProposal) {
1339
- return {
1340
- missionAnchor: editedProposal.mission,
1341
- goalText: editedProposal.goalText,
1342
- analysis: finalizeContextProposalAnalysis(
1343
- mergeContextProposalAnalysis([editedProposal.analysis, fallbackAnalysis], [editedText, editedProposal.mission]),
1344
- [editedText, editedProposal.mission],
1345
- ),
1346
- };
1347
- }
1348
- const assessment = assessMissionAnchor(editedText, projectName);
1349
- const analysis = finalizeContextProposalAnalysis(fallbackAnalysis, [editedText, assessment.derived]);
1350
- if (!confirmMissionWhenNeeded) {
1351
- return { missionAnchor: assessment.derived, goalText: editedText.trim(), analysis };
1352
- }
1353
- const missionAnchor = await confirmMissionAnchor(ctx, assessment);
1354
- if (!missionAnchor) return undefined;
1355
- return { missionAnchor, goalText: editedText.trim(), analysis };
1356
- }
1357
-
1358
1289
  async function resolveContextProposalConfirmationAction(
1359
- ctx: { hasUI: boolean; ui: any },
1360
1290
  proposal: ContextProposal,
1361
- projectName: string,
1362
- options: ContextProposalConfirmOptions,
1363
1291
  action: ContextProposalConfirmAction,
1364
- editedTextOverride?: string,
1365
1292
  ): Promise<ContextProposalDecision | undefined> {
1366
1293
  if (action === "cancel") return undefined;
1367
- const analysis = finalizeContextProposalAnalysis(proposal.analysis, [proposal.goalText, proposal.mission]);
1368
- if (action === "start") {
1369
- return { missionAnchor: proposal.mission, goalText: proposal.goalText, analysis };
1370
- }
1371
- const editedText =
1372
- editedTextOverride ??
1373
- (await getCtxUi(ctx)?.editor(
1374
- options.editorPrompt ?? `${options.title}\n\nEdit the proposed mission, scope, constraints, and acceptance details below.`,
1375
- buildContextProposalEditorText(proposal),
1376
- ));
1377
- if (!editedText?.trim()) return undefined;
1378
- return await resolveEditedContextProposalDecision(ctx, projectName, editedText, editedTextOverride === undefined, analysis);
1379
- }
1380
-
1381
- function buildContextProposalEditorText(proposal: ContextProposal): string {
1382
- return buildContextProposalGoalText(proposal);
1294
+ return {
1295
+ missionAnchor: proposal.mission,
1296
+ goalText: proposal.goalText,
1297
+ analysis: finalizeContextProposalAnalysis(proposal.analysis, [proposal.goalText, proposal.mission]),
1298
+ };
1383
1299
  }
1384
1300
 
1385
1301
  function parseContextProposal(text: string, projectName: string): ContextProposal | undefined {
@@ -1571,65 +1487,34 @@ async function buildGoalAnchoredContextProposal(
1571
1487
  async function confirmContextProposal(
1572
1488
  ctx: { hasUI: boolean; ui: any },
1573
1489
  proposal: ContextProposal,
1574
- projectName: string,
1575
1490
  options: ContextProposalConfirmOptions,
1576
1491
  ): Promise<ContextProposalDecision | undefined> {
1577
1492
  maybeWriteContextProposalSnapshot(proposal);
1578
1493
  const actionOverride = completionTestContextProposalActionOverride();
1579
1494
  if (actionOverride === "cancel") return undefined;
1580
1495
  if (actionOverride === "accept") {
1581
- return {
1582
- missionAnchor: proposal.mission,
1583
- goalText: proposal.goalText,
1584
- analysis: finalizeContextProposalAnalysis(proposal.analysis, [proposal.goalText, proposal.mission]),
1585
- };
1586
- }
1587
- if (actionOverride === "edit") {
1588
- const editedText = completionTestContextProposalEditText();
1589
- if (!editedText) return undefined;
1590
- return await resolveEditedContextProposalDecision(
1591
- ctx,
1592
- projectName,
1593
- editedText,
1594
- false,
1595
- finalizeContextProposalAnalysis(proposal.analysis, [proposal.goalText, proposal.mission]),
1596
- );
1496
+ return await resolveContextProposalConfirmationAction(proposal, "start");
1597
1497
  }
1598
1498
  const layout = buildContextProposalConfirmationLayout(options.title, proposal);
1599
1499
  maybeWriteContextProposalConfirmationSnapshot(layout);
1600
1500
  const uiActionOverride = completionTestContextProposalUiActionOverride();
1601
1501
  if (uiActionOverride) {
1602
- return await resolveContextProposalConfirmationAction(
1603
- ctx,
1604
- proposal,
1605
- projectName,
1606
- options,
1607
- uiActionOverride,
1608
- uiActionOverride === "edit" ? completionTestContextProposalEditText() : undefined,
1609
- );
1502
+ return await resolveContextProposalConfirmationAction(proposal, uiActionOverride);
1610
1503
  }
1611
1504
  if (!getCtxHasUI(ctx)) {
1612
1505
  return options.nonInteractiveBehavior === "accept"
1613
- ? {
1614
- missionAnchor: proposal.mission,
1615
- goalText: proposal.goalText,
1616
- analysis: finalizeContextProposalAnalysis(proposal.analysis, [proposal.goalText, proposal.mission]),
1617
- }
1506
+ ? await resolveContextProposalConfirmationAction(proposal, "start")
1618
1507
  : undefined;
1619
1508
  }
1620
1509
  const ui = getCtxUi(ctx);
1621
1510
  if (!ui) {
1622
1511
  return options.nonInteractiveBehavior === "accept"
1623
- ? {
1624
- missionAnchor: proposal.mission,
1625
- goalText: proposal.goalText,
1626
- analysis: finalizeContextProposalAnalysis(proposal.analysis, [proposal.goalText, proposal.mission]),
1627
- }
1512
+ ? await resolveContextProposalConfirmationAction(proposal, "start")
1628
1513
  : undefined;
1629
1514
  }
1630
1515
  const choice = await promptContextProposalConfirmationAction(ui, layout);
1631
1516
  if (!choice) return undefined;
1632
- return await resolveContextProposalConfirmationAction(ctx, proposal, projectName, options, choice);
1517
+ return await resolveContextProposalConfirmationAction(proposal, choice);
1633
1518
  }
1634
1519
 
1635
1520
  function currentMissionAnchor(snapshot: CompletionStateSnapshot): string {
@@ -1809,8 +1694,29 @@ function activeSliceContext(snapshot: CompletionStateSnapshot) {
1809
1694
  };
1810
1695
  }
1811
1696
 
1697
+ function verificationEvidenceContext(snapshot: CompletionStateSnapshot) {
1698
+ const evidence = snapshot.verificationEvidence;
1699
+ return {
1700
+ path: path.relative(snapshot.files.root, snapshot.files.verificationEvidencePath) || ".agent/verification-evidence.json",
1701
+ status: evidence ? "present" : "missing",
1702
+ subjectType: asString(evidence?.subject_type),
1703
+ sliceId: asString(evidence?.slice_id),
1704
+ goal: asString(evidence?.goal),
1705
+ contractIds: asStringArray(evidence?.contract_ids),
1706
+ basisCommit: asString(evidence?.basis_commit),
1707
+ headSha: asString(evidence?.head_sha),
1708
+ verificationCommands: asStringArray(evidence?.verification_commands),
1709
+ outcome: asString(evidence?.outcome),
1710
+ recordedAt: asString(evidence?.recorded_at),
1711
+ summary:
1712
+ asString(evidence?.summary) ??
1713
+ (evidence ? "Canonical verification evidence is present but its summary is missing." : "Canonical verification evidence is missing."),
1714
+ };
1715
+ }
1716
+
1812
1717
  function buildEvaluationRoleContextLines(snapshot: CompletionStateSnapshot, role: RubricEvaluationRole): string[] {
1813
1718
  const context = activeSliceContext(snapshot);
1719
+ const evidence = verificationEvidenceContext(snapshot);
1814
1720
  const lines = [
1815
1721
  `Canonical evaluation handoff for ${role}:`,
1816
1722
  `- task_type: ${currentTaskType(snapshot) ?? "(missing)"}`,
@@ -1829,6 +1735,17 @@ function buildEvaluationRoleContextLines(snapshot: CompletionStateSnapshot, role
1829
1735
  `- remaining_contract_ids_before: ${context.remainingBefore.length > 0 ? context.remainingBefore.join(", ") : "(none)"}`,
1830
1736
  `- release_blocker_count_before: ${context.releaseBlockerCountBefore ?? "(unknown)"}`,
1831
1737
  `- high_value_gap_count_before: ${context.highValueGapCountBefore ?? "(unknown)"}`,
1738
+ `- verification_evidence_path: ${evidence.path}`,
1739
+ `- verification_evidence_status: ${evidence.status}`,
1740
+ `- verification_evidence_subject_type: ${evidence.subjectType ?? "(missing)"}`,
1741
+ `- verification_evidence_slice_id: ${evidence.sliceId ?? "(none)"}`,
1742
+ `- verification_evidence_contract_ids: ${evidence.contractIds.length > 0 ? evidence.contractIds.join(", ") : "(none)"}`,
1743
+ `- verification_evidence_outcome: ${evidence.outcome ?? "(missing)"}`,
1744
+ `- verification_evidence_recorded_at: ${evidence.recordedAt ?? "(missing)"}`,
1745
+ `- verification_evidence_head_sha: ${evidence.headSha ?? "(missing)"}`,
1746
+ `- verification_evidence_basis_commit: ${evidence.basisCommit ?? "(missing)"}`,
1747
+ `- verification_evidence_commands: ${evidence.verificationCommands.length > 0 ? evidence.verificationCommands.join(" | ") : "(none)"}`,
1748
+ `- verification_evidence_summary: ${evidence.summary}`,
1832
1749
  ];
1833
1750
  return lines;
1834
1751
  }
@@ -1850,6 +1767,25 @@ async function confirmExistingWorkflowGoal(
1850
1767
  if (!normalizedGoal || normalizedGoal === normalizedCurrent || normalizedProposed === normalizedCurrent) {
1851
1768
  return { action: "continue", currentMissionAnchor: currentMission };
1852
1769
  }
1770
+ const title = [
1771
+ "Existing completion workflow found",
1772
+ "",
1773
+ "A workflow is already in progress. Choose how /cook should proceed:",
1774
+ "",
1775
+ "Current mission",
1776
+ currentMission,
1777
+ "",
1778
+ "New proposed mission",
1779
+ assessment.derived,
1780
+ ].join("\n");
1781
+ const continueChoice = "Continue current workflow\n\nKeep the current mission and treat the new goal as extra direction only.";
1782
+ const refocusChoice =
1783
+ "Abandon current workflow and start this new one\n\nReview the proposed replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state.";
1784
+ const cancelChoice = `Cancel\n\nKeep the current workflow unchanged. ${COOK_MAIN_CHAT_RERUN_GUIDANCE}`;
1785
+ maybeWriteTestSnapshot(
1786
+ completionTestExistingWorkflowChooserSnapshotPath(),
1787
+ `${JSON.stringify({ title, choices: [continueChoice, refocusChoice, cancelChoice] }, null, 2)}\n`,
1788
+ );
1853
1789
  const actionOverride = completionTestWorkflowActionOverride();
1854
1790
  if (actionOverride === "continue") {
1855
1791
  return { action: "continue", currentMissionAnchor: currentMission };
@@ -1857,6 +1793,7 @@ async function confirmExistingWorkflowGoal(
1857
1793
  if (actionOverride === "refocus") {
1858
1794
  return { action: "refocus", currentMissionAnchor: currentMission, missionAnchor: assessment.derived };
1859
1795
  }
1796
+ if (actionOverride === "cancel") return undefined;
1860
1797
  if (!getCtxHasUI(ctx)) {
1861
1798
  return { action: "continue", currentMissionAnchor: currentMission };
1862
1799
  }
@@ -1864,26 +1801,10 @@ async function confirmExistingWorkflowGoal(
1864
1801
  if (!ui) {
1865
1802
  return { action: "continue", currentMissionAnchor: currentMission };
1866
1803
  }
1867
- const title = [
1868
- "Existing completion workflow found",
1869
- "",
1870
- "A workflow is already in progress. Choose how /cook should proceed:",
1871
- "",
1872
- "Current mission",
1873
- currentMission,
1874
- "",
1875
- "New proposed mission",
1876
- assessment.derived,
1877
- ].join("\n");
1878
- const continueChoice = "Continue current workflow\n\nKeep the current mission and treat the new goal as extra direction only.";
1879
- const refocusChoice = "Abandon current workflow and start this new one\n\nReplace the current mission with the new goal, then rebuild canonical state from that new direction.";
1880
- const cancelChoice = "Cancel\n\nExit without changing the current workflow.";
1881
1804
  const choice = await ui.select(title, [continueChoice, refocusChoice, cancelChoice]);
1882
1805
  if (!choice || choice === cancelChoice) return undefined;
1883
1806
  if (choice === refocusChoice) {
1884
- const missionAnchor = await confirmMissionAnchor(ctx, assessment);
1885
- if (!missionAnchor) return undefined;
1886
- return { action: "refocus", currentMissionAnchor: currentMission, missionAnchor };
1807
+ return { action: "refocus", currentMissionAnchor: currentMission, missionAnchor: assessment.derived };
1887
1808
  }
1888
1809
  return { action: "continue", currentMissionAnchor: currentMission };
1889
1810
  }
@@ -1926,6 +1847,7 @@ async function refocusCompletionMission(
1926
1847
  writeJsonFile(snapshot.files.statePath, nextState),
1927
1848
  writeJsonFile(snapshot.files.planPath, nextPlan),
1928
1849
  writeJsonFile(snapshot.files.activePath, nextActive),
1850
+ writeJsonFile(snapshot.files.verificationEvidencePath, defaultVerificationEvidence()),
1929
1851
  ]);
1930
1852
  }
1931
1853
 
@@ -1946,10 +1868,6 @@ function deriveMissionAnchor(rawGoal: string, projectName: string): string {
1946
1868
  .replace(/\bwith docs\b/gi, "with docs parity")
1947
1869
  .trim();
1948
1870
 
1949
- if (mission.length > 120) {
1950
- mission = `${mission.slice(0, 117).trimEnd()}...`;
1951
- }
1952
-
1953
1871
  if (!/[.!?。!?]$/u.test(mission)) mission += ".";
1954
1872
  return mission;
1955
1873
  }
@@ -2047,8 +1965,25 @@ function defaultActiveSlice(
2047
1965
  };
2048
1966
  }
2049
1967
 
1968
+ function defaultVerificationEvidence(): JsonRecord {
1969
+ return {
1970
+ schema_version: 1,
1971
+ artifact_type: "completion-verification-evidence",
1972
+ subject_type: "none",
1973
+ slice_id: null,
1974
+ goal: null,
1975
+ contract_ids: [],
1976
+ basis_commit: null,
1977
+ head_sha: null,
1978
+ verification_commands: [],
1979
+ outcome: "not_recorded",
1980
+ recorded_at: null,
1981
+ summary: "No deterministic verification evidence is recorded yet because no selected slice or current-HEAD verification subject exists.",
1982
+ };
1983
+ }
1984
+
2050
1985
  function buildAgentReadme(projectName: string): string {
2051
- 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/*.log\`\n- \`.agent/tmp/\`\n\nThe source of truth for long-running completion work is canonical \`.agent/**\` state plus current repo truth.\n\nProject: ${projectName}\n`;
1986
+ 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`;
2052
1987
  }
2053
1988
 
2054
1989
  function buildMission(projectName: string, missionAnchor: string): string {
@@ -2074,11 +2009,13 @@ for file in \
2074
2009
  .agent/verify_completion_control_plane.sh \
2075
2010
  .agent/state.json \
2076
2011
  .agent/plan.json \
2077
- .agent/active-slice.json; do
2012
+ .agent/active-slice.json \
2013
+ .agent/verification-evidence.json; do
2078
2014
  [[ -e "$file" ]] || { echo "missing required file: $file"; exit 1; }
2079
2015
  done
2080
2016
 
2081
2017
  node <<'NODE'
2018
+ const childProcess = require('node:child_process');
2082
2019
  const fs = require('node:fs');
2083
2020
 
2084
2021
  const readJson = (file) => JSON.parse(fs.readFileSync(file, 'utf8'));
@@ -2101,8 +2038,10 @@ const requireKeys = (object, required, label) => {
2101
2038
  assert(Object.prototype.hasOwnProperty.call(object, key), label + ': missing required field: ' + key);
2102
2039
  }
2103
2040
  };
2041
+ const hasOwn = (object, key) => Object.prototype.hasOwnProperty.call(object, key);
2042
+ const sameStringArrays = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]);
2104
2043
 
2105
- for (const file of ['.agent/profile.json', '.agent/state.json', '.agent/plan.json', '.agent/active-slice.json']) {
2044
+ for (const file of ['.agent/profile.json', '.agent/state.json', '.agent/plan.json', '.agent/active-slice.json', '.agent/verification-evidence.json']) {
2106
2045
  readJson(file);
2107
2046
  }
2108
2047
 
@@ -2110,11 +2049,13 @@ const profile = readJson('.agent/profile.json');
2110
2049
  const state = readJson('.agent/state.json');
2111
2050
  const plan = readJson('.agent/plan.json');
2112
2051
  const active = readJson('.agent/active-slice.json');
2052
+ const evidence = readJson('.agent/verification-evidence.json');
2113
2053
 
2114
2054
  assert(isObject(profile), '.agent/profile.json must be an object');
2115
2055
  assert(isObject(state), '.agent/state.json must be an object');
2116
2056
  assert(isObject(plan), '.agent/plan.json must be an object');
2117
2057
  assert(isObject(active), '.agent/active-slice.json must be an object');
2058
+ assert(isObject(evidence), '.agent/verification-evidence.json must be an object');
2118
2059
 
2119
2060
  const requiredProfile = ['schema_version', 'protocol_id', 'project_name', 'required_stop_judges', 'priority_policy_id', 'task_type', 'evaluation_profile', 'docs_surfaces'];
2120
2061
  requireKeys(profile, requiredProfile, '.agent/profile.json');
@@ -2145,6 +2086,8 @@ assert(isStringArray(state.release_blocker_ids), '.agent/state.json: release_blo
2145
2086
 
2146
2087
  const requiredPlan = ['schema_version', 'mission_anchor', 'task_type', 'evaluation_profile', 'last_reground_at', 'plan_basis', 'candidate_slices'];
2147
2088
  const requiredSlice = ['slice_id', 'goal', 'acceptance_criteria', 'contract_ids', 'priority', 'status', 'why_now', 'blocked_on', 'evidence'];
2089
+ const planMirrorFields = ['locked_notes', 'must_fix_findings', 'implementation_surfaces', 'verification_commands', 'basis_commit', 'remaining_contract_ids_before', 'release_blocker_count_before', 'high_value_gap_count_before'];
2090
+ const allowedSlice = [...requiredSlice, ...planMirrorFields];
2148
2091
  const sliceStatuses = ['planned', 'selected', 'in_progress', 'blocked', 'done', 'cancelled'];
2149
2092
  requireKeys(plan, requiredPlan, '.agent/plan.json');
2150
2093
  hasOnlyKeys(plan, requiredPlan, '.agent/plan.json');
@@ -2155,7 +2098,7 @@ for (const [index, slice] of plan.candidate_slices.entries()) {
2155
2098
  const label = '.agent/plan.json candidate_slices[' + index + ']';
2156
2099
  assert(isObject(slice), label + ' must be an object');
2157
2100
  requireKeys(slice, requiredSlice, label);
2158
- hasOnlyKeys(slice, requiredSlice, label);
2101
+ hasOnlyKeys(slice, allowedSlice, label);
2159
2102
  assert(isString(slice.slice_id) && slice.slice_id.length > 0, label + ': slice_id must be a non-empty string');
2160
2103
  assert(isString(slice.goal) && slice.goal.length > 0, label + ': goal must be a non-empty string');
2161
2104
  assert(Array.isArray(slice.acceptance_criteria) && slice.acceptance_criteria.length > 0 && slice.acceptance_criteria.every((item) => typeof item === 'string' && item.length > 0), label + ': acceptance_criteria must be a non-empty array of strings');
@@ -2165,6 +2108,14 @@ for (const [index, slice] of plan.candidate_slices.entries()) {
2165
2108
  assert(isString(slice.why_now) && slice.why_now.length > 0, label + ': why_now must be a non-empty string');
2166
2109
  assert(isStringArray(slice.blocked_on), label + ': blocked_on must be an array of strings');
2167
2110
  assert(isStringArray(slice.evidence), label + ': evidence must be an array of strings');
2111
+ if (hasOwn(slice, 'locked_notes')) assert(isStringArray(slice.locked_notes), label + ': locked_notes must be an array of strings when present');
2112
+ if (hasOwn(slice, 'must_fix_findings')) assert(isStringArray(slice.must_fix_findings), label + ': must_fix_findings must be an array of strings when present');
2113
+ if (hasOwn(slice, 'implementation_surfaces')) assert(isStringArray(slice.implementation_surfaces), label + ': implementation_surfaces must be an array of strings when present');
2114
+ if (hasOwn(slice, 'verification_commands')) assert(isStringArray(slice.verification_commands), label + ': verification_commands must be an array of strings when present');
2115
+ if (hasOwn(slice, 'basis_commit')) assert(isNonEmptyString(slice.basis_commit), label + ': basis_commit must be a non-empty string when present');
2116
+ if (hasOwn(slice, 'remaining_contract_ids_before')) assert(isStringArray(slice.remaining_contract_ids_before), label + ': remaining_contract_ids_before must be an array of strings when present');
2117
+ if (hasOwn(slice, 'release_blocker_count_before')) assert(typeof slice.release_blocker_count_before === 'number' && Number.isFinite(slice.release_blocker_count_before), label + ': release_blocker_count_before must be a finite number when present');
2118
+ if (hasOwn(slice, 'high_value_gap_count_before')) assert(typeof slice.high_value_gap_count_before === 'number' && Number.isFinite(slice.high_value_gap_count_before), label + ': high_value_gap_count_before must be a finite number when present');
2168
2119
  }
2169
2120
 
2170
2121
  const isNonEmptyStringArray = (value) => Array.isArray(value) && value.length > 0 && value.every((item) => isNonEmptyString(item));
@@ -2185,6 +2136,23 @@ assert(isStringArray(active.implementation_surfaces), '.agent/active-slice.json:
2185
2136
  assert(isStringArray(active.verification_commands), '.agent/active-slice.json: verification_commands must be an array of strings');
2186
2137
  assert(isStringArray(active.remaining_contract_ids_before), '.agent/active-slice.json: remaining_contract_ids_before must be an array of strings');
2187
2138
 
2139
+ const requiredEvidence = ['schema_version', 'artifact_type', 'subject_type', 'slice_id', 'goal', 'contract_ids', 'basis_commit', 'head_sha', 'verification_commands', 'outcome', 'recorded_at', 'summary'];
2140
+ const evidenceSubjectTypes = ['none', 'selected_slice', 'current_head'];
2141
+ const evidenceOutcomes = ['not_recorded', 'passed', 'failed'];
2142
+ requireKeys(evidence, requiredEvidence, '.agent/verification-evidence.json');
2143
+ hasOnlyKeys(evidence, requiredEvidence, '.agent/verification-evidence.json');
2144
+ assert(evidence.artifact_type === 'completion-verification-evidence', '.agent/verification-evidence.json: artifact_type must be completion-verification-evidence');
2145
+ assert(evidenceSubjectTypes.includes(evidence.subject_type), '.agent/verification-evidence.json: invalid subject_type');
2146
+ assert(evidence.slice_id === null || isNonEmptyString(evidence.slice_id), '.agent/verification-evidence.json: slice_id must be null or a non-empty string');
2147
+ assert(evidence.goal === null || isNonEmptyString(evidence.goal), '.agent/verification-evidence.json: goal must be null or a non-empty string');
2148
+ assert(isStringArray(evidence.contract_ids), '.agent/verification-evidence.json: contract_ids must be an array of strings');
2149
+ assert(evidence.basis_commit === null || isNonEmptyString(evidence.basis_commit), '.agent/verification-evidence.json: basis_commit must be null or a non-empty string');
2150
+ assert(evidence.head_sha === null || isNonEmptyString(evidence.head_sha), '.agent/verification-evidence.json: head_sha must be null or a non-empty string');
2151
+ assert(isStringArray(evidence.verification_commands), '.agent/verification-evidence.json: verification_commands must be an array of strings');
2152
+ assert(evidenceOutcomes.includes(evidence.outcome), '.agent/verification-evidence.json: invalid outcome');
2153
+ assert(evidence.recorded_at === null || (isNonEmptyString(evidence.recorded_at) && !Number.isNaN(Date.parse(evidence.recorded_at))), '.agent/verification-evidence.json: recorded_at must be null or an ISO-8601 string');
2154
+ assert(isNonEmptyString(evidence.summary), '.agent/verification-evidence.json: summary must be a non-empty string');
2155
+
2188
2156
  assert(state.task_type === profile.task_type, '.agent/state.json: task_type must match .agent/profile.json');
2189
2157
  assert(plan.task_type === profile.task_type, '.agent/plan.json: task_type must match .agent/profile.json');
2190
2158
  assert(active.task_type === profile.task_type, '.agent/active-slice.json: task_type must match .agent/profile.json');
@@ -2202,7 +2170,80 @@ if (requiresExactHandoff) {
2202
2170
  assert(isString(active.basis_commit) && active.basis_commit.length > 0, '.agent/active-slice.json: basis_commit must be a non-empty string when status carries an exact handoff');
2203
2171
  assert(typeof active.release_blocker_count_before === 'number' && Number.isFinite(active.release_blocker_count_before), '.agent/active-slice.json: release_blocker_count_before must be a finite number when status carries an exact handoff');
2204
2172
  assert(typeof active.high_value_gap_count_before === 'number' && Number.isFinite(active.high_value_gap_count_before), '.agent/active-slice.json: high_value_gap_count_before must be a finite number when status carries an exact handoff');
2173
+
2174
+ const planSlice = plan.candidate_slices.find((slice) => isObject(slice) && slice.slice_id === active.slice_id);
2175
+ assert(isObject(planSlice), '.agent/active-slice.json: slice_id must match a slice in .agent/plan.json when status carries an exact handoff');
2176
+ const drift = [];
2177
+ if (planSlice.goal !== active.goal) drift.push('goal');
2178
+ if (!sameStringArrays(planSlice.contract_ids, active.contract_ids)) drift.push('contract_ids');
2179
+ if (!sameStringArrays(planSlice.acceptance_criteria, active.acceptance_criteria)) drift.push('acceptance_criteria');
2180
+ if (!sameStringArrays(planSlice.blocked_on, active.blocked_on)) drift.push('blocked_on');
2181
+ if (planSlice.priority !== active.priority) drift.push('priority');
2182
+ if (planSlice.why_now !== active.why_now) drift.push('why_now');
2183
+
2184
+ const expectPlanArrayMirror = (field) => {
2185
+ if (!hasOwn(planSlice, field) || !sameStringArrays(planSlice[field], active[field])) drift.push(field);
2186
+ };
2187
+ const expectPlanStringMirror = (field) => {
2188
+ if (!hasOwn(planSlice, field) || planSlice[field] !== active[field]) drift.push(field);
2189
+ };
2190
+ const expectPlanNumberMirror = (field) => {
2191
+ if (!hasOwn(planSlice, field) || planSlice[field] !== active[field]) drift.push(field);
2192
+ };
2193
+
2194
+ expectPlanArrayMirror('implementation_surfaces');
2195
+ expectPlanArrayMirror('verification_commands');
2196
+ expectPlanArrayMirror('locked_notes');
2197
+ expectPlanArrayMirror('must_fix_findings');
2198
+ expectPlanStringMirror('basis_commit');
2199
+ expectPlanArrayMirror('remaining_contract_ids_before');
2200
+ expectPlanNumberMirror('release_blocker_count_before');
2201
+ expectPlanNumberMirror('high_value_gap_count_before');
2202
+ assert(drift.length === 0, '.agent/active-slice.json must match the selected .agent/plan.json slice across: ' + Array.from(new Set(drift)).join(', '));
2203
+ }
2204
+
2205
+ const currentHead = (() => {
2206
+ try {
2207
+ return childProcess.execSync('git rev-parse HEAD', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
2208
+ } catch {
2209
+ return null;
2210
+ }
2211
+ })();
2212
+
2213
+ if (requiresExactHandoff) {
2214
+ assert(evidence.subject_type === 'selected_slice', '.agent/verification-evidence.json: subject_type must be selected_slice when active slice exact handoff requires verification evidence');
2215
+ assert(evidence.slice_id === active.slice_id, '.agent/verification-evidence.json: slice_id must match .agent/active-slice.json when active slice exact handoff requires verification evidence');
2216
+ assert(evidence.goal === active.goal, '.agent/verification-evidence.json: goal must match .agent/active-slice.json when active slice exact handoff requires verification evidence');
2217
+ assert(sameStringArrays(evidence.contract_ids, active.contract_ids), '.agent/verification-evidence.json: contract_ids must match .agent/active-slice.json when active slice exact handoff requires verification evidence');
2218
+ assert(evidence.basis_commit === active.basis_commit, '.agent/verification-evidence.json: basis_commit must match .agent/active-slice.json when active slice exact handoff requires verification evidence');
2219
+ assert(sameStringArrays(evidence.verification_commands, active.verification_commands), '.agent/verification-evidence.json: verification_commands must match .agent/active-slice.json when active slice exact handoff requires verification evidence');
2220
+ assert(evidence.outcome === 'passed', '.agent/verification-evidence.json: outcome must be passed when active slice exact handoff requires verification evidence');
2221
+ assert(isNonEmptyString(evidence.recorded_at) && !Number.isNaN(Date.parse(evidence.recorded_at)), '.agent/verification-evidence.json: recorded_at must be an ISO-8601 string when active slice exact handoff requires verification evidence');
2222
+ if (currentHead) assert(evidence.head_sha === currentHead, '.agent/verification-evidence.json: head_sha must match current git HEAD when active slice exact handoff requires verification evidence');
2223
+ } else if (evidence.subject_type === 'none') {
2224
+ assert(evidence.slice_id === null, '.agent/verification-evidence.json: slice_id must be null when subject_type is none');
2225
+ assert(evidence.goal === null, '.agent/verification-evidence.json: goal must be null when subject_type is none');
2226
+ assert(evidence.contract_ids.length === 0, '.agent/verification-evidence.json: contract_ids must be empty when subject_type is none');
2227
+ assert(evidence.basis_commit === null, '.agent/verification-evidence.json: basis_commit must be null when subject_type is none');
2228
+ assert(evidence.head_sha === null, '.agent/verification-evidence.json: head_sha must be null when subject_type is none');
2229
+ assert(evidence.verification_commands.length === 0, '.agent/verification-evidence.json: verification_commands must be empty when subject_type is none');
2230
+ assert(evidence.outcome === 'not_recorded', '.agent/verification-evidence.json: outcome must be not_recorded when subject_type is none');
2231
+ assert(evidence.recorded_at === null, '.agent/verification-evidence.json: recorded_at must be null when subject_type is none');
2205
2232
  } else {
2233
+ assert(evidence.outcome === 'passed', '.agent/verification-evidence.json: outcome must be passed when verification evidence is recorded');
2234
+ assert(isNonEmptyStringArray(evidence.verification_commands), '.agent/verification-evidence.json: verification_commands must be a non-empty array when verification evidence is recorded');
2235
+ assert(isNonEmptyString(evidence.recorded_at) && !Number.isNaN(Date.parse(evidence.recorded_at)), '.agent/verification-evidence.json: recorded_at must be an ISO-8601 string when verification evidence is recorded');
2236
+ if (currentHead) assert(evidence.head_sha === currentHead, '.agent/verification-evidence.json: head_sha must match current git HEAD when verification evidence is recorded');
2237
+ if (evidence.subject_type === 'selected_slice') {
2238
+ assert(isNonEmptyString(evidence.slice_id), '.agent/verification-evidence.json: slice_id must be a non-empty string when subject_type is selected_slice');
2239
+ assert(isNonEmptyString(evidence.goal), '.agent/verification-evidence.json: goal must be a non-empty string when subject_type is selected_slice');
2240
+ assert(isNonEmptyString(evidence.basis_commit), '.agent/verification-evidence.json: basis_commit must be a non-empty string when subject_type is selected_slice');
2241
+ } else {
2242
+ assert(evidence.subject_type === 'current_head', '.agent/verification-evidence.json: only current_head or selected_slice may carry recorded verification evidence');
2243
+ }
2244
+ }
2245
+
2246
+ if (!requiresExactHandoff) {
2206
2247
  assert(active.priority === null || active.priority === undefined || (typeof active.priority === 'number' && Number.isFinite(active.priority)), '.agent/active-slice.json: idle priority must be null/undefined or a finite number');
2207
2248
  assert(active.why_now === null || active.why_now === undefined || typeof active.why_now === 'string', '.agent/active-slice.json: idle why_now must be null/undefined or a string');
2208
2249
  }
@@ -2273,6 +2314,7 @@ async function scaffoldCompletionFiles(
2273
2314
  },
2274
2315
  { path: files.planPath, content: `${JSON.stringify(defaultPlan(missionAnchor, { taskType: routing.taskType, evaluationProfile: routing.evaluationProfile }), null, 2)}\n` },
2275
2316
  { path: files.activePath, content: `${JSON.stringify(defaultActiveSlice(missionAnchor, { taskType: routing.taskType, evaluationProfile: routing.evaluationProfile }), null, 2)}\n` },
2317
+ { path: files.verificationEvidencePath, content: `${JSON.stringify(defaultVerificationEvidence(), null, 2)}\n` },
2276
2318
  { path: files.sliceHistoryPath, content: "" },
2277
2319
  { path: files.stopHistoryPath, content: "" },
2278
2320
  ];
@@ -2303,10 +2345,72 @@ function historyCounts(sliceHistory: JsonRecord[], stopHistory: JsonRecord[]) {
2303
2345
  };
2304
2346
  }
2305
2347
 
2348
+ function sameStringArrays(left: string[], right: string[]): boolean {
2349
+ return left.length === right.length && left.every((item, index) => item === right[index]);
2350
+ }
2351
+
2352
+ function hasOwnField(record: JsonRecord | undefined, field: string): boolean {
2353
+ return !!record && Object.prototype.hasOwnProperty.call(record, field);
2354
+ }
2355
+
2356
+ function activeCarriesExactHandoff(active: JsonRecord | undefined): boolean {
2357
+ const status = asString(active?.status);
2358
+ return status === "selected" || status === "in_progress" || status === "committed" || status === "done";
2359
+ }
2360
+
2361
+ function activeSliceContractDriftFields(snapshot: CompletionStateSnapshot): string[] | undefined {
2362
+ const active = snapshot.active;
2363
+ const planSlice = snapshot.activeSlice;
2364
+ const activeId = asString(active?.slice_id);
2365
+ if (!activeId || !planSlice) return undefined;
2366
+ const drift: string[] = [];
2367
+ const expectPlanArrayMirror = (field: string) => {
2368
+ if (!hasOwnField(planSlice, field) || !sameStringArrays(asStringArray(planSlice[field]), asStringArray(active?.[field]))) {
2369
+ drift.push(field);
2370
+ }
2371
+ };
2372
+ const expectPlanStringMirror = (field: string) => {
2373
+ if (!hasOwnField(planSlice, field) || asString(planSlice[field]) !== asString(active?.[field])) {
2374
+ drift.push(field);
2375
+ }
2376
+ };
2377
+ const expectPlanNumberMirror = (field: string) => {
2378
+ if (!hasOwnField(planSlice, field) || asNumber(planSlice[field]) !== asNumber(active?.[field])) {
2379
+ drift.push(field);
2380
+ }
2381
+ };
2382
+ if (asString(planSlice.slice_id) !== activeId) drift.push("slice_id");
2383
+ if (asString(planSlice.goal) !== asString(active?.goal)) drift.push("goal");
2384
+ if (!sameStringArrays(asStringArray(planSlice.contract_ids), asStringArray(active?.contract_ids))) drift.push("contract_ids");
2385
+ if (!sameStringArrays(asStringArray(planSlice.acceptance_criteria), asStringArray(active?.acceptance_criteria))) drift.push("acceptance_criteria");
2386
+ if (!sameStringArrays(asStringArray(planSlice.blocked_on), asStringArray(active?.blocked_on))) drift.push("blocked_on");
2387
+ if (asNumber(planSlice.priority) !== asNumber(active?.priority)) drift.push("priority");
2388
+ if (asString(planSlice.why_now) !== asString(active?.why_now)) drift.push("why_now");
2389
+ expectPlanArrayMirror("implementation_surfaces");
2390
+ expectPlanArrayMirror("verification_commands");
2391
+ expectPlanArrayMirror("locked_notes");
2392
+ expectPlanArrayMirror("must_fix_findings");
2393
+ expectPlanStringMirror("basis_commit");
2394
+ expectPlanArrayMirror("remaining_contract_ids_before");
2395
+ expectPlanNumberMirror("release_blocker_count_before");
2396
+ expectPlanNumberMirror("high_value_gap_count_before");
2397
+ return Array.from(new Set(drift));
2398
+ }
2399
+
2400
+ function activeSliceContractDriftSummary(snapshot: CompletionStateSnapshot): string {
2401
+ const activeId = asString(snapshot.active?.slice_id);
2402
+ if (!activeId) return "unknown";
2403
+ if (!snapshot.activeSlice) return "slice_id (no matching plan slice)";
2404
+ const drift = activeSliceContractDriftFields(snapshot);
2405
+ return drift && drift.length > 0 ? drift.join(", ") : "none";
2406
+ }
2407
+
2306
2408
  function activeSliceMatchesPlan(snapshot: CompletionStateSnapshot): "yes" | "no" | "unknown" {
2307
2409
  const activeId = asString(snapshot.active?.slice_id);
2308
2410
  if (!activeId) return "unknown";
2309
- return snapshot.activeSlice ? "yes" : "no";
2411
+ const drift = activeSliceContractDriftFields(snapshot);
2412
+ if (!snapshot.activeSlice || drift === undefined) return "no";
2413
+ return drift.length === 0 ? "yes" : "no";
2310
2414
  }
2311
2415
 
2312
2416
  function handoffSnapshotState(active: JsonRecord | undefined): "present" | "missing_or_unclear" {
@@ -2326,7 +2430,7 @@ function handoffSnapshotState(active: JsonRecord | undefined): "present" | "miss
2326
2430
  active?.release_blocker_count_before,
2327
2431
  active?.high_value_gap_count_before,
2328
2432
  ];
2329
- return exactArrays.every((items) => items.length > 0) && required.every((value) => value !== undefined && value !== null)
2433
+ return activeCarriesExactHandoff(active) && exactArrays.every((items) => items.length > 0) && required.every((value) => value !== undefined && value !== null)
2330
2434
  ? "present"
2331
2435
  : "missing_or_unclear";
2332
2436
  }
@@ -2338,9 +2442,12 @@ function buildSystemReminder(snapshot: CompletionStateSnapshot, sliceHistory: Js
2338
2442
  const activePriority = asNumber(snapshot.active?.priority);
2339
2443
  const activeWhyNow = asString(snapshot.active?.why_now);
2340
2444
  const nextRole = asString(snapshot.state?.next_mandatory_role);
2445
+ const exactActiveContract = activeCarriesExactHandoff(snapshot.active);
2446
+ const activeContractDrift = activeSliceContractDriftSummary(snapshot);
2447
+ const evidence = verificationEvidenceContext(snapshot);
2341
2448
  const lines = [
2342
2449
  "Completion workflow detected.",
2343
- "Canonical truth lives in .agent/state.json, .agent/plan.json, .agent/active-slice.json, .agent/slice-history.jsonl, and .agent/stop-check-history.jsonl.",
2450
+ "Canonical truth lives in .agent/state.json, .agent/plan.json, .agent/active-slice.json, .agent/slice-history.jsonl, .agent/stop-check-history.jsonl, and .agent/verification-evidence.json.",
2344
2451
  `Mission anchor: ${asString(snapshot.state?.mission_anchor) ?? "(unknown)"}`,
2345
2452
  `Task type: ${currentTaskType(snapshot) ?? "(missing)"}`,
2346
2453
  `Evaluation profile: ${currentEvaluationProfile(snapshot) ?? "(missing)"}`,
@@ -2358,10 +2465,20 @@ function buildSystemReminder(snapshot: CompletionStateSnapshot, sliceHistory: Js
2358
2465
  "If canonical state is stale, invalid, ambiguous, or missing, route to completion-regrounder.",
2359
2466
  "When recovering from compaction, prefer a deterministic restart from canonical files over conversational inference.",
2360
2467
  ];
2468
+ if (exactActiveContract) {
2469
+ lines.push("Selected/in-progress/committed/done .agent/active-slice.json is the canonical implementation contract.");
2470
+ lines.push(`Active slice contract drift: ${activeContractDrift}`);
2471
+ }
2361
2472
  if (activePriority !== undefined) lines.push(`Active slice priority: ${activePriority}`);
2362
2473
  if (activeWhyNow) lines.push(`Active slice why_now: ${activeWhyNow}`);
2363
2474
  if (implementationSurfaces.length > 0) lines.push(`Active implementation surfaces: ${implementationSurfaces.join(", ")}`);
2364
2475
  if (verificationCommands.length > 0) lines.push(`Active verification commands: ${verificationCommands.join(" | ")}`);
2476
+ lines.push(`Verification evidence artifact: ${evidence.path} (${evidence.status})`);
2477
+ if (evidence.subjectType) lines.push(`Verification evidence subject: ${evidence.subjectType}`);
2478
+ if (evidence.outcome) lines.push(`Verification evidence outcome: ${evidence.outcome}`);
2479
+ if (evidence.recordedAt) lines.push(`Verification evidence recorded_at: ${evidence.recordedAt}`);
2480
+ if (evidence.verificationCommands.length > 0) lines.push(`Verification evidence commands: ${evidence.verificationCommands.join(" | ")}`);
2481
+ lines.push(`Verification evidence summary: ${evidence.summary}`);
2365
2482
  if (isRubricEvaluationRole(nextRole)) lines.push(buildEvaluationRoleReminderText(snapshot, nextRole));
2366
2483
  return lines.join(" ");
2367
2484
  }
@@ -2378,26 +2495,43 @@ function buildPostCompactionDriverInstructions(snapshot: CompletionStateSnapshot
2378
2495
  const verificationCommands = asStringArray(snapshot.active?.verification_commands);
2379
2496
  const activePriority = asNumber(snapshot.active?.priority);
2380
2497
  const activeWhyNow = asString(snapshot.active?.why_now);
2498
+ const exactActiveContract = activeCarriesExactHandoff(snapshot.active);
2499
+ const activeContractDrift = activeSliceContractDriftSummary(snapshot);
2500
+ const evidence = verificationEvidenceContext(snapshot);
2381
2501
  const lines = [
2382
2502
  "POST-COMPACTION RECOVERY MODE is active.",
2383
2503
  `Compaction marker time: ${markerAt}`,
2384
2504
  "Treat the previous conversation as lossy continuity support only.",
2385
- "Before taking any substantive action, re-read .agent/state.json, .agent/plan.json, .agent/active-slice.json, .agent/slice-history.jsonl, and .agent/stop-check-history.jsonl from disk.",
2505
+ "Before taking any substantive action, re-read .agent/state.json, .agent/plan.json, .agent/active-slice.json, .agent/slice-history.jsonl, .agent/stop-check-history.jsonl, and .agent/verification-evidence.json from disk.",
2386
2506
  `Canonical task_type is currently: ${taskType}`,
2387
2507
  `Canonical evaluation_profile is currently: ${evaluationProfile}`,
2388
2508
  `Canonical next mandatory role is currently: ${nextRole}`,
2389
2509
  `Canonical next mandatory action is currently: ${nextAction}`,
2390
2510
  `Canonical continuation policy is currently: ${continuation}`,
2391
2511
  `Canonical active slice is currently: ${activeSliceId}`,
2512
+ `Canonical verification evidence artifact is currently: ${evidence.path} (${evidence.status})`,
2392
2513
  "Do not trust pre-compaction memory over canonical files.",
2393
2514
  "If the canonical state is ambiguous, inconsistent, missing, or stale after re-reading it, your first mandatory action is to dispatch completion-regrounder rather than guessing.",
2394
2515
  "If continuation_policy == continue and canonical state is coherent, continue dispatching the mandatory role directly without asking the user whether to continue.",
2395
2516
  "If you are about to implement after compaction, confirm the active slice snapshot still matches .agent/plan.json before doing any work.",
2396
2517
  ];
2518
+ if (exactActiveContract) {
2519
+ lines.push("For selected/in-progress/committed/done slices, .agent/active-slice.json is the canonical implementation contract.");
2520
+ lines.push(`Canonical active-slice contract drift is currently: ${activeContractDrift}`);
2521
+ }
2397
2522
  if (activePriority !== undefined) lines.push(`Canonical active-slice priority is currently: ${activePriority}`);
2398
2523
  if (activeWhyNow) lines.push(`Canonical active-slice why_now is currently: ${activeWhyNow}`);
2399
2524
  if (implementationSurfaces.length > 0) lines.push(`Canonical implementation surfaces are currently: ${implementationSurfaces.join(", ")}`);
2400
2525
  if (verificationCommands.length > 0) lines.push(`Canonical verification commands are currently: ${verificationCommands.join(" | ")}`);
2526
+ if (evidence.subjectType) lines.push(`Canonical verification evidence subject is currently: ${evidence.subjectType}`);
2527
+ if (evidence.outcome) lines.push(`Canonical verification evidence outcome is currently: ${evidence.outcome}`);
2528
+ if (evidence.recordedAt) lines.push(`Canonical verification evidence recorded_at is currently: ${evidence.recordedAt}`);
2529
+ if (evidence.headSha) lines.push(`Canonical verification evidence head_sha is currently: ${evidence.headSha}`);
2530
+ if (evidence.basisCommit) lines.push(`Canonical verification evidence basis_commit is currently: ${evidence.basisCommit}`);
2531
+ if (evidence.verificationCommands.length > 0) {
2532
+ lines.push(`Canonical verification evidence commands are currently: ${evidence.verificationCommands.join(" | ")}`);
2533
+ }
2534
+ lines.push(`Canonical verification evidence summary is currently: ${evidence.summary}`);
2401
2535
  if (isRubricEvaluationRole(nextRole)) lines.push(buildEvaluationRoleReminderText(snapshot, nextRole));
2402
2536
  return lines.join(" ");
2403
2537
  }
@@ -2478,6 +2612,8 @@ function buildResumeCapsule(snapshot: CompletionStateSnapshot, sliceHistory: Jso
2478
2612
  const implementationSurfaces = asStringArray(snapshot.active?.implementation_surfaces);
2479
2613
  const verificationCommands = asStringArray(snapshot.active?.verification_commands);
2480
2614
  const remainingBefore = asStringArray(snapshot.active?.remaining_contract_ids_before);
2615
+ const activeContractDrift = activeSliceContractDriftSummary(snapshot);
2616
+ const evidence = verificationEvidenceContext(snapshot);
2481
2617
  const lines = [
2482
2618
  "Authoritative completion resume capsule:",
2483
2619
  "",
@@ -2494,9 +2630,23 @@ function buildResumeCapsule(snapshot: CompletionStateSnapshot, sliceHistory: Jso
2494
2630
  `remaining_slice_count: ${remainingSliceCount(snapshot.plan)}`,
2495
2631
  `remaining_stop_judges: ${asNumber(snapshot.state?.remaining_stop_judges) ?? "(unknown)"}`,
2496
2632
  `active_slice_matches_plan: ${activeSliceMatchesPlan(snapshot)}`,
2633
+ `active_slice_contract_drift_fields: ${activeContractDrift}`,
2497
2634
  `implementer_handoff_snapshot: ${handoffSnapshotState(snapshot.active)}`,
2498
2635
  `history_counts: reviewed=${history.reviewed}, audited=${history.audited}, accepted=${history.accepted}, reopened=${history.reopened}, judgments=${history.judgments}`,
2499
2636
  "",
2637
+ "verification_evidence:",
2638
+ `- path: ${evidence.path}`,
2639
+ `- status: ${evidence.status}`,
2640
+ `- subject_type: ${evidence.subjectType ?? "(missing)"}`,
2641
+ `- slice_id: ${evidence.sliceId ?? "(none)"}`,
2642
+ `- contract_ids: ${evidence.contractIds.length > 0 ? evidence.contractIds.join(", ") : "(none)"}`,
2643
+ `- outcome: ${evidence.outcome ?? "(missing)"}`,
2644
+ `- recorded_at: ${evidence.recordedAt ?? "(missing)"}`,
2645
+ `- head_sha: ${evidence.headSha ?? "(missing)"}`,
2646
+ `- basis_commit: ${evidence.basisCommit ?? "(missing)"}`,
2647
+ `- verification_commands: ${evidence.verificationCommands.length > 0 ? evidence.verificationCommands.join(" | ") : "(none)"}`,
2648
+ `- summary: ${evidence.summary}`,
2649
+ "",
2500
2650
  "active_slice:",
2501
2651
  `- slice_id: ${asString(snapshot.active?.slice_id) ?? asString(snapshot.activeSlice?.slice_id) ?? "(none)"}`,
2502
2652
  `- status: ${asString(snapshot.active?.status) ?? asString(snapshot.activeSlice?.status) ?? "unknown"}`,
@@ -2521,14 +2671,16 @@ function buildResumeCapsule(snapshot: CompletionStateSnapshot, sliceHistory: Jso
2521
2671
  "",
2522
2672
  "Rules:",
2523
2673
  "- Treat this block as continuity support derived from canonical .agent state.",
2524
- "- Preserve exact slice_id, contract_ids, acceptance criteria, priority, why_now, implementation surfaces, verification commands, locked notes, and must-fix findings where still true.",
2525
- "- After compaction, re-read .agent/state.json, .agent/plan.json, .agent/active-slice.json, .agent/slice-history.jsonl, and .agent/stop-check-history.jsonl before resuming long-running completion work.",
2674
+ "- For selected/in-progress/committed/done slices, .agent/active-slice.json is the canonical implementation contract and the selected plan slice must mirror it exactly.",
2675
+ "- Preserve exact slice_id, goal, contract_ids, acceptance criteria, blocked_on, priority, why_now, implementation surfaces, verification commands, locked notes, must-fix findings, basis_commit, and before-slice counters where still true.",
2676
+ "- When populated, .agent/verification-evidence.json is the durable canonical verification record for the selected slice or current HEAD and should be consumed instead of temp-only artifacts or conversational summaries.",
2677
+ "- After compaction, re-read .agent/state.json, .agent/plan.json, .agent/active-slice.json, .agent/slice-history.jsonl, .agent/stop-check-history.jsonl, and .agent/verification-evidence.json before resuming long-running completion work.",
2526
2678
  "- Invoke completion-regrounder before continuing when requires_reground is true or unknown.",
2527
2679
  "- Invoke completion-regrounder before continuing when next_mandatory_role or next_mandatory_action is unknown or ambiguous.",
2528
- "- Invoke completion-regrounder before continuing when active_slice_matches_plan is no or implementer_handoff_snapshot is missing_or_unclear.",
2680
+ "- Invoke completion-regrounder before continuing when active_slice_matches_plan is no, active_slice_contract_drift_fields is not none, or implementer_handoff_snapshot is missing_or_unclear.",
2529
2681
  "- If continuation_policy is continue, do not stop after a slice or ask whether to continue. Dispatch the next mandatory role directly.",
2530
2682
  "- Only stop for the user when continuation_policy is await_user_input, blocked, paused, or done.",
2531
- "- If you are completion-implementer after compaction, resume from the canonical active-slice handoff instead of asking the user to resend the original caller payload.",
2683
+ "- If you are completion-implementer after compaction, resume from the canonical active-slice implementation contract instead of asking the user to resend the original caller payload.",
2532
2684
  "- Do not replace canonical .agent state with summary inference.",
2533
2685
  "</completion-state>",
2534
2686
  );
@@ -2938,6 +3090,53 @@ function collapseRecentActivity(items: string[], maxItems = 4): string[] {
2938
3090
  return collapsed.slice(-maxItems);
2939
3091
  }
2940
3092
 
3093
+ function formatInlineRunningText(theme: any, lines: string[], options?: { primaryAssistant?: boolean }): string {
3094
+ let text = "";
3095
+ for (const [index, line] of lines.entries()) {
3096
+ if (index > 0) text += "\n";
3097
+ if (index === 0) {
3098
+ const [prefix, ...rest] = line.split(" ");
3099
+ text += theme.fg("warning", prefix);
3100
+ if (rest.length > 0) text += ` ${theme.fg("accent", rest.join(" "))}`;
3101
+ continue;
3102
+ }
3103
+ if (line.startsWith("tool:") || line.startsWith("progress:")) {
3104
+ text += theme.fg("toolOutput", line);
3105
+ continue;
3106
+ }
3107
+ if (line.startsWith("activity:")) {
3108
+ text += line.includes("stalled") ? theme.fg("warning", line) : line;
3109
+ continue;
3110
+ }
3111
+ if (line === "recent tools:") {
3112
+ text += theme.fg("muted", line);
3113
+ continue;
3114
+ }
3115
+ if (line.startsWith("- ")) {
3116
+ text += `${theme.fg("muted", "- ")}${theme.fg("muted", line.slice(2))}`;
3117
+ continue;
3118
+ }
3119
+ if (line.startsWith("elapsed:")) {
3120
+ text += line;
3121
+ continue;
3122
+ }
3123
+ if (line.startsWith("assistant:")) {
3124
+ text += options?.primaryAssistant ? line : theme.fg("muted", line);
3125
+ continue;
3126
+ }
3127
+ if (line.startsWith("next:") || line.startsWith("verifying:")) {
3128
+ text += theme.fg("muted", line);
3129
+ continue;
3130
+ }
3131
+ if (line.startsWith("rationale:") || line.startsWith("state-delta:")) {
3132
+ text += line;
3133
+ continue;
3134
+ }
3135
+ text += theme.fg("muted", line);
3136
+ }
3137
+ return text;
3138
+ }
3139
+
2941
3140
  function buildInlineRunningLines(details: {
2942
3141
  role?: string;
2943
3142
  startedAt?: number;
@@ -3150,11 +3349,11 @@ function completionKickoff(
3150
3349
  : intent === "refocus" && missionAnchor
3151
3350
  ? `Updated canonical mission anchor:\n${missionAnchor}\n\nWorkflow intent:\n- The user explicitly refocused the workflow before this kickoff.\n- Re-read canonical .agent/** state and continue from the refocused mission.\n\n`
3152
3351
  : "";
3153
- return `/skill:completion-protocol Start or continue the completion workflow for this repo.\n\nBefore acting, read:\n- ${SKILL_PATH}\n- ${REFERENCE_PATH}\n\nCanonical routing profile:\n- task_type: ${taskType}\n- evaluation_profile: ${evaluationProfile}\n\nUser goal:\n${goal}\n\n${intentBlock}Driver instructions:\n- Canonical truth is in .agent/**. Re-read .agent/state.json, .agent/plan.json, and .agent/active-slice.json before acting when they exist.\n- If tracked completion contract files are missing or onboarding is required, invoke completion_role with role completion-bootstrapper.\n- Otherwise follow the mandatory dispatch rules from completion-protocol.\n- Use completion_role for all completion-* role work. Do not directly implement tracked product changes yourself.\n- Continue dispatching mandatory roles while continuation_policy == continue.\n- Only stop for the user when continuation_policy is await_user_input, blocked, paused, or done.`;
3352
+ return `/skill:completion-protocol Start or continue the completion workflow for this repo.\n\nBefore acting, read:\n- ${SKILL_PATH}\n- ${REFERENCE_PATH}\n\nCanonical routing profile:\n- task_type: ${taskType}\n- evaluation_profile: ${evaluationProfile}\n\nUser goal:\n${goal}\n\n${intentBlock}Driver instructions:\n- Canonical truth is in .agent/**. Re-read .agent/state.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json before acting when they exist.\n- If tracked completion contract files are missing or onboarding is required, invoke completion_role with role completion-bootstrapper.\n- Otherwise follow the mandatory dispatch rules from completion-protocol.\n- For selected, in-progress, committed, or done slices, treat .agent/active-slice.json as the canonical implementation contract and route to completion-regrounder if it drifts from the selected plan slice or the exact handoff is unclear.\n- Consume .agent/verification-evidence.json instead of temp-only verification summaries when it is populated.\n- Use completion_role for all completion-* role work. Do not directly implement tracked product changes yourself.\n- Continue dispatching mandatory roles while continuation_policy == continue.\n- Only stop for the user when continuation_policy is await_user_input, blocked, paused, or done.`;
3154
3353
  }
3155
3354
 
3156
3355
  function completionResumePrompt(taskType: string, evaluationProfile: string): string {
3157
- return `/skill:completion-protocol Resume the completion workflow from canonical state.\n\nBefore acting, read:\n- ${SKILL_PATH}\n- ${REFERENCE_PATH}\n\nCanonical routing profile:\n- task_type: ${taskType}\n- evaluation_profile: ${evaluationProfile}\n\nResume instructions:\n- Re-read .agent/state.json, .agent/plan.json, and .agent/active-slice.json before acting.\n- If canonical state is missing, invalid, contradictory, stale, or ambiguous, route to completion-regrounder first.\n- Continue from next_mandatory_role and next_mandatory_action.\n- Use completion_role for all completion-* role work.\n- Continue dispatching mandatory roles while continuation_policy == continue.\n- Only stop for the user when continuation_policy is await_user_input, blocked, paused, or done.`;
3356
+ return `/skill:completion-protocol Resume the completion workflow from canonical state.\n\nBefore acting, read:\n- ${SKILL_PATH}\n- ${REFERENCE_PATH}\n\nCanonical routing profile:\n- task_type: ${taskType}\n- evaluation_profile: ${evaluationProfile}\n\nResume instructions:\n- Re-read .agent/state.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json before acting.\n- If canonical state is missing, invalid, contradictory, stale, or ambiguous, route to completion-regrounder first.\n- For selected, in-progress, committed, or done slices, treat .agent/active-slice.json as the canonical implementation contract and route to completion-regrounder if it drifts from the selected plan slice or the exact handoff is unclear.\n- Consume .agent/verification-evidence.json instead of temp-only verification summaries when it is populated.\n- Continue from next_mandatory_role and next_mandatory_action.\n- Use completion_role for all completion-* role work.\n- Continue dispatching mandatory roles while continuation_policy == continue.\n- Only stop for the user when continuation_policy is await_user_input, blocked, paused, or done.`;
3158
3357
  }
3159
3358
 
3160
3359
  export default function completionExtension(pi: ExtensionAPI) {
@@ -3473,7 +3672,7 @@ export default function completionExtension(pi: ExtensionAPI) {
3473
3672
  const task = typeof args.task === "string" ? args.task.trim() : "";
3474
3673
  let text = theme.fg("toolTitle", theme.bold("completion_role ")) + theme.fg("accent", role);
3475
3674
  if (task) {
3476
- text += `\n${theme.fg("dim", task)}`;
3675
+ text += `\n${theme.fg("muted", task)}`;
3477
3676
  }
3478
3677
  return new Text(text, 0, 0);
3479
3678
  },
@@ -3501,53 +3700,26 @@ export default function completionExtension(pi: ExtensionAPI) {
3501
3700
  };
3502
3701
  if (isPartial) {
3503
3702
  const lines = buildInlineRunningLines(details);
3504
- let text = "";
3505
- for (const [index, line] of lines.entries()) {
3506
- if (index > 0) text += "\n";
3507
- if (index === 0) {
3508
- const [prefix, ...rest] = line.split(" ");
3509
- text += theme.fg("warning", prefix);
3510
- if (rest.length > 0) text += ` ${theme.fg("accent", rest.join(" "))}`;
3511
- continue;
3512
- }
3513
- if (line.startsWith("tool:") || line.startsWith("progress:")) {
3514
- text += theme.fg("toolOutput", line);
3515
- continue;
3516
- }
3517
- if (line.startsWith("activity:")) {
3518
- text += theme.fg(line.includes("stalled") ? "warning" : "dim", line);
3519
- continue;
3520
- }
3521
- if (line === "recent tools:") {
3522
- text += theme.fg("dim", line);
3523
- continue;
3524
- }
3525
- if (line.startsWith("- ")) {
3526
- text += `${theme.fg("muted", "- ")}${theme.fg("dim", line.slice(2))}`;
3527
- continue;
3528
- }
3529
- text += theme.fg("dim", line);
3530
- }
3531
- return new Text(text, 0, 0);
3703
+ return new Text(formatInlineRunningText(theme, lines), 0, 0);
3532
3704
  }
3533
3705
  const role = details.role ?? "completion-role";
3534
3706
  const ok = details.status === "ok" && !result.isError;
3535
3707
  let text = `${theme.fg(ok ? "success" : "error", ok ? "done" : "error")} ${theme.fg("toolTitle", theme.bold(role))}`;
3536
- if (details.startedAt !== undefined) text += `\n${theme.fg("dim", `elapsed: ${formatElapsed(nowMs() - details.startedAt)}`)}`;
3708
+ if (details.startedAt !== undefined) text += `\n${theme.fg("muted", `elapsed: ${formatElapsed(nowMs() - details.startedAt)}`)}`;
3537
3709
  if (details.toolActivity) text += `\n${theme.fg("toolOutput", `tool: ${details.toolActivity}`)}`;
3538
3710
  if (details.progress) text += `\n${theme.fg("toolOutput", `progress: ${details.progress}`)}`;
3539
- else if (details.assistantSummary) text += `\n${theme.fg("dim", `assistant: ${details.assistantSummary}`)}`;
3540
- if (details.rationale) text += `\n${theme.fg("dim", `rationale: ${details.rationale}`)}`;
3541
- if (details.nextStep) text += `\n${theme.fg("dim", `next: ${details.nextStep}`)}`;
3542
- if (details.verifying) text += `\n${theme.fg("dim", `verifying: ${details.verifying}`)}`;
3711
+ else if (details.assistantSummary) text += `\nassistant: ${details.assistantSummary}`;
3712
+ if (details.rationale) text += `\n${theme.fg("muted", `rationale: ${details.rationale}`)}`;
3713
+ if (details.nextStep) text += `\n${theme.fg("muted", `next: ${details.nextStep}`)}`;
3714
+ if (details.verifying) text += `\n${theme.fg("muted", `verifying: ${details.verifying}`)}`;
3543
3715
  if (details.stateDeltas?.length) {
3544
- for (const delta of details.stateDeltas.slice(-4)) text += `\n${theme.fg("dim", `state-delta: ${delta}`)}`;
3716
+ for (const delta of details.stateDeltas.slice(-4)) text += `\n${theme.fg("muted", `state-delta: ${delta}`)}`;
3545
3717
  }
3546
3718
  if (details.transcription?.appended?.length) {
3547
3719
  text += `\n${theme.fg("success", `transcribed: ${details.transcription.appended.join(", ")}`)}`;
3548
3720
  }
3549
3721
  if (details.transcription?.skipped?.length && expanded) {
3550
- text += `\n${theme.fg("dim", `skipped: ${details.transcription.skipped.join(" | ")}`)}`;
3722
+ text += `\n${theme.fg("muted", `skipped: ${details.transcription.skipped.join(" | ")}`)}`;
3551
3723
  }
3552
3724
  if (details.transcription?.errors?.length) {
3553
3725
  text += `\n${theme.fg("warning", `warnings: ${details.transcription.errors.join(" | ")}`)}`;
@@ -3565,14 +3737,14 @@ export default function completionExtension(pi: ExtensionAPI) {
3565
3737
  for (const key of summaryKeys) {
3566
3738
  const value = reportFields[key];
3567
3739
  if (!value) continue;
3568
- text += `\n${theme.fg("dim", `${key}: `)}${value}`;
3740
+ text += `\n${theme.fg("muted", `${key}: `)}${value}`;
3569
3741
  }
3570
3742
  const body = result.content.find((item) => item.type === "text");
3571
3743
  if (expanded && body?.type === "text") {
3572
3744
  text += `\n\n${body.text}`;
3573
3745
  } else if (!expanded && body?.type === "text") {
3574
3746
  const preview = body.text.split("\n").slice(0, 4).join("\n");
3575
- text += `\n${theme.fg("dim", preview)}`;
3747
+ text += `\n${theme.fg("muted", preview)}`;
3576
3748
  }
3577
3749
  if (details.stderr && expanded) text += `\n${theme.fg("error", details.stderr)}`;
3578
3750
  return new Text(text, 0, 0);
@@ -3605,13 +3777,11 @@ export default function completionExtension(pi: ExtensionAPI) {
3605
3777
  );
3606
3778
  return;
3607
3779
  }
3608
- const decision = await confirmContextProposal(ctx, proposal, projectName, {
3780
+ const decision = await confirmContextProposal(ctx, proposal, {
3609
3781
  title: "Start a completion workflow from the recent discussion?",
3610
- editorPrompt:
3611
- "Start a completion workflow from the recent discussion?\n\nEdit the proposed mission, scope, constraints, and acceptance details below.",
3612
3782
  });
3613
3783
  if (!decision) {
3614
- emitCommandText(ctx, "Cancelled recent-discussion workflow proposal", "info");
3784
+ emitCommandText(ctx, buildCookCancellationMessage("Cancelled recent-discussion workflow proposal"), "info");
3615
3785
  return;
3616
3786
  }
3617
3787
  goal = decision.goalText;
@@ -3619,14 +3789,12 @@ export default function completionExtension(pi: ExtensionAPI) {
3619
3789
  kickoffAnalysis = decision.analysis;
3620
3790
  } else {
3621
3791
  const proposal = await buildGoalAnchoredContextProposal(ctx, goal, projectName);
3622
- const decision = await confirmContextProposal(ctx, proposal, projectName, {
3792
+ const decision = await confirmContextProposal(ctx, proposal, {
3623
3793
  title: "Start a completion workflow from this goal?",
3624
3794
  nonInteractiveBehavior: "accept",
3625
- editorPrompt:
3626
- "Start a completion workflow from this goal?\n\nEdit the proposed mission, scope, constraints, and acceptance details below.",
3627
3795
  });
3628
3796
  if (!decision) {
3629
- emitCommandText(ctx, "Cancelled workflow startup proposal", "info");
3797
+ emitCommandText(ctx, buildCookCancellationMessage("Cancelled workflow startup proposal"), "info");
3630
3798
  return;
3631
3799
  }
3632
3800
  goal = decision.goalText;
@@ -3665,13 +3833,11 @@ export default function completionExtension(pi: ExtensionAPI) {
3665
3833
  );
3666
3834
  return;
3667
3835
  }
3668
- const decision = await confirmContextProposal(ctx, proposal, projectName, {
3836
+ const decision = await confirmContextProposal(ctx, proposal, {
3669
3837
  title: "The previous completion workflow is done. Start the next workflow round from the recent discussion?",
3670
- editorPrompt:
3671
- "The previous completion workflow is done. Start the next workflow round from the recent discussion?\n\nEdit the proposed mission, scope, constraints, and acceptance details below.",
3672
3838
  });
3673
3839
  if (!decision) {
3674
- emitCommandText(ctx, "Cancelled next workflow round proposal", "info");
3840
+ emitCommandText(ctx, buildCookCancellationMessage("Cancelled next workflow round proposal"), "info");
3675
3841
  return;
3676
3842
  }
3677
3843
  goal = decision.goalText;
@@ -3694,7 +3860,9 @@ export default function completionExtension(pi: ExtensionAPI) {
3694
3860
  current_phase: asString(snapshot.state?.current_phase) ?? null,
3695
3861
  next_mandatory_role: asString(snapshot.state?.next_mandatory_role) ?? null,
3696
3862
  });
3697
- await queueCompletionDriverPrompt(pi, ctx, rootKey, fingerprint, resumePrompt, "resume");
3863
+ const resumeKind =
3864
+ shouldTestAutoContinueOnSessionStart() && completionTestAutoContinuePromptPath() ? "auto-resume" : "resume";
3865
+ await queueCompletionDriverPrompt(pi, ctx, rootKey, fingerprint, resumePrompt, resumeKind);
3698
3866
  return;
3699
3867
  }
3700
3868
  }
@@ -3703,14 +3871,12 @@ export default function completionExtension(pi: ExtensionAPI) {
3703
3871
  if (workflowDone) {
3704
3872
  const projectName = path.basename(snapshot.files.root);
3705
3873
  const proposal = await buildGoalAnchoredContextProposal(ctx, goal, projectName);
3706
- const decision = await confirmContextProposal(ctx, proposal, projectName, {
3874
+ const decision = await confirmContextProposal(ctx, proposal, {
3707
3875
  title: "Start the next workflow round from this goal?",
3708
3876
  nonInteractiveBehavior: "accept",
3709
- editorPrompt:
3710
- "Start the next workflow round from this goal?\n\nEdit the proposed mission, scope, constraints, and acceptance details below.",
3711
3877
  });
3712
3878
  if (!decision) {
3713
- emitCommandText(ctx, "Cancelled next workflow round proposal", "info");
3879
+ emitCommandText(ctx, buildCookCancellationMessage("Cancelled next workflow round proposal"), "info");
3714
3880
  return;
3715
3881
  }
3716
3882
  goal = decision.goalText;
@@ -3722,7 +3888,7 @@ export default function completionExtension(pi: ExtensionAPI) {
3722
3888
  } else {
3723
3889
  const decision = await confirmExistingWorkflowGoal(ctx, snapshot, goal);
3724
3890
  if (!decision) {
3725
- emitCommandText(ctx, "Cancelled existing workflow confirmation", "info");
3891
+ emitCommandText(ctx, buildCookCancellationMessage("Cancelled existing workflow confirmation"), "info");
3726
3892
  return;
3727
3893
  }
3728
3894
  kickoffIntent = decision.action;
@@ -3730,14 +3896,12 @@ export default function completionExtension(pi: ExtensionAPI) {
3730
3896
  if (decision.action === "refocus") {
3731
3897
  const projectName = path.basename(snapshot.files.root);
3732
3898
  const proposal = await buildGoalAnchoredContextProposal(ctx, goal, projectName);
3733
- const proposalDecision = await confirmContextProposal(ctx, proposal, projectName, {
3899
+ const proposalDecision = await confirmContextProposal(ctx, proposal, {
3734
3900
  title: "Start the replacement workflow from this goal?",
3735
3901
  nonInteractiveBehavior: "accept",
3736
- editorPrompt:
3737
- "Start the replacement workflow from this goal?\n\nEdit the proposed mission, scope, constraints, and acceptance details below.",
3738
3902
  });
3739
3903
  if (!proposalDecision) {
3740
- emitCommandText(ctx, "Cancelled replacement workflow proposal", "info");
3904
+ emitCommandText(ctx, buildCookCancellationMessage("Cancelled replacement workflow proposal"), "info");
3741
3905
  return;
3742
3906
  }
3743
3907
  goal = proposalDecision.goalText;