@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.
- package/.agent/README.md +28 -0
- package/.agent/mission.md +8 -0
- package/.agent/profile.json +13 -0
- package/.agent/verify_completion_control_plane.sh +203 -0
- package/.agent/verify_completion_stop.sh +20 -0
- package/CHANGELOG.md +22 -2
- package/README.md +27 -18
- package/extensions/completion/driver.ts +77 -35
- package/extensions/completion/index.ts +78 -53
- package/extensions/completion/prompt-surfaces.ts +10 -5
- package/extensions/completion/proposal.ts +134 -28
- package/package.json +6 -1
- package/scripts/active-slice-contract-test.sh +93 -2
- package/scripts/canonical-evidence-artifact-test.sh +93 -2
- package/scripts/context-proposal-test.sh +751 -741
- package/scripts/refocus-test.sh +196 -28
- package/scripts/release-check.sh +51 -22
- package/scripts/smoke-test.sh +115 -10
- package/skills/cook-handoff-boundary/SKILL.md +20 -7
package/scripts/refocus-test.sh
CHANGED
|
@@ -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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
|
192
|
-
assert routing['reason'] == '
|
|
193
|
-
assert routing['proposedMissionAnchor'] == new_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
|
-
#
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
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', '
|
|
247
|
-
assert routing['reason'] == '
|
|
248
|
-
assert routing['currentMissionAnchor'] == updated_mission, '
|
|
249
|
-
assert routing['proposedMissionAnchor'] == 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
|
|
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
|
-
|
|
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
|
|
290
|
-
assert routing['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
|
-
|
|
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 = '
|
|
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
|
|
331
|
-
assert routing['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'
|
package/scripts/release-check.sh
CHANGED
|
@@ -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
|
-
"
|
|
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
|
-
"
|
|
23
|
-
"
|
|
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
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
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
|
-
'"
|
|
33
|
-
'"
|
|
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
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
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
|
|
50
|
-
'"/cook failed closed because recent discussion did not produce a clear execution-ready Mission/Scope/Constraints/Acceptance
|
|
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
|
|
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
|
-
|
|
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"
|
package/scripts/smoke-test.sh
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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'] == [
|
|
140
|
-
|
|
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 '
|
|
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'] == '
|
|
203
|
-
assert routing['reason'] == '
|
|
204
|
-
assert routing['currentMissionAnchor'] == state['mission_anchor'], '
|
|
205
|
-
assert routing['proposedMissionAnchor'] is None, '
|
|
206
|
-
assert not chooser_path.exists(), '
|
|
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 \
|