@linimin/pi-letscook 0.1.36 → 0.1.37

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.
@@ -101,20 +101,102 @@ mkdir -p "$ROOT"
101
101
  cd "$ROOT"
102
102
  git init -q
103
103
 
104
- # No workflow yet: /cook with no goal should not bootstrap from discussion alone when analyst output is unavailable.
104
+ # No workflow yet: bare /cook should use strict structured discussion fallback when analyst output is unavailable.
105
105
  SESSION_ZERO="$TMPDIR/session-zero.jsonl"
106
106
  DISCUSSION_ZERO=$'Mission: Remove the completion status line while keeping the completion widget.\nScope:\n- Keep the non-running completion widget.\n- Suppress the widget while a completion role is active.\nConstraints:\n- Do not reintroduce any other completion status surface.\nAcceptance:\n- Update README to match the shipped behavior.\n- Keep observability regression coverage truthful.'
107
+ DISCUSSION_SNAPSHOT_ZERO="$TMPDIR/context-proposal-structured-fallback.json"
107
108
  write_session "$SESSION_ZERO" "$ROOT" "$DISCUSSION_ZERO"
108
109
 
109
110
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
110
111
  PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
112
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO" \
111
113
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
112
- pi --session "$SESSION_ZERO" -e "$PKG_ROOT" -p "/cook" >/tmp/pi-completion-context-proposal-no-analyst.out 2>/tmp/pi-completion-context-proposal-no-analyst.err
114
+ pi --session "$SESSION_ZERO" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-structured-fallback.out" 2>"$TMPDIR/pi-completion-context-proposal-structured-fallback.err"
113
115
 
114
- python3 - <<'PY'
116
+ python3 - "$DISCUSSION_SNAPSHOT_ZERO" <<'PY'
117
+ import json
118
+ import sys
119
+ from pathlib import Path
120
+
121
+ mission = 'Remove the completion status line while keeping the completion widget.'
122
+ expected_task_type = 'completion-workflow'
123
+ expected_eval_profile = 'completion-rubric-v1'
124
+ mission_text = Path('.agent/mission.md').read_text()
125
+ profile = json.loads(Path('.agent/profile.json').read_text())
126
+ state = json.loads(Path('.agent/state.json').read_text())
127
+ plan = json.loads(Path('.agent/plan.json').read_text())
128
+ active = json.loads(Path('.agent/active-slice.json').read_text())
129
+ proposal = json.loads(Path(sys.argv[1]).read_text())
130
+
131
+ assert Path('.agent').exists(), 'strict structured fallback should only create canonical state after Start is accepted'
132
+ assert mission in mission_text, '.agent/mission.md did not record the structured-fallback mission anchor'
133
+ assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after structured-fallback bootstrap'
134
+ assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after structured-fallback bootstrap'
135
+ assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after structured-fallback bootstrap'
136
+ assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after structured-fallback bootstrap'
137
+ assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after structured-fallback bootstrap'
138
+ assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after structured-fallback bootstrap'
139
+ assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after structured-fallback bootstrap'
140
+ assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after structured-fallback bootstrap'
141
+ assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after structured-fallback bootstrap'
142
+ assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after structured-fallback bootstrap'
143
+ assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after structured-fallback bootstrap'
144
+ assert proposal['mission'] == mission, 'structured-fallback proposal snapshot should preserve the discussion mission anchor'
145
+ assert proposal['source'] == 'session', 'structured-fallback proposal snapshot should record the strict session fallback source'
146
+ assert proposal['scope'] == ['Keep the non-running completion widget.', 'Suppress the widget while a completion role is active.'], 'structured-fallback proposal snapshot should preserve discussion scope'
147
+ assert proposal['constraints'] == ['Do not reintroduce any other completion status surface.'], 'structured-fallback proposal snapshot should preserve discussion constraints'
148
+ assert proposal['acceptance'] == ['Update README to match the shipped behavior.', 'Keep observability regression coverage truthful.'], 'structured-fallback proposal snapshot should preserve discussion acceptance'
149
+ assert state['current_phase'] == 'reground', 'state.json current_phase should start at reground after structured-fallback bootstrap'
150
+ assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should start at completion-regrounder after structured-fallback bootstrap'
151
+ assert state['continuation_reason'].startswith('User started workflow via /cook:'), 'structured-fallback startup should record the accepted startup routing in continuation_reason'
152
+ assert 'task_type=completion-workflow' in state['continuation_reason'], 'structured-fallback startup should persist the selected task_type in continuation_reason'
153
+ assert 'evaluation_profile=completion-rubric-v1' in state['continuation_reason'], 'structured-fallback startup should persist the selected evaluation_profile in continuation_reason'
154
+ PY
155
+
156
+ rm -rf .agent
157
+
158
+ # No workflow yet: bare /cook should fail closed when a required structured section is missing and analyst output is unavailable.
159
+ SESSION_ZERO_MISSING="$TMPDIR/session-zero-missing-section.jsonl"
160
+ DISCUSSION_ZERO_MISSING=$'Mission: Remove the completion status line while keeping the completion widget.\nScope:\n- Keep the non-running completion widget.\n- Suppress the widget while a completion role is active.\nConstraints:\n- Do not reintroduce any other completion status surface.'
161
+ DISCUSSION_SNAPSHOT_ZERO_MISSING="$TMPDIR/context-proposal-missing-section.json"
162
+ write_session "$SESSION_ZERO_MISSING" "$ROOT" "$DISCUSSION_ZERO_MISSING"
163
+
164
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
165
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
166
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_MISSING" \
167
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
168
+ pi --session "$SESSION_ZERO_MISSING" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-missing-section.out" 2>"$TMPDIR/pi-completion-context-proposal-missing-section.err"
169
+
170
+ python3 - "$TMPDIR/pi-completion-context-proposal-missing-section.out" "$TMPDIR/pi-completion-context-proposal-missing-section.err" "$DISCUSSION_SNAPSHOT_ZERO_MISSING" <<'PY'
171
+ import sys
172
+ from pathlib import Path
173
+
174
+ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
175
+ snapshot = Path(sys.argv[3])
176
+ assert not Path('.agent').exists(), 'missing-section structured discussion should fail closed without writing canonical state'
177
+ assert not snapshot.exists(), 'missing-section structured discussion should not emit a proposal snapshot when bare /cook fails closed'
178
+ assert 'Bare /cook failed closed' in output, 'missing-section structured discussion should explain the fail-closed startup outcome'
179
+ assert 'Mission/Scope/Constraints/Acceptance' in output, 'missing-section structured discussion should explain the strict fallback requirement'
180
+ PY
181
+
182
+ # No workflow yet: bare /cook should fail closed on ambiguous structured discussion when analyst output is unavailable.
183
+ SESSION_ZERO_AMBIG="$TMPDIR/session-zero-ambiguous.jsonl"
184
+ DISCUSSION_ZERO_AMBIG=$'Mission: Remove the completion status line while keeping the completion widget.\nScope:\n- Keep the non-running completion widget.\nConstraints:\n- Do not reintroduce any other completion status surface.\nAcceptance:\n- Update README to match the shipped behavior.\nMission: Ship an unrelated widget overhaul.\nScope:\n- Replace the widget entirely.'
185
+ write_session "$SESSION_ZERO_AMBIG" "$ROOT" "$DISCUSSION_ZERO_AMBIG"
186
+
187
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
188
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
189
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
190
+ pi --session "$SESSION_ZERO_AMBIG" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-ambiguous.out" 2>"$TMPDIR/pi-completion-context-proposal-ambiguous.err"
191
+
192
+ python3 - "$TMPDIR/pi-completion-context-proposal-ambiguous.out" "$TMPDIR/pi-completion-context-proposal-ambiguous.err" <<'PY'
193
+ import sys
115
194
  from pathlib import Path
116
195
 
117
- assert not Path('.agent').exists(), '/cook should not bootstrap canonical state from discussion alone without analyst output'
196
+ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
197
+ assert not Path('.agent').exists(), 'ambiguous structured discussion should fail closed without writing canonical state'
198
+ assert 'Bare /cook failed closed' in output, 'ambiguous structured discussion should explain the fail-closed startup outcome'
199
+ assert 'Mission/Scope/Constraints/Acceptance' in output, 'ambiguous structured discussion should explain the strict fallback requirement'
118
200
  PY
119
201
 
120
202
  # No workflow yet: /cook with no goal should infer from recent discussion through analyst output.
@@ -173,21 +255,64 @@ assert 'evaluation_profile=completion-rubric-v1' in state['continuation_reason']
173
255
  assert 'Keep critique separate from the mission anchor so startup analysis does not rewrite the workflow goal.' in state['continuation_reason'], 'initial startup should persist the accepted critique outcome in continuation_reason'
174
256
  PY
175
257
 
176
- # Completed workflow: /cook with no goal should infer the next round from recent discussion through analyst output.
258
+ # Active workflow: bare /cook with matching structured discussion should classify as continue
259
+ # and resume the current workflow without opening the chooser or rewriting canonical state.
260
+ SESSION_ONE_CONTINUE="$TMPDIR/session-one-continue.jsonl"
261
+ DISCUSSION_ONE_CONTINUE=$'Mission: Remove the completion status line while keeping the completion widget.\nScope:\n- Keep the current mission focused on the non-running completion widget.\nConstraints:\n- Do not start a different workflow from this discussion.\nAcceptance:\n- Resume the current workflow from canonical state without rewriting it.'
262
+ CONTINUE_ROUTING_ONE="$TMPDIR/active-continue-routing.json"
263
+ CONTINUE_RESUME_PROMPT_ONE="$TMPDIR/active-continue-resume.txt"
264
+ CONTINUE_CHOOSER_ONE="$TMPDIR/unexpected-active-continue-chooser.json"
265
+ write_session "$SESSION_ONE_CONTINUE" "$ROOT" "$DISCUSSION_ONE_CONTINUE"
266
+
267
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
268
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$CONTINUE_ROUTING_ONE" \
269
+ PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$CONTINUE_RESUME_PROMPT_ONE" \
270
+ PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$CONTINUE_CHOOSER_ONE" \
271
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
272
+ pi --session "$SESSION_ONE_CONTINUE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-active-continue.out" 2>"$TMPDIR/pi-completion-context-proposal-active-continue.err"
273
+
274
+ python3 - "$CONTINUE_ROUTING_ONE" "$CONTINUE_RESUME_PROMPT_ONE" "$CONTINUE_CHOOSER_ONE" <<'PY'
275
+ import json
276
+ import sys
277
+ from pathlib import Path
278
+
279
+ mission = 'Remove the completion status line while keeping the completion widget.'
280
+ routing = json.loads(Path(sys.argv[1]).read_text())
281
+ resume = Path(sys.argv[2]).read_text()
282
+ chooser_path = Path(sys.argv[3])
283
+ state = json.loads(Path('.agent/state.json').read_text())
284
+ plan = json.loads(Path('.agent/plan.json').read_text())
285
+ active = json.loads(Path('.agent/active-slice.json').read_text())
286
+
287
+ assert routing['mode'] == 'bare', 'active bare /cook continue regression should snapshot bare routing mode'
288
+ assert routing['action'] == 'continue', 'matching structured discussion should classify active bare /cook as continue'
289
+ assert routing['reason'] == 'matching_mission', 'matching structured discussion should keep the current mission rather than refocus'
290
+ assert routing['currentMissionAnchor'] == mission, 'continue routing should preserve the current mission anchor'
291
+ assert routing['proposedMissionAnchor'] == mission, 'continue routing should keep the proposed mission anchored to the current mission'
292
+ assert 'Resume the completion workflow from canonical state.' in resume, 'active bare /cook continue should still use the canonical resume prompt'
293
+ assert not chooser_path.exists(), 'active bare /cook continue should not open the refocus chooser'
294
+ assert state['mission_anchor'] == mission, 'active bare /cook continue should keep state.json unchanged'
295
+ assert plan['mission_anchor'] == mission, 'active bare /cook continue should keep plan.json unchanged'
296
+ assert active['mission_anchor'] == mission, 'active bare /cook continue should keep active-slice.json unchanged'
297
+ PY
298
+
299
+ # Completed workflow: bare /cook should use the same strict structured fallback for the next workflow round when analyst output is unavailable.
177
300
  mark_done
178
301
 
179
302
  SESSION_TWO="$TMPDIR/session-two.jsonl"
180
303
  DISCUSSION_TWO=$'Mission: Ship the next workflow round for richer context-derived /cook startup.\nScope:\n- Start a new workflow round from recent discussion after the previous one is done.\n- Keep using canonical .agent state after confirmation.\nConstraints:\n- Do not resume the completed workflow when the new round is clearly different.\nAcceptance:\n- Reset canonical state back to reground for the new mission.\n- Preserve the tracked completion control-plane files.'
181
- ANALYST_OUTPUT_TWO='{"mission":"Ship the next workflow round for richer context-derived /cook startup.","scope":["Start a new workflow round from recent discussion after the previous one is done.","Keep using canonical .agent state after confirmation."],"constraints":["Do not resume the completed workflow when the new round is clearly different."],"acceptance":["Reset canonical state back to reground for the new mission.","Preserve the tracked completion control-plane files."],"confidence":0.93}'
304
+ DISCUSSION_SNAPSHOT_TWO="$TMPDIR/context-proposal-next-round-structured-fallback.json"
182
305
  write_session "$SESSION_TWO" "$ROOT" "$DISCUSSION_TWO"
183
306
 
184
307
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
185
- PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$ANALYST_OUTPUT_TWO" \
308
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
309
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_TWO" \
186
310
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
187
- pi --session "$SESSION_TWO" -e "$PKG_ROOT" -p "/cook" >/tmp/pi-completion-context-proposal-next-round.out 2>/tmp/pi-completion-context-proposal-next-round.err
311
+ pi --session "$SESSION_TWO" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-next-round.out" 2>"$TMPDIR/pi-completion-context-proposal-next-round.err"
188
312
 
189
- python3 - <<'PY'
313
+ python3 - "$DISCUSSION_SNAPSHOT_TWO" <<'PY'
190
314
  import json
315
+ import sys
191
316
  from pathlib import Path
192
317
 
193
318
  mission = 'Ship the next workflow round for richer context-derived /cook startup.'
@@ -198,6 +323,7 @@ profile = json.loads(Path('.agent/profile.json').read_text())
198
323
  state = json.loads(Path('.agent/state.json').read_text())
199
324
  plan = json.loads(Path('.agent/plan.json').read_text())
200
325
  active = json.loads(Path('.agent/active-slice.json').read_text())
326
+ proposal = json.loads(Path(sys.argv[1]).read_text())
201
327
 
202
328
  assert mission in mission_text, '.agent/mission.md did not update to the next-round context-derived mission anchor'
203
329
  assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after next-round startup'
@@ -211,6 +337,8 @@ assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluatio
211
337
  assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after starting the next workflow round'
212
338
  assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after starting the next workflow round'
213
339
  assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after starting the next workflow round'
340
+ assert proposal['mission'] == mission, 'next-round structured-fallback proposal snapshot should preserve the discussion mission anchor'
341
+ assert proposal['source'] == 'session', 'next-round structured-fallback proposal snapshot should record the strict session fallback source'
214
342
  assert state['current_phase'] == 'reground', 'state.json current_phase should reset to reground for the next workflow round'
215
343
  assert state['continuation_policy'] == 'continue', 'continuation_policy should reset to continue for the next workflow round'
216
344
  assert state['requires_reground'] is True, 'requires_reground should reset to true for the next workflow round'
@@ -224,25 +352,32 @@ assert plan['plan_basis'] == 'user_refocus', 'plan_basis should reset to user_re
224
352
  assert active['status'] == 'idle', 'active-slice should reset to idle for the next workflow round'
225
353
  PY
226
354
 
227
- # Active workflow: /cook <goal> plus refocus should use the explicit goal as the mission anchor
228
- # even when analyst output is unavailable, without falling back to session-derived proposal parsing.
355
+ # Active workflow: /cook <goal> plus refocus should keep the explicit goal as the mission anchor
356
+ # even when analyst output is unavailable and structured session fallback is present.
229
357
  SESSION_THREE="$TMPDIR/session-three.jsonl"
230
358
  DISCUSSION_THREE=$'Scope:\n- Preserve the richer proposal structure from discussion.\nConstraints:\n- Keep explicit goals as the mission anchor when they conflict with earlier text.\nAcceptance:\n- Refresh canonical state from the replacement mission.'
359
+ DISCUSSION_SNAPSHOT_THREE="$TMPDIR/context-proposal-active-goal.json"
360
+ ROUTING_SNAPSHOT_THREE="$TMPDIR/context-proposal-active-goal-routing.json"
231
361
  write_session "$SESSION_THREE" "$ROOT" "$DISCUSSION_THREE"
232
362
 
233
363
  PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
234
364
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
235
365
  PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
366
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_THREE" \
367
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$ROUTING_SNAPSHOT_THREE" \
236
368
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
237
- pi --session "$SESSION_THREE" -e "$PKG_ROOT" -p "/cook Explicit replacement mission for the active workflow" >/tmp/pi-completion-context-proposal-active-goal.out 2>/tmp/pi-completion-context-proposal-active-goal.err
369
+ pi --session "$SESSION_THREE" -e "$PKG_ROOT" -p "/cook Explicit replacement mission for the active workflow" >"$TMPDIR/pi-completion-context-proposal-active-goal.out" 2>"$TMPDIR/pi-completion-context-proposal-active-goal.err"
238
370
 
239
- python3 - <<'PY'
371
+ python3 - "$DISCUSSION_SNAPSHOT_THREE" "$ROUTING_SNAPSHOT_THREE" <<'PY'
240
372
  import json
373
+ import sys
241
374
  from pathlib import Path
242
375
 
243
376
  mission = 'Explicit replacement mission for the active workflow.'
244
377
  expected_task_type = 'completion-workflow'
245
378
  expected_eval_profile = 'completion-rubric-v1'
379
+ proposal = json.loads(Path(sys.argv[1]).read_text())
380
+ routing = json.loads(Path(sys.argv[2]).read_text())
246
381
  mission_text = Path('.agent/mission.md').read_text()
247
382
  profile = json.loads(Path('.agent/profile.json').read_text())
248
383
  state = json.loads(Path('.agent/state.json').read_text())
@@ -261,6 +396,11 @@ assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluatio
261
396
  assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after explicit-goal replacement'
262
397
  assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after explicit-goal replacement'
263
398
  assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after explicit-goal replacement'
399
+ assert proposal['mission'] == mission, 'explicit-goal replacement should route through the shared proposal confirmation pipeline'
400
+ assert routing['mode'] == 'explicit', 'explicit-goal replacement should snapshot explicit active-workflow routing mode'
401
+ assert routing['action'] == 'refocus', 'explicit-goal replacement should classify as refocus when the mission changes'
402
+ assert routing['reason'] == 'explicit_goal', 'explicit-goal replacement should record the explicit-goal routing reason'
403
+ assert routing['proposedMissionAnchor'] == mission, 'explicit-goal routing should expose the replacement mission anchor'
264
404
  assert state['current_phase'] == 'reground', 'current_phase should reset to reground after explicit-goal replacement'
265
405
  assert state['continuation_policy'] == 'continue', 'continuation_policy should stay continue after explicit-goal replacement'
266
406
  assert state['next_mandatory_role'] == 'completion-regrounder', 'next role should reset to completion-regrounder after explicit-goal replacement'
@@ -5,10 +5,49 @@ PKG_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
5
  TMPDIR="$(mktemp -d)"
6
6
  trap 'rm -rf "$TMPDIR"' EXIT
7
7
 
8
+ write_session() {
9
+ local session_path="$1"
10
+ local cwd="$2"
11
+ local text="$3"
12
+ python3 - "$session_path" "$cwd" "$text" <<'PY'
13
+ import json
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ session_path = Path(sys.argv[1])
18
+ cwd = sys.argv[2]
19
+ text = sys.argv[3]
20
+ session_path.parent.mkdir(parents=True, exist_ok=True)
21
+ entries = [
22
+ {
23
+ "type": "session",
24
+ "version": 3,
25
+ "id": "11111111-1111-4111-8111-111111111111",
26
+ "timestamp": "2026-01-01T00:00:00.000Z",
27
+ "cwd": cwd,
28
+ },
29
+ {
30
+ "type": "message",
31
+ "id": "a1b2c3d4",
32
+ "parentId": None,
33
+ "timestamp": "2026-01-01T00:00:01.000Z",
34
+ "message": {
35
+ "role": "user",
36
+ "content": text,
37
+ "timestamp": 1767225601000,
38
+ },
39
+ },
40
+ ]
41
+ with session_path.open('w', encoding='utf-8') as fh:
42
+ for entry in entries:
43
+ fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
44
+ PY
45
+ }
46
+
8
47
  cd "$TMPDIR"
9
48
  git init -q
10
49
 
11
- pi -e "$PKG_ROOT" -p "/cook smoke-test mission" >/tmp/pi-completion-refocus-bootstrap.out 2>/tmp/pi-completion-refocus-bootstrap.err &
50
+ pi -e "$PKG_ROOT" -p "/cook smoke-test mission" >"$TMPDIR/pi-completion-refocus-bootstrap.out" 2>"$TMPDIR/pi-completion-refocus-bootstrap.err" &
12
51
  PI_PID=$!
13
52
  for _ in $(seq 1 60); do
14
53
  if [[ -f .agent/profile.json && -f .agent/state.json && -f .agent/plan.json && -f .agent/active-slice.json ]]; then
@@ -18,7 +57,7 @@ for _ in $(seq 1 60); do
18
57
  done
19
58
  if [[ ! -f .agent/profile.json || ! -f .agent/state.json || ! -f .agent/plan.json || ! -f .agent/active-slice.json ]]; then
20
59
  echo "completion bootstrap did not materialize canonical files in time" >&2
21
- cat /tmp/pi-completion-refocus-bootstrap.err >&2 || true
60
+ cat "$TMPDIR/pi-completion-refocus-bootstrap.err" >&2 || true
22
61
  kill "$PI_PID" >/dev/null 2>&1 || true
23
62
  wait "$PI_PID" >/dev/null 2>&1 || true
24
63
  exit 1
@@ -39,9 +78,9 @@ PI_COMPLETION_EXISTING_WORKFLOW_ACTION=cancel \
39
78
  PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$CHOOSER_SNAPSHOT" \
40
79
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
41
80
  pi -e "$PKG_ROOT" -p "/cook replacement mission that should stay in the main chat" \
42
- >/tmp/pi-completion-refocus-cancel.out 2>/tmp/pi-completion-refocus-cancel.err
81
+ >"$TMPDIR/pi-completion-refocus-cancel.out" 2>"$TMPDIR/pi-completion-refocus-cancel.err"
43
82
 
44
- python3 - "$CHOOSER_SNAPSHOT" "/tmp/pi-completion-refocus-cancel.out" "/tmp/pi-completion-refocus-cancel.err" "$INITIAL_MISSION" <<'PY'
83
+ python3 - "$CHOOSER_SNAPSHOT" "$TMPDIR/pi-completion-refocus-cancel.out" "$TMPDIR/pi-completion-refocus-cancel.err" "$INITIAL_MISSION" <<'PY'
45
84
  import json
46
85
  import sys
47
86
  from pathlib import Path
@@ -58,25 +97,29 @@ assert plan['mission_anchor'] == initial_mission, 'cancelled chooser should keep
58
97
  assert active['mission_anchor'] == initial_mission, 'cancelled chooser should keep active-slice.json unchanged'
59
98
  assert chooser['title'].startswith('Existing completion workflow found'), 'chooser snapshot should describe the existing-workflow prompt'
60
99
  assert chooser['choices'][0].startswith('Continue current workflow'), 'chooser should keep the continue option'
61
- assert chooser['choices'][1].startswith('Abandon current workflow and start this new one'), 'chooser should keep the refocus option'
100
+ assert chooser['choices'][1].startswith('Abandon current workflow and start this new one'), 'chooser should keep the explicit-goal refocus option'
62
101
  assert 'Start/Cancel confirmation' in chooser['choices'][1], 'chooser should mention the approval-only replacement confirmation'
63
102
  assert chooser['choices'][2].startswith('Cancel'), 'chooser should keep the cancel option'
64
103
  assert 'Discuss changes in the main chat and rerun /cook.' in chooser['choices'][2], 'chooser cancel copy should redirect users back to the main chat and rerun /cook'
65
104
  assert 'Discuss changes in the main chat and rerun /cook.' in output, 'chooser cancel output should redirect users back to the main chat and rerun /cook'
66
105
  PY
67
106
 
107
+ EXPLICIT_ROUTING_SNAPSHOT="$TMPDIR/explicit-goal-routing.json"
68
108
  PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
109
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$EXPLICIT_ROUTING_SNAPSHOT" \
69
110
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
70
- pi -e "$PKG_ROOT" -p "/cook refocused smoke-test mission with tests and docs" \
71
- >/tmp/pi-completion-refocus.out 2>/tmp/pi-completion-refocus.err
111
+ pi -e "$PKG_ROOT" -p "/cook Remove completion status line, keep widget" \
112
+ >"$TMPDIR/pi-completion-refocus.out" 2>"$TMPDIR/pi-completion-refocus.err"
72
113
 
73
- python3 - <<'PY'
114
+ python3 - "$EXPLICIT_ROUTING_SNAPSHOT" <<'PY'
74
115
  import json
116
+ import sys
75
117
  from pathlib import Path
76
118
 
77
- new_anchor = 'refocused smoke-test mission with tests and docs parity.'
119
+ new_anchor = 'Remove completion status line, keep widget.'
78
120
  expected_task_type = 'completion-workflow'
79
121
  expected_eval_profile = 'completion-rubric-v1'
122
+ routing = json.loads(Path(sys.argv[1]).read_text())
80
123
  mission_text = Path('.agent/mission.md').read_text()
81
124
  profile = json.loads(Path('.agent/profile.json').read_text())
82
125
  state = json.loads(Path('.agent/state.json').read_text())
@@ -101,6 +144,10 @@ assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_
101
144
  assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'continuation_reason should record the refocus'
102
145
  assert plan['plan_basis'] == 'user_refocus', 'plan.json plan_basis should be user_refocus after refocus'
103
146
  assert active['status'] == 'idle', 'active-slice.json status should reset to idle after refocus'
147
+ assert routing['mode'] == 'explicit', 'explicit /cook <goal> should use explicit active-workflow routing mode'
148
+ assert routing['action'] == 'refocus', 'explicit /cook <goal> should classify as refocus when the mission changes'
149
+ assert routing['reason'] == 'explicit_goal', 'explicit /cook <goal> should record the explicit-goal routing reason'
150
+ assert routing['proposedMissionAnchor'] == new_anchor, 'explicit routing snapshot should expose the replacement mission anchor'
104
151
  PY
105
152
 
106
153
  UPDATED_MISSION="$(python3 - <<'PY'
@@ -112,8 +159,148 @@ PY
112
159
  )"
113
160
 
114
161
  if [[ "$INITIAL_MISSION" == "$UPDATED_MISSION" ]]; then
115
- echo "expected mission anchor to change during refocus" >&2
162
+ echo "expected mission anchor to change during explicit refocus" >&2
116
163
  exit 1
117
164
  fi
118
165
 
166
+ # Negated replacement missions that contain the current anchor must still reach the conservative chooser and final Start/Cancel gate.
167
+ BARE_REFOCUS_MISSION='Do not remove completion status line, keep widget.'
168
+ 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.'
169
+
170
+ SESSION_BARE_CHOOSER_CANCEL="$TMPDIR/session-bare-chooser-cancel.jsonl"
171
+ BARE_CHOOSER_SNAPSHOT="$TMPDIR/bare-existing-workflow-chooser.json"
172
+ BARE_ROUTING_CHOOSER_CANCEL="$TMPDIR/bare-routing-chooser-cancel.json"
173
+ write_session "$SESSION_BARE_CHOOSER_CANCEL" "$TMPDIR" "$BARE_REFOCUS_DISCUSSION"
174
+
175
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
176
+ PI_COMPLETION_EXISTING_WORKFLOW_ACTION=cancel \
177
+ PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$BARE_CHOOSER_SNAPSHOT" \
178
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$BARE_ROUTING_CHOOSER_CANCEL" \
179
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
180
+ pi --session "$SESSION_BARE_CHOOSER_CANCEL" -e "$PKG_ROOT" -p "/cook" \
181
+ >"$TMPDIR/pi-completion-bare-chooser-cancel.out" 2>"$TMPDIR/pi-completion-bare-chooser-cancel.err"
182
+
183
+ python3 - "$BARE_CHOOSER_SNAPSHOT" "$BARE_ROUTING_CHOOSER_CANCEL" "$TMPDIR/pi-completion-bare-chooser-cancel.out" "$TMPDIR/pi-completion-bare-chooser-cancel.err" "$UPDATED_MISSION" "$BARE_REFOCUS_MISSION" <<'PY'
184
+ import json
185
+ import sys
186
+ from pathlib import Path
187
+
188
+ chooser = json.loads(Path(sys.argv[1]).read_text())
189
+ routing = json.loads(Path(sys.argv[2]).read_text())
190
+ output = Path(sys.argv[3]).read_text() + Path(sys.argv[4]).read_text()
191
+ updated_mission = sys.argv[5]
192
+ replacement_mission = sys.argv[6]
193
+ state = json.loads(Path('.agent/state.json').read_text())
194
+ plan = json.loads(Path('.agent/plan.json').read_text())
195
+ active = json.loads(Path('.agent/active-slice.json').read_text())
196
+
197
+ assert state['mission_anchor'] == updated_mission, 'chooser cancel should keep the current mission anchor'
198
+ assert plan['mission_anchor'] == updated_mission, 'chooser cancel should keep plan.json unchanged'
199
+ assert active['mission_anchor'] == updated_mission, 'chooser cancel should keep active-slice.json unchanged'
200
+ assert routing['mode'] == 'bare', 'bare /cook should snapshot bare active-workflow routing mode'
201
+ assert routing['action'] == 'refocus', 'clear structured discussion should classify active bare /cook as refocus'
202
+ assert routing['reason'] == 'clear_refocus', 'clear structured discussion should record the clear-refocus routing reason'
203
+ assert routing['currentMissionAnchor'] == updated_mission, 'clear-refocus routing should keep the current mission anchor until the user approves replacement'
204
+ assert routing['proposedMissionAnchor'] == replacement_mission, 'clear-refocus routing should expose the proposed replacement mission'
205
+ assert chooser['title'].startswith('Existing completion workflow found'), 'bare chooser snapshot should describe the existing-workflow prompt'
206
+ assert chooser['choices'][0].startswith('Continue current workflow'), 'bare chooser should keep the continue option'
207
+ assert chooser['choices'][1].startswith('Start new workflow from recent discussion'), 'bare chooser should offer the recent-discussion refocus option'
208
+ assert 'Start/Cancel confirmation' in chooser['choices'][1], 'bare chooser should mention the approval-only replacement confirmation'
209
+ assert chooser['choices'][2].startswith('Cancel'), 'bare chooser should keep the cancel option'
210
+ 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'
211
+ PY
212
+
213
+ SESSION_BARE_FINAL_CANCEL="$TMPDIR/session-bare-final-cancel.jsonl"
214
+ BARE_ROUTING_FINAL_CANCEL="$TMPDIR/bare-routing-final-cancel.json"
215
+ BARE_PROPOSAL_CANCEL="$TMPDIR/bare-replacement-proposal-cancel.json"
216
+ write_session "$SESSION_BARE_FINAL_CANCEL" "$TMPDIR" "$BARE_REFOCUS_DISCUSSION"
217
+
218
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
219
+ PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
220
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=cancel \
221
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$BARE_PROPOSAL_CANCEL" \
222
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$BARE_ROUTING_FINAL_CANCEL" \
223
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
224
+ pi --session "$SESSION_BARE_FINAL_CANCEL" -e "$PKG_ROOT" -p "/cook" \
225
+ >"$TMPDIR/pi-completion-bare-final-cancel.out" 2>"$TMPDIR/pi-completion-bare-final-cancel.err"
226
+
227
+ python3 - "$BARE_PROPOSAL_CANCEL" "$BARE_ROUTING_FINAL_CANCEL" "$TMPDIR/pi-completion-bare-final-cancel.out" "$TMPDIR/pi-completion-bare-final-cancel.err" "$UPDATED_MISSION" "$BARE_REFOCUS_MISSION" <<'PY'
228
+ import json
229
+ import sys
230
+ from pathlib import Path
231
+
232
+ proposal = json.loads(Path(sys.argv[1]).read_text())
233
+ routing = json.loads(Path(sys.argv[2]).read_text())
234
+ output = Path(sys.argv[3]).read_text() + Path(sys.argv[4]).read_text()
235
+ updated_mission = sys.argv[5]
236
+ replacement_mission = sys.argv[6]
237
+ state = json.loads(Path('.agent/state.json').read_text())
238
+ plan = json.loads(Path('.agent/plan.json').read_text())
239
+ active = json.loads(Path('.agent/active-slice.json').read_text())
240
+
241
+ assert state['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep the current mission anchor'
242
+ assert plan['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep plan.json unchanged'
243
+ assert active['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep active-slice.json unchanged'
244
+ assert routing['action'] == 'refocus', 'final Start/Cancel cancel should still come from a clear-refocus classification'
245
+ assert routing['reason'] == 'clear_refocus', 'final Start/Cancel cancel should preserve the clear-refocus reason'
246
+ assert routing['currentMissionAnchor'] == updated_mission, 'final Start/Cancel cancel should keep the current mission anchor until the user approves replacement'
247
+ assert proposal['mission'] == replacement_mission, 'final Start/Cancel cancel should still prepare the replacement proposal before rewriting state'
248
+ 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'
249
+ PY
250
+
251
+ SESSION_BARE_ACCEPT="$TMPDIR/session-bare-accept.jsonl"
252
+ BARE_ROUTING_ACCEPT="$TMPDIR/bare-routing-accept.json"
253
+ BARE_PROPOSAL_ACCEPT="$TMPDIR/bare-replacement-proposal-accept.json"
254
+ write_session "$SESSION_BARE_ACCEPT" "$TMPDIR" "$BARE_REFOCUS_DISCUSSION"
255
+
256
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
257
+ PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
258
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
259
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$BARE_PROPOSAL_ACCEPT" \
260
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$BARE_ROUTING_ACCEPT" \
261
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
262
+ pi --session "$SESSION_BARE_ACCEPT" -e "$PKG_ROOT" -p "/cook" \
263
+ >"$TMPDIR/pi-completion-bare-accept.out" 2>"$TMPDIR/pi-completion-bare-accept.err"
264
+
265
+ python3 - "$BARE_PROPOSAL_ACCEPT" "$BARE_ROUTING_ACCEPT" <<'PY'
266
+ import json
267
+ import sys
268
+ from pathlib import Path
269
+
270
+ new_anchor = 'Do not remove completion status line, keep widget.'
271
+ expected_task_type = 'completion-workflow'
272
+ expected_eval_profile = 'completion-rubric-v1'
273
+ proposal = json.loads(Path(sys.argv[1]).read_text())
274
+ routing = json.loads(Path(sys.argv[2]).read_text())
275
+ mission_text = Path('.agent/mission.md').read_text()
276
+ profile = json.loads(Path('.agent/profile.json').read_text())
277
+ state = json.loads(Path('.agent/state.json').read_text())
278
+ plan = json.loads(Path('.agent/plan.json').read_text())
279
+ active = json.loads(Path('.agent/active-slice.json').read_text())
280
+
281
+ assert proposal['mission'] == new_anchor, 'accepted bare refocus should preserve the replacement proposal mission'
282
+ assert routing['mode'] == 'bare', 'accepted bare refocus should keep bare routing mode'
283
+ assert routing['action'] == 'refocus', 'accepted bare refocus should keep the clear-refocus classification'
284
+ assert routing['reason'] == 'clear_refocus', 'accepted bare refocus should keep the clear-refocus reason'
285
+ assert routing['currentMissionAnchor'] == 'Remove completion status line, keep widget.', 'accepted bare refocus should expose the original mission until Start is accepted'
286
+ assert new_anchor in mission_text, '.agent/mission.md did not update to the bare refocus mission anchor'
287
+ assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after bare refocus'
288
+ assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after bare refocus'
289
+ assert state['mission_anchor'] == new_anchor, 'state.json mission_anchor mismatch after bare refocus'
290
+ assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after bare refocus'
291
+ assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after bare refocus'
292
+ assert plan['mission_anchor'] == new_anchor, 'plan.json mission_anchor mismatch after bare refocus'
293
+ assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after bare refocus'
294
+ assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after bare refocus'
295
+ assert active['mission_anchor'] == new_anchor, 'active-slice.json mission_anchor mismatch after bare refocus'
296
+ assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after bare refocus'
297
+ assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after bare refocus'
298
+ assert state['current_phase'] == 'reground', 'state.json current_phase should reset to reground after bare refocus'
299
+ assert state['requires_reground'] is True, 'state.json requires_reground should be true after bare refocus'
300
+ assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should reset to completion-regrounder after bare refocus'
301
+ assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'continuation_reason should record the bare refocus'
302
+ assert plan['plan_basis'] == 'user_refocus', 'plan.json plan_basis should be user_refocus after bare refocus'
303
+ assert active['status'] == 'idle', 'active-slice.json status should reset to idle after bare refocus'
304
+ PY
305
+
119
306
  echo "refocus test passed: $TMPDIR"
@@ -4,8 +4,42 @@ set -euo pipefail
4
4
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
5
  cd "$ROOT"
6
6
 
7
- echo "[release-check] running control-plane validation, startup/refocus/context regressions, canonical evidence artifact, active-slice contract, observability, evaluator calibration, and rubric contract coverage"
7
+ echo "[release-check] running control-plane validation, public /cook parity, startup/refocus/context regressions, canonical evidence artifact, active-slice contract, observability, evaluator calibration, and rubric contract coverage"
8
8
  bash .agent/verify_completion_control_plane.sh
9
+
10
+ echo "[release-check] verifying public /cook single-command parity"
11
+ python3 - <<'PY'
12
+ from pathlib import Path
13
+
14
+ checks = {
15
+ "README.md": [
16
+ "Bare `/cook` is now the primary workflow entrypoint.",
17
+ "`/cook <text>` is still supported as a temporary compatibility shim",
18
+ "Matching or unclear discussion resumes from canonical `.agent/**` state.",
19
+ "approval-only Start/Cancel gate",
20
+ "Start new workflow from recent discussion",
21
+ "fails closed instead of guessing",
22
+ ],
23
+ "CHANGELOG.md": [
24
+ "single public discussion-first workflow command",
25
+ "temporary compatibility shim",
26
+ "approval-only Start/Cancel gate",
27
+ "fail-closed ambiguous-discussion behavior",
28
+ "release-gated public-parity assertions",
29
+ ],
30
+ "extensions/completion/index.ts": [
31
+ 'description: "Discussion-driven /cook workflow: start, continue, refocus, or start the next round"',
32
+ "temporary compatibility shim, pass /cook <text>",
33
+ ],
34
+ }
35
+
36
+ for path, needles in checks.items():
37
+ text = Path(path).read_text()
38
+ for needle in needles:
39
+ if needle not in text:
40
+ raise SystemExit(f"[release-check] missing expected public /cook parity text in {path}: {needle}")
41
+ PY
42
+
9
43
  npm run smoke-test
10
44
  npm run refocus-test
11
45
  npm run context-proposal-test
@@ -8,6 +8,8 @@ trap 'rm -rf "$TMPDIR"' EXIT
8
8
  ROOT="$TMPDIR/repo"
9
9
  KICKOFF_PROMPT="$TMPDIR/kickoff-prompt.txt"
10
10
  RESUME_PROMPT="$TMPDIR/resume-prompt.txt"
11
+ UNCLEAR_ROUTING_SNAPSHOT="$TMPDIR/active-unclear-routing.json"
12
+ UNCLEAR_CHOOSER_SNAPSHOT="$TMPDIR/unexpected-existing-workflow-chooser.json"
11
13
  AUTO_RESUME_PROMPT="$TMPDIR/auto-resume-prompt.txt"
12
14
 
13
15
  mkdir -p "$ROOT"
@@ -62,20 +64,32 @@ PY
62
64
 
63
65
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
64
66
  PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$RESUME_PROMPT" \
67
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$UNCLEAR_ROUTING_SNAPSHOT" \
68
+ PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$UNCLEAR_CHOOSER_SNAPSHOT" \
65
69
  pi -e "$PKG_ROOT" -p "/cook" \
66
70
  >"$TMPDIR/pi-completion-smoke-resume.out" 2>"$TMPDIR/pi-completion-smoke-resume.err"
67
71
 
68
- python3 - "$RESUME_PROMPT" <<'PY'
72
+ python3 - "$RESUME_PROMPT" "$UNCLEAR_ROUTING_SNAPSHOT" "$UNCLEAR_CHOOSER_SNAPSHOT" <<'PY'
73
+ import json
69
74
  import sys
70
75
  from pathlib import Path
71
76
 
72
77
  expected_task_type = 'completion-workflow'
73
78
  expected_eval_profile = 'completion-rubric-v1'
74
79
  resume = Path(sys.argv[1]).read_text()
80
+ routing = json.loads(Path(sys.argv[2]).read_text())
81
+ chooser_path = Path(sys.argv[3])
82
+ state = json.loads(Path('.agent/state.json').read_text())
75
83
 
76
84
  assert 'Canonical routing profile:' in resume, 'resume prompt should expose canonical routing profile'
77
85
  assert f'- task_type: {expected_task_type}' in resume, 'resume prompt missing canonical task_type'
78
86
  assert f'- evaluation_profile: {expected_eval_profile}' in resume, 'resume prompt missing canonical evaluation_profile'
87
+ assert routing['mode'] == 'bare', 'active bare /cook should snapshot bare routing mode'
88
+ assert routing['action'] == 'unclear', 'no-discussion active bare /cook should classify as unclear'
89
+ assert routing['reason'] == 'missing_proposal', 'no-discussion active bare /cook should fail closed because no replacement proposal was derived'
90
+ assert routing['currentMissionAnchor'] == state['mission_anchor'], 'unclear routing snapshot should keep the current mission anchor'
91
+ assert routing['proposedMissionAnchor'] is None, 'unclear no-discussion routing should not propose a replacement mission'
92
+ assert not chooser_path.exists(), 'unclear active bare /cook should fall back to resume without opening the chooser'
79
93
  PY
80
94
 
81
95
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \