@linimin/pi-letscook 0.1.37 → 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.
@@ -5,6 +5,45 @@ PKG_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
5
  TMPDIR="$(mktemp -d)"
6
6
  CURRENT_EVIDENCE_BACKUP=""
7
7
 
8
+ write_session() {
9
+ local session_path="$1"
10
+ local cwd="$2"
11
+ local text="$3"
12
+ python3 - "$session_path" "$cwd" "$text" <<'PY'
13
+ import json
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ session_path = Path(sys.argv[1])
18
+ cwd = sys.argv[2]
19
+ text = sys.argv[3]
20
+ session_path.parent.mkdir(parents=True, exist_ok=True)
21
+ entries = [
22
+ {
23
+ "type": "session",
24
+ "version": 3,
25
+ "id": "11111111-1111-4111-8111-111111111111",
26
+ "timestamp": "2026-01-01T00:00:00.000Z",
27
+ "cwd": cwd,
28
+ },
29
+ {
30
+ "type": "message",
31
+ "id": "a1b2c3d4",
32
+ "parentId": None,
33
+ "timestamp": "2026-01-01T00:00:01.000Z",
34
+ "message": {
35
+ "role": "user",
36
+ "content": text,
37
+ "timestamp": 1767225601000,
38
+ },
39
+ },
40
+ ]
41
+ with session_path.open('w', encoding='utf-8') as fh:
42
+ for entry in entries:
43
+ fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
44
+ PY
45
+ }
46
+
8
47
  cleanup() {
9
48
  if [[ -n "$CURRENT_EVIDENCE_BACKUP" && -f "$CURRENT_EVIDENCE_BACKUP" ]]; then
10
49
  cp "$CURRENT_EVIDENCE_BACKUP" "$PKG_ROOT/.agent/verification-evidence.json"
@@ -96,12 +135,17 @@ bash .agent/verify_completion_control_plane.sh >/dev/null
96
135
 
97
136
  ROOT="$TMPDIR/repo"
98
137
  SYSTEM_REMINDER="$TMPDIR/system-reminder.txt"
138
+ BOOTSTRAP_SESSION="$TMPDIR/session-canonical-evidence-bootstrap.jsonl"
139
+ BOOTSTRAP_DISCUSSION=$'Mission: Exercise canonical evidence fixture bootstrap.\nScope:\n- Materialize canonical completion files for the evidence artifact fixture.\nConstraints:\n- Use supported bare /cook startup only.\nAcceptance:\n- Scaffold canonical files before the fixture rewrites them.'
99
140
  mkdir -p "$ROOT"
100
141
  cd "$ROOT"
101
142
  git init -q
143
+ write_session "$BOOTSTRAP_SESSION" "$ROOT" "$BOOTSTRAP_DISCUSSION"
102
144
 
145
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
146
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
103
147
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
104
- pi -e "$PKG_ROOT" -p "/cook canonical evidence fixture" \
148
+ pi --session "$BOOTSTRAP_SESSION" -e "$PKG_ROOT" -p "/cook" \
105
149
  >"$TMPDIR/pi-canonical-evidence-bootstrap.out" 2>"$TMPDIR/pi-canonical-evidence-bootstrap.err"
106
150
 
107
151
  for file in .agent/profile.json .agent/state.json .agent/plan.json .agent/active-slice.json .agent/verification-evidence.json; do
@@ -199,6 +199,155 @@ assert 'Bare /cook failed closed' in output, 'ambiguous structured discussion sh
199
199
  assert 'Mission/Scope/Constraints/Acceptance' in output, 'ambiguous structured discussion should explain the strict fallback requirement'
200
200
  PY
201
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
254
+ from pathlib import Path
255
+
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'
349
+ PY
350
+
202
351
  # No workflow yet: /cook with no goal should infer from recent discussion through analyst output.
203
352
  SESSION_ONE="$TMPDIR/session-one.jsonl"
204
353
  DISCUSSION_ONE="$DISCUSSION_ZERO"
@@ -285,6 +434,8 @@ plan = json.loads(Path('.agent/plan.json').read_text())
285
434
  active = json.loads(Path('.agent/active-slice.json').read_text())
286
435
 
287
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'
288
439
  assert routing['action'] == 'continue', 'matching structured discussion should classify active bare /cook as continue'
289
440
  assert routing['reason'] == 'matching_mission', 'matching structured discussion should keep the current mission rather than refocus'
290
441
  assert routing['currentMissionAnchor'] == mission, 'continue routing should preserve the current mission anchor'
@@ -296,6 +447,87 @@ assert plan['mission_anchor'] == mission, 'active bare /cook continue should kee
296
447
  assert active['mission_anchor'] == mission, 'active bare /cook continue should keep active-slice.json unchanged'
297
448
  PY
298
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
+
299
531
  # Completed workflow: bare /cook should use the same strict structured fallback for the next workflow round when analyst output is unavailable.
300
532
  mark_done
301
533
 
@@ -352,165 +584,124 @@ assert plan['plan_basis'] == 'user_refocus', 'plan_basis should reset to user_re
352
584
  assert active['status'] == 'idle', 'active-slice should reset to idle for the next workflow round'
353
585
  PY
354
586
 
355
- # Active workflow: /cook <goal> plus refocus should keep the explicit goal as the mission anchor
356
- # even when analyst output is unavailable and structured session fallback is present.
357
- SESSION_THREE="$TMPDIR/session-three.jsonl"
358
- DISCUSSION_THREE=$'Scope:\n- Preserve the richer proposal structure from discussion.\nConstraints:\n- Keep explicit goals as the mission anchor when they conflict with earlier text.\nAcceptance:\n- Refresh canonical state from the replacement mission.'
359
- DISCUSSION_SNAPSHOT_THREE="$TMPDIR/context-proposal-active-goal.json"
360
- ROUTING_SNAPSHOT_THREE="$TMPDIR/context-proposal-active-goal-routing.json"
361
- write_session "$SESSION_THREE" "$ROOT" "$DISCUSSION_THREE"
362
-
363
- PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
364
- PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
365
- PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
366
- PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_THREE" \
367
- PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$ROUTING_SNAPSHOT_THREE" \
368
- PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
369
- pi --session "$SESSION_THREE" -e "$PKG_ROOT" -p "/cook Explicit replacement mission for the active workflow" >"$TMPDIR/pi-completion-context-proposal-active-goal.out" 2>"$TMPDIR/pi-completion-context-proposal-active-goal.err"
370
-
371
- python3 - "$DISCUSSION_SNAPSHOT_THREE" "$ROUTING_SNAPSHOT_THREE" <<'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'
372
594
  import json
373
595
  import sys
374
596
  from pathlib import Path
375
597
 
376
- mission = 'Explicit replacement mission for the active workflow.'
377
- expected_task_type = 'completion-workflow'
378
- expected_eval_profile = 'completion-rubric-v1'
379
- proposal = json.loads(Path(sys.argv[1]).read_text())
380
- routing = json.loads(Path(sys.argv[2]).read_text())
381
- mission_text = Path('.agent/mission.md').read_text()
382
- profile = json.loads(Path('.agent/profile.json').read_text())
383
- state = json.loads(Path('.agent/state.json').read_text())
384
- plan = json.loads(Path('.agent/plan.json').read_text())
385
- active = json.loads(Path('.agent/active-slice.json').read_text())
386
-
387
- assert mission in mission_text, '.agent/mission.md did not update to the explicit replacement mission anchor'
388
- assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after explicit-goal replacement'
389
- assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after explicit-goal replacement'
390
- assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after explicit-goal replacement'
391
- assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after explicit-goal replacement'
392
- assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after explicit-goal replacement'
393
- assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after explicit-goal replacement'
394
- assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after explicit-goal replacement'
395
- assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after explicit-goal replacement'
396
- assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after explicit-goal replacement'
397
- assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after explicit-goal replacement'
398
- assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after explicit-goal replacement'
399
- assert proposal['mission'] == mission, 'explicit-goal replacement should route through the shared proposal confirmation pipeline'
400
- assert routing['mode'] == 'explicit', 'explicit-goal replacement should snapshot explicit active-workflow routing mode'
401
- assert routing['action'] == 'refocus', 'explicit-goal replacement should classify as refocus when the mission changes'
402
- assert routing['reason'] == 'explicit_goal', 'explicit-goal replacement should record the explicit-goal routing reason'
403
- assert routing['proposedMissionAnchor'] == mission, 'explicit-goal routing should expose the replacement mission anchor'
404
- assert state['current_phase'] == 'reground', 'current_phase should reset to reground after explicit-goal replacement'
405
- assert state['continuation_policy'] == 'continue', 'continuation_policy should stay continue after explicit-goal replacement'
406
- assert state['next_mandatory_role'] == 'completion-regrounder', 'next role should reset to completion-regrounder after explicit-goal replacement'
407
- assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'continuation_reason should record the explicit-goal replacement'
408
- assert 'task_type=completion-workflow' in state['continuation_reason'], 'explicit-goal replacement should persist the selected task_type'
409
- assert 'evaluation_profile=completion-rubric-v1' in state['continuation_reason'], 'explicit-goal replacement should persist the selected evaluation_profile'
410
- 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'
411
- assert 'Preserve the richer proposal structure from discussion.' not in state['continuation_reason'], 'session scope should not be merged when analyst output is unavailable'
412
- 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'
413
- assert 'Refresh canonical state from the replacement mission.' not in state['continuation_reason'], 'session acceptance should not be merged when analyst output is unavailable'
414
- assert plan['plan_basis'] == 'user_refocus', 'plan_basis should be user_refocus after explicit-goal replacement'
415
- 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')
416
607
  PY
417
608
 
418
- # Active workflow: cancelling the replacement proposal should keep the current workflow and redirect
419
- # the user back to the main chat before rerunning /cook.
420
- SESSION_THREE_CANCEL="$TMPDIR/session-three-cancel.jsonl"
421
- 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.'
422
- write_session "$SESSION_THREE_CANCEL" "$ROOT" "$DISCUSSION_THREE_CANCEL"
423
-
424
609
  PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
425
- PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=cancel \
610
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
426
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" \
427
615
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
428
- 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"
429
617
 
430
- 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'
431
619
  import json
432
620
  import sys
433
621
  from pathlib import Path
434
622
 
435
- mission = 'Explicit replacement mission for the active workflow.'
436
- state = json.loads(Path('.agent/state.json').read_text())
437
- plan = json.loads(Path('.agent/plan.json').read_text())
438
- active = json.loads(Path('.agent/active-slice.json').read_text())
439
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
+ ]
440
636
 
441
- assert state['mission_anchor'] == mission, 'replacement proposal cancel should keep the existing mission anchor'
442
- assert plan['mission_anchor'] == mission, 'replacement proposal cancel should keep plan.json unchanged'
443
- assert active['mission_anchor'] == mission, 'replacement proposal cancel should keep active-slice.json unchanged'
444
- 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'
445
644
  PY
446
645
 
447
- # Completed workflow again: /cook <goal> should start the next round directly from the explicit goal
448
- # 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.
449
647
  mark_done
450
648
 
451
- SESSION_FOUR="$TMPDIR/session-four.jsonl"
452
- DISCUSSION_FOUR=$'Scope:\n- Add session-only scope.\n- Restyle widget.\nConstraints:\n- Keep rules.\nAcceptance:\n- Add test.'
453
- 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'
454
- EXPLICIT_SNAPSHOT_FOUR="$TMPDIR/context-proposal-explicit-hints.json"
455
- 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
456
668
 
457
669
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
458
670
  PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
459
- 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" \
460
674
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
461
- 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"
462
676
 
463
- 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'
464
678
  import json
465
679
  import sys
466
680
  from pathlib import Path
467
681
 
468
- mission = 'Filter scope by mission.'
469
- expected_task_type = 'completion-workflow'
470
- expected_eval_profile = 'completion-rubric-v1'
471
- mission_text = Path('.agent/mission.md').read_text()
472
- profile = json.loads(Path('.agent/profile.json').read_text())
473
- state = json.loads(Path('.agent/state.json').read_text())
474
- plan = json.loads(Path('.agent/plan.json').read_text())
475
- active = json.loads(Path('.agent/active-slice.json').read_text())
476
- proposal = json.loads(Path(sys.argv[1]).read_text())
477
- continuation_reason = state['continuation_reason']
478
-
479
- assert mission in mission_text, '.agent/mission.md did not update to the explicit next-round mission anchor'
480
- assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after explicit-goal next-round start'
481
- assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after explicit-goal next-round start'
482
- assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after explicit-goal next-round start'
483
- assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after explicit-goal next-round start'
484
- assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after explicit-goal next-round start'
485
- assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after explicit-goal next-round start'
486
- assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after explicit-goal next-round start'
487
- assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after explicit-goal next-round start'
488
- assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after explicit-goal next-round start'
489
- assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after explicit-goal next-round start'
490
- assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after explicit-goal next-round start'
491
- assert proposal['mission'] == mission, 'explicit-goal proposal snapshot should preserve the explicit mission anchor'
492
- assert proposal['analysis']['taskType'] == expected_task_type, 'explicit-goal proposal snapshot should preserve task_type hints from the goal text'
493
- assert proposal['analysis']['evaluationProfile'] == expected_eval_profile, 'explicit-goal proposal snapshot should preserve evaluation_profile hints from the goal text'
494
- assert proposal['analysis']['critique'] == ['Keep critique notes separate from the mission anchor.'], 'explicit-goal proposal snapshot should preserve critique hints from the goal text'
495
- 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'
496
- assert 'Critique:' not in proposal['goalText'], 'goalText should keep critique notes separate from mission/scope/constraints/acceptance'
497
- assert 'Task type:' not in proposal['goalText'], 'goalText should keep task_type hints separate from the mission body'
498
- assert state['current_phase'] == 'reground', 'current_phase should reset to reground after explicit-goal next-round start'
499
- assert state['continuation_policy'] == 'continue', 'continuation_policy should reset to continue after explicit-goal next-round start'
500
- assert state['project_done'] is False, 'project_done should reset to false after explicit-goal next-round start'
501
- assert state['requires_reground'] is True, 'requires_reground should reset to true after explicit-goal next-round start'
502
- assert state['next_mandatory_role'] == 'completion-regrounder', 'next role should reset to completion-regrounder after explicit-goal next-round start'
503
- assert continuation_reason.startswith('User refocused workflow via /cook:'), 'continuation_reason should record the explicit-goal next-round start'
504
- assert 'task_type=completion-workflow' in continuation_reason, 'explicit-goal next-round start should persist the selected task_type'
505
- assert 'evaluation_profile=completion-rubric-v1' in continuation_reason, 'explicit-goal next-round start should persist the selected evaluation_profile'
506
- assert 'Keep critique notes separate from the mission anchor.' in continuation_reason, 'explicit-goal next-round start should persist the accepted critique outcome'
507
- assert 'Keep explicit scope.' in continuation_reason, 'explicit scope should remain in the explicit-goal proposal'
508
- assert 'Add session-only scope.' not in continuation_reason, 'session-derived scope should not be merged when analyst output is unavailable'
509
- assert 'Restyle widget.' not in continuation_reason, 'unrelated session-derived scope should not be merged when analyst output is unavailable'
510
- assert 'Keep rules.' not in continuation_reason, 'session-derived constraints should not merge when analyst output is unavailable'
511
- assert 'Add test.' not in continuation_reason, 'session-derived acceptance should not merge when analyst output is unavailable'
512
- assert plan['plan_basis'] == 'user_refocus', 'plan_basis should be user_refocus after explicit-goal next-round start'
513
- 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'
514
705
  PY
515
706
 
516
707
  # Completed workflow again: /cook with no goal should be able to use model-assisted
@@ -518,8 +709,8 @@ PY
518
709
  mark_done
519
710
 
520
711
  SESSION_FIVE="$TMPDIR/session-five.jsonl"
521
- 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.'
522
- 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"]}'
523
714
  write_session "$SESSION_FIVE" "$ROOT" "$DISCUSSION_FIVE"
524
715
 
525
716
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
@@ -559,7 +750,7 @@ assert continuation_reason.startswith('User refocused workflow via /cook:'), 'co
559
750
  assert 'task_type=completion-workflow' in continuation_reason, 'analyst-derived restart should persist the selected task_type'
560
751
  assert 'evaluation_profile=completion-rubric-v1' in continuation_reason, 'analyst-derived restart should persist the selected evaluation_profile'
561
752
  assert 'critique outcome=accepted critique=none' in continuation_reason, 'analyst-derived restart should persist that no critique notes were accepted'
562
- 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'
563
754
  PY
564
755
 
565
756
  # Custom confirmation UI: start should render proposal content separately from approval-only Start/Cancel actions.