@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.
@@ -5,10 +5,55 @@ PKG_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
5
  TMPDIR="$(mktemp -d)"
6
6
  trap 'rm -rf "$TMPDIR"' EXIT
7
7
 
8
+ write_session() {
9
+ local session_path="$1"
10
+ local cwd="$2"
11
+ local text="$3"
12
+ python3 - "$session_path" "$cwd" "$text" <<'PY'
13
+ import json
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ session_path = Path(sys.argv[1])
18
+ cwd = sys.argv[2]
19
+ text = sys.argv[3]
20
+ session_path.parent.mkdir(parents=True, exist_ok=True)
21
+ entries = [
22
+ {
23
+ "type": "session",
24
+ "version": 3,
25
+ "id": "11111111-1111-4111-8111-111111111111",
26
+ "timestamp": "2026-01-01T00:00:00.000Z",
27
+ "cwd": cwd,
28
+ },
29
+ {
30
+ "type": "message",
31
+ "id": "a1b2c3d4",
32
+ "parentId": None,
33
+ "timestamp": "2026-01-01T00:00:01.000Z",
34
+ "message": {
35
+ "role": "user",
36
+ "content": text,
37
+ "timestamp": 1767225601000,
38
+ },
39
+ },
40
+ ]
41
+ with session_path.open('w', encoding='utf-8') as fh:
42
+ for entry in entries:
43
+ fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
44
+ PY
45
+ }
46
+
8
47
  cd "$TMPDIR"
9
48
  git init -q
10
49
 
11
- pi -e "$PKG_ROOT" -p "/cook smoke-test mission" >/tmp/pi-completion-refocus-bootstrap.out 2>/tmp/pi-completion-refocus-bootstrap.err &
50
+ BOOTSTRAP_SESSION="$TMPDIR/session-bootstrap.jsonl"
51
+ BOOTSTRAP_DISCUSSION=$'Mission: Smoke-test mission.\nScope:\n- Bootstrap a completion workflow for the refocus regression fixture.\nConstraints:\n- Use supported bare /cook discussion flow only.\nAcceptance:\n- Materialize canonical state for active-workflow refocus tests.'
52
+ write_session "$BOOTSTRAP_SESSION" "$TMPDIR" "$BOOTSTRAP_DISCUSSION"
53
+
54
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
55
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
56
+ pi --session "$BOOTSTRAP_SESSION" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-refocus-bootstrap.out" 2>"$TMPDIR/pi-completion-refocus-bootstrap.err" &
12
57
  PI_PID=$!
13
58
  for _ in $(seq 1 60); do
14
59
  if [[ -f .agent/profile.json && -f .agent/state.json && -f .agent/plan.json && -f .agent/active-slice.json ]]; then
@@ -18,7 +63,7 @@ for _ in $(seq 1 60); do
18
63
  done
19
64
  if [[ ! -f .agent/profile.json || ! -f .agent/state.json || ! -f .agent/plan.json || ! -f .agent/active-slice.json ]]; then
20
65
  echo "completion bootstrap did not materialize canonical files in time" >&2
21
- cat /tmp/pi-completion-refocus-bootstrap.err >&2 || true
66
+ cat "$TMPDIR/pi-completion-refocus-bootstrap.err" >&2 || true
22
67
  kill "$PI_PID" >/dev/null 2>&1 || true
23
68
  wait "$PI_PID" >/dev/null 2>&1 || true
24
69
  exit 1
@@ -34,49 +79,86 @@ print(state['mission_anchor'])
34
79
  PY
35
80
  )"
36
81
 
37
- CHOOSER_SNAPSHOT="$TMPDIR/existing-workflow-chooser.json"
82
+ INLINE_REJECTION_ROUTING="$TMPDIR/inline-arg-routing.json"
83
+ INLINE_REJECTION_PROPOSAL="$TMPDIR/inline-arg-proposal.json"
84
+ INLINE_REJECTION_CHOOSER="$TMPDIR/inline-arg-chooser.json"
85
+ INLINE_REJECTION_BASELINE="$TMPDIR/inline-arg-before.json"
86
+ python3 - "$INLINE_REJECTION_BASELINE" <<'PY'
87
+ import json
88
+ import sys
89
+ from pathlib import Path
90
+
91
+ tracked = [
92
+ Path('.agent/mission.md'),
93
+ Path('.agent/profile.json'),
94
+ Path('.agent/state.json'),
95
+ Path('.agent/plan.json'),
96
+ Path('.agent/active-slice.json'),
97
+ Path('.agent/verification-evidence.json'),
98
+ ]
99
+ Path(sys.argv[1]).write_text(json.dumps({path.name: path.read_text() for path in tracked}, indent=2) + '\n')
100
+ PY
101
+
38
102
  PI_COMPLETION_EXISTING_WORKFLOW_ACTION=cancel \
39
- PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$CHOOSER_SNAPSHOT" \
103
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$INLINE_REJECTION_ROUTING" \
104
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$INLINE_REJECTION_PROPOSAL" \
105
+ PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$INLINE_REJECTION_CHOOSER" \
40
106
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
41
107
  pi -e "$PKG_ROOT" -p "/cook replacement mission that should stay in the main chat" \
42
- >/tmp/pi-completion-refocus-cancel.out 2>/tmp/pi-completion-refocus-cancel.err
108
+ >"$TMPDIR/pi-completion-refocus-inline-arg.out" 2>"$TMPDIR/pi-completion-refocus-inline-arg.err"
43
109
 
44
- python3 - "$CHOOSER_SNAPSHOT" "/tmp/pi-completion-refocus-cancel.out" "/tmp/pi-completion-refocus-cancel.err" "$INITIAL_MISSION" <<'PY'
110
+ python3 - "$TMPDIR/pi-completion-refocus-inline-arg.out" "$TMPDIR/pi-completion-refocus-inline-arg.err" "$INLINE_REJECTION_ROUTING" "$INLINE_REJECTION_PROPOSAL" "$INLINE_REJECTION_CHOOSER" "$INITIAL_MISSION" "$INLINE_REJECTION_BASELINE" <<'PY'
45
111
  import json
46
112
  import sys
47
113
  from pathlib import Path
48
114
 
49
- chooser = json.loads(Path(sys.argv[1]).read_text())
50
- output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
51
- initial_mission = sys.argv[4]
52
- state = json.loads(Path('.agent/state.json').read_text())
53
- plan = json.loads(Path('.agent/plan.json').read_text())
54
- active = json.loads(Path('.agent/active-slice.json').read_text())
55
-
56
- assert state['mission_anchor'] == initial_mission, 'cancelled chooser should keep the current mission anchor'
57
- assert plan['mission_anchor'] == initial_mission, 'cancelled chooser should keep plan.json unchanged'
58
- assert active['mission_anchor'] == initial_mission, 'cancelled chooser should keep active-slice.json unchanged'
59
- assert chooser['title'].startswith('Existing completion workflow found'), 'chooser snapshot should describe the existing-workflow prompt'
60
- assert chooser['choices'][0].startswith('Continue current workflow'), 'chooser should keep the continue option'
61
- assert chooser['choices'][1].startswith('Abandon current workflow and start this new one'), 'chooser should keep the refocus option'
62
- assert 'Start/Cancel confirmation' in chooser['choices'][1], 'chooser should mention the approval-only replacement confirmation'
63
- assert chooser['choices'][2].startswith('Cancel'), 'chooser should keep the cancel option'
64
- assert 'Discuss changes in the main chat and rerun /cook.' in chooser['choices'][2], 'chooser cancel copy should redirect users back to the main chat and rerun /cook'
65
- assert 'Discuss changes in the main chat and rerun /cook.' in output, 'chooser cancel output should redirect users back to the main chat and rerun /cook'
115
+ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
116
+ routing = Path(sys.argv[3])
117
+ proposal = Path(sys.argv[4])
118
+ chooser = Path(sys.argv[5])
119
+ initial_mission = sys.argv[6]
120
+ before = json.loads(Path(sys.argv[7]).read_text())
121
+ tracked = [
122
+ Path('.agent/mission.md'),
123
+ Path('.agent/profile.json'),
124
+ Path('.agent/state.json'),
125
+ Path('.agent/plan.json'),
126
+ Path('.agent/active-slice.json'),
127
+ Path('.agent/verification-evidence.json'),
128
+ ]
129
+ current_state = json.loads(before['state.json'])
130
+ assert current_state['mission_anchor'] == initial_mission, 'active inline /cook args should start from the current mission anchor'
131
+ assert 'Inline /cook arguments are no longer supported.' in output, 'active inline /cook args should explain the hard rejection'
132
+ 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'
133
+ assert not routing.exists(), 'active inline /cook args should not run active-workflow routing'
134
+ assert not proposal.exists(), 'active inline /cook args should not open proposal confirmation'
135
+ assert not chooser.exists(), 'active inline /cook args should not open the existing-workflow chooser'
136
+ after = {path.name: path.read_text() for path in tracked}
137
+ assert before == after, 'active inline /cook args should leave canonical files unchanged'
66
138
  PY
67
139
 
140
+ SESSION_INITIAL_REFOCUS="$TMPDIR/session-initial-bare-refocus.jsonl"
141
+ INITIAL_REFOCUS_DISCUSSION=$'Mission: Remove completion status line, keep widget.\nScope:\n- Replace the initial smoke-test workflow with the widget mission.\nConstraints:\n- Keep the approval-only Start/Cancel refocus gate.\nAcceptance:\n- Rewrite canonical state only after the replacement mission is approved.'
142
+ INITIAL_REFOCUS_ROUTING="$TMPDIR/initial-bare-refocus-routing.json"
143
+ write_session "$SESSION_INITIAL_REFOCUS" "$TMPDIR" "$INITIAL_REFOCUS_DISCUSSION"
144
+
145
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
68
146
  PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
147
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
148
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$INITIAL_REFOCUS_ROUTING" \
69
149
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
70
- pi -e "$PKG_ROOT" -p "/cook refocused smoke-test mission with tests and docs" \
71
- >/tmp/pi-completion-refocus.out 2>/tmp/pi-completion-refocus.err
150
+ pi --session "$SESSION_INITIAL_REFOCUS" -e "$PKG_ROOT" -p "/cook" \
151
+ >"$TMPDIR/pi-completion-refocus.out" 2>"$TMPDIR/pi-completion-refocus.err"
72
152
 
73
- python3 - <<'PY'
153
+ python3 - "$INITIAL_REFOCUS_ROUTING" <<'PY'
74
154
  import json
155
+ import sys
75
156
  from pathlib import Path
76
157
 
77
- new_anchor = 'refocused smoke-test mission with tests and docs parity.'
158
+ new_anchor = 'Remove completion status line, keep widget.'
78
159
  expected_task_type = 'completion-workflow'
79
160
  expected_eval_profile = 'completion-rubric-v1'
161
+ routing = json.loads(Path(sys.argv[1]).read_text())
80
162
  mission_text = Path('.agent/mission.md').read_text()
81
163
  profile = json.loads(Path('.agent/profile.json').read_text())
82
164
  state = json.loads(Path('.agent/state.json').read_text())
@@ -101,6 +183,12 @@ assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_
101
183
  assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'continuation_reason should record the refocus'
102
184
  assert plan['plan_basis'] == 'user_refocus', 'plan.json plan_basis should be user_refocus after refocus'
103
185
  assert active['status'] == 'idle', 'active-slice.json status should reset to idle after refocus'
186
+ assert routing['mode'] == 'bare', 'supported refocus should use bare active-workflow routing mode'
187
+ assert 'explicitGoal' not in routing, 'supported bare refocus should not expose removed explicit-goal shim fields'
188
+ assert 'explicitGoalProvided' not in routing, 'supported bare refocus should not expose removed explicit-goal shim fields'
189
+ assert routing['action'] == 'refocus', 'supported bare /cook should classify as refocus when the mission changes'
190
+ assert routing['reason'] == 'clear_refocus', 'supported bare /cook should record the clear-refocus routing reason'
191
+ assert routing['proposedMissionAnchor'] == new_anchor, 'bare refocus routing snapshot should expose the replacement mission anchor'
104
192
  PY
105
193
 
106
194
  UPDATED_MISSION="$(python3 - <<'PY'
@@ -112,8 +200,152 @@ PY
112
200
  )"
113
201
 
114
202
  if [[ "$INITIAL_MISSION" == "$UPDATED_MISSION" ]]; then
115
- echo "expected mission anchor to change during refocus" >&2
203
+ echo "expected mission anchor to change during supported bare refocus" >&2
116
204
  exit 1
117
205
  fi
118
206
 
