@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.
@@ -47,15 +47,99 @@ 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 "$TMPDIR"
51
94
  git init -q
52
95
 
53
96
  BOOTSTRAP_SESSION="$TMPDIR/session-bootstrap.jsonl"
54
- BOOTSTRAP_DISCUSSION=$'Mission: Smoke-test mission.\nScope:\n- Bootstrap a completion workflow for the refocus regression fixture.\nConstraints:\n- Use supported bare /cook discussion flow only.\nAcceptance:\n- Materialize canonical state for active-workflow refocus tests.'
55
- write_session "$BOOTSTRAP_SESSION" "$TMPDIR" "$BOOTSTRAP_DISCUSSION"
97
+ BOOTSTRAP_MESSAGES="$(python3 - <<'PY'
98
+ import json
99
+ capsule = {
100
+ "kind": "cook_handoff",
101
+ "source": "primary_agent",
102
+ "captured_at": "2026-01-01T00:00:02.000Z",
103
+ "source_turn_id": "m0002",
104
+ "mission": "Smoke-test mission.",
105
+ "scope": [
106
+ "Bootstrap a completion workflow for the refocus regression fixture."
107
+ ],
108
+ "constraints": [
109
+ "Keep active-workflow refocus behavior under the explicit-handoff startup contract."
110
+ ],
111
+ "acceptance": [
112
+ "Bootstrap canonical refocus-fixture state for the active-workflow regression.",
113
+ "Verify the refocus regression with npm run refocus-test."
114
+ ],
115
+ "risks": [],
116
+ "notes": [
117
+ "Use explicit primary-agent handoff startup for the refocus regression fixture."
118
+ ],
119
+ "handoff_kind": "implementation_workflow_handoff",
120
+ "first_slice_goal": "Bootstrap the refocus regression fixture from a fresh explicit handoff.",
121
+ "first_slice_non_goals": [],
122
+ "implementation_surfaces": [
123
+ "scripts/refocus-test.sh"
124
+ ],
125
+ "verification_commands": [
126
+ "npm run refocus-test"
127
+ ],
128
+ "why_this_slice_first": "The refocus regression fixture needs canonical state before active-workflow routing can be exercised.",
129
+ "task_type": "completion-workflow",
130
+ "evaluation_profile": "completion-rubric-v1",
131
+ "why_cook_now": "The active-workflow refocus regression needs a fresh explicit startup boundary."
132
+ }
133
+ messages = [
134
+ {"role": "user", "content": "Prepare the refocus regression fixture and tell me when it is ready for /cook."},
135
+ {"role": "assistant", "content": "The refocus regression fixture is ready for /cook.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
136
+ ]
137
+ print(json.dumps(messages, ensure_ascii=False))
138
+ PY
139
+ )"
140
+ write_session_messages "$BOOTSTRAP_SESSION" "$TMPDIR" "$BOOTSTRAP_MESSAGES"
56
141
 
57
142
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
58
- PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
59
143
  pi --session "$BOOTSTRAP_SESSION" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-refocus-bootstrap.out" 2>"$TMPDIR/pi-completion-refocus-bootstrap.err" &
60
144
  PI_PID=$!
61
145
  for _ in $(seq 1 60); do
@@ -139,11 +223,51 @@ assert before == after, 'active /cook inline-args rejection should leave canonic
139
223
  PY
140
224
 
141
225
  SESSION_INITIAL_REFOCUS="$TMPDIR/session-initial-bare-refocus.jsonl"
142
- INITIAL_REFOCUS_DISCUSSION=$'Mission: Remove completion status line, keep widget.\nScope:\n- Replace the initial smoke-test workflow with the widget mission.\nConstraints:\n- Keep the approval-only Start/Cancel refocus gate.\nAcceptance:\n- Rewrite canonical state only after the replacement mission is approved.'
143
226
  INITIAL_REFOCUS_ROUTING="$TMPDIR/initial-bare-refocus-routing.json"
144
- write_session "$SESSION_INITIAL_REFOCUS" "$TMPDIR" "$INITIAL_REFOCUS_DISCUSSION"
227
+ INITIAL_REFOCUS_MESSAGES="$(python3 - <<'PY'
228
+ import json
229
+ capsule = {
230
+ "kind": "cook_handoff",
231
+ "source": "primary_agent",
232
+ "captured_at": "2026-01-01T00:00:02.000Z",
233
+ "source_turn_id": "m0002",
234
+ "mission": "Remove completion status line, keep widget.",
235
+ "scope": [
236
+ "Replace the initial smoke-test workflow with the widget mission."
237
+ ],
238
+ "constraints": [
239
+ "Keep the approval-only Start/Cancel refocus gate."
240
+ ],
241
+ "acceptance": [
242
+ "Rewrite canonical state only after the replacement mission is approved."
243
+ ],
244
+ "risks": [],
245
+ "notes": [
246
+ "Use a fresh explicit primary-agent handoff for the active-workflow replacement."
247
+ ],
248
+ "handoff_kind": "implementation_workflow_handoff",
249
+ "first_slice_goal": "Replace the initial smoke-test workflow with the widget mission.",
250
+ "first_slice_non_goals": [],
251
+ "implementation_surfaces": [
252
+ "scripts/refocus-test.sh"
253
+ ],
254
+ "verification_commands": [
255
+ "npm run refocus-test"
256
+ ],
257
+ "why_this_slice_first": "The fresh explicit handoff is the only supported replacement entry while a workflow is active.",
258
+ "task_type": "completion-workflow",
259
+ "evaluation_profile": "completion-rubric-v1",
260
+ "why_cook_now": "A different active workflow is ready and explicitly handed off by the primary agent."
261
+ }
262
+ messages = [
263
+ {"role": "user", "content": "The smoke-test workflow is active, but a different replacement workflow may now be ready."},
264
+ {"role": "assistant", "content": "Use this fresh explicit handoff if you want /cook to replace the active workflow.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
265
+ ]
266
+ print(json.dumps(messages, ensure_ascii=False))
267
+ PY
268
+ )"
269
+ write_session_messages "$SESSION_INITIAL_REFOCUS" "$TMPDIR" "$INITIAL_REFOCUS_MESSAGES"
145
270
 
146
- PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
147
271
  PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
148
272
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
149
273
  PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$INITIAL_REFOCUS_ROUTING" \
@@ -188,9 +312,10 @@ assert active['status'] == 'idle', 'active-slice.json status should reset to idl
188
312
  assert routing['mode'] == 'bare', 'supported refocus should use bare active-workflow routing mode'
189
313
  assert 'explicitGoal' not in routing, 'supported bare refocus should not expose removed explicit-goal shim fields'
190
314
  assert 'explicitGoalProvided' not in routing, 'supported bare refocus should not expose removed explicit-goal shim fields'
191
- assert routing['action'] == 'refocus', 'supported bare /cook should classify as refocus when the mission changes'
192
- assert routing['reason'] == 'clear_refocus', 'supported bare /cook should record the clear-refocus routing reason'
193
- assert routing['proposedMissionAnchor'] == new_anchor, 'bare refocus routing snapshot should expose the replacement mission anchor'
315
+ assert routing['action'] == 'refocus', 'supported bare /cook should classify as refocus when a fresh explicit handoff proposes a different mission'
316
+ assert routing['reason'] == 'fresh_explicit_handoff', 'supported bare /cook should record the explicit-handoff replacement reason'
317
+ assert routing['proposedMissionAnchor'] == new_anchor, 'explicit handoff routing snapshot should expose the replacement mission anchor'
318
+ assert routing['proposalSource'] == 'handoff_capsule', 'explicit handoff routing snapshot should preserve the handoff source'
194
319
  PY
195
320
 
196
321
  UPDATED_MISSION="$(python3 - <<'PY'
@@ -206,16 +331,58 @@ if [[ "$INITIAL_MISSION" == "$UPDATED_MISSION" ]]; then
206
331
  exit 1
207
332
  fi
208
333
 
209
- # Negated replacement missions that contain the current anchor must still reach the conservative chooser and final Start/Cancel gate.
210
- BARE_REFOCUS_MISSION='Do not remove completion status line, keep widget.'
211
- BARE_REFOCUS_DISCUSSION=$'Mission: Do not remove completion status line, keep widget.\nScope:\n- Treat the active bare /cook discussion as a replacement workflow rather than a resume.\n- Keep the replacement behind the existing approval-only Start/Cancel gate.\nConstraints:\n- Do not rewrite canonical state before the final Start confirmation.\nAcceptance:\n- Add deterministic coverage proving the chooser and final approval path for this negated replacement mission.'
334
+ # Fresh explicit handoff replacements must still reach the chooser and final Start/Cancel gate while the
335
+ # workflow is active.
336
+ BARE_REFOCUS_MISSION='Exercise explicit active-workflow replacement coverage.'
337
+ BARE_REFOCUS_MESSAGES="$(python3 - <<'PY'
338
+ import json
339
+ capsule = {
340
+ "kind": "cook_handoff",
341
+ "source": "primary_agent",
342
+ "captured_at": "2026-01-01T00:00:02.000Z",
343
+ "source_turn_id": "m0002",
344
+ "mission": "Exercise explicit active-workflow replacement coverage.",
345
+ "scope": [
346
+ "Treat the active bare /cook request as an explicit replacement workflow.",
347
+ "Keep the replacement behind the existing approval-only Start/Cancel gate."
348
+ ],
349
+ "constraints": [
350
+ "Do not rewrite canonical state before the final Start confirmation."
351
+ ],
352
+ "acceptance": [
353
+ "Add deterministic coverage proving the chooser and final approval path for this explicit replacement mission."
354
+ ],
355
+ "risks": [],
356
+ "notes": [
357
+ "This replacement should come only from the fresh explicit handoff, not recent discussion inference."
358
+ ],
359
+ "handoff_kind": "implementation_workflow_handoff",
360
+ "first_slice_goal": "Exercise the active-workflow explicit-handoff replacement path.",
361
+ "first_slice_non_goals": [],
362
+ "implementation_surfaces": [
363
+ "scripts/refocus-test.sh"
364
+ ],
365
+ "verification_commands": [
366
+ "npm run refocus-test"
367
+ ],
368
+ "why_this_slice_first": "The active workflow should only replace from a fresh explicit handoff.",
369
+ "task_type": "completion-workflow",
370
+ "evaluation_profile": "completion-rubric-v1",
371
+ "why_cook_now": "The primary agent explicitly handed off a replacement workflow while the current one is active."
372
+ }
373
+ messages = [
374
+ {"role": "user", "content": "The current workflow is active, but there is a fresh explicit replacement handoff ready."},
375
+ {"role": "assistant", "content": "Use this fresh explicit handoff if you want /cook to replace the active workflow.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
376
+ ]
377
+ print(json.dumps(messages, ensure_ascii=False))
378
+ PY
379
+ )"
212
380
 
213
381
  SESSION_BARE_CHOOSER_CANCEL="$TMPDIR/session-bare-chooser-cancel.jsonl"
214
382
  BARE_CHOOSER_SNAPSHOT="$TMPDIR/bare-existing-workflow-chooser.json"
215
383
  BARE_ROUTING_CHOOSER_CANCEL="$TMPDIR/bare-routing-chooser-cancel.json"
216
- write_session "$SESSION_BARE_CHOOSER_CANCEL" "$TMPDIR" "$BARE_REFOCUS_DISCUSSION"
384
+ write_session_messages "$SESSION_BARE_CHOOSER_CANCEL" "$TMPDIR" "$BARE_REFOCUS_MESSAGES"
217
385
 
218
- PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
219
386
  PI_COMPLETION_EXISTING_WORKFLOW_ACTION=cancel \
220
387
  PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$BARE_CHOOSER_SNAPSHOT" \
221
388
  PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$BARE_ROUTING_CHOOSER_CANCEL" \
@@ -243,13 +410,14 @@ assert active['mission_anchor'] == updated_mission, 'chooser cancel should keep
243
410
  assert routing['mode'] == 'bare', 'bare /cook should snapshot bare active-workflow routing mode'
244
411
  assert 'explicitGoal' not in routing, 'bare chooser routing should not expose removed explicit-goal shim fields'
245
412
  assert 'explicitGoalProvided' not in routing, 'bare chooser routing should not expose removed explicit-goal shim fields'
246
- assert routing['action'] == 'refocus', 'clear structured discussion should classify active bare /cook as refocus'
247
- assert routing['reason'] == 'clear_refocus', 'clear structured discussion should record the clear-refocus routing reason'
248
- assert routing['currentMissionAnchor'] == updated_mission, 'clear-refocus routing should keep the current mission anchor until the user approves replacement'
249
- assert routing['proposedMissionAnchor'] == replacement_mission, 'clear-refocus routing should expose the proposed replacement mission'
413
+ assert routing['action'] == 'refocus', 'fresh explicit replacement handoff should classify active bare /cook as refocus'
414
+ assert routing['reason'] == 'fresh_explicit_handoff', 'fresh explicit replacement handoff should record the explicit-handoff reason'
415
+ assert routing['currentMissionAnchor'] == updated_mission, 'explicit-handoff routing should keep the current mission anchor until the user approves replacement'
416
+ assert routing['proposedMissionAnchor'] == replacement_mission, 'explicit-handoff routing should expose the proposed replacement mission'
417
+ assert routing['proposalSource'] == 'handoff_capsule', 'explicit-handoff routing should preserve the handoff source'
250
418
  assert chooser['title'].startswith('Existing completion workflow found'), 'bare chooser snapshot should describe the existing-workflow prompt'
251
419
  assert chooser['choices'][0].startswith('Continue current workflow'), 'bare chooser should keep the continue option'
252
- assert chooser['choices'][1].startswith('Start new workflow from recent discussion'), 'bare chooser should offer the recent-discussion refocus option'
420
+ assert chooser['choices'][1].startswith('Start new workflow from explicit primary-agent handoff'), 'bare chooser should offer the explicit-handoff replacement option'
253
421
  assert 'Start/Cancel confirmation' in chooser['choices'][1], 'bare chooser should mention the approval-only replacement confirmation'
254
422
  assert chooser['choices'][2].startswith('Cancel'), 'bare chooser should keep the cancel option'
255
423
  assert 'Discuss changes in the main chat and rerun /cook.' in output, 'bare chooser cancel should redirect users back to the main chat and rerun /cook'
@@ -258,9 +426,8 @@ PY
258
426
  SESSION_BARE_FINAL_CANCEL="$TMPDIR/session-bare-final-cancel.jsonl"
259
427
  BARE_ROUTING_FINAL_CANCEL="$TMPDIR/bare-routing-final-cancel.json"
260
428
  BARE_PROPOSAL_CANCEL="$TMPDIR/bare-replacement-proposal-cancel.json"
261
- write_session "$SESSION_BARE_FINAL_CANCEL" "$TMPDIR" "$BARE_REFOCUS_DISCUSSION"
429
+ write_session_messages "$SESSION_BARE_FINAL_CANCEL" "$TMPDIR" "$BARE_REFOCUS_MESSAGES"
262
430
 
263
- PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
264
431
  PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
265
432
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=cancel \
266
433
  PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$BARE_PROPOSAL_CANCEL" \
@@ -286,19 +453,19 @@ active = json.loads(Path('.agent/active-slice.json').read_text())
286
453
  assert state['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep the current mission anchor'
287
454
  assert plan['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep plan.json unchanged'
288
455
  assert active['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep active-slice.json unchanged'
289
- assert routing['action'] == 'refocus', 'final Start/Cancel cancel should still come from a clear-refocus classification'
290
- assert routing['reason'] == 'clear_refocus', 'final Start/Cancel cancel should preserve the clear-refocus reason'
456
+ assert routing['action'] == 'refocus', 'final Start/Cancel cancel should still come from an explicit-handoff refocus classification'
457
+ assert routing['reason'] == 'fresh_explicit_handoff', 'final Start/Cancel cancel should preserve the explicit-handoff reason'
291
458
  assert routing['currentMissionAnchor'] == updated_mission, 'final Start/Cancel cancel should keep the current mission anchor until the user approves replacement'
292
459
  assert proposal['mission'] == replacement_mission, 'final Start/Cancel cancel should still prepare the replacement proposal before rewriting state'
460
+ assert proposal['source'] == 'handoff_capsule', 'final Start/Cancel cancel should preserve the explicit-handoff proposal source'
293
461
  assert 'Discuss changes in the main chat and rerun /cook.' in output, 'final Start/Cancel cancel should redirect users back to the main chat and rerun /cook'
294
462
  PY
295
463
 
296
464
  SESSION_BARE_ACCEPT="$TMPDIR/session-bare-accept.jsonl"
297
465
  BARE_ROUTING_ACCEPT="$TMPDIR/bare-routing-accept.json"
298
466
  BARE_PROPOSAL_ACCEPT="$TMPDIR/bare-replacement-proposal-accept.json"
299
- write_session "$SESSION_BARE_ACCEPT" "$TMPDIR" "$BARE_REFOCUS_DISCUSSION"
467
+ write_session_messages "$SESSION_BARE_ACCEPT" "$TMPDIR" "$BARE_REFOCUS_MESSAGES"
300
468
 
301
- PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
302
469
  PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
303
470
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
304
471
  PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$BARE_PROPOSAL_ACCEPT" \
@@ -312,7 +479,7 @@ import json
312
479
  import sys
313
480
  from pathlib import Path
314
481
 
315
- new_anchor = 'Do not remove completion status line, keep widget.'
482
+ new_anchor = 'Exercise explicit active-workflow replacement coverage.'
316
483
  expected_task_type = 'completion-workflow'
317
484
  expected_eval_profile = 'completion-rubric-v1'
318
485
  proposal = json.loads(Path(sys.argv[1]).read_text())
@@ -327,9 +494,10 @@ assert proposal['mission'] == new_anchor, 'accepted bare refocus should preserve
327
494
  assert routing['mode'] == 'bare', 'accepted bare refocus should keep bare routing mode'
328
495
  assert 'explicitGoal' not in routing, 'accepted bare refocus should not expose removed explicit-goal shim fields'
329
496
  assert 'explicitGoalProvided' not in routing, 'accepted bare refocus should not expose removed explicit-goal shim fields'
330
- assert routing['action'] == 'refocus', 'accepted bare refocus should keep the clear-refocus classification'
331
- assert routing['reason'] == 'clear_refocus', 'accepted bare refocus should keep the clear-refocus reason'
497
+ assert routing['action'] == 'refocus', 'accepted bare refocus should keep the explicit-handoff refocus classification'
498
+ assert routing['reason'] == 'fresh_explicit_handoff', 'accepted bare refocus should keep the explicit-handoff reason'
332
499
  assert routing['currentMissionAnchor'] == 'Remove completion status line, keep widget.', 'accepted bare refocus should expose the original mission until Start is accepted'
500
+ assert routing['proposalSource'] == 'handoff_capsule', 'accepted bare refocus should preserve the explicit-handoff source'
333
501
  assert new_anchor in mission_text, '.agent/mission.md did not update to the bare refocus mission anchor'
334
502
  assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after bare refocus'
335
503
  assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after bare refocus'
@@ -5,57 +5,58 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
5
  cd "$ROOT"
6
6
  export PI_COMPLETION_RUNNING_RELEASE_CHECK=1
7
7
 
8
- echo "[release-check] running control-plane validation, tracked .agent contract coverage, slice-surface parity, explicit-/cook parity, startup/refocus/context regressions, canonical evidence artifact, active-slice contract, observability, legacy cleanup, evaluator calibration, and rubric contract coverage"
8
+ echo "[release-check] running control-plane validation, tracked .agent contract coverage, slice-surface parity, mixed-model /cook parity, startup/refocus/context regressions, canonical evidence artifact, active-slice contract, observability, legacy cleanup, evaluator calibration, and rubric contract coverage"
9
9
  bash .agent/verify_completion_control_plane.sh
10
10
  git ls-files --error-unmatch .agent/README.md .agent/mission.md .agent/profile.json .agent/verify_completion_stop.sh .agent/verify_completion_control_plane.sh >/dev/null
11
11
 
12
- echo "[release-check] verifying public /cook parity and explicit-entry docs/help"
12
+ echo "[release-check] verifying public /cook mixed-model parity across docs/help"
13
13
  python3 - <<'PY'
14
- import re
15
14
  from pathlib import Path
16
15
 
17
16
  checks = {
18
17
  "README.md": [
19
18
  "`/cook` is the explicit workflow boundary for starting, continuing, refocusing, or beginning the next round of long-running repo work.",
20
- "Only explicit `/cook` enters the workflow. Ordinary prompts stay in the main chat and go straight to the primary agent.",
21
- "If a task has clearly matured into completion-workflow scope, the primary agent should hand you off to `/cook` instead of starting long-running implementation directly in ordinary chat.",
22
- "That handoff should include an explicit structured `/cook` capsule in the assistant reply so `/cook` can confirm the already-formed mission instead of re-deriving it from broad ambient context.",
23
- "for that handoff capsule to start workflow immediately, it must already be implementation-startable: a bounded `first_slice_goal`, repo-change-oriented acceptance, `implementation_surfaces`, `verification_commands`, and `why_this_slice_first`",
24
- "The preferred capsule is still advisory startup intake, not canonical workflow state, and it only counts as implementation-ready when it already names the first bounded slice, repo-change-oriented acceptance, implementation surfaces, and verification commands.",
25
- "`/cook` first looks for a fresh explicit primary-agent handoff capsule. If one is valid and implementation-startable, `/cook` builds the startup brief from that handoff and only uses recent discussion as validation or supplemental notes.",
26
- "`/cook` falls back to deriving a startup brief from recent discussion only when no fresh explicit handoff is blocking startup—for example, when there is no fresh capsule or only stale or invalidated capsules—before showing the existing approval-only Start/Cancel gate.",
27
- "The pre-`/cook` handoff capsule itself is not canonical workflow state. It is only startup intake for `/cook`.",
19
+ "Before you explicitly run `/cook`, the conversation can still stay in ordinary chat: the primary agent may keep answering follow-up questions and refining requirements rather than switching into a hard handoff-only refusal mode.",
20
+ "If you explicitly ask for a pre-`/cook` preview or capsule, the primary agent may provide one, but that preview is opt-in only and stays non-canonical until you later run `/cook` and choose **Start**.",
21
+ "Bare `/cook` is still the canonical workflow boundary: it synthesizes the startup brief from recent ordinary-chat discussion at `/cook` time, then waits for **Start** or **Cancel** before any canonical `.agent/**` write.",
22
+ "- startup and next-round entry stay confirm-first: bare `/cook` synthesizes the startup brief from recent discussion, then waits for **Start** or **Cancel**",
23
+ "- any pre-`/cook` preview or capsule is explicit-request-only and non-canonical",
24
+ "When no workflow is active, bare `/cook` synthesizes a startup brief from recent ordinary-chat discussion and then waits for **Start** or **Cancel**.",
25
+ "| No workflow yet | `/cook` synthesizes a startup brief from recent discussion and shows **Start** / **Cancel**.",
26
+ "| Previous workflow is `done` | `/cook` synthesizes the next implementation round from recent discussion behind **Start** / **Cancel**.",
27
+ "any pre-`/cook` preview or capsule is advisory only and never writes canonical workflow state by itself",
28
28
  ],
29
29
  "CHANGELOG.md": [
30
- "made explicit primary-agent `/cook` handoff the preferred startup-intake path by teaching ordinary-chat handoff turns to emit a structured `cook_handoff` capsule and letting `/cook` prefer that capsule over broad context re-inference when it is fresh, valid, and implementation-startable",
31
- "tightened implementation-ready explicit handoffs so the structured capsule must already carry a bounded `first_slice_goal`, repo-change-oriented acceptance, `implementation_surfaces`, `verification_commands`, and `why_this_slice_first` before `/cook` will start workflow from it",
32
- "kept the pre-`/cook` handoff capsule as advisory startup intake only, not canonical `.agent/**` workflow state, while still using context-derived startup as the fallback only when no fresh explicit handoff is blocking startup",
33
- "kept context-derived startup as a fallback only when there is no fresh explicit handoff blocking startup, so stale or invalidated capsules can still fall back to recent discussion while fresh non-startable handoffs fail closed instead of silently rewriting canonical state",
34
- "made finished-workflow suppression stay a safety layer instead of a replacement mission when a fresh explicit `/cook` handoff exists, and blocked negative rejection/suppression text from becoming a Startable startup mission",
30
+ "moving default startup and done-workflow next-round synthesis to bare `/cook` from recent ordinary-chat discussion behind the existing **Start** / **Cancel** approval gate",
31
+ "kept pre-`/cook` previews or `cook_handoff` capsules opt-in only, non-canonical, and advisory until the user explicitly runs `/cook`; bare `/cook` no longer depends on a default prebuilt capsule for new-workflow startup",
32
+ "updated public parity and packaged release verification so README/help/changelog/release-check all describe and gate the shipped mixed model truthfully",
35
33
  ],
36
34
  "extensions/completion/prompt-surfaces.ts": [
37
- '"/cook is the only explicit entrypoint into long-running completion workflow."',
38
- '"When you judge that the task has matured into completion-workflow scope',
39
- '"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."',
40
- '"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."',
41
- '"The capsule is startup intake for /cook only: do not present it as canonical .agent state',
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
+ '"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."',
37
+ '"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',
38
+ '"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."',
39
+ ],
40
+ "extensions/completion/index.ts": [
41
+ '"/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."',
42
+ '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"',
42
43
  ],
43
44
  }
44
45
 
45
46
  forbidden = {
46
47
  "README.md": [
47
- "`/cook <hint>`",
48
- "Natural-language routing is optional and shipped in two modes",
49
- "PI_COMPLETION_TRIGGER_MODE",
50
- "workflow-aware router",
51
- "Send as normal chat",
52
- "bash ./scripts/cook-trigger-routing-test.sh",
48
+ "wait for a fresh primary-agent handoff, then run `/cook`",
49
+ "That handoff should include an explicit structured `/cook` capsule in the assistant reply once the first slice is implementation-ready, so `/cook` can confirm the already-formed mission instead of re-deriving it from broad ambient context.",
50
+ "- startup and next-round entry stay confirm-first and require a fresh valid explicit primary-agent handoff",
51
+ "`/cook` first looks for a fresh explicit primary-agent handoff capsule from recent ordinary-chat discussion.",
52
+ "Without one, `/cook` fails closed instead of deriving the next round from recent discussion.",
53
+ ],
54
+ "CHANGELOG.md": [
55
+ "kept bare `/cook` startup and done-workflow next-round entry fail-closed on missing or non-startable explicit handoffs, while active workflows still resume from canonical `.agent/**` state unless a fresh explicit handoff proposes replacement",
53
56
  ],
54
- "CHANGELOG.md": ["compatibility" + " shim"],
55
57
  "extensions/completion/index.ts": [
56
- 'description: "/cook workflow: start, continue, refocus, or start the next round from an explicit /cook command"',
57
- '"/cook failed closed because recent discussion did not produce a clear execution-ready Mission/Scope/Constraints/Acceptance proposal for concrete repo changes. Clarify the concrete repo changes in the main chat and rerun /cook."',
58
- 'handleCookNaturalLanguageTrigger',
58
+ 'description: "/cook workflow: derive a 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"',
59
+ '"/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."',
59
60
  ],
60
61
  }
61
62
 
@@ -63,13 +64,13 @@ for path, needles in checks.items():
63
64
  text = Path(path).read_text()
64
65
  for needle in needles:
65
66
  if needle not in text:
66
- raise SystemExit(f"[release-check] missing expected /cook parity text in {path}: {needle}")
67
+ raise SystemExit(f"[release-check] missing expected /cook mixed-model parity text in {path}: {needle}")
67
68
 
68
69
  for path, needles in forbidden.items():
69
70
  text = Path(path).read_text()
70
71
  for needle in needles:
71
72
  if needle in text:
72
- raise SystemExit(f"[release-check] found stale compatibility wording in {path}: {needle}")
73
+ raise SystemExit(f"[release-check] found stale /cook explicit-handoff-only text in {path}: {needle}")
73
74
  PY
74
75
 
75
76
  npm run smoke-test
@@ -82,6 +83,28 @@ npm run observability-status-test
82
83
  bash ./scripts/legacy-cleanup-test.sh
83
84
  npm run evaluator-calibration-test
84
85
  npm run rubric-contract-test
85
- npm pack --dry-run >/dev/null
86
+
87
+ echo "[release-check] verifying packaged .agent contract files in npm pack output"
88
+ PACK_JSON="$(npm pack --dry-run --json)"
89
+ python3 - "$PACK_JSON" <<'PY'
90
+ import json
91
+ import sys
92
+
93
+ required = {
94
+ '.agent/README.md',
95
+ '.agent/mission.md',
96
+ '.agent/profile.json',
97
+ '.agent/verify_completion_stop.sh',
98
+ '.agent/verify_completion_control_plane.sh',
99
+ }
100
+
101
+ payload = json.loads(sys.argv[1])
102
+ if not isinstance(payload, list) or not payload:
103
+ raise SystemExit('[release-check] npm pack --dry-run --json returned no package payload')
104
+ files = {item.get('path') for item in payload[0].get('files', []) if isinstance(item, dict)}
105
+ missing = sorted(required - files)
106
+ if missing:
107
+ raise SystemExit(f"[release-check] npm pack --dry-run is missing tracked .agent contract files: {', '.join(missing)}")
108
+ PY
86
109
 
87
110
  echo "release check passed"
@@ -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
  ROOT="$TMPDIR/repo"
51
94
  KICKOFF_PROMPT="$TMPDIR/kickoff-prompt.txt"
52
95
  RESUME_PROMPT="$TMPDIR/resume-prompt.txt"
@@ -60,7 +103,8 @@ INLINE_REJECTION_ROUTING_SNAPSHOT="$TMPDIR/inline-arg-routing.json"
60
103
  INLINE_REJECTION_PROPOSAL_SNAPSHOT="$TMPDIR/inline-arg-proposal.json"
61
104
  INLINE_REJECTION_CHOOSER_SNAPSHOT="$TMPDIR/inline-arg-chooser.json"
62
105
  BOOTSTRAP_SESSION="$TMPDIR/session-smoke-bootstrap.jsonl"
63
- BOOTSTRAP_DISCUSSION=$'Mission: Exercise smoke-test bootstrap.\nScope:\n- Materialize the canonical completion control-plane files.\n- Keep the smoke test on supported /cook startup behavior.\nConstraints:\n- Keep startup proposal confirmation approval-only.\nAcceptance:\n- Scaffold canonical files and kickoff prompts for the smoke fixture.'
106
+ BOOTSTRAP_DISCUSSION=$'Mission: Exercise smoke-test bootstrap.\nScope:\n- Materialize the canonical completion control-plane files.\n- Keep the smoke test on supported /cook startup behavior.\nConstraints:\n- Keep startup proposal confirmation approval-only.\nAcceptance:\n- Scaffold .agent/profile.json, .agent/state.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json for the smoke fixture.\n- Keep scripts/smoke-test.sh and kickoff-prompt coverage truthful for packaged bootstrap.'
107
+ BOOTSTRAP_PROPOSAL="$TMPDIR/bootstrap-proposal.json"
64
108
 
65
109
  mkdir -p "$ROOT"
66
110
  cd "$ROOT"
@@ -94,6 +138,7 @@ write_session "$BOOTSTRAP_SESSION" "$ROOT" "$BOOTSTRAP_DISCUSSION"
94
138
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
95
139
  PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
96
140
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
141
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$BOOTSTRAP_PROPOSAL" \
97
142
  PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$KICKOFF_PROMPT" \
98
143
  pi --session "$BOOTSTRAP_SESSION" -e "$PKG_ROOT" -p "/cook" \
99
144
  >"$TMPDIR/pi-completion-smoke-bootstrap.out" 2>"$TMPDIR/pi-completion-smoke-bootstrap.err"
@@ -106,7 +151,7 @@ git ls-files --error-unmatch .agent/README.md .agent/mission.md .agent/profile.j
106
151
  bash .agent/verify_completion_control_plane.sh >/dev/null
107
152
  bash .agent/verify_completion_stop.sh >/dev/null
108
153
 
109
- python3 - "$KICKOFF_PROMPT" <<'PY'
154
+ python3 - "$KICKOFF_PROMPT" "$BOOTSTRAP_PROPOSAL" <<'PY'
110
155
  import json
111
156
  import sys
112
157
  from pathlib import Path
@@ -120,6 +165,7 @@ plan = json.loads(Path('.agent/plan.json').read_text())
120
165
  active = json.loads(Path('.agent/active-slice.json').read_text())
121
166
  evidence = json.loads(Path('.agent/verification-evidence.json').read_text())
122
167
  kickoff = Path(sys.argv[1]).read_text()
168
+ proposal = json.loads(Path(sys.argv[2]).read_text())
123
169
 
124
170
  assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after bootstrap'
125
171
  assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after bootstrap'
@@ -133,11 +179,18 @@ assert active['implementation_surfaces'] == [], 'active-slice.json should scaffo
133
179
  assert active['verification_commands'] == [], 'active-slice.json should scaffold empty verification_commands'
134
180
  brief = state['advisory_startup_brief']
135
181
  assert brief['kind'] == 'startup_brief', 'state.json should preserve the confirmed startup brief as advisory intake'
182
+ assert brief['source'] == 'recent_discussion', 'smoke bootstrap should record recent-discussion synthesis in advisory intake'
136
183
  assert brief['mission'] == state['mission_anchor'], 'advisory startup brief mission should match the canonical mission anchor after bootstrap'
137
184
  assert brief['scope'] == ['Materialize the canonical completion control-plane files.', 'Keep the smoke test on supported /cook startup behavior.'], 'advisory startup brief should preserve scope items'
138
185
  assert brief['constraints'] == ['Keep startup proposal confirmation approval-only.'], 'advisory startup brief should preserve constraints'
139
- assert brief['acceptance'] == ['Scaffold canonical files and kickoff prompts for the smoke fixture.'], 'advisory startup brief should preserve acceptance'
140
- assert brief['notes'] == ['No additional operator notes were derived from recent discussion.'], 'advisory startup brief should still preserve operator-note structure when no explicit notes exist'
186
+ assert brief['acceptance'] == [
187
+ 'Scaffold .agent/profile.json, .agent/state.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json for the smoke fixture.',
188
+ 'Keep scripts/smoke-test.sh and kickoff-prompt coverage truthful for packaged bootstrap.'
189
+ ], 'advisory startup brief should preserve acceptance'
190
+ assert brief['risks'] == [], 'recent-discussion smoke bootstrap should not invent handoff-only risks'
191
+ assert brief['notes'] == ['No additional operator notes were derived from recent discussion.'], 'recent-discussion smoke bootstrap should keep advisory notes non-canonical'
192
+ assert proposal['mission'] == state['mission_anchor'], 'recent-discussion smoke bootstrap should persist the synthesized mission in the proposal snapshot'
193
+ assert proposal['source'] == 'session', 'recent-discussion smoke bootstrap should snapshot the structured-session proposal source'
141
194
  assert evidence['artifact_type'] == 'completion-verification-evidence', 'verification-evidence.json artifact_type mismatch after bootstrap'
142
195
  assert evidence['subject_type'] == 'none', 'verification-evidence.json should scaffold idle subject_type'
143
196
  assert evidence['verification_commands'] == [], 'verification-evidence.json should scaffold empty verification_commands'
@@ -169,11 +222,17 @@ assert not reminder.exists(), 'ordinary non-/cook turn should not inject complet
169
222
  assert handoff.exists(), 'ordinary non-/cook turn should inject the /cook handoff boundary reminder'
170
223
  handoff_text = handoff.read_text()
171
224
  assert '/cook is the only explicit entrypoint into long-running completion workflow.' in handoff_text, 'ordinary handoff reminder should preserve the explicit /cook workflow boundary'
172
- assert 'stop short of long-running implementation and tell the user to run /cook.' in handoff_text, 'ordinary handoff reminder should require primary-agent handoff before implementation'
173
- assert '```cook_handoff ... ``` JSON' in handoff_text, 'ordinary handoff reminder should require the explicit structured /cook handoff capsule'
174
- assert 'implementation_workflow_handoff' in handoff_text, 'ordinary handoff reminder should require the implementation-ready handoff kind'
175
- assert 'first_slice_goal, first_slice_non_goals, implementation_surfaces, verification_commands, why_this_slice_first' in handoff_text, 'ordinary handoff reminder should require first-slice startability fields'
176
- assert 'The capsule is startup intake for /cook only' in handoff_text, 'ordinary handoff reminder should keep the capsule non-canonical'
225
+ assert 'do not begin long-running product implementation in ordinary chat' in handoff_text, 'ordinary handoff reminder should block workflow-level implementation before /cook'
226
+ assert 'recommend /cook as the workflow boundary while keeping the conversation in ordinary chat until the user explicitly runs /cook.' in handoff_text, 'ordinary handoff reminder should keep pre-/cook discussion advisory-first'
227
+ assert 'continue that ordinary-chat discussion normally instead of switching into a handoff-only refusal mode' in handoff_text, 'ordinary handoff reminder should avoid hard refusal mode before explicit /cook'
228
+ assert 'do not emit a structured preview or cook_handoff capsule in ordinary chat once the task is concrete enough' in handoff_text, 'ordinary handoff reminder should keep pre-/cook capsules opt-in instead of default'
229
+ assert 'bare /cook will synthesize a startup brief from recent ordinary-chat discussion' in handoff_text, 'ordinary handoff reminder should describe /cook-time startup synthesis truthfully'
230
+ assert 'If the user explicitly asks for a /cook preview or capsule before running /cook' in handoff_text, 'ordinary handoff reminder should keep previews opt-in'
231
+ assert 'implementation_workflow_handoff' in handoff_text, 'ordinary handoff reminder should preserve the explicit preview handoff kind'
232
+ assert 'first_slice_goal, first_slice_non_goals, implementation_surfaces, verification_commands, why_this_slice_first' in handoff_text, 'ordinary handoff reminder should preserve first-slice preview fields when explicitly requested'
233
+ assert 'Any preview capsule is startup intake for /cook only' in handoff_text, 'ordinary handoff reminder should keep any preview non-canonical'
234
+ assert 'resume from canonical .agent state' in handoff_text, 'ordinary handoff reminder should preserve active-workflow canonical resume wording'
235
+ assert 'fresh valid explicit primary-agent handoff capsule from recent ordinary-chat discussion' not in handoff_text, 'ordinary handoff reminder should no longer describe explicit capsules as the default startup path'
177
236
  assert not auto_resume.exists(), 'ordinary non-/cook turn should not queue auto-resume before /cook activation'
178
237
  assert 'Skipped completion workflow auto-resume prompt (test mode)' not in output, 'ordinary non-/cook turn should not attempt auto-resume'
179
238
  PY
@@ -201,11 +260,11 @@ assert 'Canonical routing profile:' in resume, 'resume prompt should expose cano
201
260
  assert f'- task_type: {expected_task_type}' in resume, 'resume prompt missing canonical task_type'
202
261
  assert f'- evaluation_profile: {expected_eval_profile}' in resume, 'resume prompt missing canonical evaluation_profile'
203
262
  assert routing['mode'] == 'bare', 'active bare /cook should snapshot bare routing mode'
204
- assert routing['action'] == 'unclear', 'no-discussion active bare /cook should classify as unclear'
205
- assert routing['reason'] == 'missing_proposal', 'no-discussion active bare /cook should fail closed because no replacement proposal was derived'
206
- assert routing['currentMissionAnchor'] == state['mission_anchor'], 'unclear routing snapshot should keep the current mission anchor'
207
- assert routing['proposedMissionAnchor'] is None, 'unclear no-discussion routing should not propose a replacement mission'
208
- assert not chooser_path.exists(), 'unclear active bare /cook should fall back to resume without opening the chooser'
263
+ assert routing['action'] == 'continue', 'no-discussion active bare /cook should resume from canonical state without a fresh explicit handoff'
264
+ assert routing['reason'] == 'missing_explicit_handoff', 'no-discussion active bare /cook should explain that resume happened because no fresh explicit handoff existed'
265
+ assert routing['currentMissionAnchor'] == state['mission_anchor'], 'resume routing snapshot should keep the current mission anchor'
266
+ assert routing['proposedMissionAnchor'] is None, 'no-discussion active bare /cook should not propose a replacement mission'
267
+ assert not chooser_path.exists(), 'active bare /cook resume should not open the chooser without a fresh explicit handoff'
209
268
  PY
210
269
 
211
270
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \