@linimin/pi-letscook 0.1.57 → 0.1.59

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'
@@ -3,6 +3,7 @@ set -euo pipefail
3
3
 
4
4
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
5
  cd "$ROOT"
6
+ export PI_COMPLETION_RUNNING_RELEASE_CHECK=1
6
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
9
  bash .agent/verify_completion_control_plane.sh
@@ -10,45 +11,51 @@ git ls-files --error-unmatch .agent/README.md .agent/mission.md .agent/profile.j
10
11
 
11
12
  echo "[release-check] verifying public /cook parity and explicit-entry docs/help"
12
13
  python3 - <<'PY'
13
- import re
14
14
  from pathlib import Path
15
15
 
16
16
  checks = {
17
17
  "README.md": [
18
18
  "`/cook` is the explicit workflow boundary for starting, continuing, refocusing, or beginning the next round of long-running repo work.",
19
19
  "Only explicit `/cook` enters the workflow. Ordinary prompts stay in the main chat and go straight to the primary agent.",
20
- "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.",
21
- "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.",
22
- "`/cook` first looks for a fresh explicit primary-agent handoff capsule.",
23
- "The pre-`/cook` handoff capsule itself is not canonical workflow state. It is only startup intake for `/cook`.",
20
+ "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.",
21
+ "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.",
22
+ "The capsule is still advisory startup intake, not canonical workflow state, and new-workflow or next-round entry only proceeds when it already names the first bounded slice, repo-change-oriented acceptance, implementation surfaces, and verification commands.",
23
+ "- startup and next-round entry stay confirm-first and require a fresh valid explicit primary-agent handoff",
24
+ "- active workflows resume from canonical `.agent/**` state unless a fresh valid explicit handoff proposes a replacement",
25
+ "`/cook` first looks for a fresh explicit primary-agent handoff capsule from recent ordinary-chat discussion. New-workflow entry and done-workflow next-round entry start only when that capsule is fresh, valid, and implementation-startable; otherwise `/cook` fails closed instead of deriving startup from recent discussion.",
26
+ "When a workflow is already active and no fresh valid explicit handoff is present, `/cook` resumes from canonical `.agent/**` state instead of deriving replacement startup from recent discussion.",
27
+ "Without one, `/cook` fails closed instead of deriving the next round from recent discussion.",
28
+ "when a fresh explicit handoff suggests replacing an active workflow, `/cook` shows a chooser before any canonical state rewrite",
24
29
  ],
25
30
  "CHANGELOG.md": [
26
- "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 and valid",
27
- "kept context-derived startup as a fallback only, so stale, drifted, or non-startable handoff capsules still fail closed or fall back to recent discussion instead of silently rewriting canonical state",
28
- "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",
31
+ "relaxed the pre-`/cook` ordinary-chat boundary so the primary agent can keep discussing and refining requirements before explicit `/cook` instead of switching into a hard handoff-only refusal mode as soon as workflow-worthiness is detected",
32
+ "made fresh explicit `/cook` handoffs reusable from recent ordinary-chat discussion instead of requiring the immediately preceding turn, so Cancel can return users cleanly to ordinary discussion before they rerun `/cook`",
33
+ "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",
29
34
  ],
30
35
  "extensions/completion/prompt-surfaces.ts": [
31
36
  '"/cook is the only explicit entrypoint into long-running completion workflow."',
32
- '"When you judge that the task has matured into completion-workflow scope',
33
- '"Also append one exact fenced block in the same assistant reply using ```cook_handoff ... ``` JSON',
37
+ '"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."',
38
+ '"When handing off, explain that /cook can start a new workflow or next round only from a fresh valid explicit primary-agent handoff capsule from recent ordinary-chat discussion; otherwise it fails closed, while already-active workflows resume from canonical .agent state unless a fresh valid explicit handoff proposes replacement."',
34
39
  '"The capsule is startup intake for /cook only: do not present it as canonical .agent state',
35
40
  ],
41
+ "extensions/completion/index.ts": [
42
+ '"/cook failed closed because new-workflow startup now requires a fresh valid explicit primary-agent handoff from recent ordinary-chat discussion; recent discussion alone no longer starts a workflow. Ask the primary agent to hand off explicitly in the main chat, then rerun /cook."',
43
+ 'description: "/cook workflow: start a new workflow or next round only from a fresh recent explicit primary-agent handoff, resume the current workflow from canonical state, or confirm an explicit replacement from the explicit /cook command"',
44
+ ],
36
45
  }
37
46
 
38
47
  forbidden = {
39
48
  "README.md": [
40
- "`/cook <hint>`",
41
- "Natural-language routing is optional and shipped in two modes",
42
- "PI_COMPLETION_TRIGGER_MODE",
43
- "workflow-aware router",
44
- "Send as normal chat",
45
- "bash ./scripts/cook-trigger-routing-test.sh",
49
+ "Start a new workflow from recent discussion:",
50
+ "`/cook` falls back to deriving a startup brief from recent discussion only when no fresh explicit handoff is blocking startup",
51
+ "Without a fresh explicit handoff blocking startup, `/cook` can fall back to recent discussion.",
52
+ ],
53
+ "extensions/completion/prompt-surfaces.ts": [
54
+ '"When handing off, explain that /cook will first look for a fresh explicit primary-agent handoff capsule and otherwise fall back to recent discussion."',
46
55
  ],
47
- "CHANGELOG.md": ["compatibility" + " shim"],
48
56
  "extensions/completion/index.ts": [
49
- 'description: "/cook workflow: start, continue, refocus, or start the next round from an explicit /cook command"',
50
- '"/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."',
51
- 'handleCookNaturalLanguageTrigger',
57
+ 'description: "/cook workflow: derive a startup brief from recent discussion, then start, continue, refocus, or start the next round from the explicit /cook command"',
58
+ '"/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."',
52
59
  ],
53
60
  }
54
61
 
@@ -62,7 +69,7 @@ for path, needles in forbidden.items():
62
69
  text = Path(path).read_text()
63
70
  for needle in needles:
64
71
  if needle in text:
65
- raise SystemExit(f"[release-check] found stale compatibility wording in {path}: {needle}")
72
+ raise SystemExit(f"[release-check] found stale /cook parity text in {path}: {needle}")
66
73
  PY
67
74
 
68
75
  npm run smoke-test
@@ -75,6 +82,28 @@ npm run observability-status-test
75
82
  bash ./scripts/legacy-cleanup-test.sh
76
83
  npm run evaluator-calibration-test
77
84
  npm run rubric-contract-test
78
- npm pack --dry-run >/dev/null
85
+
86
+ echo "[release-check] verifying packaged .agent contract files in npm pack output"
87
+ PACK_JSON="$(npm pack --dry-run --json)"
88
+ python3 - "$PACK_JSON" <<'PY'
89
+ import json
90
+ import sys
91
+
92
+ required = {
93
+ '.agent/README.md',
94
+ '.agent/mission.md',
95
+ '.agent/profile.json',
96
+ '.agent/verify_completion_stop.sh',
97
+ '.agent/verify_completion_control_plane.sh',
98
+ }
99
+
100
+ payload = json.loads(sys.argv[1])
101
+ if not isinstance(payload, list) or not payload:
102
+ raise SystemExit('[release-check] npm pack --dry-run --json returned no package payload')
103
+ files = {item.get('path') for item in payload[0].get('files', []) if isinstance(item, dict)}
104
+ missing = sorted(required - files)
105
+ if missing:
106
+ raise SystemExit(f"[release-check] npm pack --dry-run is missing tracked .agent contract files: {', '.join(missing)}")
107
+ PY
79
108
 
80
109
  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,55 @@ 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_MESSAGES="$(python3 - <<'PY'
107
+ import json
108
+ capsule = {
109
+ "kind": "cook_handoff",
110
+ "source": "primary_agent",
111
+ "captured_at": "2026-01-01T00:00:02.000Z",
112
+ "source_turn_id": "m0002",
113
+ "mission": "Exercise smoke-test bootstrap.",
114
+ "scope": [
115
+ "Materialize the canonical completion control-plane files.",
116
+ "Keep the smoke test on supported /cook startup behavior."
117
+ ],
118
+ "constraints": [
119
+ "Keep startup proposal confirmation approval-only."
120
+ ],
121
+ "acceptance": [
122
+ "Scaffold .agent/profile.json, .agent/state.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json for the smoke fixture.",
123
+ "Keep scripts/smoke-test.sh and kickoff-prompt coverage truthful for packaged bootstrap."
124
+ ],
125
+ "risks": [
126
+ "Smoke-test bootstrap should stay anchored to the fresh explicit handoff."
127
+ ],
128
+ "notes": [
129
+ "Keep the smoke fixture aligned with the shipped explicit-handoff-only startup contract."
130
+ ],
131
+ "handoff_kind": "implementation_workflow_handoff",
132
+ "first_slice_goal": "Scaffold canonical completion files and verify the packaged startup contract.",
133
+ "first_slice_non_goals": [
134
+ "Do not broaden the smoke fixture beyond the packaged startup surfaces."
135
+ ],
136
+ "implementation_surfaces": [
137
+ ".agent/README.md",
138
+ "scripts/smoke-test.sh"
139
+ ],
140
+ "verification_commands": [
141
+ "npm run smoke-test"
142
+ ],
143
+ "why_this_slice_first": "The packaged explicit-handoff startup path must work before later workflow verification can run.",
144
+ "task_type": "completion-workflow",
145
+ "evaluation_profile": "completion-rubric-v1",
146
+ "why_cook_now": "The startup handoff is concrete enough to bootstrap canonical workflow files."
147
+ }
148
+ messages = [
149
+ {"role": "user", "content": "Please prepare the packaged smoke-test bootstrap path and tell me when it is ready for /cook."},
150
+ {"role": "assistant", "content": "This bootstrap path is ready for /cook. Run /cook to confirm the startup brief.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
151
+ ]
152
+ print(json.dumps(messages, ensure_ascii=False))
153
+ PY
154
+ )"
64
155
 
65
156
  mkdir -p "$ROOT"
66
157
  cd "$ROOT"
@@ -89,7 +180,7 @@ assert not chooser.exists(), 'startup /cook inline-args rejection should not ope
89
180
  assert '/cook no longer accepts inline arguments.' in output, 'startup /cook inline-args rejection should explain the bare-only entry contract'
90
181
  PY
91
182
 
92
- write_session "$BOOTSTRAP_SESSION" "$ROOT" "$BOOTSTRAP_DISCUSSION"
183
+ write_session_messages "$BOOTSTRAP_SESSION" "$ROOT" "$BOOTSTRAP_MESSAGES"
93
184
 
94
185
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
95
186
  PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
@@ -133,11 +224,17 @@ assert active['implementation_surfaces'] == [], 'active-slice.json should scaffo
133
224
  assert active['verification_commands'] == [], 'active-slice.json should scaffold empty verification_commands'
134
225
  brief = state['advisory_startup_brief']
135
226
  assert brief['kind'] == 'startup_brief', 'state.json should preserve the confirmed startup brief as advisory intake'
227
+ assert brief['source'] == 'primary_agent_handoff', 'smoke bootstrap should record the explicit handoff source in advisory intake'
136
228
  assert brief['mission'] == state['mission_anchor'], 'advisory startup brief mission should match the canonical mission anchor after bootstrap'
137
229
  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
230
  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'
231
+ assert brief['acceptance'] == [
232
+ 'Scaffold .agent/profile.json, .agent/state.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json for the smoke fixture.',
233
+ 'Keep scripts/smoke-test.sh and kickoff-prompt coverage truthful for packaged bootstrap.'
234
+ ], 'advisory startup brief should preserve acceptance'
235
+ assert brief['risks'] == ['Smoke-test bootstrap should stay anchored to the fresh explicit handoff.'], 'advisory startup brief should preserve handoff risks'
236
+ assert 'First slice goal: Scaffold canonical completion files and verify the packaged startup contract.' in brief['notes'], 'advisory startup brief should preserve the first_slice_goal in notes'
237
+ assert 'Verification commands: npm run smoke-test' in brief['notes'], 'advisory startup brief should preserve verification_commands in notes'
141
238
  assert evidence['artifact_type'] == 'completion-verification-evidence', 'verification-evidence.json artifact_type mismatch after bootstrap'
142
239
  assert evidence['subject_type'] == 'none', 'verification-evidence.json should scaffold idle subject_type'
143
240
  assert evidence['verification_commands'] == [], 'verification-evidence.json should scaffold empty verification_commands'
@@ -169,9 +266,17 @@ assert not reminder.exists(), 'ordinary non-/cook turn should not inject complet
169
266
  assert handoff.exists(), 'ordinary non-/cook turn should inject the /cook handoff boundary reminder'
170
267
  handoff_text = handoff.read_text()
171
268
  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'
269
+ assert 'do not begin long-running product implementation in ordinary chat' in handoff_text, 'ordinary handoff reminder should block workflow-level implementation before /cook'
270
+ 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'
271
+ 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'
173
272
  assert '```cook_handoff ... ``` JSON' in handoff_text, 'ordinary handoff reminder should require the explicit structured /cook handoff capsule'
273
+ assert 'implementation_workflow_handoff' in handoff_text, 'ordinary handoff reminder should require the implementation-ready handoff kind'
274
+ 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'
174
275
  assert 'The capsule is startup intake for /cook only' in handoff_text, 'ordinary handoff reminder should keep the capsule non-canonical'
276
+ assert 'fresh valid explicit primary-agent handoff capsule from recent ordinary-chat discussion' in handoff_text, 'ordinary handoff reminder should describe recent explicit-handoff startup truthfully'
277
+ assert 'fails closed' in handoff_text, 'ordinary handoff reminder should describe fail-closed startup when no fresh valid handoff exists'
278
+ assert 'resume from canonical .agent state' in handoff_text, 'ordinary handoff reminder should preserve active-workflow canonical resume wording'
279
+ assert 'fall back to recent discussion' not in handoff_text, 'ordinary handoff reminder should no longer promise recent-discussion startup fallback'
175
280
  assert not auto_resume.exists(), 'ordinary non-/cook turn should not queue auto-resume before /cook activation'
176
281
  assert 'Skipped completion workflow auto-resume prompt (test mode)' not in output, 'ordinary non-/cook turn should not attempt auto-resume'
177
282
  PY
@@ -199,11 +304,11 @@ assert 'Canonical routing profile:' in resume, 'resume prompt should expose cano
199
304
  assert f'- task_type: {expected_task_type}' in resume, 'resume prompt missing canonical task_type'
200
305
  assert f'- evaluation_profile: {expected_eval_profile}' in resume, 'resume prompt missing canonical evaluation_profile'
201
306
  assert routing['mode'] == 'bare', 'active bare /cook should snapshot bare routing mode'
202
- assert routing['action'] == 'unclear', 'no-discussion active bare /cook should classify as unclear'
203
- assert routing['reason'] == 'missing_proposal', 'no-discussion active bare /cook should fail closed because no replacement proposal was derived'
204
- assert routing['currentMissionAnchor'] == state['mission_anchor'], 'unclear routing snapshot should keep the current mission anchor'
205
- assert routing['proposedMissionAnchor'] is None, 'unclear no-discussion routing should not propose a replacement mission'
206
- assert not chooser_path.exists(), 'unclear active bare /cook should fall back to resume without opening the chooser'
307
+ assert routing['action'] == 'continue', 'no-discussion active bare /cook should resume from canonical state without a fresh explicit handoff'
308
+ assert routing['reason'] == 'missing_explicit_handoff', 'no-discussion active bare /cook should explain that resume happened because no fresh explicit handoff existed'
309
+ assert routing['currentMissionAnchor'] == state['mission_anchor'], 'resume routing snapshot should keep the current mission anchor'
310
+ assert routing['proposedMissionAnchor'] is None, 'no-discussion active bare /cook should not propose a replacement mission'
311
+ assert not chooser_path.exists(), 'active bare /cook resume should not open the chooser without a fresh explicit handoff'
207
312
  PY
208
313
 
209
314
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \