@linimin/pi-letscook 0.1.58 → 0.1.60

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.
@@ -25,7 +25,6 @@ import {
25
25
  missionAnchorsStrictlyEquivalent,
26
26
  normalizeMissionAnchorText,
27
27
  resolveContextProposalConfirmationAction,
28
- shouldTreatBareActiveWorkflowProposalAsClearRefocus,
29
28
  stripCodeBlocks,
30
29
  } from "./proposal";
31
30
  import type {
@@ -128,15 +127,14 @@ type CookContextProposalResult = {
128
127
  };
129
128
 
130
129
  type ActiveWorkflowProposalAssessment = {
131
- action: "continue" | "refocus" | "unclear" | "blocked";
130
+ action: "continue" | "refocus" | "blocked";
132
131
  currentMissionAnchor: string;
133
132
  proposal?: ContextProposal;
134
133
  blockedFailureMessage?: string;
135
134
  reason:
136
135
  | "matching_mission"
137
- | "clear_refocus"
138
- | "missing_proposal"
139
- | "ambiguous_discussion"
136
+ | "missing_explicit_handoff"
137
+ | "fresh_explicit_handoff"
140
138
  | "fresh_explicit_handoff_not_startable";
141
139
  };
142
140
 
@@ -211,7 +209,7 @@ function maybeWriteTestSnapshot(targetPath: string | undefined, content: string)
211
209
 
212
210
  const COOK_MAIN_CHAT_RERUN_GUIDANCE = "Discuss changes in the main chat and rerun /cook.";
213
211
  const COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL =
214
- "/cook failed closed because recent discussion did not produce a clear execution-ready startup brief with Mission/Scope/Constraints/Acceptance for concrete repo changes. Clarify the concrete repo changes in the main chat and rerun /cook.";
212
+ "/cook failed closed because recent discussion did not produce a clear execution-ready startup brief for bare /cook with Mission/Scope/Constraints/Acceptance for concrete repo changes. Clarify the concrete repo changes in the main chat and rerun /cook; canonical workflow state is still only written after Start.";
215
213
 
216
214
  function isWorkflowDone(snapshot: CompletionStateSnapshot | undefined): boolean {
217
215
  return asString(snapshot?.state?.continuation_policy) === "done";
@@ -374,6 +372,27 @@ async function promptContextProposalConfirmationAction(
374
372
  });
375
373
  }
376
374
 
375
+ async function deriveCookStartupProposal(
376
+ ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
377
+ projectName: string,
378
+ ): Promise<CookContextProposalResult> {
379
+ const recentMessages = collectRecentSessionMessages(ctx, { isRecord, asString, asNumber, isStaleContextError });
380
+ const explicitHandoff = assessLatestCookHandoffProposal(recentMessages, projectName, {
381
+ asString,
382
+ asStringArray,
383
+ assessMissionAnchor,
384
+ normalizeMissionAnchorText,
385
+ isWeakMissionAnchor,
386
+ missionAnchorsStrictlyEquivalent,
387
+ stripCodeBlocks,
388
+ });
389
+ if (explicitHandoff.status === "startable") return { proposal: explicitHandoff.proposal };
390
+ if (explicitHandoff.status === "fresh_but_not_startable") {
391
+ return { blockedFailureMessage: explicitHandoff.message };
392
+ }
393
+ return {};
394
+ }
395
+
377
396
  async function deriveCookContextProposal(
378
397
  ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
379
398
  projectName: string,
@@ -396,19 +415,8 @@ async function deriveCookContextProposal(
396
415
  `verification summary: ${asString(snapshot.verificationEvidence?.summary) ?? "(none)"}`,
397
416
  ]
398
417
  : [];
399
- const explicitHandoff = assessLatestCookHandoffProposal(recentMessages, projectName, {
400
- asString,
401
- asStringArray,
402
- assessMissionAnchor,
403
- normalizeMissionAnchorText,
404
- isWeakMissionAnchor,
405
- missionAnchorsStrictlyEquivalent,
406
- stripCodeBlocks,
407
- });
408
- if (explicitHandoff.status === "startable") return { proposal: explicitHandoff.proposal };
409
- if (explicitHandoff.status === "fresh_but_not_startable") {
410
- return { blockedFailureMessage: explicitHandoff.message };
411
- }
418
+ const explicitHandoff = await deriveCookStartupProposal(ctx, projectName);
419
+ if (explicitHandoff.proposal || explicitHandoff.blockedFailureMessage) return explicitHandoff;
412
420
  return {
413
421
  proposal: await deriveCookContextProposalFromRecentDiscussion(projectName, recentEntries, {
414
422
  asString,
@@ -921,7 +929,7 @@ export default function completionExtension(pi: ExtensionAPI) {
921
929
  structuredDiscussionFailureDetail: COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL,
922
930
  mainChatRerunGuidance: COOK_MAIN_CHAT_RERUN_GUIDANCE,
923
931
  cookCommandSpec: {
924
- description: "/cook workflow: derive a startup brief from recent discussion, then start, continue, refocus, or start the next round from the explicit /cook command",
932
+ description: "/cook workflow: synthesize an approval-gated startup brief from recent discussion for new-workflow or next-round entry, resume the current workflow from canonical state, or confirm an explicit active-workflow replacement",
925
933
  },
926
934
  buildContextProposalContinuationReason,
927
935
  completionKickoff,
@@ -934,6 +942,7 @@ export default function completionExtension(pi: ExtensionAPI) {
934
942
  completionTestWorkflowMissionOverride,
935
943
  confirmContextProposal,
936
944
  deriveCookContextProposal,
945
+ deriveCookStartupProposal,
937
946
  emitCommandText,
938
947
  finalizeContextProposalAnalysis,
939
948
  getCtxCwd,
@@ -948,7 +957,6 @@ export default function completionExtension(pi: ExtensionAPI) {
948
957
  scaffoldCompletionFiles,
949
958
  shouldSkipDriverKickoffForTests,
950
959
  shouldTestAutoContinueOnSessionStart,
951
- shouldTreatBareActiveWorkflowProposalAsClearRefocus,
952
960
  };
953
961
 
954
962
 
@@ -27,17 +27,19 @@ export type AdvisoryStartupBrief = {
27
27
  export function buildCookHandoffBoundaryReminder(): string {
28
28
  return [
29
29
  "You are still in ordinary main chat before any explicit /cook workflow entry.",
30
- "Use ordinary chat to clarify requirements, discuss tradeoffs, and propose implementation approaches.",
30
+ "Use ordinary chat to clarify requirements, discuss tradeoffs, propose implementation approaches, and refine scope with the user.",
31
31
  "/cook is the only explicit entrypoint into long-running completion workflow.",
32
- "When you judge that the task has matured into completion-workflow scope — for example the user has clearly shifted from exploration into implementation intent, you have just produced a concrete plan or proposal whose next step would naturally be implementation, or the task spans multiple files, steps, or verification surfaces — stop short of long-running implementation and tell the user to run /cook.",
33
- "At that handoff point, do not begin long-running product implementation in ordinary chat, do not edit tracked product files for that workflow-level task, and do not act as though /cook had already been invoked.",
34
- "Distinguish a workflow-worthy handoff from an implementation-ready handoff: only emit the implementation-ready capsule when the first bounded implementation slice is concrete enough to start immediately.",
35
- "When handing off, explain that /cook will first look for a fresh explicit primary-agent handoff capsule and otherwise fall back to recent discussion.",
36
- "If the task is workflow-worthy but that first slice is still vague, tell the user to run /cook without emitting an implementation-ready capsule yet.",
37
- "Otherwise append one exact fenced block in the same assistant reply using ```cook_handoff ... ``` JSON with kind/source/handoff_kind plus mission, scope, constraints or non_goals, acceptance, risks, notes, captured_at, source_turn_id, first_slice_goal, first_slice_non_goals, implementation_surfaces, verification_commands, why_this_slice_first, and optional task_type/evaluation_profile/why_cook_now.",
38
- "Use handoff_kind implementation_workflow_handoff for that implementation-ready capsule.",
39
- "The capsule is startup intake for /cook only: do not present it as canonical .agent state, an active slice, or a persistent repo contract.",
40
- "If the task is still ordinary Q&A, lightweight brainstorming, or a tiny one-off fix, continue normally without forcing /cook.",
32
+ "When you judge that the task has matured into completion-workflow scope — for example the user has clearly shifted from exploration into implementation intent, you have just produced a concrete plan or proposal whose next step would naturally be implementation, or the task spans multiple files, steps, or verification surfaces — do not begin long-running product implementation in ordinary chat and do not edit tracked product files for that workflow-level task.",
33
+ "Instead, recommend /cook as the workflow boundary while keeping the conversation in ordinary chat until the user explicitly runs /cook.",
34
+ "If the user keeps asking follow-up questions or refining requirements before /cook, continue that ordinary-chat discussion normally instead of switching into a handoff-only refusal mode, but do not act as though /cook had already been invoked.",
35
+ "Distinguish a workflow-worthy handoff from an opt-in preview request: by default, do not emit a structured preview or cook_handoff capsule in ordinary chat once the task is concrete enough; recommend bare /cook instead.",
36
+ "If the task is workflow-worthy but the user still wants to refine scope before /cook, keep refining in ordinary chat without acting as though workflow already started.",
37
+ "When handing off, explain that bare /cook will synthesize a startup brief from recent ordinary-chat discussion for a new workflow or next round, while already-active workflows resume from canonical .agent state unless the user explicitly chooses a replacement path backed by a fresh valid explicit handoff.",
38
+ "If the user explicitly asks for a /cook preview or capsule before running /cook, you may append one exact fenced block in the same assistant reply using ```cook_handoff ... ``` JSON with kind/source/handoff_kind plus mission, scope, constraints or non_goals, acceptance, risks, notes, captured_at, source_turn_id, first_slice_goal, first_slice_non_goals, implementation_surfaces, verification_commands, why_this_slice_first, and optional task_type/evaluation_profile/why_cook_now.",
39
+ "Use handoff_kind implementation_workflow_handoff for that opt-in preview capsule.",
40
+ "If later ordinary-chat discussion materially changes the startup brief before /cook runs, update or replace the preview capsule in a later assistant reply instead of pretending the workflow already started.",
41
+ "Any preview capsule is startup intake for /cook only: do not present it as canonical .agent state, an active slice, or a persistent repo contract.",
42
+ "If the task is still ordinary Q&A, lightweight brainstorming, or a tiny one-off fix, continue normally without forcing /cook or emitting an unsolicited preview capsule.",
41
43
  ].join(" ");
42
44
  }
43
45
 
@@ -1246,7 +1246,6 @@ export function extractContextProposalFromStructuredSession(
1246
1246
 
1247
1247
  const COOK_HANDOFF_BLOCK_REGEX = /```cook_handoff\s*([\s\S]*?)```/giu;
1248
1248
  const COOK_HANDOFF_MAX_AGE_MS = 45 * 60 * 1000;
1249
- const COOK_HANDOFF_MAX_LATER_NON_COMMAND_MESSAGES = 2;
1250
1249
  const COOK_HANDOFF_NEGATIVE_MISSION_REGEX =
1251
1250
  /(?:\b(?:do not|don't|dont|not|never|avoid|skip|refuse|recognize that|suppress|ignore|block|prevent)\b|(?:不要|別|别|勿|禁止|避免|忽略|阻止))/iu;
1252
1251
  const COOK_HANDOFF_WORKFLOW_ONLY_ACCEPTANCE_REGEX =
@@ -1389,19 +1388,6 @@ function isStartableCookHandoffCapsule(
1389
1388
  return cookHandoffStartabilityFailures(capsule, deps).length === 0;
1390
1389
  }
1391
1390
 
1392
- function laterMessagesInvalidateCookHandoff(
1393
- laterMessages: RecentSessionMessage[],
1394
- deps: Pick<ProposalParseDeps, "stripCodeBlocks">,
1395
- ): boolean {
1396
- const laterNonCommandMessages = laterMessages.filter((entry) => !entry.isCommand);
1397
- if (laterNonCommandMessages.length > COOK_HANDOFF_MAX_LATER_NON_COMMAND_MESSAGES) return true;
1398
- return laterNonCommandMessages.some((entry) => {
1399
- if (entry.role === "summary") return false;
1400
- if (!hasRecentDiscussionImplementationIntent(entry.text, deps.stripCodeBlocks)) return false;
1401
- return true;
1402
- });
1403
- }
1404
-
1405
1391
  function cookHandoffIsFreshEnough(capsule: CookHandoffCapsule, laterMessages: RecentSessionMessage[]): boolean {
1406
1392
  const capturedAtMs = Date.parse(capsule.captured_at);
1407
1393
  if (!Number.isFinite(capturedAtMs)) return false;
@@ -1484,7 +1470,6 @@ export function assessLatestCookHandoffProposal(
1484
1470
  const capsule = capsules[capsuleIndex];
1485
1471
  const laterMessages = recentMessages.slice(0, index);
1486
1472
  if (!cookHandoffIsFreshEnough(capsule, laterMessages)) continue;
1487
- if (laterMessagesInvalidateCookHandoff(laterMessages, deps)) continue;
1488
1473
  const failures = cookHandoffStartabilityFailures(capsule, deps);
1489
1474
  if (failures.length > 0) {
1490
1475
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linimin/pi-letscook",
3
- "version": "0.1.58",
3
+ "version": "0.1.60",
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,
@@ -22,6 +22,11 @@
22
22
  "subagent"
23
23
  ],
24
24
  "files": [
25
+ ".agent/README.md",
26
+ ".agent/mission.md",
27
+ ".agent/profile.json",
28
+ ".agent/verify_completion_stop.sh",
29
+ ".agent/verify_completion_control_plane.sh",
25
30
  "extensions",
26
31
  "skills",
27
32
  "agents",
@@ -47,6 +47,49 @@ with session_path.open('w', encoding='utf-8') as fh:
47
47
  PY
48
48
  }
49
49
 
50
+ write_session_messages() {
51
+ local session_path="$1"
52
+ local cwd="$2"
53
+ local messages_json="$3"
54
+ python3 - "$session_path" "$cwd" "$messages_json" <<'PY'
55
+ import json
56
+ import sys
57
+ from pathlib import Path
58
+
59
+ session_path = Path(sys.argv[1])
60
+ cwd = sys.argv[2]
61
+ messages = json.loads(sys.argv[3])
62
+ session_path.parent.mkdir(parents=True, exist_ok=True)
63
+ entries = [
64
+ {
65
+ "type": "session",
66
+ "version": 3,
67
+ "id": "11111111-1111-4111-8111-111111111111",
68
+ "timestamp": "2026-01-01T00:00:00.000Z",
69
+ "cwd": cwd,
70
+ },
71
+ ]
72
+ parent_id = None
73
+ for index, message in enumerate(messages, start=1):
74
+ entry_id = f"m{index:04d}"
75
+ entries.append({
76
+ "type": "message",
77
+ "id": entry_id,
78
+ "parentId": parent_id,
79
+ "timestamp": f"2026-01-01T00:00:{index:02d}.000Z",
80
+ "message": {
81
+ "role": message["role"],
82
+ "content": message["content"],
83
+ "timestamp": 1767225600000 + index * 1000,
84
+ },
85
+ })
86
+ parent_id = entry_id
87
+ with session_path.open('w', encoding='utf-8') as fh:
88
+ for entry in entries:
89
+ fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
90
+ PY
91
+ }
92
+
50
93
  cd "$PKG_ROOT"
51
94
 
52
95
  node <<'NODE'
@@ -94,11 +137,59 @@ NODE
94
137
  ROOT="$TMPDIR/repo"
95
138
  PROMPT="$TMPDIR/resume-prompt.txt"
96
139
  BOOTSTRAP_SESSION="$TMPDIR/session-active-slice-bootstrap.jsonl"
97
- BOOTSTRAP_DISCUSSION=$'Mission: Exercise active-slice contract parity.\nScope:\n- Bootstrap canonical completion files for the active-slice contract fixture.\nConstraints:\n- Use supported bare /cook startup only.\nAcceptance:\n- Materialize canonical files before the fixture rewrites them.'
140
+ BOOTSTRAP_MESSAGES="$(python3 - <<'PY'
141
+ import json
142
+ capsule = {
143
+ "kind": "cook_handoff",
144
+ "source": "primary_agent",
145
+ "captured_at": "2026-01-01T00:00:02.000Z",
146
+ "source_turn_id": "m0002",
147
+ "mission": "Exercise active-slice contract parity.",
148
+ "scope": [
149
+ "Bootstrap canonical completion files for the active-slice contract fixture.",
150
+ "Keep the fixture on the shipped explicit-handoff startup path."
151
+ ],
152
+ "constraints": [
153
+ "Use supported bare /cook startup only."
154
+ ],
155
+ "acceptance": [
156
+ "Materialize .agent/profile.json, .agent/state.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json before the fixture rewrites them.",
157
+ "Keep scripts/active-slice-contract-test.sh aligned with the packaged startup contract."
158
+ ],
159
+ "risks": [
160
+ "Active-slice fixture bootstrap must stay anchored to the fresh explicit handoff."
161
+ ],
162
+ "notes": [
163
+ "This handoff exists only to scaffold canonical files before the fixture rewrites them for contract parity coverage."
164
+ ],
165
+ "handoff_kind": "implementation_workflow_handoff",
166
+ "first_slice_goal": "Scaffold active-slice contract fixture files before rewriting them for parity verification.",
167
+ "first_slice_non_goals": [
168
+ "Do not broaden the fixture beyond active-slice contract surfaces."
169
+ ],
170
+ "implementation_surfaces": [
171
+ ".agent/active-slice.json",
172
+ "scripts/active-slice-contract-test.sh"
173
+ ],
174
+ "verification_commands": [
175
+ "bash scripts/active-slice-contract-test.sh"
176
+ ],
177
+ "why_this_slice_first": "The active-slice fixture cannot validate parity until canonical files exist.",
178
+ "task_type": "completion-workflow",
179
+ "evaluation_profile": "completion-rubric-v1",
180
+ "why_cook_now": "The fixture bootstrap is concrete enough to scaffold canonical control-plane files."
181
+ }
182
+ messages = [
183
+ {"role": "user", "content": "Prepare the active-slice contract bootstrap fixture and tell me when it is ready for /cook."},
184
+ {"role": "assistant", "content": "The active-slice contract bootstrap fixture is ready for /cook. Run /cook to confirm it.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
185
+ ]
186
+ print(json.dumps(messages, ensure_ascii=False))
187
+ PY
188
+ )"
98
189
  mkdir -p "$ROOT"
99
190
  cd "$ROOT"
100
191
  git init -q
101
- write_session "$BOOTSTRAP_SESSION" "$ROOT" "$BOOTSTRAP_DISCUSSION"
192
+ write_session_messages "$BOOTSTRAP_SESSION" "$ROOT" "$BOOTSTRAP_MESSAGES"
102
193
 
103
194
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
104
195
  PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
@@ -47,6 +47,49 @@ with session_path.open('w', encoding='utf-8') as fh:
47
47
  PY
48
48
  }
49
49
 
50
+ write_session_messages() {
51
+ local session_path="$1"
52
+ local cwd="$2"
53
+ local messages_json="$3"
54
+ python3 - "$session_path" "$cwd" "$messages_json" <<'PY'
55
+ import json
56
+ import sys
57
+ from pathlib import Path
58
+
59
+ session_path = Path(sys.argv[1])
60
+ cwd = sys.argv[2]
61
+ messages = json.loads(sys.argv[3])
62
+ session_path.parent.mkdir(parents=True, exist_ok=True)
63
+ entries = [
64
+ {
65
+ "type": "session",
66
+ "version": 3,
67
+ "id": "11111111-1111-4111-8111-111111111111",
68
+ "timestamp": "2026-01-01T00:00:00.000Z",
69
+ "cwd": cwd,
70
+ },
71
+ ]
72
+ parent_id = None
73
+ for index, message in enumerate(messages, start=1):
74
+ entry_id = f"m{index:04d}"
75
+ entries.append({
76
+ "type": "message",
77
+ "id": entry_id,
78
+ "parentId": parent_id,
79
+ "timestamp": f"2026-01-01T00:00:{index:02d}.000Z",
80
+ "message": {
81
+ "role": message["role"],
82
+ "content": message["content"],
83
+ "timestamp": 1767225600000 + index * 1000,
84
+ },
85
+ })
86
+ parent_id = entry_id
87
+ with session_path.open('w', encoding='utf-8') as fh:
88
+ for entry in entries:
89
+ fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
90
+ PY
91
+ }
92
+
50
93
  cleanup() {
51
94
  if [[ -n "$CURRENT_EVIDENCE_BACKUP" && -f "$CURRENT_EVIDENCE_BACKUP" ]]; then
52
95
  cp "$CURRENT_EVIDENCE_BACKUP" "$PKG_ROOT/.agent/verification-evidence.json"
@@ -139,11 +182,59 @@ bash .agent/verify_completion_control_plane.sh >/dev/null
139
182
  ROOT="$TMPDIR/repo"
140
183
  SYSTEM_REMINDER="$TMPDIR/system-reminder.txt"
141
184
  BOOTSTRAP_SESSION="$TMPDIR/session-canonical-evidence-bootstrap.jsonl"
142
- BOOTSTRAP_DISCUSSION=$'Mission: Exercise canonical evidence fixture bootstrap.\nScope:\n- Materialize canonical completion files for the evidence artifact fixture.\nConstraints:\n- Use supported bare /cook startup only.\nAcceptance:\n- Scaffold canonical files before the fixture rewrites them.'
185
+ BOOTSTRAP_MESSAGES="$(python3 - <<'PY'
186
+ import json
187
+ capsule = {
188
+ "kind": "cook_handoff",
189
+ "source": "primary_agent",
190
+ "captured_at": "2026-01-01T00:00:02.000Z",
191
+ "source_turn_id": "m0002",
192
+ "mission": "Exercise canonical evidence fixture bootstrap.",
193
+ "scope": [
194
+ "Materialize canonical completion files for the evidence artifact fixture.",
195
+ "Keep the verification-evidence bootstrap on the supported explicit-handoff startup path."
196
+ ],
197
+ "constraints": [
198
+ "Use supported bare /cook startup only."
199
+ ],
200
+ "acceptance": [
201
+ "Scaffold .agent/profile.json, .agent/state.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json before the fixture rewrites them.",
202
+ "Keep scripts/canonical-evidence-artifact-test.sh aligned with packaged bootstrap behavior."
203
+ ],
204
+ "risks": [
205
+ "Evidence-artifact bootstrap must stay anchored to the fresh explicit handoff."
206
+ ],
207
+ "notes": [
208
+ "This fixture exists only to scaffold canonical files before rewriting them for evidence parity coverage."
209
+ ],
210
+ "handoff_kind": "implementation_workflow_handoff",
211
+ "first_slice_goal": "Scaffold canonical evidence-artifact fixture files before rewriting them for parity checks.",
212
+ "first_slice_non_goals": [
213
+ "Do not broaden the bootstrap fixture beyond the evidence-artifact surfaces."
214
+ ],
215
+ "implementation_surfaces": [
216
+ ".agent/verification-evidence.json",
217
+ "scripts/canonical-evidence-artifact-test.sh"
218
+ ],
219
+ "verification_commands": [
220
+ "bash ./scripts/canonical-evidence-artifact-test.sh"
221
+ ],
222
+ "why_this_slice_first": "The evidence-artifact fixture cannot validate fail-closed parity until canonical files exist.",
223
+ "task_type": "completion-workflow",
224
+ "evaluation_profile": "completion-rubric-v1",
225
+ "why_cook_now": "The fixture bootstrap is concrete enough to create canonical control-plane files."
226
+ }
227
+ messages = [
228
+ {"role": "user", "content": "Prepare the canonical evidence bootstrap fixture and tell me when it is ready for /cook."},
229
+ {"role": "assistant", "content": "The canonical evidence bootstrap fixture is ready for /cook. Run /cook to confirm it.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
230
+ ]
231
+ print(json.dumps(messages, ensure_ascii=False))
232
+ PY
233
+ )"
143
234
  mkdir -p "$ROOT"
144
235
  cd "$ROOT"
145
236
  git init -q
146
- write_session "$BOOTSTRAP_SESSION" "$ROOT" "$BOOTSTRAP_DISCUSSION"
237
+ write_session_messages "$BOOTSTRAP_SESSION" "$ROOT" "$BOOTSTRAP_MESSAGES"
147
238
 
148
239
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
149
240
  PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \