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