@linimin/pi-letscook 0.1.68 → 0.1.69

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,31 +47,130 @@ with session_path.open('w', encoding='utf-8') as fh:
47
47
  PY
48
48
  }
49
49
 
50
- bootstrap_active_workflow() {
51
- local repo_root="$1"
52
- local session_path="$2"
53
- local bootstrap_discussion="$3"
54
- local generated_handoff="$4"
55
-
56
- mkdir -p "$repo_root"
57
- cd "$repo_root"
58
- git init -q
59
- write_session "$session_path" "$repo_root" "$bootstrap_discussion"
60
-
61
- PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
62
- PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$generated_handoff" \
63
- PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
64
- pi --session "$session_path" -e "$PKG_ROOT" -p "/cook" \
65
- >"$TMPDIR/bootstrap.out" 2>"$TMPDIR/bootstrap.err"
66
-
67
- for file in .agent/profile.json .agent/state.json .agent/startup-plan.json .agent/startup-plan.md .agent/plan.json .agent/active-slice.json .agent/verification-evidence.json; do
68
- [[ -f "$file" ]] || { echo "missing canonical bootstrap file: $file" >&2; exit 1; }
69
- done
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
+
93
+ cd "$TMPDIR"
94
+ git init -q
95
+
96
+ BOOTSTRAP_SESSION="$TMPDIR/session-bootstrap.jsonl"
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."
70
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"
141
+
142
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
143
+ pi --session "$BOOTSTRAP_SESSION" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-refocus-bootstrap.out" 2>"$TMPDIR/pi-completion-refocus-bootstrap.err" &
144
+ PI_PID=$!
145
+ for _ in $(seq 1 60); do
146
+ if [[ -f .agent/profile.json && -f .agent/state.json && -f .agent/plan.json && -f .agent/active-slice.json ]]; then
147
+ break
148
+ fi
149
+ sleep 1
150
+ done
151
+ if [[ ! -f .agent/profile.json || ! -f .agent/state.json || ! -f .agent/plan.json || ! -f .agent/active-slice.json ]]; then
152
+ echo "completion bootstrap did not materialize canonical files in time" >&2
153
+ cat "$TMPDIR/pi-completion-refocus-bootstrap.err" >&2 || true
154
+ kill "$PI_PID" >/dev/null 2>&1 || true
155
+ wait "$PI_PID" >/dev/null 2>&1 || true
156
+ exit 1
157
+ fi
158
+ kill "$PI_PID" >/dev/null 2>&1 || true
159
+ wait "$PI_PID" >/dev/null 2>&1 || true
71
160
 
72
- snapshot_tracked() {
73
- local baseline_path="$1"
74
- python3 - "$baseline_path" <<'PY'
161
+ INITIAL_MISSION="$(python3 - <<'PY'
162
+ import json
163
+ from pathlib import Path
164
+ state = json.loads(Path('.agent/state.json').read_text())
165
+ print(state['mission_anchor'])
166
+ PY
167
+ )"
168
+
169
+ INLINE_REJECTION_ROUTING="$TMPDIR/inline-arg-routing.json"
170
+ INLINE_REJECTION_PROPOSAL="$TMPDIR/inline-arg-proposal.json"
171
+ INLINE_REJECTION_CHOOSER="$TMPDIR/inline-arg-chooser.json"
172
+ INLINE_REJECTION_BASELINE="$TMPDIR/inline-arg-before.json"
173
+ python3 - "$INLINE_REJECTION_BASELINE" <<'PY'
75
174
  import json
76
175
  import sys
77
176
  from pathlib import Path
@@ -80,246 +179,421 @@ tracked = [
80
179
  Path('.agent/mission.md'),
81
180
  Path('.agent/profile.json'),
82
181
  Path('.agent/state.json'),
83
- Path('.agent/startup-plan.json'),
84
- Path('.agent/startup-plan.md'),
85
182
  Path('.agent/plan.json'),
86
183
  Path('.agent/active-slice.json'),
87
184
  Path('.agent/verification-evidence.json'),
88
185
  ]
89
186
  Path(sys.argv[1]).write_text(json.dumps({path.name: path.read_text() for path in tracked}, indent=2) + '\n')
90
187
  PY
91
- }
92
188
 
93
- BOOTSTRAP_DISCUSSION=$'Prepare the active workflow fixture and keep the initial mission small and truthful.'
94
- BOOTSTRAP_HANDOFF="$(python3 - <<'PY'
189
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$INLINE_REJECTION_ROUTING" \
190
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$INLINE_REJECTION_PROPOSAL" \
191
+ PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$INLINE_REJECTION_CHOOSER" \
192
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
193
+ pi -e "$PKG_ROOT" -p "/cook replacement mission that should stay in the main chat" \
194
+ >"$TMPDIR/pi-completion-refocus-inline-arg.out" 2>"$TMPDIR/pi-completion-refocus-inline-arg.err"
195
+
196
+ python3 - "$TMPDIR/pi-completion-refocus-inline-arg.out" "$TMPDIR/pi-completion-refocus-inline-arg.err" "$INLINE_REJECTION_ROUTING" "$INLINE_REJECTION_PROPOSAL" "$INLINE_REJECTION_CHOOSER" "$INITIAL_MISSION" "$INLINE_REJECTION_BASELINE" <<'PY'
197
+ import json
198
+ import sys
199
+ from pathlib import Path
200
+
201
+ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
202
+ routing = Path(sys.argv[3])
203
+ proposal = Path(sys.argv[4])
204
+ chooser = Path(sys.argv[5])
205
+ initial_mission = sys.argv[6]
206
+ before = json.loads(Path(sys.argv[7]).read_text())
207
+ tracked = [
208
+ Path('.agent/mission.md'),
209
+ Path('.agent/profile.json'),
210
+ Path('.agent/state.json'),
211
+ Path('.agent/plan.json'),
212
+ Path('.agent/active-slice.json'),
213
+ Path('.agent/verification-evidence.json'),
214
+ ]
215
+ current_state = json.loads(before['state.json'])
216
+ assert current_state['mission_anchor'] == initial_mission, 'active /cook inline-args rejection should start from the current mission anchor'
217
+ assert not routing.exists(), 'active /cook inline-args rejection should not run active-workflow routing'
218
+ assert not proposal.exists(), 'active /cook inline-args rejection should not open final startup-brief confirmation'
219
+ assert not chooser.exists(), 'active /cook inline-args rejection should not open the existing-workflow chooser'
220
+ assert '/cook no longer accepts inline arguments.' in output, 'active /cook inline-args rejection should explain the bare-only entry contract'
221
+ after = {path.name: path.read_text() for path in tracked}
222
+ assert before == after, 'active /cook inline-args rejection should leave canonical files unchanged'
223
+ PY
224
+
225
+ SESSION_INITIAL_REFOCUS="$TMPDIR/session-initial-bare-refocus.jsonl"
226
+ INITIAL_REFOCUS_ROUTING="$TMPDIR/initial-bare-refocus-routing.json"
227
+ INITIAL_REFOCUS_MESSAGES="$(python3 - <<'PY'
95
228
  import json
96
229
  capsule = {
97
230
  "kind": "cook_handoff",
98
231
  "source": "primary_agent",
99
- "mission": "Maintain the active workflow fixture baseline.",
232
+ "captured_at": "2026-01-01T00:00:02.000Z",
233
+ "source_turn_id": "m0002",
234
+ "mission": "Remove completion status line, keep widget.",
100
235
  "scope": [
101
- "Scaffold canonical completion files for the refocus regression fixture.",
102
- "Keep the initial workflow ready for later replacement checks."
236
+ "Replace the initial smoke-test workflow with the widget mission."
103
237
  ],
104
238
  "constraints": [
105
- "Use supported bare /cook startup only."
239
+ "Keep the approval-only Start/Cancel refocus gate."
106
240
  ],
107
241
  "acceptance": [
108
- "Bootstrap canonical completion state for the refocus regression fixture.",
109
- "Keep scripts/refocus-test.sh aligned with the shipped startup behavior."
242
+ "Rewrite canonical state only after the replacement mission is approved."
110
243
  ],
111
244
  "risks": [],
112
245
  "notes": [
113
- "Bootstrap from same-entry primary-agent startup-plan synthesis."
246
+ "Use a fresh explicit primary-agent handoff for the active-workflow replacement."
114
247
  ],
115
248
  "handoff_kind": "implementation_workflow_handoff",
116
- "first_slice_goal": "Scaffold canonical files for the refocus regression fixture.",
249
+ "first_slice_goal": "Replace the initial smoke-test workflow with the widget mission.",
117
250
  "first_slice_non_goals": [],
118
- "implementation_surfaces": ["scripts/refocus-test.sh"],
119
- "verification_commands": ["npm run refocus-test"],
120
- "why_this_slice_first": "Canonical state must exist before refocus can be exercised.",
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.",
121
258
  "task_type": "completion-workflow",
122
259
  "evaluation_profile": "completion-rubric-v1",
123
- "why_cook_now": "The fixture startup is concrete enough to begin workflow planning."
260
+ "why_cook_now": "A different active workflow is ready and explicitly handed off by the primary agent."
124
261
  }
125
- print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
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))
126
267
  PY
127
268
  )"
128
- REPLACEMENT_DISCUSSION=$'Switch the active workflow to the widget mission and keep the change confirm-first.'
129
- REPLACEMENT_HANDOFF="$(python3 - <<'PY'
269
+ write_session_messages "$SESSION_INITIAL_REFOCUS" "$TMPDIR" "$INITIAL_REFOCUS_MESSAGES"
270
+
271
+ PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
272
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
273
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$INITIAL_REFOCUS_ROUTING" \
274
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
275
+ pi --session "$SESSION_INITIAL_REFOCUS" -e "$PKG_ROOT" -p "/cook" \
276
+ >"$TMPDIR/pi-completion-refocus.out" 2>"$TMPDIR/pi-completion-refocus.err"
277
+
278
+ python3 - "$INITIAL_REFOCUS_ROUTING" <<'PY'
279
+ import json
280
+ import sys
281
+ from pathlib import Path
282
+
283
+ new_anchor = 'Remove completion status line, keep widget.'
284
+ expected_task_type = 'completion-workflow'
285
+ expected_eval_profile = 'completion-rubric-v1'
286
+ routing = json.loads(Path(sys.argv[1]).read_text())
287
+ mission_text = Path('.agent/mission.md').read_text()
288
+ profile = json.loads(Path('.agent/profile.json').read_text())
289
+ state = json.loads(Path('.agent/state.json').read_text())
290
+ plan = json.loads(Path('.agent/plan.json').read_text())
291
+ active = json.loads(Path('.agent/active-slice.json').read_text())
292
+
293
+ assert new_anchor in mission_text, '.agent/mission.md did not update to the refocused mission anchor'
294
+ assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after refocus'
295
+ assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after refocus'
296
+ assert state['mission_anchor'] == new_anchor, 'state.json mission_anchor mismatch after refocus'
297
+ assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after refocus'
298
+ assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after refocus'
299
+ assert state['advisory_startup_brief']['mission'] == new_anchor, 'refocus should preserve the confirmed startup brief as advisory intake'
300
+ assert plan['mission_anchor'] == new_anchor, 'plan.json mission_anchor mismatch after refocus'
301
+ assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after refocus'
302
+ assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after refocus'
303
+ assert active['mission_anchor'] == new_anchor, 'active-slice.json mission_anchor mismatch after refocus'
304
+ assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after refocus'
305
+ assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after refocus'
306
+ assert state['current_phase'] == 'reground', 'state.json current_phase should reset to reground after refocus'
307
+ assert state['requires_reground'] is True, 'state.json requires_reground should be true after refocus'
308
+ assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should reset to completion-regrounder'
309
+ assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'continuation_reason should record the refocus'
310
+ assert plan['plan_basis'] == 'user_refocus', 'plan.json plan_basis should be user_refocus after refocus'
311
+ assert active['status'] == 'idle', 'active-slice.json status should reset to idle after refocus'
312
+ assert routing['mode'] == 'bare', 'supported refocus should use bare active-workflow routing mode'
313
+ assert 'explicitGoal' not in routing, 'supported bare refocus should not expose removed explicit-goal shim fields'
314
+ assert 'explicitGoalProvided' not in routing, 'supported bare refocus should not expose removed explicit-goal shim fields'
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'
319
+ PY
320
+
321
+ UPDATED_MISSION="$(python3 - <<'PY'
322
+ import json
323
+ from pathlib import Path
324
+ state = json.loads(Path('.agent/state.json').read_text())
325
+ print(state['mission_anchor'])
326
+ PY
327
+ )"
328
+
329
+ if [[ "$INITIAL_MISSION" == "$UPDATED_MISSION" ]]; then
330
+ echo "expected mission anchor to change during supported bare refocus" >&2
331
+ exit 1
332
+ fi
333
+
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'
130
338
  import json
131
339
  capsule = {
132
340
  "kind": "cook_handoff",
133
341
  "source": "primary_agent",
134
- "mission": "Remove the completion status line while keeping the completion widget.",
342
+ "captured_at": "2026-01-01T00:00:02.000Z",
343
+ "source_turn_id": "m0002",
344
+ "mission": "Exercise explicit active-workflow replacement coverage.",
135
345
  "scope": [
136
- "Replace the active fixture mission with the widget mission.",
137
- "Keep refocus approval-only before canonical rewrite."
346
+ "Treat the active bare /cook request as an explicit replacement workflow.",
347
+ "Keep the replacement behind the existing approval-only Start/Cancel gate."
138
348
  ],
139
349
  "constraints": [
140
- "Do not bypass the Start/Cancel gate."
350
+ "Do not rewrite canonical state before the final Start confirmation."
141
351
  ],
142
352
  "acceptance": [
143
- "Rewrite canonical workflow state only after the replacement mission is approved.",
144
- "Keep refocus regression coverage aligned with same-entry synthesis behavior."
145
- ],
146
- "risks": [
147
- "Do not leave the old mission partially active after refocus."
353
+ "Add deterministic coverage proving the chooser and final approval path for this explicit replacement mission."
148
354
  ],
355
+ "risks": [],
149
356
  "notes": [
150
- "This replacement must come from same-entry primary-agent startup-plan synthesis."
357
+ "This replacement should come only from the fresh explicit handoff, not recent discussion inference."
151
358
  ],
152
359
  "handoff_kind": "implementation_workflow_handoff",
153
- "first_slice_goal": "Rewrite canonical workflow state for the widget mission.",
154
- "first_slice_non_goals": ["Do not start implementation before the refocus is approved."],
155
- "implementation_surfaces": ["scripts/refocus-test.sh", ".agent/startup-plan.json"],
156
- "verification_commands": ["npm run refocus-test"],
157
- "why_this_slice_first": "The active workflow must be re-anchored before later role dispatch.",
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.",
158
369
  "task_type": "completion-workflow",
159
370
  "evaluation_profile": "completion-rubric-v1",
160
- "why_cook_now": "A different workflow mission is now concrete enough to replace the active one."
371
+ "why_cook_now": "The primary agent explicitly handed off a replacement workflow while the current one is active."
161
372
  }
162
- print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
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))
163
378
  PY
164
379
  )"
165
380
 
166
- # Case 1: chooser cancel leaves canonical state untouched while still surfacing synthesized replacement routing.
167
- ROOT_ONE="$TMPDIR/repo-one"
168
- SESSION_ONE_BOOTSTRAP="$TMPDIR/session-one-bootstrap.jsonl"
169
- SESSION_ONE_REFOCUS="$TMPDIR/session-one-refocus.jsonl"
170
- ROUTING_ONE="$TMPDIR/routing-one.json"
171
- CHOOSER_ONE="$TMPDIR/chooser-one.json"
172
- PROPOSAL_ONE="$TMPDIR/proposal-one.json"
173
- BASELINE_ONE="$TMPDIR/baseline-one.json"
174
- bootstrap_active_workflow "$ROOT_ONE" "$SESSION_ONE_BOOTSTRAP" "$BOOTSTRAP_DISCUSSION" "$BOOTSTRAP_HANDOFF"
175
- cd "$ROOT_ONE"
176
- snapshot_tracked "$BASELINE_ONE"
177
- write_session "$SESSION_ONE_REFOCUS" "$ROOT_ONE" "$REPLACEMENT_DISCUSSION"
178
-
179
- PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$REPLACEMENT_HANDOFF" \
381
+ SESSION_BARE_CHOOSER_CANCEL="$TMPDIR/session-bare-chooser-cancel.jsonl"
382
+ BARE_CHOOSER_SNAPSHOT="$TMPDIR/bare-existing-workflow-chooser.json"
383
+ BARE_ROUTING_CHOOSER_CANCEL="$TMPDIR/bare-routing-chooser-cancel.json"
384
+ write_session_messages "$SESSION_BARE_CHOOSER_CANCEL" "$TMPDIR" "$BARE_REFOCUS_MESSAGES"
385
+
180
386
  PI_COMPLETION_EXISTING_WORKFLOW_ACTION=cancel \
387
+ PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$BARE_CHOOSER_SNAPSHOT" \
388
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$BARE_ROUTING_CHOOSER_CANCEL" \
181
389
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
182
- PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$ROUTING_ONE" \
183
- PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$CHOOSER_ONE" \
184
- PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$PROPOSAL_ONE" \
185
- pi --session "$SESSION_ONE_REFOCUS" -e "$PKG_ROOT" -p "/cook" \
186
- >"$TMPDIR/refocus-one.out" 2>"$TMPDIR/refocus-one.err"
390
+ pi --session "$SESSION_BARE_CHOOSER_CANCEL" -e "$PKG_ROOT" -p "/cook" \
391
+ >"$TMPDIR/pi-completion-bare-chooser-cancel.out" 2>"$TMPDIR/pi-completion-bare-chooser-cancel.err"
187
392
 
188
- python3 - "$ROUTING_ONE" "$CHOOSER_ONE" "$PROPOSAL_ONE" "$BASELINE_ONE" "$TMPDIR/refocus-one.out" "$TMPDIR/refocus-one.err" <<'PY'
393
+ 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'
189
394
  import json
190
395
  import sys
191
396
  from pathlib import Path
192
397
 
193
- routing = json.loads(Path(sys.argv[1]).read_text())
194
- chooser = json.loads(Path(sys.argv[2]).read_text())
195
- proposal = Path(sys.argv[3])
196
- before = json.loads(Path(sys.argv[4]).read_text())
197
- output = Path(sys.argv[5]).read_text() + Path(sys.argv[6]).read_text()
198
- tracked = [
199
- Path('.agent/mission.md'),
200
- Path('.agent/profile.json'),
201
- Path('.agent/state.json'),
202
- Path('.agent/startup-plan.json'),
203
- Path('.agent/startup-plan.md'),
204
- Path('.agent/plan.json'),
205
- Path('.agent/active-slice.json'),
206
- Path('.agent/verification-evidence.json'),
207
- ]
208
- after = {path.name: path.read_text() for path in tracked}
398
+ chooser = json.loads(Path(sys.argv[1]).read_text())
399
+ routing = json.loads(Path(sys.argv[2]).read_text())
400
+ output = Path(sys.argv[3]).read_text() + Path(sys.argv[4]).read_text()
401
+ updated_mission = sys.argv[5]
402
+ replacement_mission = sys.argv[6]
403
+ state = json.loads(Path('.agent/state.json').read_text())
404
+ plan = json.loads(Path('.agent/plan.json').read_text())
405
+ active = json.loads(Path('.agent/active-slice.json').read_text())
209
406
 
210
- assert routing['action'] == 'refocus', routing
211
- assert routing['reason'] == 'generated_replacement_startup_plan', routing
212
- assert routing['proposedMissionAnchor'] == 'Remove the completion status line while keeping the completion widget.', routing
213
- assert chooser['choices'][1].startswith('Start new workflow from same-entry primary-agent startup plan'), chooser
214
- assert not proposal.exists(), 'chooser cancel should not open the final Start/Cancel proposal snapshot'
215
- assert before == after, 'chooser cancel should leave canonical workflow files unchanged'
216
- assert 'Cancelled existing workflow confirmation.' in output, output
407
+ assert state['mission_anchor'] == updated_mission, 'chooser cancel should keep the current mission anchor'
408
+ assert plan['mission_anchor'] == updated_mission, 'chooser cancel should keep plan.json unchanged'
409
+ assert active['mission_anchor'] == updated_mission, 'chooser cancel should keep active-slice.json unchanged'
410
+ assert routing['mode'] == 'bare', 'bare /cook should snapshot bare active-workflow routing mode'
411
+ assert 'explicitGoal' not in routing, 'bare chooser routing should not expose removed explicit-goal shim fields'
412
+ assert 'explicitGoalProvided' not in routing, 'bare chooser routing should not expose removed explicit-goal shim fields'
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'
418
+ assert chooser['title'].startswith('Existing completion workflow found'), 'bare chooser snapshot should describe the existing-workflow prompt'
419
+ assert chooser['choices'][0].startswith('Continue current workflow'), 'bare chooser should keep the continue option'
420
+ assert chooser['choices'][1].startswith('Start new workflow from explicit primary-agent handoff'), 'bare chooser should offer the explicit-handoff replacement option'
421
+ assert 'Start/Cancel confirmation' in chooser['choices'][1], 'bare chooser should mention the approval-only replacement confirmation'
422
+ assert chooser['choices'][2].startswith('Cancel'), 'bare chooser should keep the cancel option'
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'
217
424
  PY
218
425
 
219
- # Case 2: final Start/Cancel cancel still leaves canonical state untouched after chooser acceptance.
220
- ROOT_TWO="$TMPDIR/repo-two"
221
- SESSION_TWO_BOOTSTRAP="$TMPDIR/session-two-bootstrap.jsonl"
222
- SESSION_TWO_REFOCUS="$TMPDIR/session-two-refocus.jsonl"
223
- ROUTING_TWO="$TMPDIR/routing-two.json"
224
- CHOOSER_TWO="$TMPDIR/chooser-two.json"
225
- PROPOSAL_TWO="$TMPDIR/proposal-two.json"
226
- BASELINE_TWO="$TMPDIR/baseline-two.json"
227
- bootstrap_active_workflow "$ROOT_TWO" "$SESSION_TWO_BOOTSTRAP" "$BOOTSTRAP_DISCUSSION" "$BOOTSTRAP_HANDOFF"
228
- cd "$ROOT_TWO"
229
- snapshot_tracked "$BASELINE_TWO"
230
- write_session "$SESSION_TWO_REFOCUS" "$ROOT_TWO" "$REPLACEMENT_DISCUSSION"
231
-
232
- PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$REPLACEMENT_HANDOFF" \
426
+ SESSION_BARE_FINAL_CANCEL="$TMPDIR/session-bare-final-cancel.jsonl"
427
+ BARE_ROUTING_FINAL_CANCEL="$TMPDIR/bare-routing-final-cancel.json"
428
+ BARE_PROPOSAL_CANCEL="$TMPDIR/bare-replacement-proposal-cancel.json"
429
+ write_session_messages "$SESSION_BARE_FINAL_CANCEL" "$TMPDIR" "$BARE_REFOCUS_MESSAGES"
430
+
233
431
  PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
234
432
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=cancel \
433
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$BARE_PROPOSAL_CANCEL" \
434
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$BARE_ROUTING_FINAL_CANCEL" \
235
435
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
236
- PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$ROUTING_TWO" \
237
- PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$CHOOSER_TWO" \
238
- PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$PROPOSAL_TWO" \
239
- pi --session "$SESSION_TWO_REFOCUS" -e "$PKG_ROOT" -p "/cook" \
240
- >"$TMPDIR/refocus-two.out" 2>"$TMPDIR/refocus-two.err"
436
+ pi --session "$SESSION_BARE_FINAL_CANCEL" -e "$PKG_ROOT" -p "/cook" \
437
+ >"$TMPDIR/pi-completion-bare-final-cancel.out" 2>"$TMPDIR/pi-completion-bare-final-cancel.err"
241
438
 
242
- python3 - "$ROUTING_TWO" "$CHOOSER_TWO" "$PROPOSAL_TWO" "$BASELINE_TWO" "$TMPDIR/refocus-two.out" "$TMPDIR/refocus-two.err" <<'PY'
439
+ 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'
243
440
  import json
244
441
  import sys
245
442
  from pathlib import Path
246
443
 
247
- routing = json.loads(Path(sys.argv[1]).read_text())
248
- chooser = json.loads(Path(sys.argv[2]).read_text())
249
- proposal = json.loads(Path(sys.argv[3]).read_text())
250
- before = json.loads(Path(sys.argv[4]).read_text())
251
- output = Path(sys.argv[5]).read_text() + Path(sys.argv[6]).read_text()
252
- tracked = [
253
- Path('.agent/mission.md'),
254
- Path('.agent/profile.json'),
255
- Path('.agent/state.json'),
256
- Path('.agent/startup-plan.json'),
257
- Path('.agent/startup-plan.md'),
258
- Path('.agent/plan.json'),
259
- Path('.agent/active-slice.json'),
260
- Path('.agent/verification-evidence.json'),
261
- ]
262
- after = {path.name: path.read_text() for path in tracked}
444
+ proposal = json.loads(Path(sys.argv[1]).read_text())
445
+ routing = json.loads(Path(sys.argv[2]).read_text())
446
+ output = Path(sys.argv[3]).read_text() + Path(sys.argv[4]).read_text()
447
+ updated_mission = sys.argv[5]
448
+ replacement_mission = sys.argv[6]
449
+ state = json.loads(Path('.agent/state.json').read_text())
450
+ plan = json.loads(Path('.agent/plan.json').read_text())
451
+ active = json.loads(Path('.agent/active-slice.json').read_text())
263
452
 
264
- assert routing['action'] == 'refocus', routing
265
- assert routing['reason'] == 'generated_replacement_startup_plan', routing
266
- assert chooser['choices'][1].startswith('Start new workflow from same-entry primary-agent startup plan'), chooser
267
- assert proposal['source'] == 'deferred_primary_agent_handoff', proposal
268
- assert proposal['mission'] == 'Remove the completion status line while keeping the completion widget.', proposal
269
- assert before == after, 'final Start/Cancel cancel should leave canonical workflow files unchanged'
270
- assert 'Cancelled replacement workflow proposal.' in output, output
453
+ assert state['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep the current mission anchor'
454
+ assert plan['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep plan.json unchanged'
455
+ assert active['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep active-slice.json unchanged'
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'
458
+ assert routing['currentMissionAnchor'] == updated_mission, 'final Start/Cancel cancel should keep the current mission anchor until the user approves replacement'
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'
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'
271
462
  PY
272
463
 
273
- # Case 3: accepting the synthesized replacement rewrites canonical mission and startup plan.
274
- ROOT_THREE="$TMPDIR/repo-three"
275
- SESSION_THREE_BOOTSTRAP="$TMPDIR/session-three-bootstrap.jsonl"
276
- SESSION_THREE_REFOCUS="$TMPDIR/session-three-refocus.jsonl"
277
- ROUTING_THREE="$TMPDIR/routing-three.json"
278
- CHOOSER_THREE="$TMPDIR/chooser-three.json"
279
- PROPOSAL_THREE="$TMPDIR/proposal-three.json"
280
- bootstrap_active_workflow "$ROOT_THREE" "$SESSION_THREE_BOOTSTRAP" "$BOOTSTRAP_DISCUSSION" "$BOOTSTRAP_HANDOFF"
281
- cd "$ROOT_THREE"
282
- write_session "$SESSION_THREE_REFOCUS" "$ROOT_THREE" "$REPLACEMENT_DISCUSSION"
283
-
284
- PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$REPLACEMENT_HANDOFF" \
464
+ SESSION_BARE_ACCEPT="$TMPDIR/session-bare-accept.jsonl"
465
+ BARE_ROUTING_ACCEPT="$TMPDIR/bare-routing-accept.json"
466
+ BARE_PROPOSAL_ACCEPT="$TMPDIR/bare-replacement-proposal-accept.json"
467
+ write_session_messages "$SESSION_BARE_ACCEPT" "$TMPDIR" "$BARE_REFOCUS_MESSAGES"
468
+
285
469
  PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
286
470
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
471
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$BARE_PROPOSAL_ACCEPT" \
472
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$BARE_ROUTING_ACCEPT" \
287
473
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
288
- PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$ROUTING_THREE" \
289
- PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$CHOOSER_THREE" \
290
- PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$PROPOSAL_THREE" \
291
- pi --session "$SESSION_THREE_REFOCUS" -e "$PKG_ROOT" -p "/cook" \
292
- >"$TMPDIR/refocus-three.out" 2>"$TMPDIR/refocus-three.err"
474
+ pi --session "$SESSION_BARE_ACCEPT" -e "$PKG_ROOT" -p "/cook" \
475
+ >"$TMPDIR/pi-completion-bare-accept.out" 2>"$TMPDIR/pi-completion-bare-accept.err"
293
476
 
294
- python3 - "$ROUTING_THREE" "$CHOOSER_THREE" "$PROPOSAL_THREE" "$TMPDIR/refocus-three.out" "$TMPDIR/refocus-three.err" <<'PY'
477
+ python3 - "$BARE_PROPOSAL_ACCEPT" "$BARE_ROUTING_ACCEPT" <<'PY'
295
478
  import json
296
479
  import sys
297
480
  from pathlib import Path
298
481
 
299
- routing = json.loads(Path(sys.argv[1]).read_text())
300
- chooser = json.loads(Path(sys.argv[2]).read_text())
301
- proposal = json.loads(Path(sys.argv[3]).read_text())
302
- output = Path(sys.argv[4]).read_text() + Path(sys.argv[5]).read_text()
482
+ new_anchor = 'Exercise explicit active-workflow replacement coverage.'
483
+ expected_task_type = 'completion-workflow'
484
+ expected_eval_profile = 'completion-rubric-v1'
485
+ proposal = json.loads(Path(sys.argv[1]).read_text())
486
+ routing = json.loads(Path(sys.argv[2]).read_text())
487
+ mission_text = Path('.agent/mission.md').read_text()
488
+ profile = json.loads(Path('.agent/profile.json').read_text())
489
+ state = json.loads(Path('.agent/state.json').read_text())
490
+ plan = json.loads(Path('.agent/plan.json').read_text())
491
+ active = json.loads(Path('.agent/active-slice.json').read_text())
492
+
493
+ assert proposal['mission'] == new_anchor, 'accepted bare refocus should preserve the replacement proposal mission'
494
+ assert routing['mode'] == 'bare', 'accepted bare refocus should keep bare routing mode'
495
+ assert 'explicitGoal' not in routing, 'accepted bare refocus should not expose removed explicit-goal shim fields'
496
+ assert 'explicitGoalProvided' not in routing, 'accepted bare refocus should not expose removed explicit-goal shim fields'
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'
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'
501
+ assert new_anchor in mission_text, '.agent/mission.md did not update to the bare refocus mission anchor'
502
+ assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after bare refocus'
503
+ assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after bare refocus'
504
+ assert state['mission_anchor'] == new_anchor, 'state.json mission_anchor mismatch after bare refocus'
505
+ assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after bare refocus'
506
+ assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after bare refocus'
507
+ assert state['advisory_startup_brief']['mission'] == new_anchor, 'bare refocus should preserve the confirmed startup brief as advisory intake'
508
+ assert plan['mission_anchor'] == new_anchor, 'plan.json mission_anchor mismatch after bare refocus'
509
+ assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after bare refocus'
510
+ assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after bare refocus'
511
+ assert active['mission_anchor'] == new_anchor, 'active-slice.json mission_anchor mismatch after bare refocus'
512
+ assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after bare refocus'
513
+ assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after bare refocus'
514
+ assert state['current_phase'] == 'reground', 'state.json current_phase should reset to reground after bare refocus'
515
+ assert state['requires_reground'] is True, 'state.json requires_reground should be true after bare refocus'
516
+ assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should reset to completion-regrounder after bare refocus'
517
+ assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'continuation_reason should record the bare refocus'
518
+ assert plan['plan_basis'] == 'user_refocus', 'plan.json plan_basis should be user_refocus after bare refocus'
519
+ assert active['status'] == 'idle', 'active-slice.json status should reset to idle after bare refocus'
520
+ PY
521
+
522
+ SYNTH_REPLACEMENT_SESSION="$TMPDIR/session-synthesized-active-replacement.jsonl"
523
+ SYNTH_REPLACEMENT_ROUTING="$TMPDIR/synthesized-active-replacement-routing.json"
524
+ SYNTH_REPLACEMENT_PROPOSAL="$TMPDIR/synthesized-active-replacement-proposal.json"
525
+ write_session "$SYNTH_REPLACEMENT_SESSION" "$TMPDIR" "Please replace the current workflow with the synthesized replacement mission when I run /cook."
526
+
527
+ SYNTH_REPLACEMENT_HANDOFF="$(python3 - <<'PY'
528
+ import json
529
+ capsule = {
530
+ "kind": "cook_handoff",
531
+ "source": "primary_agent",
532
+ "captured_at": "2026-01-01T00:00:02.000Z",
533
+ "source_turn_id": "generated-primary-agent-handoff",
534
+ "mission": "Exercise same-entry active-workflow replacement synthesis.",
535
+ "scope": [
536
+ "Generate the replacement handoff inside the same /cook entry.",
537
+ "Keep the chooser and final Start/Cancel confirmation truthful."
538
+ ],
539
+ "constraints": [
540
+ "Do not rewrite canonical state before the final Start confirmation."
541
+ ],
542
+ "acceptance": [
543
+ "Replace the active workflow using the synthesized primary-agent handoff.",
544
+ "Keep deterministic coverage for same-entry active replacement."
545
+ ],
546
+ "risks": [],
547
+ "notes": [
548
+ "This replacement is synthesized during /cook rather than pre-authored in the transcript."
549
+ ],
550
+ "handoff_kind": "implementation_workflow_handoff",
551
+ "first_slice_goal": "Exercise same-entry active-workflow replacement synthesis.",
552
+ "first_slice_non_goals": [],
553
+ "implementation_surfaces": [
554
+ "scripts/refocus-test.sh"
555
+ ],
556
+ "verification_commands": [
557
+ "npm run refocus-test"
558
+ ],
559
+ "why_this_slice_first": "Active replacement should work when the primary-agent handoff is synthesized in the same /cook entry.",
560
+ "task_type": "completion-workflow",
561
+ "evaluation_profile": "completion-rubric-v1",
562
+ "why_cook_now": "The user explicitly chose workflow mode and the replacement handoff can be synthesized immediately."
563
+ }
564
+ print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
565
+ PY
566
+ )"
567
+
568
+ PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$SYNTH_REPLACEMENT_HANDOFF" \
569
+ PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
570
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
571
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$SYNTH_REPLACEMENT_PROPOSAL" \
572
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$SYNTH_REPLACEMENT_ROUTING" \
573
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
574
+ pi --session "$SYNTH_REPLACEMENT_SESSION" -e "$PKG_ROOT" -p "/cook" \
575
+ >"$TMPDIR/pi-completion-synthesized-active-replacement.out" 2>"$TMPDIR/pi-completion-synthesized-active-replacement.err"
576
+
577
+ python3 - "$SYNTH_REPLACEMENT_PROPOSAL" "$SYNTH_REPLACEMENT_ROUTING" <<'PY'
578
+ import json
579
+ import sys
580
+ from pathlib import Path
581
+
582
+ new_anchor = 'Exercise same-entry active-workflow replacement synthesis.'
583
+ proposal = json.loads(Path(sys.argv[1]).read_text())
584
+ routing = json.loads(Path(sys.argv[2]).read_text())
303
585
  state = json.loads(Path('.agent/state.json').read_text())
304
- startup_plan = json.loads(Path('.agent/startup-plan.json').read_text())
305
- startup_plan_md = Path('.agent/startup-plan.md').read_text()
306
586
  plan = json.loads(Path('.agent/plan.json').read_text())
307
587
  active = json.loads(Path('.agent/active-slice.json').read_text())
308
588
 
309
- mission = 'Remove the completion status line while keeping the completion widget.'
310
- assert routing['action'] == 'refocus', routing
311
- assert routing['reason'] == 'generated_replacement_startup_plan', routing
312
- assert chooser['choices'][1].startswith('Start new workflow from same-entry primary-agent startup plan'), chooser
313
- assert proposal['source'] == 'deferred_primary_agent_handoff', proposal
314
- assert state['mission_anchor'] == mission, state
315
- assert state['current_phase'] == 'reground', state
316
- assert state['next_mandatory_role'] == 'completion-regrounder', state
317
- assert startup_plan['mission_anchor'] == mission, startup_plan
318
- assert startup_plan['source'] == 'deferred_primary_agent_handoff', startup_plan
319
- assert '## Goal' in startup_plan_md and mission in startup_plan_md, startup_plan_md
320
- assert plan['mission_anchor'] == mission, plan
321
- assert active['mission_anchor'] == mission, active
322
- assert 'Refocused completion mission from same-entry primary-agent startup plan' in output, output
589
+ assert proposal['mission'] == new_anchor, 'same-entry synthesized replacement should preserve the replacement proposal mission'
590
+ assert routing['action'] == 'refocus', 'same-entry synthesized replacement should classify active bare /cook as refocus'
591
+ assert routing['reason'] == 'fresh_explicit_handoff', 'same-entry synthesized replacement should reuse the explicit-handoff routing reason because /cook synthesized an explicit handoff'
592
+ assert routing['proposalSource'] == 'handoff_capsule', 'same-entry synthesized replacement should surface the synthesized handoff as a handoff capsule source'
593
+ assert state['mission_anchor'] == new_anchor, 'state.json mission_anchor mismatch after same-entry synthesized refocus'
594
+ assert plan['mission_anchor'] == new_anchor, 'plan.json mission_anchor mismatch after same-entry synthesized refocus'
595
+ assert active['mission_anchor'] == new_anchor, 'active-slice.json mission_anchor mismatch after same-entry synthesized refocus'
596
+ assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'same-entry synthesized replacement should record the /cook refocus continuation reason'
323
597
  PY
324
598
 
325
- echo "refocus test passed: $PKG_ROOT"
599
+ echo "refocus test passed: $TMPDIR"