@linimin/pi-letscook 0.1.36 → 0.1.39

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,251 @@ 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
194
+ from pathlib import Path
195
+
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'
200
+ PY
201
+
202
+ # No workflow yet: bare /cook structured fallback should normalize placeholder planning phrasing
203
+ # into the concrete implementation mission when scope/acceptance clearly describe shipped work.
204
+ SESSION_ZERO_NORMALIZED="$TMPDIR/session-zero-normalized.jsonl"
205
+ DISCUSSION_ZERO_NORMALIZED=$'Mission: 開始實作這個方案\nScope:\n- Normalize bare /cook planning phrasing into shipped implementation missions.\n- Keep analyst-derived and structured-fallback proposals aligned.\nConstraints:\n- Do not rewrite the supported bare-discussion mission anchor once it is clear.\nAcceptance:\n- Add deterministic regression coverage for startup normalization and refocus gating.\n- Keep the approval-only Start/Cancel rewrite gate.'
206
+ DISCUSSION_SNAPSHOT_ZERO_NORMALIZED="$TMPDIR/context-proposal-normalized-fallback.json"
207
+ write_session "$SESSION_ZERO_NORMALIZED" "$ROOT" "$DISCUSSION_ZERO_NORMALIZED"
208
+
209
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
210
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
211
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_NORMALIZED" \
212
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
213
+ pi --session "$SESSION_ZERO_NORMALIZED" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-normalized-fallback.out" 2>"$TMPDIR/pi-completion-context-proposal-normalized-fallback.err"
214
+
215
+ python3 - "$DISCUSSION_SNAPSHOT_ZERO_NORMALIZED" <<'PY'
216
+ import json
217
+ import sys
218
+ from pathlib import Path
219
+
220
+ mission = 'Normalize bare /cook planning phrasing into shipped implementation missions.'
221
+ proposal = json.loads(Path(sys.argv[1]).read_text())
222
+ state = json.loads(Path('.agent/state.json').read_text())
223
+ plan = json.loads(Path('.agent/plan.json').read_text())
224
+ active = json.loads(Path('.agent/active-slice.json').read_text())
225
+ mission_text = Path('.agent/mission.md').read_text()
226
+
227
+ assert mission in mission_text, 'normalized structured-fallback startup should update .agent/mission.md to the implementation mission'
228
+ assert proposal['mission'] == mission, 'structured-fallback startup should normalize the placeholder planning mission'
229
+ assert state['mission_anchor'] == mission, 'state.json mission_anchor should use the normalized implementation mission'
230
+ assert plan['mission_anchor'] == mission, 'plan.json mission_anchor should use the normalized implementation mission'
231
+ assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor should use the normalized implementation mission'
232
+ assert proposal['source'] == 'session', 'normalized structured-fallback startup should still record session fallback as the proposal source'
233
+ assert proposal['scope'][0] == mission, 'normalized structured-fallback startup should derive the mission from shipped-work scope'
234
+ PY
235
+
236
+ rm -rf .agent
237
+
238
+ # No workflow yet: analyst-derived and strict structured fallback proposals should converge on the same
239
+ # normalized implementation mission for the same planning-phrased discussion.
240
+ SESSION_ZERO_ANALYST_NORMALIZED="$TMPDIR/session-zero-analyst-normalized.jsonl"
241
+ ANALYST_OUTPUT_ZERO_NORMALIZED='{"mission":"開始實作這個方案","scope":["Normalize bare /cook planning phrasing into shipped implementation missions.","Keep analyst-derived and structured-fallback proposals aligned."],"constraints":["Do not rewrite the supported bare-discussion mission anchor once it is clear."],"acceptance":["Add deterministic regression coverage for startup normalization and refocus gating.","Keep the approval-only Start/Cancel rewrite gate."],"task_type":"completion-workflow","evaluation_profile":"completion-rubric-v1","confidence":0.93}'
242
+ DISCUSSION_SNAPSHOT_ZERO_ANALYST_NORMALIZED="$TMPDIR/context-proposal-normalized-analyst.json"
243
+ write_session "$SESSION_ZERO_ANALYST_NORMALIZED" "$ROOT" "$DISCUSSION_ZERO_NORMALIZED"
244
+
245
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
246
+ PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$ANALYST_OUTPUT_ZERO_NORMALIZED" \
247
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_ANALYST_NORMALIZED" \
248
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
249
+ pi --session "$SESSION_ZERO_ANALYST_NORMALIZED" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-normalized-analyst.out" 2>"$TMPDIR/pi-completion-context-proposal-normalized-analyst.err"
250
+
251
+ python3 - "$DISCUSSION_SNAPSHOT_ZERO_ANALYST_NORMALIZED" <<'PY'
252
+ import json
253
+ import sys
115
254
  from pathlib import Path
116
255
 
117
- assert not Path('.agent').exists(), '/cook should not bootstrap canonical state from discussion alone without analyst output'
256
+ mission = 'Normalize bare /cook planning phrasing into shipped implementation missions.'
257
+ proposal = json.loads(Path(sys.argv[1]).read_text())
258
+ state = json.loads(Path('.agent/state.json').read_text())
259
+
260
+ assert proposal['mission'] == mission, 'analyst-derived startup should normalize the same placeholder planning mission to the same implementation mission'
261
+ assert state['mission_anchor'] == mission, 'analyst-derived startup should converge on the same canonical mission anchor as structured fallback'
262
+ assert proposal['analysis']['taskType'] == 'completion-workflow', 'analyst-derived normalization should preserve task_type hints'
263
+ assert proposal['analysis']['evaluationProfile'] == 'completion-rubric-v1', 'analyst-derived normalization should preserve evaluation_profile hints'
264
+ PY
265
+
266
+ rm -rf .agent
267
+
268
+ # No workflow yet: planning-only deliverables should preserve planning missions when discussion clearly says
269
+ # docs-only / no-code plan output instead of shipped code, test, or runtime work.
270
+ SESSION_ZERO_PLANNING_ONLY="$TMPDIR/session-zero-planning-only.jsonl"
271
+ DISCUSSION_ZERO_PLANNING_ONLY=$'Mission: 開始實作這個方案\nScope:\n- Draft the migration plan for the /cook mission-normalization rollout.\nConstraints:\n- Docs only; do not implement runtime changes.\nAcceptance:\n- Produce the proposal text for review.'
272
+ DISCUSSION_SNAPSHOT_ZERO_PLANNING_ONLY="$TMPDIR/context-proposal-planning-only.json"
273
+ write_session "$SESSION_ZERO_PLANNING_ONLY" "$ROOT" "$DISCUSSION_ZERO_PLANNING_ONLY"
274
+
275
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
276
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
277
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_PLANNING_ONLY" \
278
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
279
+ pi --session "$SESSION_ZERO_PLANNING_ONLY" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-planning-only.out" 2>"$TMPDIR/pi-completion-context-proposal-planning-only.err"
280
+
281
+ python3 - "$DISCUSSION_SNAPSHOT_ZERO_PLANNING_ONLY" <<'PY'
282
+ import json
283
+ import sys
284
+ from pathlib import Path
285
+
286
+ mission = '開始實作這個方案.'
287
+ proposal = json.loads(Path(sys.argv[1]).read_text())
288
+ state = json.loads(Path('.agent/state.json').read_text())
289
+
290
+ assert proposal['mission'] == mission, 'planning-only startup should preserve the planning mission when the deliverable is explicitly a plan'
291
+ assert state['mission_anchor'] == mission, 'planning-only startup should keep the planning mission anchor in canonical state'
292
+ PY
293
+
294
+ rm -rf .agent
295
+
296
+ # No workflow yet: support-docs-only deliverables should also preserve planning missions even without
297
+ # an explicit docs-only/no-code phrase, as long as the deliverable stays limited to support documentation.
298
+ SESSION_ZERO_SUPPORT_DOCS_ONLY="$TMPDIR/session-zero-support-docs-only.jsonl"
299
+ DISCUSSION_ZERO_SUPPORT_DOCS_ONLY=$'Mission: 開始實作這個方案\nScope:\n- Update README for the /cook mission-normalization behavior.\nConstraints:\n- Keep the approval-only Start/Cancel gate unchanged.\nAcceptance:\n- Add documentation for the operator-facing refocus flow.'
300
+ DISCUSSION_SNAPSHOT_ZERO_SUPPORT_DOCS_ONLY="$TMPDIR/context-proposal-support-docs-only.json"
301
+ write_session "$SESSION_ZERO_SUPPORT_DOCS_ONLY" "$ROOT" "$DISCUSSION_ZERO_SUPPORT_DOCS_ONLY"
302
+
303
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
304
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
305
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_SUPPORT_DOCS_ONLY" \
306
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
307
+ pi --session "$SESSION_ZERO_SUPPORT_DOCS_ONLY" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-support-docs-only.out" 2>"$TMPDIR/pi-completion-context-proposal-support-docs-only.err"
308
+
309
+ python3 - "$DISCUSSION_SNAPSHOT_ZERO_SUPPORT_DOCS_ONLY" <<'PY'
310
+ import json
311
+ import sys
312
+ from pathlib import Path
313
+
314
+ mission = '開始實作這個方案.'
315
+ proposal = json.loads(Path(sys.argv[1]).read_text())
316
+ state = json.loads(Path('.agent/state.json').read_text())
317
+
318
+ assert proposal['mission'] == mission, 'support-docs-only startup should preserve the planning mission even without an explicit docs-only phrase'
319
+ assert state['mission_anchor'] == mission, 'support-docs-only startup should keep the planning mission anchor in canonical state'
320
+ assert proposal['scope'] == ['Update README for the /cook mission-normalization behavior.'], 'support-docs-only startup should keep the documentation scope item'
321
+ assert proposal['acceptance'] == ['Add documentation for the operator-facing refocus flow.'], 'support-docs-only startup should keep the documentation acceptance item'
322
+ PY
323
+
324
+ rm -rf .agent
325
+
326
+ # No workflow yet: analyst-derived generic planning missions should still fail closed when discussion
327
+ # never provides a clear implementation anchor, instead of promoting vague non-doc scope.
328
+ SESSION_ZERO_ANALYST_AMBIGUOUS_GENERIC="$TMPDIR/session-zero-analyst-ambiguous-generic.jsonl"
329
+ DISCUSSION_ZERO_ANALYST_AMBIGUOUS_GENERIC=$'We should revisit the completion widget while roles are active and make the outcome easier to follow without deciding the exact implementation yet.'
330
+ ANALYST_OUTPUT_ZERO_AMBIGUOUS_GENERIC='{"mission":"開始實作這個方案","scope":["The completion widget during active roles."],"constraints":["Keep the approval-only Start/Cancel gate unchanged."],"acceptance":["Current behavior stays understandable."],"task_type":"completion-workflow","evaluation_profile":"completion-rubric-v1","confidence":0.74}'
331
+ DISCUSSION_SNAPSHOT_ZERO_ANALYST_AMBIGUOUS_GENERIC="$TMPDIR/context-proposal-analyst-ambiguous-generic.json"
332
+ write_session "$SESSION_ZERO_ANALYST_AMBIGUOUS_GENERIC" "$ROOT" "$DISCUSSION_ZERO_ANALYST_AMBIGUOUS_GENERIC"
333
+
334
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
335
+ PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$ANALYST_OUTPUT_ZERO_AMBIGUOUS_GENERIC" \
336
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_ANALYST_AMBIGUOUS_GENERIC" \
337
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
338
+ pi --session "$SESSION_ZERO_ANALYST_AMBIGUOUS_GENERIC" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-analyst-ambiguous-generic.out" 2>"$TMPDIR/pi-completion-context-proposal-analyst-ambiguous-generic.err"
339
+
340
+ python3 - "$TMPDIR/pi-completion-context-proposal-analyst-ambiguous-generic.out" "$TMPDIR/pi-completion-context-proposal-analyst-ambiguous-generic.err" "$DISCUSSION_SNAPSHOT_ZERO_ANALYST_AMBIGUOUS_GENERIC" <<'PY'
341
+ import sys
342
+ from pathlib import Path
343
+
344
+ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
345
+ snapshot = Path(sys.argv[3])
346
+ assert not Path('.agent').exists(), 'analyst-derived ambiguous generic discussion should fail closed without writing canonical state'
347
+ assert not snapshot.exists(), 'analyst-derived ambiguous generic discussion should not emit a proposal snapshot when bare /cook fails closed'
348
+ assert 'Bare /cook failed closed' in output, 'analyst-derived ambiguous generic discussion should explain the fail-closed startup outcome'
118
349
  PY
119
350
 
120
351
  # No workflow yet: /cook with no goal should infer from recent discussion through analyst output.
@@ -173,21 +404,147 @@ assert 'evaluation_profile=completion-rubric-v1' in state['continuation_reason']
173
404
  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
405
  PY
175
406
 
176
- # Completed workflow: /cook with no goal should infer the next round from recent discussion through analyst output.
407
+ # Active workflow: bare /cook with matching structured discussion should classify as continue
408
+ # and resume the current workflow without opening the chooser or rewriting canonical state.
409
+ SESSION_ONE_CONTINUE="$TMPDIR/session-one-continue.jsonl"
410
+ 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.'
411
+ CONTINUE_ROUTING_ONE="$TMPDIR/active-continue-routing.json"
412
+ CONTINUE_RESUME_PROMPT_ONE="$TMPDIR/active-continue-resume.txt"
413
+ CONTINUE_CHOOSER_ONE="$TMPDIR/unexpected-active-continue-chooser.json"
414
+ write_session "$SESSION_ONE_CONTINUE" "$ROOT" "$DISCUSSION_ONE_CONTINUE"
415
+
416
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
417
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$CONTINUE_ROUTING_ONE" \
418
+ PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$CONTINUE_RESUME_PROMPT_ONE" \
419
+ PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$CONTINUE_CHOOSER_ONE" \
420
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
421
+ 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"
422
+
423
+ python3 - "$CONTINUE_ROUTING_ONE" "$CONTINUE_RESUME_PROMPT_ONE" "$CONTINUE_CHOOSER_ONE" <<'PY'
424
+ import json
425
+ import sys
426
+ from pathlib import Path
427
+
428
+ mission = 'Remove the completion status line while keeping the completion widget.'
429
+ routing = json.loads(Path(sys.argv[1]).read_text())
430
+ resume = Path(sys.argv[2]).read_text()
431
+ chooser_path = Path(sys.argv[3])
432
+ state = json.loads(Path('.agent/state.json').read_text())
433
+ plan = json.loads(Path('.agent/plan.json').read_text())
434
+ active = json.loads(Path('.agent/active-slice.json').read_text())
435
+
436
+ assert routing['mode'] == 'bare', 'active bare /cook continue regression should snapshot bare routing mode'
437
+ assert 'explicitGoal' not in routing, 'active bare /cook continue routing should not expose removed explicit-goal shim fields'
438
+ assert 'explicitGoalProvided' not in routing, 'active bare /cook continue routing should not expose removed explicit-goal shim fields'
439
+ assert routing['action'] == 'continue', 'matching structured discussion should classify active bare /cook as continue'
440
+ assert routing['reason'] == 'matching_mission', 'matching structured discussion should keep the current mission rather than refocus'
441
+ assert routing['currentMissionAnchor'] == mission, 'continue routing should preserve the current mission anchor'
442
+ assert routing['proposedMissionAnchor'] == mission, 'continue routing should keep the proposed mission anchored to the current mission'
443
+ assert 'Resume the completion workflow from canonical state.' in resume, 'active bare /cook continue should still use the canonical resume prompt'
444
+ assert not chooser_path.exists(), 'active bare /cook continue should not open the refocus chooser'
445
+ assert state['mission_anchor'] == mission, 'active bare /cook continue should keep state.json unchanged'
446
+ assert plan['mission_anchor'] == mission, 'active bare /cook continue should keep plan.json unchanged'
447
+ assert active['mission_anchor'] == mission, 'active bare /cook continue should keep active-slice.json unchanged'
448
+ PY
449
+
450
+ # Active workflow: bare /cook with a placeholder planning mission should still route through the existing
451
+ # refocus chooser and final Start/Cancel gate before canonical state is rewritten.
452
+ SESSION_ONE_REFOCUS_NORMALIZED="$TMPDIR/session-one-refocus-normalized.jsonl"
453
+ DISCUSSION_ONE_REFOCUS_NORMALIZED=$'Mission: 開始實作這個方案\nScope:\n- Normalize bare /cook planning phrasing into implementation-result missions.\n- Keep the approval-only Start/Cancel gate before rewriting canonical state.\nConstraints:\n- Do not resume the current widget mission.\nAcceptance:\n- Route through chooser-driven refocus before rewriting canonical state.'
454
+ REFOCUS_ROUTING_ONE="$TMPDIR/active-refocus-routing.json"
455
+ REFOCUS_CHOOSER_ONE="$TMPDIR/active-refocus-chooser.json"
456
+ REFOCUS_PROPOSAL_ONE="$TMPDIR/active-refocus-proposal.json"
457
+ REFOCUS_UI_ONE="$TMPDIR/active-refocus-ui.json"
458
+ write_session "$SESSION_ONE_REFOCUS_NORMALIZED" "$ROOT" "$DISCUSSION_ONE_REFOCUS_NORMALIZED"
459
+
460
+ PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
461
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_ACTION=start \
462
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
463
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$REFOCUS_ROUTING_ONE" \
464
+ PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$REFOCUS_CHOOSER_ONE" \
465
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$REFOCUS_PROPOSAL_ONE" \
466
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_PATH="$REFOCUS_UI_ONE" \
467
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
468
+ pi --session "$SESSION_ONE_REFOCUS_NORMALIZED" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-active-refocus-normalized.out" 2>"$TMPDIR/pi-completion-context-proposal-active-refocus-normalized.err"
469
+
470
+ python3 - "$REFOCUS_ROUTING_ONE" "$REFOCUS_CHOOSER_ONE" "$REFOCUS_PROPOSAL_ONE" "$REFOCUS_UI_ONE" <<'PY'
471
+ import json
472
+ import sys
473
+ from pathlib import Path
474
+
475
+ mission = 'Normalize bare /cook planning phrasing into implementation-result missions.'
476
+ routing = json.loads(Path(sys.argv[1]).read_text())
477
+ chooser = json.loads(Path(sys.argv[2]).read_text())
478
+ proposal = json.loads(Path(sys.argv[3]).read_text())
479
+ ui = json.loads(Path(sys.argv[4]).read_text())
480
+ state = json.loads(Path('.agent/state.json').read_text())
481
+ plan = json.loads(Path('.agent/plan.json').read_text())
482
+ active = json.loads(Path('.agent/active-slice.json').read_text())
483
+
484
+ assert routing['mode'] == 'bare', 'active bare /cook refocus normalization should snapshot bare routing mode'
485
+ assert 'explicitGoal' not in routing, 'active bare /cook refocus routing should not expose removed explicit-goal shim fields'
486
+ assert 'explicitGoalProvided' not in routing, 'active bare /cook refocus routing should not expose removed explicit-goal shim fields'
487
+ assert routing['action'] == 'refocus', 'placeholder planning mission should still classify active bare /cook as refocus when the normalized mission changes'
488
+ assert routing['reason'] == 'clear_refocus', 'active bare /cook refocus normalization should keep the clear_refocus routing reason'
489
+ assert routing['proposedMissionAnchor'] == mission, 'active bare /cook refocus should normalize the proposed mission before canonical rewrite'
490
+ assert chooser['choices'][1].startswith('Start new workflow from recent discussion'), 'active bare /cook refocus should still route through the existing chooser copy before rewrite'
491
+ assert [action['id'] for action in ui['actions']] == ['start', 'cancel'], 'active bare /cook refocus should still end at the approval-only Start/Cancel gate'
492
+ assert proposal['mission'] == mission, 'active bare /cook refocus proposal snapshot should expose the normalized implementation mission'
493
+ assert state['mission_anchor'] == mission, 'active bare /cook refocus should rewrite canonical state to the normalized mission only after approval'
494
+ assert plan['mission_anchor'] == mission, 'active bare /cook refocus should rewrite plan.json only after approval'
495
+ assert active['mission_anchor'] == mission, 'active bare /cook refocus should rewrite active-slice.json only after approval'
496
+ PY
497
+
498
+ # Completed workflow: bare /cook should normalize placeholder planning phrasing for the next workflow
499
+ # round too, not only for fresh startup.
500
+ mark_done
501
+
502
+ SESSION_TWO_NORMALIZED="$TMPDIR/session-two-normalized.jsonl"
503
+ DISCUSSION_TWO_NORMALIZED=$'Mission: 開始實作這個方案\nScope:\n- Normalize bare /cook planning phrasing for the next workflow round.\n- Reset canonical state for the new implementation mission.\nConstraints:\n- Do not resume the completed workflow when the new round is clearly different.\nAcceptance:\n- Start a new round with the normalized mission anchor.'
504
+ DISCUSSION_SNAPSHOT_TWO_NORMALIZED="$TMPDIR/context-proposal-next-round-normalized.json"
505
+ write_session "$SESSION_TWO_NORMALIZED" "$ROOT" "$DISCUSSION_TWO_NORMALIZED"
506
+
507
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
508
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
509
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_TWO_NORMALIZED" \
510
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
511
+ pi --session "$SESSION_TWO_NORMALIZED" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-next-round-normalized.out" 2>"$TMPDIR/pi-completion-context-proposal-next-round-normalized.err"
512
+
513
+ python3 - "$DISCUSSION_SNAPSHOT_TWO_NORMALIZED" <<'PY'
514
+ import json
515
+ import sys
516
+ from pathlib import Path
517
+
518
+ mission = 'Normalize bare /cook planning phrasing for the next workflow round.'
519
+ proposal = json.loads(Path(sys.argv[1]).read_text())
520
+ state = json.loads(Path('.agent/state.json').read_text())
521
+ plan = json.loads(Path('.agent/plan.json').read_text())
522
+ active = json.loads(Path('.agent/active-slice.json').read_text())
523
+
524
+ assert proposal['mission'] == mission, 'done-workflow structured fallback should normalize the placeholder planning mission for the next round'
525
+ assert state['mission_anchor'] == mission, 'done-workflow startup should rewrite canonical state to the normalized next-round mission'
526
+ assert plan['mission_anchor'] == mission, 'done-workflow startup should rewrite plan.json to the normalized next-round mission'
527
+ assert active['mission_anchor'] == mission, 'done-workflow startup should rewrite active-slice.json to the normalized next-round mission'
528
+ assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'done-workflow normalization should still route through refocus semantics for the next round'
529
+ PY
530
+
531
+ # Completed workflow: bare /cook should use the same strict structured fallback for the next workflow round when analyst output is unavailable.
177
532
  mark_done
178
533
 
179
534
  SESSION_TWO="$TMPDIR/session-two.jsonl"
180
535
  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}'
536
+ DISCUSSION_SNAPSHOT_TWO="$TMPDIR/context-proposal-next-round-structured-fallback.json"
182
537
  write_session "$SESSION_TWO" "$ROOT" "$DISCUSSION_TWO"
183
538
 
184
539
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
185
- PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$ANALYST_OUTPUT_TWO" \
540
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
541
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_TWO" \
186
542
  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
543
+ 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
544
 
189
- python3 - <<'PY'
545
+ python3 - "$DISCUSSION_SNAPSHOT_TWO" <<'PY'
190
546
  import json
547
+ import sys
191
548
  from pathlib import Path
192
549
 
193
550
  mission = 'Ship the next workflow round for richer context-derived /cook startup.'
@@ -198,6 +555,7 @@ profile = json.loads(Path('.agent/profile.json').read_text())
198
555
  state = json.loads(Path('.agent/state.json').read_text())
199
556
  plan = json.loads(Path('.agent/plan.json').read_text())
200
557
  active = json.loads(Path('.agent/active-slice.json').read_text())
558
+ proposal = json.loads(Path(sys.argv[1]).read_text())
201
559
 
202
560
  assert mission in mission_text, '.agent/mission.md did not update to the next-round context-derived mission anchor'
203
561
  assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after next-round startup'
@@ -211,6 +569,8 @@ assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluatio
211
569
  assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after starting the next workflow round'
212
570
  assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after starting the next workflow round'
213
571
  assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after starting the next workflow round'
572
+ assert proposal['mission'] == mission, 'next-round structured-fallback proposal snapshot should preserve the discussion mission anchor'
573
+ assert proposal['source'] == 'session', 'next-round structured-fallback proposal snapshot should record the strict session fallback source'
214
574
  assert state['current_phase'] == 'reground', 'state.json current_phase should reset to reground for the next workflow round'
215
575
  assert state['continuation_policy'] == 'continue', 'continuation_policy should reset to continue for the next workflow round'
216
576
  assert state['requires_reground'] is True, 'requires_reground should reset to true for the next workflow round'
@@ -224,153 +584,124 @@ assert plan['plan_basis'] == 'user_refocus', 'plan_basis should reset to user_re
224
584
  assert active['status'] == 'idle', 'active-slice should reset to idle for the next workflow round'
225
585
  PY
226
586
 
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.
229
- SESSION_THREE="$TMPDIR/session-three.jsonl"
230
- 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.'
231
- write_session "$SESSION_THREE" "$ROOT" "$DISCUSSION_THREE"
232
-
233
- PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
234
- PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
235
- PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
236
- 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
238
-
239
- python3 - <<'PY'
587
+ # Active workflow: inline /cook args should be rejected before proposal/routing helpers run
588
+ # and should leave canonical state unchanged.
589
+ ACTIVE_INLINE_REJECTION_ROUTING="$TMPDIR/context-proposal-active-inline-arg-routing.json"
590
+ ACTIVE_INLINE_REJECTION_PROPOSAL="$TMPDIR/context-proposal-active-inline-arg-proposal.json"
591
+ ACTIVE_INLINE_REJECTION_CHOOSER="$TMPDIR/context-proposal-active-inline-arg-chooser.json"
592
+ ACTIVE_INLINE_REJECTION_BASELINE="$TMPDIR/context-proposal-active-inline-before.json"
593
+ python3 - "$ACTIVE_INLINE_REJECTION_BASELINE" <<'PY'
240
594
  import json
595
+ import sys
241
596
  from pathlib import Path
242
597
 
243
- mission = 'Explicit replacement mission for the active workflow.'
244
- expected_task_type = 'completion-workflow'
245
- expected_eval_profile = 'completion-rubric-v1'
246
- mission_text = Path('.agent/mission.md').read_text()
247
- profile = json.loads(Path('.agent/profile.json').read_text())
248
- state = json.loads(Path('.agent/state.json').read_text())
249
- plan = json.loads(Path('.agent/plan.json').read_text())
250
- active = json.loads(Path('.agent/active-slice.json').read_text())
251
-
252
- assert mission in mission_text, '.agent/mission.md did not update to the explicit replacement mission anchor'
253
- assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after explicit-goal replacement'
254
- assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after explicit-goal replacement'
255
- assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after explicit-goal replacement'
256
- assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after explicit-goal replacement'
257
- assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after explicit-goal replacement'
258
- assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after explicit-goal replacement'
259
- assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after explicit-goal replacement'
260
- assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after explicit-goal replacement'
261
- assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after explicit-goal replacement'
262
- assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after explicit-goal replacement'
263
- assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after explicit-goal replacement'
264
- assert state['current_phase'] == 'reground', 'current_phase should reset to reground after explicit-goal replacement'
265
- assert state['continuation_policy'] == 'continue', 'continuation_policy should stay continue after explicit-goal replacement'
266
- assert state['next_mandatory_role'] == 'completion-regrounder', 'next role should reset to completion-regrounder after explicit-goal replacement'
267
- assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'continuation_reason should record the explicit-goal replacement'
268
- assert 'task_type=completion-workflow' in state['continuation_reason'], 'explicit-goal replacement should persist the selected task_type'
269
- assert 'evaluation_profile=completion-rubric-v1' in state['continuation_reason'], 'explicit-goal replacement should persist the selected evaluation_profile'
270
- assert 'critique outcome=accepted critique=none' in state['continuation_reason'], 'explicit-goal replacement should persist the accepted critique outcome even when no critique was derived'
271
- assert 'Preserve the richer proposal structure from discussion.' not in state['continuation_reason'], 'session scope should not be merged when analyst output is unavailable'
272
- assert 'Keep explicit goals as the mission anchor when they conflict with earlier text.' not in state['continuation_reason'], 'session constraints should not be merged when analyst output is unavailable'
273
- assert 'Refresh canonical state from the replacement mission.' not in state['continuation_reason'], 'session acceptance should not be merged when analyst output is unavailable'
274
- assert plan['plan_basis'] == 'user_refocus', 'plan_basis should be user_refocus after explicit-goal replacement'
275
- assert active['status'] == 'idle', 'active slice should reset to idle after explicit-goal replacement'
598
+ tracked = [
599
+ Path('.agent/mission.md'),
600
+ Path('.agent/profile.json'),
601
+ Path('.agent/state.json'),
602
+ Path('.agent/plan.json'),
603
+ Path('.agent/active-slice.json'),
604
+ Path('.agent/verification-evidence.json'),
605
+ ]
606
+ Path(sys.argv[1]).write_text(json.dumps({path.name: path.read_text() for path in tracked}, indent=2) + '\n')
276
607
  PY
277
608
 
278
- # Active workflow: cancelling the replacement proposal should keep the current workflow and redirect
279
- # the user back to the main chat before rerunning /cook.
280
- SESSION_THREE_CANCEL="$TMPDIR/session-three-cancel.jsonl"
281
- DISCUSSION_THREE_CANCEL=$'Scope:\n- Keep the current workflow unchanged when replacement confirmation is cancelled.\nConstraints:\n- Do not rewrite canonical state after cancel.\nAcceptance:\n- Print rerun guidance.'
282
- write_session "$SESSION_THREE_CANCEL" "$ROOT" "$DISCUSSION_THREE_CANCEL"
283
-
284
609
  PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
285
- PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=cancel \
610
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
286
611
  PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
612
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$ACTIVE_INLINE_REJECTION_PROPOSAL" \
613
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$ACTIVE_INLINE_REJECTION_ROUTING" \
614
+ PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$ACTIVE_INLINE_REJECTION_CHOOSER" \
287
615
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
288
- pi --session "$SESSION_THREE_CANCEL" -e "$PKG_ROOT" -p "/cook Cancelled replacement mission for the active workflow" >"$TMPDIR/pi-completion-context-proposal-replacement-cancel.out" 2>"$TMPDIR/pi-completion-context-proposal-replacement-cancel.err"
616
+ pi -e "$PKG_ROOT" -p "/cook Replacement mission for the active workflow" >"$TMPDIR/pi-completion-context-proposal-active-inline-arg.out" 2>"$TMPDIR/pi-completion-context-proposal-active-inline-arg.err"
289
617
 
290
- python3 - "$TMPDIR/pi-completion-context-proposal-replacement-cancel.out" "$TMPDIR/pi-completion-context-proposal-replacement-cancel.err" <<'PY'
618
+ python3 - "$TMPDIR/pi-completion-context-proposal-active-inline-arg.out" "$TMPDIR/pi-completion-context-proposal-active-inline-arg.err" "$ACTIVE_INLINE_REJECTION_ROUTING" "$ACTIVE_INLINE_REJECTION_PROPOSAL" "$ACTIVE_INLINE_REJECTION_CHOOSER" "$ACTIVE_INLINE_REJECTION_BASELINE" <<'PY'
291
619
  import json
292
620
  import sys
293
621
  from pathlib import Path
294
622
 
295
- mission = 'Explicit replacement mission for the active workflow.'
296
- state = json.loads(Path('.agent/state.json').read_text())
297
- plan = json.loads(Path('.agent/plan.json').read_text())
298
- active = json.loads(Path('.agent/active-slice.json').read_text())
299
623
  output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
624
+ routing = Path(sys.argv[3])
625
+ proposal = Path(sys.argv[4])
626
+ chooser = Path(sys.argv[5])
627
+ before = json.loads(Path(sys.argv[6]).read_text())
628
+ tracked = [
629
+ Path('.agent/mission.md'),
630
+ Path('.agent/profile.json'),
631
+ Path('.agent/state.json'),
632
+ Path('.agent/plan.json'),
633
+ Path('.agent/active-slice.json'),
634
+ Path('.agent/verification-evidence.json'),
635
+ ]
300
636
 
301
- assert state['mission_anchor'] == mission, 'replacement proposal cancel should keep the existing mission anchor'
302
- assert plan['mission_anchor'] == mission, 'replacement proposal cancel should keep plan.json unchanged'
303
- assert active['mission_anchor'] == mission, 'replacement proposal cancel should keep active-slice.json unchanged'
304
- assert 'Discuss changes in the main chat and rerun /cook.' in output, 'replacement proposal cancel should redirect back to the main chat and rerun /cook'
637
+ assert 'Inline /cook arguments are no longer supported.' in output, 'active inline /cook args should explain the hard rejection'
638
+ assert 'Clarify the mission in the main chat and rerun bare /cook.' in output, 'active inline /cook args should redirect users back to main chat plus bare /cook'
639
+ assert not routing.exists(), 'active inline /cook args should not run active-workflow routing'
640
+ assert not proposal.exists(), 'active inline /cook args should not open proposal confirmation'
641
+ assert not chooser.exists(), 'active inline /cook args should not open the existing-workflow chooser'
642
+ after = {path.name: path.read_text() for path in tracked}
643
+ assert before == after, 'active inline /cook args should leave canonical files unchanged'
305
644
  PY
306
645
 
307
- # Completed workflow again: /cook <goal> should start the next round directly from the explicit goal
308
- # even when analyst output is unavailable, without merging session-derived scope, constraints, or acceptance.
646
+ # Completed workflow: inline /cook args should also fail closed without starting the next round.
309
647
  mark_done
310
648
 
311
- SESSION_FOUR="$TMPDIR/session-four.jsonl"
312
- DISCUSSION_FOUR=$'Scope:\n- Add session-only scope.\n- Restyle widget.\nConstraints:\n- Keep rules.\nAcceptance:\n- Add test.'
313
- EXPLICIT_GOAL_FOUR=$'Mission: Filter scope by mission.\nScope:\n- Keep explicit scope.\nCritique:\n- Keep critique notes separate from the mission anchor.\nRisks:\n- Session-only scope could leak into the next workflow round.\nTask type: completion-workflow\nEvaluation profile: completion-rubric-v1'
314
- EXPLICIT_SNAPSHOT_FOUR="$TMPDIR/context-proposal-explicit-hints.json"
315
- write_session "$SESSION_FOUR" "$ROOT" "$DISCUSSION_FOUR"
649
+ DONE_INLINE_REJECTION_ROUTING="$TMPDIR/context-proposal-done-inline-arg-routing.json"
650
+ DONE_INLINE_REJECTION_PROPOSAL="$TMPDIR/context-proposal-done-inline-arg-proposal.json"
651
+ DONE_INLINE_REJECTION_CHOOSER="$TMPDIR/context-proposal-done-inline-arg-chooser.json"
652
+ DONE_INLINE_REJECTION_BASELINE="$TMPDIR/context-proposal-done-inline-before.json"
653
+ python3 - "$DONE_INLINE_REJECTION_BASELINE" <<'PY'
654
+ import json
655
+ import sys
656
+ from pathlib import Path
657
+
658
+ tracked = [
659
+ Path('.agent/mission.md'),
660
+ Path('.agent/profile.json'),
661
+ Path('.agent/state.json'),
662
+ Path('.agent/plan.json'),
663
+ Path('.agent/active-slice.json'),
664
+ Path('.agent/verification-evidence.json'),
665
+ ]
666
+ Path(sys.argv[1]).write_text(json.dumps({path.name: path.read_text() for path in tracked}, indent=2) + '\n')
667
+ PY
316
668
 
317
669
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
318
670
  PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
319
- PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$EXPLICIT_SNAPSHOT_FOUR" \
671
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DONE_INLINE_REJECTION_PROPOSAL" \
672
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$DONE_INLINE_REJECTION_ROUTING" \
673
+ PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$DONE_INLINE_REJECTION_CHOOSER" \
320
674
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
321
- pi --session "$SESSION_FOUR" -e "$PKG_ROOT" -p "/cook $EXPLICIT_GOAL_FOUR" >/tmp/pi-completion-context-proposal-done-goal.out 2>/tmp/pi-completion-context-proposal-done-goal.err
675
+ pi -e "$PKG_ROOT" -p "/cook done-workflow replacement mission" >"$TMPDIR/pi-completion-context-proposal-done-inline-arg.out" 2>"$TMPDIR/pi-completion-context-proposal-done-inline-arg.err"
322
676
 
323
- python3 - "$EXPLICIT_SNAPSHOT_FOUR" <<'PY'
677
+ python3 - "$TMPDIR/pi-completion-context-proposal-done-inline-arg.out" "$TMPDIR/pi-completion-context-proposal-done-inline-arg.err" "$DONE_INLINE_REJECTION_ROUTING" "$DONE_INLINE_REJECTION_PROPOSAL" "$DONE_INLINE_REJECTION_CHOOSER" "$DONE_INLINE_REJECTION_BASELINE" <<'PY'
324
678
  import json
325
679
  import sys
326
680
  from pathlib import Path
327
681
 
328
- mission = 'Filter scope by mission.'
329
- expected_task_type = 'completion-workflow'
330
- expected_eval_profile = 'completion-rubric-v1'
331
- mission_text = Path('.agent/mission.md').read_text()
332
- profile = json.loads(Path('.agent/profile.json').read_text())
333
- state = json.loads(Path('.agent/state.json').read_text())
334
- plan = json.loads(Path('.agent/plan.json').read_text())
335
- active = json.loads(Path('.agent/active-slice.json').read_text())
336
- proposal = json.loads(Path(sys.argv[1]).read_text())
337
- continuation_reason = state['continuation_reason']
338
-
339
- assert mission in mission_text, '.agent/mission.md did not update to the explicit next-round mission anchor'
340
- assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after explicit-goal next-round start'
341
- assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after explicit-goal next-round start'
342
- assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after explicit-goal next-round start'
343
- assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after explicit-goal next-round start'
344
- assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after explicit-goal next-round start'
345
- assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after explicit-goal next-round start'
346
- assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after explicit-goal next-round start'
347
- assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after explicit-goal next-round start'
348
- assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after explicit-goal next-round start'
349
- assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after explicit-goal next-round start'
350
- assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after explicit-goal next-round start'
351
- assert proposal['mission'] == mission, 'explicit-goal proposal snapshot should preserve the explicit mission anchor'
352
- assert proposal['analysis']['taskType'] == expected_task_type, 'explicit-goal proposal snapshot should preserve task_type hints from the goal text'
353
- assert proposal['analysis']['evaluationProfile'] == expected_eval_profile, 'explicit-goal proposal snapshot should preserve evaluation_profile hints from the goal text'
354
- assert proposal['analysis']['critique'] == ['Keep critique notes separate from the mission anchor.'], 'explicit-goal proposal snapshot should preserve critique hints from the goal text'
355
- assert proposal['analysis']['risks'] == ['Session-only scope could leak into the next workflow round.'], 'explicit-goal proposal snapshot should preserve risk hints from the goal text'
356
- assert 'Critique:' not in proposal['goalText'], 'goalText should keep critique notes separate from mission/scope/constraints/acceptance'
357
- assert 'Task type:' not in proposal['goalText'], 'goalText should keep task_type hints separate from the mission body'
358
- assert state['current_phase'] == 'reground', 'current_phase should reset to reground after explicit-goal next-round start'
359
- assert state['continuation_policy'] == 'continue', 'continuation_policy should reset to continue after explicit-goal next-round start'
360
- assert state['project_done'] is False, 'project_done should reset to false after explicit-goal next-round start'
361
- assert state['requires_reground'] is True, 'requires_reground should reset to true after explicit-goal next-round start'
362
- assert state['next_mandatory_role'] == 'completion-regrounder', 'next role should reset to completion-regrounder after explicit-goal next-round start'
363
- assert continuation_reason.startswith('User refocused workflow via /cook:'), 'continuation_reason should record the explicit-goal next-round start'
364
- assert 'task_type=completion-workflow' in continuation_reason, 'explicit-goal next-round start should persist the selected task_type'
365
- assert 'evaluation_profile=completion-rubric-v1' in continuation_reason, 'explicit-goal next-round start should persist the selected evaluation_profile'
366
- assert 'Keep critique notes separate from the mission anchor.' in continuation_reason, 'explicit-goal next-round start should persist the accepted critique outcome'
367
- assert 'Keep explicit scope.' in continuation_reason, 'explicit scope should remain in the explicit-goal proposal'
368
- assert 'Add session-only scope.' not in continuation_reason, 'session-derived scope should not be merged when analyst output is unavailable'
369
- assert 'Restyle widget.' not in continuation_reason, 'unrelated session-derived scope should not be merged when analyst output is unavailable'
370
- assert 'Keep rules.' not in continuation_reason, 'session-derived constraints should not merge when analyst output is unavailable'
371
- assert 'Add test.' not in continuation_reason, 'session-derived acceptance should not merge when analyst output is unavailable'
372
- assert plan['plan_basis'] == 'user_refocus', 'plan_basis should be user_refocus after explicit-goal next-round start'
373
- assert active['status'] == 'idle', 'active slice should reset to idle after explicit-goal next-round start'
682
+ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
683
+ routing = Path(sys.argv[3])
684
+ proposal = Path(sys.argv[4])
685
+ chooser = Path(sys.argv[5])
686
+ before = json.loads(Path(sys.argv[6]).read_text())
687
+ tracked = [
688
+ Path('.agent/mission.md'),
689
+ Path('.agent/profile.json'),
690
+ Path('.agent/state.json'),
691
+ Path('.agent/plan.json'),
692
+ Path('.agent/active-slice.json'),
693
+ Path('.agent/verification-evidence.json'),
694
+ ]
695
+ state_before = json.loads(before['state.json'])
696
+ assert state_before['current_phase'] == 'done', 'done inline /cook rejection should start from a completed workflow'
697
+ assert state_before['project_done'] is True, 'done inline /cook rejection should start from project_done=true'
698
+ assert 'Inline /cook arguments are no longer supported.' in output, 'done inline /cook args should explain the hard rejection'
699
+ assert 'Clarify the mission in the main chat and rerun bare /cook.' in output, 'done inline /cook args should redirect users back to main chat plus bare /cook'
700
+ assert not routing.exists(), 'done inline /cook args should not run active/done workflow routing'
701
+ assert not proposal.exists(), 'done inline /cook args should not open next-round proposal confirmation'
702
+ assert not chooser.exists(), 'done inline /cook args should not open the chooser flow'
703
+ after = {path.name: path.read_text() for path in tracked}
704
+ assert before == after, 'done inline /cook args should leave canonical files unchanged'
374
705
  PY
375
706
 
376
707
  # Completed workflow again: /cook with no goal should be able to use model-assisted
@@ -378,8 +709,8 @@ PY
378
709
  mark_done
379
710
 
380
711
  SESSION_FIVE="$TMPDIR/session-five.jsonl"
381
- DISCUSSION_FIVE=$'I do not want to rewrite the parser. The safer path is to let /cook analyze the discussion first, keep the user\'s explicit mission if they provided one, and ignore stale scope that drifted in from earlier turns. We should still prove it with a regression test before writing canonical state.'
382
- ANALYST_OUTPUT_FIVE='{"mission":"Use a proposal analyst to summarize natural discussion before /cook writes canonical state.","scope":["Keep explicit goals anchored.","Drop stale scope from earlier turns."],"constraints":["Do not rewrite the parser."],"acceptance":["Add a regression test."],"confidence":0.91,"possible_noise":["old unrelated scope"]}'
712
+ DISCUSSION_FIVE=$'I do not want to rewrite the parser. The safer path is to let /cook analyze the discussion first, keep the discussion-derived mission anchored once it is clear, and ignore stale scope that drifted in from earlier turns. We should still prove it with a regression test before writing canonical state.'
713
+ ANALYST_OUTPUT_FIVE='{"mission":"Use a proposal analyst to summarize natural discussion before /cook writes canonical state.","scope":["Keep the discussion-derived mission anchored once it is clear.","Drop stale scope from earlier turns."],"constraints":["Do not rewrite the parser."],"acceptance":["Add a regression test."],"confidence":0.91,"possible_noise":["old unrelated scope"]}'
383
714
  write_session "$SESSION_FIVE" "$ROOT" "$DISCUSSION_FIVE"
384
715
 
385
716
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
@@ -419,7 +750,7 @@ assert continuation_reason.startswith('User refocused workflow via /cook:'), 'co
419
750
  assert 'task_type=completion-workflow' in continuation_reason, 'analyst-derived restart should persist the selected task_type'
420
751
  assert 'evaluation_profile=completion-rubric-v1' in continuation_reason, 'analyst-derived restart should persist the selected evaluation_profile'
421
752
  assert 'critique outcome=accepted critique=none' in continuation_reason, 'analyst-derived restart should persist that no critique notes were accepted'
422
- assert 'Keep explicit goals anchored.' in continuation_reason, 'analyst-derived scope should be preserved'
753
+ assert 'Keep the discussion-derived mission anchored once it is clear.' in continuation_reason, 'analyst-derived scope should be preserved'
423
754
  PY
424
755
 
425
756
  # Custom confirmation UI: start should render proposal content separately from approval-only Start/Cancel actions.