207
+ # Negated replacement missions that contain the current anchor must still reach the conservative chooser and final Start/Cancel gate.
208
+ BARE_REFOCUS_MISSION='Do not remove completion status line, keep widget.'
209
+ BARE_REFOCUS_DISCUSSION=$'Mission: Do not remove completion status line, keep widget.\nScope:\n- Treat the active bare /cook discussion as a replacement workflow rather than a resume.\n- Keep the replacement behind the existing approval-only Start/Cancel gate.\nConstraints:\n- Do not rewrite canonical state before the final Start confirmation.\nAcceptance:\n- Add deterministic coverage proving the chooser and final approval path for this negated replacement mission.'
210
+
211
+ SESSION_BARE_CHOOSER_CANCEL="$TMPDIR/session-bare-chooser-cancel.jsonl"
212
+ BARE_CHOOSER_SNAPSHOT="$TMPDIR/bare-existing-workflow-chooser.json"
213
+ BARE_ROUTING_CHOOSER_CANCEL="$TMPDIR/bare-routing-chooser-cancel.json"
214
+ write_session "$SESSION_BARE_CHOOSER_CANCEL" "$TMPDIR" "$BARE_REFOCUS_DISCUSSION"
215
+
216
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
217
+ PI_COMPLETION_EXISTING_WORKFLOW_ACTION=cancel \
218
+ PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$BARE_CHOOSER_SNAPSHOT" \
219
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$BARE_ROUTING_CHOOSER_CANCEL" \
220
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
221
+ pi --session "$SESSION_BARE_CHOOSER_CANCEL" -e "$PKG_ROOT" -p "/cook" \
222
+ >"$TMPDIR/pi-completion-bare-chooser-cancel.out" 2>"$TMPDIR/pi-completion-bare-chooser-cancel.err"
223
+
224
+ python3 - "$BARE_CHOOSER_SNAPSHOT" "$BARE_ROUTING_CHOOSER_CANCEL" "$TMPDIR/pi-completion-bare-chooser-cancel.out" "$TMPDIR/pi-completion-bare-chooser-cancel.err" "$UPDATED_MISSION" "$BARE_REFOCUS_MISSION" <<'PY'
225
+ import json
226
+ import sys
227
+ from pathlib import Path
228
+
229
+ chooser = json.loads(Path(sys.argv[1]).read_text())
230
+ routing = json.loads(Path(sys.argv[2]).read_text())
231
+ output = Path(sys.argv[3]).read_text() + Path(sys.argv[4]).read_text()
232
+ updated_mission = sys.argv[5]
233
+ replacement_mission = sys.argv[6]
234
+ state = json.loads(Path('.agent/state.json').read_text())
235
+ plan = json.loads(Path('.agent/plan.json').read_text())
236
+ active = json.loads(Path('.agent/active-slice.json').read_text())
237
+
238
+ assert state['mission_anchor'] == updated_mission, 'chooser cancel should keep the current mission anchor'
239
+ assert plan['mission_anchor'] == updated_mission, 'chooser cancel should keep plan.json unchanged'
240
+ assert active['mission_anchor'] == updated_mission, 'chooser cancel should keep active-slice.json unchanged'
241
+ assert routing['mode'] == 'bare', 'bare /cook should snapshot bare active-workflow routing mode'
242
+ assert 'explicitGoal' not in routing, 'bare chooser routing should not expose removed explicit-goal shim fields'
243
+ assert 'explicitGoalProvided' not in routing, 'bare chooser routing should not expose removed explicit-goal shim fields'
244
+ assert routing['action'] == 'refocus', 'clear structured discussion should classify active bare /cook as refocus'
245
+ assert routing['reason'] == 'clear_refocus', 'clear structured discussion should record the clear-refocus routing reason'
246
+ assert routing['currentMissionAnchor'] == updated_mission, 'clear-refocus routing should keep the current mission anchor until the user approves replacement'
247
+ assert routing['proposedMissionAnchor'] == replacement_mission, 'clear-refocus routing should expose the proposed replacement mission'
248
+ assert chooser['title'].startswith('Existing completion workflow found'), 'bare chooser snapshot should describe the existing-workflow prompt'
249
+ assert chooser['choices'][0].startswith('Continue current workflow'), 'bare chooser should keep the continue option'
250
+ assert chooser['choices'][1].startswith('Start new workflow from recent discussion'), 'bare chooser should offer the recent-discussion refocus option'
251
+ assert 'Start/Cancel confirmation' in chooser['choices'][1], 'bare chooser should mention the approval-only replacement confirmation'
252
+ assert chooser['choices'][2].startswith('Cancel'), 'bare chooser should keep the cancel option'
253
+ assert 'Discuss changes in the main chat and rerun /cook.' in output, 'bare chooser cancel should redirect users back to the main chat and rerun /cook'
254
+ PY
255
+
256
+ SESSION_BARE_FINAL_CANCEL="$TMPDIR/session-bare-final-cancel.jsonl"
257
+ BARE_ROUTING_FINAL_CANCEL="$TMPDIR/bare-routing-final-cancel.json"
258
+ BARE_PROPOSAL_CANCEL="$TMPDIR/bare-replacement-proposal-cancel.json"
259
+ write_session "$SESSION_BARE_FINAL_CANCEL" "$TMPDIR" "$BARE_REFOCUS_DISCUSSION"
260
+
261
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
262
+ PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
263
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=cancel \
264
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$BARE_PROPOSAL_CANCEL" \
265
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$BARE_ROUTING_FINAL_CANCEL" \
266
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
267
+ pi --session "$SESSION_BARE_FINAL_CANCEL" -e "$PKG_ROOT" -p "/cook" \
268
+ >"$TMPDIR/pi-completion-bare-final-cancel.out" 2>"$TMPDIR/pi-completion-bare-final-cancel.err"
269
+
270
+ python3 - "$BARE_PROPOSAL_CANCEL" "$BARE_ROUTING_FINAL_CANCEL" "$TMPDIR/pi-completion-bare-final-cancel.out" "$TMPDIR/pi-completion-bare-final-cancel.err" "$UPDATED_MISSION" "$BARE_REFOCUS_MISSION" <<'PY'
271
+ import json
272
+ import sys
273
+ from pathlib import Path
274
+
275
+ proposal = json.loads(Path(sys.argv[1]).read_text())
276
+ routing = json.loads(Path(sys.argv[2]).read_text())
277
+ output = Path(sys.argv[3]).read_text() + Path(sys.argv[4]).read_text()
278
+ updated_mission = sys.argv[5]
279
+ replacement_mission = sys.argv[6]
280
+ state = json.loads(Path('.agent/state.json').read_text())
281
+ plan = json.loads(Path('.agent/plan.json').read_text())
282
+ active = json.loads(Path('.agent/active-slice.json').read_text())
283
+
284
+ assert state['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep the current mission anchor'
285
+ assert plan['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep plan.json unchanged'
286
+ assert active['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep active-slice.json unchanged'
287
+ assert routing['action'] == 'refocus', 'final Start/Cancel cancel should still come from a clear-refocus classification'
288
+ assert routing['reason'] == 'clear_refocus', 'final Start/Cancel cancel should preserve the clear-refocus reason'
289
+ assert routing['currentMissionAnchor'] == updated_mission, 'final Start/Cancel cancel should keep the current mission anchor until the user approves replacement'
290
+ assert proposal['mission'] == replacement_mission, 'final Start/Cancel cancel should still prepare the replacement proposal before rewriting state'
291
+ assert 'Discuss changes in the main chat and rerun /cook.' in output, 'final Start/Cancel cancel should redirect users back to the main chat and rerun /cook'
292
+ PY
293
+
294
+ SESSION_BARE_ACCEPT="$TMPDIR/session-bare-accept.jsonl"
295
+ BARE_ROUTING_ACCEPT="$TMPDIR/bare-routing-accept.json"
296
+ BARE_PROPOSAL_ACCEPT="$TMPDIR/bare-replacement-proposal-accept.json"
297
+ write_session "$SESSION_BARE_ACCEPT" "$TMPDIR" "$BARE_REFOCUS_DISCUSSION"
298
+
299
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
300
+ PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
301
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
302
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$BARE_PROPOSAL_ACCEPT" \
303
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$BARE_ROUTING_ACCEPT" \
304
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
305
+ pi --session "$SESSION_BARE_ACCEPT" -e "$PKG_ROOT" -p "/cook" \
306
+ >"$TMPDIR/pi-completion-bare-accept.out" 2>"$TMPDIR/pi-completion-bare-accept.err"
307
+
308
+ python3 - "$BARE_PROPOSAL_ACCEPT" "$BARE_ROUTING_ACCEPT" <<'PY'
309
+ import json
310
+ import sys
311
+ from pathlib import Path
312
+
313
+ new_anchor = 'Do not remove completion status line, keep widget.'
314
+ expected_task_type = 'completion-workflow'
315
+ expected_eval_profile = 'completion-rubric-v1'
316
+ proposal = json.loads(Path(sys.argv[1]).read_text())
317
+ routing = json.loads(Path(sys.argv[2]).read_text())
318
+ mission_text = Path('.agent/mission.md').read_text()
319
+ profile = json.loads(Path('.agent/profile.json').read_text())
320
+ state = json.loads(Path('.agent/state.json').read_text())
321
+ plan = json.loads(Path('.agent/plan.json').read_text())
322
+ active = json.loads(Path('.agent/active-slice.json').read_text())
323
+
324
+ assert proposal['mission'] == new_anchor, 'accepted bare refocus should preserve the replacement proposal mission'
325
+ assert routing['mode'] == 'bare', 'accepted bare refocus should keep bare routing mode'
326
+ assert 'explicitGoal' not in routing, 'accepted bare refocus should not expose removed explicit-goal shim fields'
327
+ assert 'explicitGoalProvided' not in routing, 'accepted bare refocus should not expose removed explicit-goal shim fields'
328
+ assert routing['action'] == 'refocus', 'accepted bare refocus should keep the clear-refocus classification'
329
+ assert routing['reason'] == 'clear_refocus', 'accepted bare refocus should keep the clear-refocus reason'
330
+ assert routing['currentMissionAnchor'] == 'Remove completion status line, keep widget.', 'accepted bare refocus should expose the original mission until Start is accepted'
331
+ assert new_anchor in mission_text, '.agent/mission.md did not update to the bare refocus mission anchor'
332
+ assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after bare refocus'
333
+ assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after bare refocus'
334
+ assert state['mission_anchor'] == new_anchor, 'state.json mission_anchor mismatch after bare refocus'
335
+ assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after bare refocus'
336
+ assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after bare refocus'
337
+ assert plan['mission_anchor'] == new_anchor, 'plan.json mission_anchor mismatch after bare refocus'
338
+ assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after bare refocus'
339
+ assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after bare refocus'
340
+ assert active['mission_anchor'] == new_anchor, 'active-slice.json mission_anchor mismatch after bare refocus'
341
+ assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after bare refocus'
342
+ assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after bare refocus'
343
+ assert state['current_phase'] == 'reground', 'state.json current_phase should reset to reground after bare refocus'
344
+ assert state['requires_reground'] is True, 'state.json requires_reground should be true after bare refocus'
345
+ assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should reset to completion-regrounder after bare refocus'
346
+ assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'continuation_reason should record the bare refocus'
347
+ assert plan['plan_basis'] == 'user_refocus', 'plan.json plan_basis should be user_refocus after bare refocus'
348
+ assert active['status'] == 'idle', 'active-slice.json status should reset to idle after bare refocus'
349
+ PY
350
+
119
351
  echo "refocus test passed: $TMPDIR"
@@ -4,8 +4,70 @@ set -euo pipefail
4
4
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
5
  cd "$ROOT"
6
6
 
7
- echo "[release-check] running control-plane validation, startup/refocus/context regressions, canonical evidence artifact, active-slice contract, observability, evaluator calibration, and rubric contract coverage"
7
+ echo "[release-check] running control-plane validation, bare /cook parity, startup/refocus/context regressions, canonical evidence artifact, active-slice contract, observability, evaluator calibration, and rubric contract coverage"
8
8
  bash .agent/verify_completion_control_plane.sh
9
+
10
+ echo "[release-check] verifying public bare /cook parity"
11
+ python3 - <<'PY'
12
+ import re
13
+ from pathlib import Path
14
+
15
+ checks = {
16
+ "README.md": [
17
+ "Bare `/cook` is the only supported workflow entrypoint.",
18
+ "clarify the mission in the main chat before rerunning bare `/cook`",
19
+ "Matching or unclear discussion resumes from canonical `.agent/**` state.",
20
+ "approval-only Start/Cancel gate",
21
+ "Start new workflow from recent discussion",
22
+ "fails closed instead of guessing",
23
+ ],
24
+ "CHANGELOG.md": [
25
+ "bare `/cook` as the only supported workflow entrypoint",
26
+ "clarify the mission before rerunning bare `/cook`",
27
+ "packaged parity now fails closed on the bare-only contract",
28
+ "that old inline-argument path is no longer supported now that bare `/cook` is the only public entrypoint",
29
+ ],
30
+ "extensions/completion/index.ts": [
31
+ 'description: "Discussion-driven /cook workflow: start, continue, refocus, or start the next round"',
32
+ "Inline /cook arguments are no longer supported. Clarify the mission in the main chat and rerun bare /cook.",
33
+ "Bare /cook failed closed because recent discussion did not contain a clear structured Mission/Scope/Constraints/Acceptance proposal. Add that structure in the main chat and rerun bare /cook.",
34
+ ],
35
+ }
36
+
37
+ forbidden = {
38
+ "README.md": ["compatibility" + " shim"],
39
+ "CHANGELOG.md": ["compatibility" + " shim"],
40
+ "extensions/completion/index.ts": ["temporary" + " compatibility" + " shim, pass /cook"],
41
+ }
42
+
43
+ public_doc_paths = [
44
+ Path("README.md"),
45
+ Path("CHANGELOG.md"),
46
+ Path("PUBLISHING.md"),
47
+ ]
48
+ inline_cook_pattern = re.compile(r"/cook\s*<[^>]+>")
49
+
50
+ for path, needles in checks.items():
51
+ text = Path(path).read_text()
52
+ for needle in needles:
53
+ if needle not in text:
54
+ raise SystemExit(f"[release-check] missing expected bare /cook parity text in {path}: {needle}")
55
+
56
+ for path, needles in forbidden.items():
57
+ text = Path(path).read_text()
58
+ for needle in needles:
59
+ if needle in text:
60
+ raise SystemExit(f"[release-check] found stale compatibility wording in {path}: {needle}")
61
+
62
+ for path in public_doc_paths:
63
+ text = path.read_text()
64
+ match = inline_cook_pattern.search(text)
65
+ if match:
66
+ raise SystemExit(
67
+ f"[release-check] found unsupported inline /cook syntax in {path}: {match.group(0)}"
68
+ )
69
+ PY
70
+
9
71
  npm run smoke-test
10
72
  npm run refocus-test
11
73
  npm run context-proposal-test
@@ -5,18 +5,92 @@ PKG_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
5
  TMPDIR="$(mktemp -d)"
6
6
  trap 'rm -rf "$TMPDIR"' EXIT
7
7
 
8
+ write_session() {
9
+ local session_path="$1"
10
+ local cwd="$2"
11
+ local text="$3"
12
+ python3 - "$session_path" "$cwd" "$text" <<'PY'
13
+ import json
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ session_path = Path(sys.argv[1])
18
+ cwd = sys.argv[2]
19
+ text = sys.argv[3]
20
+ session_path.parent.mkdir(parents=True, exist_ok=True)
21
+ entries = [
22
+ {
23
+ "type": "session",
24
+ "version": 3,
25
+ "id": "11111111-1111-4111-8111-111111111111",
26
+ "timestamp": "2026-01-01T00:00:00.000Z",
27
+ "cwd": cwd,
28
+ },
29
+ {
30
+ "type": "message",
31
+ "id": "a1b2c3d4",
32
+ "parentId": None,
33
+ "timestamp": "2026-01-01T00:00:01.000Z",
34
+ "message": {
35
+ "role": "user",
36
+ "content": text,
37
+ "timestamp": 1767225601000,
38
+ },
39
+ },
40
+ ]
41
+ with session_path.open('w', encoding='utf-8') as fh:
42
+ for entry in entries:
43
+ fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
44
+ PY
45
+ }
46
+
8
47
  ROOT="$TMPDIR/repo"
9
48
  KICKOFF_PROMPT="$TMPDIR/kickoff-prompt.txt"
10
49
  RESUME_PROMPT="$TMPDIR/resume-prompt.txt"
50
+ UNCLEAR_ROUTING_SNAPSHOT="$TMPDIR/active-unclear-routing.json"
51
+ UNCLEAR_CHOOSER_SNAPSHOT="$TMPDIR/unexpected-existing-workflow-chooser.json"
11
52
  AUTO_RESUME_PROMPT="$TMPDIR/auto-resume-prompt.txt"
53
+ INLINE_REJECTION_ROUTING_SNAPSHOT="$TMPDIR/inline-arg-routing.json"
54
+ INLINE_REJECTION_PROPOSAL_SNAPSHOT="$TMPDIR/inline-arg-proposal.json"
55
+ INLINE_REJECTION_CHOOSER_SNAPSHOT="$TMPDIR/inline-arg-chooser.json"
56
+ BOOTSTRAP_SESSION="$TMPDIR/session-smoke-bootstrap.jsonl"
57
+ BOOTSTRAP_DISCUSSION=$'Mission: Exercise smoke-test bootstrap.\nScope:\n- Materialize the canonical completion control-plane files.\n- Keep the smoke test on supported bare /cook behavior.\nConstraints:\n- Do not rely on inline /cook arguments.\nAcceptance:\n- Scaffold canonical files and kickoff prompts for the smoke fixture.'
12
58
 
13
59
  mkdir -p "$ROOT"
14
60
  cd "$ROOT"
15
61
  git init -q
16
62
 
17
63
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
18
- PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$KICKOFF_PROMPT" \
64
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$INLINE_REJECTION_ROUTING_SNAPSHOT" \
65
+ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$INLINE_REJECTION_PROPOSAL_SNAPSHOT" \
66
+ PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$INLINE_REJECTION_CHOOSER_SNAPSHOT" \
19
67
  pi -e "$PKG_ROOT" -p "/cook smoke-test mission" \
68
+ >"$TMPDIR/pi-completion-smoke-inline-arg.out" 2>"$TMPDIR/pi-completion-smoke-inline-arg.err"
69
+
70
+ python3 - "$TMPDIR/pi-completion-smoke-inline-arg.out" "$TMPDIR/pi-completion-smoke-inline-arg.err" "$INLINE_REJECTION_ROUTING_SNAPSHOT" "$INLINE_REJECTION_PROPOSAL_SNAPSHOT" "$INLINE_REJECTION_CHOOSER_SNAPSHOT" <<'PY'
71
+ import sys
72
+ from pathlib import Path
73
+
74
+ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
75
+ routing = Path(sys.argv[3])
76
+ proposal = Path(sys.argv[4])
77
+ chooser = Path(sys.argv[5])
78
+
79
+ assert not Path('.agent').exists(), 'startup inline /cook args should fail closed without creating canonical state'
80
+ assert not routing.exists(), 'startup inline /cook args should not open active-workflow routing'
81
+ assert not proposal.exists(), 'startup inline /cook args should not open the proposal confirmation flow'
82
+ assert not chooser.exists(), 'startup inline /cook args should not open the chooser flow'
83
+ assert 'Inline /cook arguments are no longer supported.' in output, 'startup inline /cook args should explain the hard rejection'
84
+ assert 'Clarify the mission in the main chat and rerun bare /cook.' in output, 'startup inline /cook args should redirect users back to main chat plus bare /cook'
85
+ PY
86
+
87
+ write_session "$BOOTSTRAP_SESSION" "$ROOT" "$BOOTSTRAP_DISCUSSION"
88
+
89
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
90
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
91
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
92
+ PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$KICKOFF_PROMPT" \
93
+ pi --session "$BOOTSTRAP_SESSION" -e "$PKG_ROOT" -p "/cook" \
20
94
  >"$TMPDIR/pi-completion-smoke-bootstrap.out" 2>"$TMPDIR/pi-completion-smoke-bootstrap.err"
21
95
 
22
96
  for file in .agent/profile.json .agent/state.json .agent/plan.json .agent/active-slice.json .agent/verification-evidence.json; do
@@ -62,20 +136,32 @@ PY
62
136
 
63
137
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
64
138
  PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$RESUME_PROMPT" \
139
+ PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$UNCLEAR_ROUTING_SNAPSHOT" \
140
+ PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$UNCLEAR_CHOOSER_SNAPSHOT" \
65
141
  pi -e "$PKG_ROOT" -p "/cook" \
66
142
  >"$TMPDIR/pi-completion-smoke-resume.out" 2>"$TMPDIR/pi-completion-smoke-resume.err"
67
143
 
68
- python3 - "$RESUME_PROMPT" <<'PY'
144
+ python3 - "$RESUME_PROMPT" "$UNCLEAR_ROUTING_SNAPSHOT" "$UNCLEAR_CHOOSER_SNAPSHOT" <<'PY'
145
+ import json
69
146
  import sys
70
147
  from pathlib import Path
71
148
 
72
149
  expected_task_type = 'completion-workflow'
73
150
  expected_eval_profile = 'completion-rubric-v1'
74
151
  resume = Path(sys.argv[1]).read_text()
152
+ routing = json.loads(Path(sys.argv[2]).read_text())
153
+ chooser_path = Path(sys.argv[3])
154
+ state = json.loads(Path('.agent/state.json').read_text())
75
155
 
76
156
  assert 'Canonical routing profile:' in resume, 'resume prompt should expose canonical routing profile'
77
157
  assert f'- task_type: {expected_task_type}' in resume, 'resume prompt missing canonical task_type'
78
158
  assert f'- evaluation_profile: {expected_eval_profile}' in resume, 'resume prompt missing canonical evaluation_profile'
159
+ assert routing['mode'] == 'bare', 'active bare /cook should snapshot bare routing mode'
160
+ assert routing['action'] == 'unclear', 'no-discussion active bare /cook should classify as unclear'
161
+ assert routing['reason'] == 'missing_proposal', 'no-discussion active bare /cook should fail closed because no replacement proposal was derived'
162
+ assert routing['currentMissionAnchor'] == state['mission_anchor'], 'unclear routing snapshot should keep the current mission anchor'
163
+ assert routing['proposedMissionAnchor'] is None, 'unclear no-discussion routing should not propose a replacement mission'
164
+ assert not chooser_path.exists(), 'unclear active bare /cook should fall back to resume without opening the chooser'
79
165
  PY
80
166
 
81
167
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \