@linimin/pi-letscook 0.1.57 → 0.1.59
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/.agent/README.md +28 -0
- package/.agent/mission.md +8 -0
- package/.agent/profile.json +13 -0
- package/.agent/verify_completion_control_plane.sh +203 -0
- package/.agent/verify_completion_stop.sh +20 -0
- package/CHANGELOG.md +22 -2
- package/README.md +27 -18
- package/extensions/completion/driver.ts +77 -35
- package/extensions/completion/index.ts +78 -53
- package/extensions/completion/prompt-surfaces.ts +10 -5
- package/extensions/completion/proposal.ts +134 -28
- package/package.json +6 -1
- package/scripts/active-slice-contract-test.sh +93 -2
- package/scripts/canonical-evidence-artifact-test.sh +93 -2
- package/scripts/context-proposal-test.sh +751 -741
- package/scripts/refocus-test.sh +196 -28
- package/scripts/release-check.sh +51 -22
- package/scripts/smoke-test.sh +115 -10
- package/skills/cook-handoff-boundary/SKILL.md +20 -7
|
@@ -147,7 +147,8 @@ mkdir -p "$ROOT"
|
|
|
147
147
|
cd "$ROOT"
|
|
148
148
|
git init -q
|
|
149
149
|
|
|
150
|
-
# No workflow yet: bare /cook should
|
|
150
|
+
# No workflow yet: bare /cook should fail closed without a fresh valid explicit primary-agent handoff,
|
|
151
|
+
# even when recent discussion is fully structured.
|
|
151
152
|
SESSION_ZERO="$TMPDIR/session-zero.jsonl"
|
|
152
153
|
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.'
|
|
153
154
|
DISCUSSION_SNAPSHOT_ZERO="$TMPDIR/context-proposal-structured-fallback.json"
|
|
@@ -159,518 +160,144 @@ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO" \
|
|
|
159
160
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
160
161
|
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"
|
|
161
162
|
|
|
162
|
-
python3 - "$DISCUSSION_SNAPSHOT_ZERO" <<'PY'
|
|
163
|
-
import json
|
|
164
|
-
import sys
|
|
165
|
-
from pathlib import Path
|
|
166
|
-
|
|
167
|
-
mission = 'Remove the completion status line while keeping the completion widget.'
|
|
168
|
-
expected_task_type = 'completion-workflow'
|
|
169
|
-
expected_eval_profile = 'completion-rubric-v1'
|
|
170
|
-
mission_text = Path('.agent/mission.md').read_text()
|
|
171
|
-
profile = json.loads(Path('.agent/profile.json').read_text())
|
|
172
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
173
|
-
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
174
|
-
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
175
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
176
|
-
|
|
177
|
-
assert Path('.agent').exists(), 'strict structured fallback should only create canonical state after Start is accepted'
|
|
178
|
-
assert mission in mission_text, '.agent/mission.md did not record the structured-fallback mission anchor'
|
|
179
|
-
assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after structured-fallback bootstrap'
|
|
180
|
-
assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after structured-fallback bootstrap'
|
|
181
|
-
assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after structured-fallback bootstrap'
|
|
182
|
-
assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after structured-fallback bootstrap'
|
|
183
|
-
assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after structured-fallback bootstrap'
|
|
184
|
-
assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after structured-fallback bootstrap'
|
|
185
|
-
assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after structured-fallback bootstrap'
|
|
186
|
-
assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after structured-fallback bootstrap'
|
|
187
|
-
assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after structured-fallback bootstrap'
|
|
188
|
-
assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after structured-fallback bootstrap'
|
|
189
|
-
assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after structured-fallback bootstrap'
|
|
190
|
-
assert proposal['mission'] == mission, 'structured-fallback proposal snapshot should preserve the discussion mission anchor'
|
|
191
|
-
assert proposal['source'] == 'session', 'structured-fallback proposal snapshot should record the strict session fallback source'
|
|
192
|
-
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'
|
|
193
|
-
assert proposal['constraints'] == ['Do not reintroduce any other completion status surface.'], 'structured-fallback proposal snapshot should preserve discussion constraints'
|
|
194
|
-
assert proposal['acceptance'] == ['Update README to match the shipped behavior.', 'Keep observability regression coverage truthful.'], 'structured-fallback proposal snapshot should preserve discussion acceptance'
|
|
195
|
-
assert state['current_phase'] == 'reground', 'state.json current_phase should start at reground after structured-fallback bootstrap'
|
|
196
|
-
assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should start at completion-regrounder after structured-fallback bootstrap'
|
|
197
|
-
assert state['continuation_reason'].startswith('User started workflow via /cook:'), 'structured-fallback startup should record the accepted startup routing in continuation_reason'
|
|
198
|
-
assert 'task_type=completion-workflow' in state['continuation_reason'], 'structured-fallback startup should persist the selected task_type in continuation_reason'
|
|
199
|
-
assert 'evaluation_profile=completion-rubric-v1' in state['continuation_reason'], 'structured-fallback startup should persist the selected evaluation_profile in continuation_reason'
|
|
200
|
-
PY
|
|
201
|
-
|
|
202
|
-
rm -rf .agent
|
|
203
|
-
|
|
204
|
-
# No workflow yet: when multiple structured discussions exist, bare /cook should prioritize the latest
|
|
205
|
-
# concrete implementation mission instead of failing closed on older structured context.
|
|
206
|
-
SESSION_ZERO_LATEST_WINDOW="$TMPDIR/session-zero-latest-window.jsonl"
|
|
207
|
-
DISCUSSION_ZERO_LATEST_OLDER=$'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- Keep observability regression coverage truthful.'
|
|
208
|
-
DISCUSSION_ZERO_LATEST_NEWER=$'Mission: Fix login redirect callback behavior.\nScope:\n- Update the callback redirect decision logic.\nConstraints:\n- Do not refactor the broader auth flow.\nAcceptance:\n- Add a regression test for returning to the requested page.'
|
|
209
|
-
DISCUSSION_SNAPSHOT_ZERO_LATEST_WINDOW="$TMPDIR/context-proposal-latest-window.json"
|
|
210
|
-
python3 - "$SESSION_ZERO_LATEST_WINDOW" "$ROOT" "$DISCUSSION_ZERO_LATEST_OLDER" "$DISCUSSION_ZERO_LATEST_NEWER" <<'PY'
|
|
211
|
-
import json
|
|
212
|
-
import sys
|
|
213
|
-
from pathlib import Path
|
|
214
|
-
|
|
215
|
-
session_path = Path(sys.argv[1])
|
|
216
|
-
cwd = sys.argv[2]
|
|
217
|
-
older = sys.argv[3]
|
|
218
|
-
newer = sys.argv[4]
|
|
219
|
-
session_path.parent.mkdir(parents=True, exist_ok=True)
|
|
220
|
-
entries = [
|
|
221
|
-
{
|
|
222
|
-
"type": "session",
|
|
223
|
-
"version": 3,
|
|
224
|
-
"id": "11111111-1111-4111-8111-111111111111",
|
|
225
|
-
"timestamp": "2026-01-01T00:00:00.000Z",
|
|
226
|
-
"cwd": cwd,
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
"type": "message",
|
|
230
|
-
"id": "a1b2c3d4",
|
|
231
|
-
"parentId": None,
|
|
232
|
-
"timestamp": "2026-01-01T00:00:01.000Z",
|
|
233
|
-
"message": {
|
|
234
|
-
"role": "user",
|
|
235
|
-
"content": older,
|
|
236
|
-
"timestamp": 1767225601000,
|
|
237
|
-
},
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
"type": "message",
|
|
241
|
-
"id": "b2c3d4e5",
|
|
242
|
-
"parentId": "a1b2c3d4",
|
|
243
|
-
"timestamp": "2026-01-01T00:00:02.000Z",
|
|
244
|
-
"message": {
|
|
245
|
-
"role": "user",
|
|
246
|
-
"content": newer,
|
|
247
|
-
"timestamp": 1767225602000,
|
|
248
|
-
},
|
|
249
|
-
},
|
|
250
|
-
]
|
|
251
|
-
with session_path.open('w', encoding='utf-8') as fh:
|
|
252
|
-
for entry in entries:
|
|
253
|
-
fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
254
|
-
PY
|
|
255
|
-
|
|
256
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
257
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
258
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_LATEST_WINDOW" \
|
|
259
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
260
|
-
pi --session "$SESSION_ZERO_LATEST_WINDOW" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-latest-window.out" 2>"$TMPDIR/pi-completion-context-proposal-latest-window.err"
|
|
261
|
-
|
|
262
|
-
python3 - "$DISCUSSION_SNAPSHOT_ZERO_LATEST_WINDOW" <<'PY'
|
|
263
|
-
import json
|
|
264
|
-
import sys
|
|
265
|
-
from pathlib import Path
|
|
266
|
-
|
|
267
|
-
mission = 'Fix login redirect callback behavior.'
|
|
268
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
269
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
270
|
-
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
271
|
-
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
272
|
-
|
|
273
|
-
assert proposal['mission'] == mission, 'latest structured discussion should win over older structured context'
|
|
274
|
-
assert proposal['scope'] == ['Update the callback redirect decision logic.'], 'latest structured discussion should preserve the latest scope only'
|
|
275
|
-
assert proposal['analysis']['suppressedNegatedTopics'] == ['Do not refactor the broader auth flow.'], 'latest structured discussion should preserve negated implementation topics separately from the mission'
|
|
276
|
-
assert state['mission_anchor'] == mission, 'latest structured discussion should initialize state.json with the latest mission'
|
|
277
|
-
assert plan['mission_anchor'] == mission, 'latest structured discussion should initialize plan.json with the latest mission'
|
|
278
|
-
assert active['mission_anchor'] == mission, 'latest structured discussion should initialize active-slice.json with the latest mission'
|
|
279
|
-
PY
|
|
280
|
-
|
|
281
|
-
rm -rf .agent
|
|
282
|
-
|
|
283
|
-
# No workflow yet: bare /cook should fail closed when a required structured section is missing and analyst output is unavailable.
|
|
284
|
-
SESSION_ZERO_MISSING="$TMPDIR/session-zero-missing-section.jsonl"
|
|
285
|
-
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.'
|
|
286
|
-
DISCUSSION_SNAPSHOT_ZERO_MISSING="$TMPDIR/context-proposal-missing-section.json"
|
|
287
|
-
write_session "$SESSION_ZERO_MISSING" "$ROOT" "$DISCUSSION_ZERO_MISSING"
|
|
288
|
-
|
|
289
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
290
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
291
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_MISSING" \
|
|
292
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
293
|
-
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"
|
|
294
|
-
|
|
295
|
-
python3 - "$TMPDIR/pi-completion-context-proposal-missing-section.out" "$TMPDIR/pi-completion-context-proposal-missing-section.err" "$DISCUSSION_SNAPSHOT_ZERO_MISSING" <<'PY'
|
|
296
|
-
import sys
|
|
297
|
-
from pathlib import Path
|
|
298
|
-
|
|
299
|
-
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
300
|
-
snapshot = Path(sys.argv[3])
|
|
301
|
-
assert not Path('.agent').exists(), 'missing-section structured discussion should fail closed without writing canonical state'
|
|
302
|
-
assert not snapshot.exists(), 'missing-section structured discussion should not emit a proposal snapshot when bare /cook fails closed'
|
|
303
|
-
assert '/cook failed closed' in output, 'missing-section structured discussion should explain the fail-closed startup outcome'
|
|
304
|
-
assert 'Mission/Scope/Constraints/Acceptance' in output, 'missing-section structured discussion should explain the strict fallback requirement'
|
|
305
|
-
PY
|
|
306
|
-
|
|
307
|
-
# No workflow yet: when one structured discussion message contains multiple complete mission blocks,
|
|
308
|
-
# bare /cook should prioritize the latest block and preserve earlier blocks as alternate missions.
|
|
309
|
-
SESSION_ZERO_AMBIG="$TMPDIR/session-zero-ambiguous.jsonl"
|
|
310
|
-
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.\nConstraints:\n- Do not modify the completion widget.\nAcceptance:\n- Land the unrelated overhaul changes only.'
|
|
311
|
-
DISCUSSION_SNAPSHOT_ZERO_AMBIG="$TMPDIR/context-proposal-ambiguous-latest-block.json"
|
|
312
|
-
write_session "$SESSION_ZERO_AMBIG" "$ROOT" "$DISCUSSION_ZERO_AMBIG"
|
|
313
|
-
|
|
314
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
315
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
316
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_AMBIG" \
|
|
317
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
318
|
-
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"
|
|
319
|
-
|
|
320
|
-
python3 - "$DISCUSSION_SNAPSHOT_ZERO_AMBIG" <<'PY'
|
|
321
|
-
import json
|
|
322
|
-
import sys
|
|
323
|
-
from pathlib import Path
|
|
324
|
-
|
|
325
|
-
mission = 'Ship an unrelated widget overhaul.'
|
|
326
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
327
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
328
|
-
|
|
329
|
-
assert proposal['mission'] == mission, 'latest complete mission block should win inside a single structured discussion message'
|
|
330
|
-
assert proposal['analysis']['alternateMissions'] == ['Remove the completion status line while keeping the completion widget.'], 'earlier complete mission blocks should be preserved as alternate missions'
|
|
331
|
-
assert state['mission_anchor'] == mission, 'latest complete mission block should initialize canonical mission state'
|
|
332
|
-
PY
|
|
333
|
-
|
|
334
|
-
rm -rf .agent
|
|
335
|
-
|
|
336
|
-
# No workflow yet: bare /cook structured fallback should normalize placeholder planning phrasing
|
|
337
|
-
# into the concrete implementation mission when scope/acceptance clearly describe shipped work.
|
|
338
|
-
SESSION_ZERO_NORMALIZED="$TMPDIR/session-zero-normalized.jsonl"
|
|
339
|
-
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.'
|
|
340
|
-
DISCUSSION_SNAPSHOT_ZERO_NORMALIZED="$TMPDIR/context-proposal-normalized-fallback.json"
|
|
341
|
-
write_session "$SESSION_ZERO_NORMALIZED" "$ROOT" "$DISCUSSION_ZERO_NORMALIZED"
|
|
342
|
-
|
|
343
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
344
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
345
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_NORMALIZED" \
|
|
346
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
347
|
-
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"
|
|
348
|
-
|
|
349
|
-
python3 - "$DISCUSSION_SNAPSHOT_ZERO_NORMALIZED" <<'PY'
|
|
350
|
-
import json
|
|
351
|
-
import sys
|
|
352
|
-
from pathlib import Path
|
|
353
|
-
|
|
354
|
-
mission = 'Normalize bare /cook planning phrasing into shipped implementation missions.'
|
|
355
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
356
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
357
|
-
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
358
|
-
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
359
|
-
mission_text = Path('.agent/mission.md').read_text()
|
|
360
|
-
|
|
361
|
-
assert mission in mission_text, 'normalized structured-fallback startup should update .agent/mission.md to the implementation mission'
|
|
362
|
-
assert proposal['mission'] == mission, 'structured-fallback startup should normalize the placeholder planning mission'
|
|
363
|
-
assert state['mission_anchor'] == mission, 'state.json mission_anchor should use the normalized implementation mission'
|
|
364
|
-
assert plan['mission_anchor'] == mission, 'plan.json mission_anchor should use the normalized implementation mission'
|
|
365
|
-
assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor should use the normalized implementation mission'
|
|
366
|
-
assert proposal['source'] == 'session', 'normalized structured-fallback startup should still record session fallback as the proposal source'
|
|
367
|
-
assert proposal['scope'][0] == mission, 'normalized structured-fallback startup should derive the mission from shipped-work scope'
|
|
368
|
-
PY
|
|
369
|
-
|
|
370
|
-
rm -rf .agent
|
|
371
|
-
|
|
372
|
-
# No workflow yet: analyst-derived and strict structured fallback proposals should converge on the same
|
|
373
|
-
# normalized implementation mission for the same planning-phrased discussion.
|
|
374
|
-
SESSION_ZERO_ANALYST_NORMALIZED="$TMPDIR/session-zero-analyst-normalized.jsonl"
|
|
375
|
-
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}'
|
|
376
|
-
DISCUSSION_SNAPSHOT_ZERO_ANALYST_NORMALIZED="$TMPDIR/context-proposal-normalized-analyst.json"
|
|
377
|
-
write_session "$SESSION_ZERO_ANALYST_NORMALIZED" "$ROOT" "$DISCUSSION_ZERO_NORMALIZED"
|
|
378
|
-
|
|
379
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
380
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$ANALYST_OUTPUT_ZERO_NORMALIZED" \
|
|
381
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_ANALYST_NORMALIZED" \
|
|
382
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
383
|
-
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"
|
|
384
|
-
|
|
385
|
-
python3 - "$DISCUSSION_SNAPSHOT_ZERO_ANALYST_NORMALIZED" <<'PY'
|
|
386
|
-
import json
|
|
387
|
-
import sys
|
|
388
|
-
from pathlib import Path
|
|
389
|
-
|
|
390
|
-
mission = 'Normalize bare /cook planning phrasing into shipped implementation missions.'
|
|
391
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
392
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
393
|
-
|
|
394
|
-
assert proposal['mission'] == mission, 'analyst-derived startup should normalize the same placeholder planning mission to the same implementation mission'
|
|
395
|
-
assert state['mission_anchor'] == mission, 'analyst-derived startup should converge on the same canonical mission anchor as structured fallback'
|
|
396
|
-
assert proposal['analysis']['taskType'] == 'completion-workflow', 'analyst-derived normalization should preserve task_type hints'
|
|
397
|
-
assert proposal['analysis']['evaluationProfile'] == 'completion-rubric-v1', 'analyst-derived normalization should preserve evaluation_profile hints'
|
|
398
|
-
PY
|
|
399
|
-
|
|
400
|
-
rm -rf .agent
|
|
401
|
-
|
|
402
|
-
# No workflow yet: planning-artifact-only context should fail closed even when the discussion is
|
|
403
|
-
# clearly structured, because bare /cook now expects execution-ready repo changes.
|
|
404
|
-
SESSION_ZERO_PLANNING_ONLY="$TMPDIR/session-zero-planning-only.jsonl"
|
|
405
|
-
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.'
|
|
406
|
-
DISCUSSION_SNAPSHOT_ZERO_PLANNING_ONLY="$TMPDIR/context-proposal-planning-only.json"
|
|
407
|
-
write_session "$SESSION_ZERO_PLANNING_ONLY" "$ROOT" "$DISCUSSION_ZERO_PLANNING_ONLY"
|
|
408
|
-
|
|
409
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
410
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
411
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_PLANNING_ONLY" \
|
|
412
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
413
|
-
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"
|
|
414
|
-
|
|
415
|
-
python3 - "$TMPDIR/pi-completion-context-proposal-planning-only.out" "$TMPDIR/pi-completion-context-proposal-planning-only.err" "$DISCUSSION_SNAPSHOT_ZERO_PLANNING_ONLY" <<'PY'
|
|
163
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-structured-fallback.out" "$TMPDIR/pi-completion-context-proposal-structured-fallback.err" "$DISCUSSION_SNAPSHOT_ZERO" <<'PY'
|
|
416
164
|
import sys
|
|
417
165
|
from pathlib import Path
|
|
418
166
|
|
|
419
167
|
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
420
168
|
snapshot = Path(sys.argv[3])
|
|
421
|
-
assert not Path('.agent').exists(), '
|
|
422
|
-
assert not snapshot.exists(), '
|
|
423
|
-
assert '
|
|
424
|
-
assert 'Mission/Scope/Constraints/Acceptance' in output, 'planning-only startup should still explain the structured discussion requirement'
|
|
425
|
-
assert 'concrete repo changes' in output, 'planning-only startup should explain that bare /cook now expects execution-ready repo changes'
|
|
426
|
-
PY
|
|
427
|
-
|
|
428
|
-
# No workflow yet: docs-only tracked deliverables such as README/CHANGELOG updates should
|
|
429
|
-
# normalize placeholder planning missions into concrete repo-change missions.
|
|
430
|
-
SESSION_ZERO_SUPPORT_DOCS_ONLY="$TMPDIR/session-zero-support-docs-only.jsonl"
|
|
431
|
-
DISCUSSION_ZERO_SUPPORT_DOCS_ONLY=$'Mission: 開始實作這個方案\nScope:\n- Update README and CHANGELOG 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.'
|
|
432
|
-
DISCUSSION_SNAPSHOT_ZERO_SUPPORT_DOCS_ONLY="$TMPDIR/context-proposal-support-docs-only.json"
|
|
433
|
-
write_session "$SESSION_ZERO_SUPPORT_DOCS_ONLY" "$ROOT" "$DISCUSSION_ZERO_SUPPORT_DOCS_ONLY"
|
|
434
|
-
|
|
435
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
436
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
437
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_SUPPORT_DOCS_ONLY" \
|
|
438
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
439
|
-
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"
|
|
440
|
-
|
|
441
|
-
python3 - "$DISCUSSION_SNAPSHOT_ZERO_SUPPORT_DOCS_ONLY" <<'PY'
|
|
442
|
-
import json
|
|
443
|
-
import sys
|
|
444
|
-
from pathlib import Path
|
|
445
|
-
|
|
446
|
-
mission = 'Update README and CHANGELOG for the /cook mission-normalization behavior.'
|
|
447
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
448
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
449
|
-
|
|
450
|
-
assert proposal['mission'] == mission, 'docs-only startup should normalize the placeholder planning mission to the tracked-doc repo change'
|
|
451
|
-
assert state['mission_anchor'] == mission, 'docs-only startup should write the normalized tracked-doc mission into canonical state'
|
|
452
|
-
assert proposal['scope'][0] == mission, 'docs-only startup should derive the mission from the tracked-doc scope item'
|
|
453
|
-
assert proposal['acceptance'] == ['Add documentation for the operator-facing refocus flow.'], 'docs-only startup should keep the documentation acceptance item'
|
|
454
|
-
PY
|
|
455
|
-
|
|
456
|
-
rm -rf .agent
|
|
457
|
-
|
|
458
|
-
# No workflow yet: reviewer-reproduced docs-only phrasing with edit/document wording should
|
|
459
|
-
# also normalize placeholder planning missions into concrete repo-change missions.
|
|
460
|
-
SESSION_ZERO_EDIT_DOCUMENT_DOCS_ONLY="$TMPDIR/session-zero-edit-document-docs-only.jsonl"
|
|
461
|
-
DISCUSSION_ZERO_EDIT_DOCUMENT_DOCS_ONLY=$'Mission: 開始實作這個方案\nScope:\n- Edit README to explain the /cook mission-normalization behavior.\nConstraints:\n- Keep the approval-only Start/Cancel gate unchanged.\nAcceptance:\n- Document the operator-facing refocus flow.'
|
|
462
|
-
DISCUSSION_SNAPSHOT_ZERO_EDIT_DOCUMENT_DOCS_ONLY="$TMPDIR/context-proposal-edit-document-docs-only.json"
|
|
463
|
-
write_session "$SESSION_ZERO_EDIT_DOCUMENT_DOCS_ONLY" "$ROOT" "$DISCUSSION_ZERO_EDIT_DOCUMENT_DOCS_ONLY"
|
|
464
|
-
|
|
465
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
466
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
467
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_EDIT_DOCUMENT_DOCS_ONLY" \
|
|
468
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
469
|
-
pi --session "$SESSION_ZERO_EDIT_DOCUMENT_DOCS_ONLY" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-edit-document-docs-only.out" 2>"$TMPDIR/pi-completion-context-proposal-edit-document-docs-only.err"
|
|
470
|
-
|
|
471
|
-
python3 - "$DISCUSSION_SNAPSHOT_ZERO_EDIT_DOCUMENT_DOCS_ONLY" <<'PY'
|
|
472
|
-
import json
|
|
473
|
-
import sys
|
|
474
|
-
from pathlib import Path
|
|
475
|
-
|
|
476
|
-
mission = 'Edit README to explain the /cook mission-normalization behavior.'
|
|
477
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
478
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
479
|
-
|
|
480
|
-
assert proposal['mission'] == mission, 'edit/document docs-only startup should normalize the placeholder planning mission to the tracked-doc repo change'
|
|
481
|
-
assert state['mission_anchor'] == mission, 'edit/document docs-only startup should write the normalized tracked-doc mission into canonical state'
|
|
482
|
-
assert proposal['scope'][0] == mission, 'edit/document docs-only startup should derive the mission from the tracked-doc scope item'
|
|
483
|
-
assert proposal['acceptance'] == ['Document the operator-facing refocus flow.'], 'edit/document docs-only startup should keep the documentation acceptance item'
|
|
484
|
-
PY
|
|
485
|
-
|
|
486
|
-
rm -rf .agent
|
|
487
|
-
|
|
488
|
-
# No workflow yet: acceptance-only docs deliverables using write phrasing should also
|
|
489
|
-
# normalize placeholder planning missions into concrete repo-change missions.
|
|
490
|
-
SESSION_ZERO_WRITE_DOCS_ONLY="$TMPDIR/session-zero-write-docs-only.jsonl"
|
|
491
|
-
DISCUSSION_ZERO_WRITE_DOCS_ONLY=$'Mission: 開始實作這個方案\nScope:\n- README and CHANGELOG guidance for bare /cook.\nConstraints:\n- Keep the approval-only Start/Cancel gate unchanged.\nAcceptance:\n- Write README and CHANGELOG notes for the bare /cook fail-closed clarification path.'
|
|
492
|
-
DISCUSSION_SNAPSHOT_ZERO_WRITE_DOCS_ONLY="$TMPDIR/context-proposal-write-docs-only.json"
|
|
493
|
-
write_session "$SESSION_ZERO_WRITE_DOCS_ONLY" "$ROOT" "$DISCUSSION_ZERO_WRITE_DOCS_ONLY"
|
|
494
|
-
|
|
495
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
496
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
497
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_WRITE_DOCS_ONLY" \
|
|
498
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
499
|
-
pi --session "$SESSION_ZERO_WRITE_DOCS_ONLY" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-write-docs-only.out" 2>"$TMPDIR/pi-completion-context-proposal-write-docs-only.err"
|
|
500
|
-
|
|
501
|
-
python3 - "$DISCUSSION_SNAPSHOT_ZERO_WRITE_DOCS_ONLY" <<'PY'
|
|
502
|
-
import json
|
|
503
|
-
import sys
|
|
504
|
-
from pathlib import Path
|
|
505
|
-
|
|
506
|
-
mission = 'Write README and CHANGELOG notes for the bare /cook fail-closed clarification path.'
|
|
507
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
508
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
509
|
-
|
|
510
|
-
assert proposal['mission'] == mission, 'write docs-only startup should normalize the placeholder planning mission from acceptance-only tracked-doc work'
|
|
511
|
-
assert state['mission_anchor'] == mission, 'write docs-only startup should write the acceptance-derived tracked-doc mission into canonical state'
|
|
512
|
-
assert proposal['scope'] == ['README and CHANGELOG guidance for bare /cook.'], 'write docs-only startup should preserve the noun-only scope item while deriving the mission from acceptance'
|
|
513
|
-
assert proposal['acceptance'] == [mission], 'write docs-only startup should keep the acceptance-derived docs deliverable intact'
|
|
169
|
+
assert not Path('.agent').exists(), 'missing explicit handoff should fail closed without writing canonical state'
|
|
170
|
+
assert not snapshot.exists(), 'missing explicit handoff should not emit a startup proposal snapshot'
|
|
171
|
+
assert 'fresh valid explicit primary-agent handoff' in output, 'missing explicit handoff should explain the explicit-handoff-only startup contract'
|
|
514
172
|
PY
|
|
515
173
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
SESSION_ZERO_EXPLICIT_DOCS_ONLY_SCOPE="$TMPDIR/session-zero-explicit-docs-only-scope.jsonl"
|
|
521
|
-
DISCUSSION_ZERO_EXPLICIT_DOCS_ONLY_SCOPE=$'Mission: 開始實作這個方案\nScope:\n- Docs only: Update README and CHANGELOG for the /cook mission-normalization behavior.\nConstraints:\n- Keep the approval-only Start/Cancel gate unchanged.\nAcceptance:\n- Keep the operator-facing refocus flow guidance truthful.'
|
|
522
|
-
DISCUSSION_SNAPSHOT_ZERO_EXPLICIT_DOCS_ONLY_SCOPE="$TMPDIR/context-proposal-explicit-docs-only-scope.json"
|
|
523
|
-
write_session "$SESSION_ZERO_EXPLICIT_DOCS_ONLY_SCOPE" "$ROOT" "$DISCUSSION_ZERO_EXPLICIT_DOCS_ONLY_SCOPE"
|
|
524
|
-
|
|
525
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
526
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
527
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_EXPLICIT_DOCS_ONLY_SCOPE" \
|
|
528
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
529
|
-
pi --session "$SESSION_ZERO_EXPLICIT_DOCS_ONLY_SCOPE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-explicit-docs-only-scope.out" 2>"$TMPDIR/pi-completion-context-proposal-explicit-docs-only-scope.err"
|
|
530
|
-
|
|
531
|
-
python3 - "$DISCUSSION_SNAPSHOT_ZERO_EXPLICIT_DOCS_ONLY_SCOPE" <<'PY'
|
|
174
|
+
# No workflow yet: user-authored faux handoffs must not bootstrap canonical workflow state.
|
|
175
|
+
SESSION_ZERO_USER_AUTHORED="$TMPDIR/session-zero-user-authored.jsonl"
|
|
176
|
+
USER_AUTHORED_SNAPSHOT_ZERO="$TMPDIR/context-proposal-user-authored-handoff.json"
|
|
177
|
+
USER_AUTHORED_MESSAGES_ZERO="$(python3 - <<'PY'
|
|
532
178
|
import json
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
write_session "$SESSION_ZERO_EXPLICIT_DOCUMENTATION_ONLY_ACCEPTANCE" "$ROOT" "$DISCUSSION_ZERO_EXPLICIT_DOCUMENTATION_ONLY_ACCEPTANCE"
|
|
554
|
-
|
|
555
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
556
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
557
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_EXPLICIT_DOCUMENTATION_ONLY_ACCEPTANCE" \
|
|
558
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
559
|
-
pi --session "$SESSION_ZERO_EXPLICIT_DOCUMENTATION_ONLY_ACCEPTANCE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-explicit-documentation-only-acceptance.out" 2>"$TMPDIR/pi-completion-context-proposal-explicit-documentation-only-acceptance.err"
|
|
560
|
-
|
|
561
|
-
python3 - "$DISCUSSION_SNAPSHOT_ZERO_EXPLICIT_DOCUMENTATION_ONLY_ACCEPTANCE" <<'PY'
|
|
562
|
-
import json
|
|
563
|
-
import sys
|
|
564
|
-
from pathlib import Path
|
|
565
|
-
|
|
566
|
-
mission = 'Write README and CHANGELOG notes for the bare /cook fail-closed clarification path.'
|
|
567
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
568
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
569
|
-
|
|
570
|
-
assert proposal['mission'] == mission, 'explicit documentation only acceptance should strip the docs-only qualifier while normalizing to the tracked-doc repo change'
|
|
571
|
-
assert state['mission_anchor'] == mission, 'explicit documentation only acceptance should write the stripped tracked-doc mission into canonical state'
|
|
572
|
-
assert proposal['scope'] == ['README and CHANGELOG guidance for bare /cook.'], 'explicit documentation only acceptance should preserve the noun-only scope item while deriving the mission from acceptance'
|
|
573
|
-
assert proposal['acceptance'] == ['Documentation only: Write README and CHANGELOG notes for the bare /cook fail-closed clarification path.'], 'explicit documentation only acceptance should preserve the original acceptance wording in the proposal body'
|
|
574
|
-
PY
|
|
575
|
-
|
|
576
|
-
rm -rf .agent
|
|
577
|
-
|
|
578
|
-
# No workflow yet: assistant-authored completed-plan summaries should fail closed instead of
|
|
579
|
-
# seeding startup proposals when the user has not restated an execution-ready mission.
|
|
580
|
-
SESSION_ZERO_ASSISTANT_SUMMARY="$TMPDIR/session-zero-assistant-summary.jsonl"
|
|
581
|
-
DISCUSSION_SNAPSHOT_ZERO_ASSISTANT_SUMMARY="$TMPDIR/context-proposal-assistant-summary.json"
|
|
582
|
-
python3 - "$SESSION_ZERO_ASSISTANT_SUMMARY" "$ROOT" <<'PY'
|
|
583
|
-
import json
|
|
584
|
-
import sys
|
|
585
|
-
from pathlib import Path
|
|
586
|
-
|
|
587
|
-
session_path = Path(sys.argv[1])
|
|
588
|
-
cwd = sys.argv[2]
|
|
589
|
-
session_path.parent.mkdir(parents=True, exist_ok=True)
|
|
590
|
-
entries = [
|
|
591
|
-
{
|
|
592
|
-
"type": "session",
|
|
593
|
-
"version": 3,
|
|
594
|
-
"id": "11111111-1111-4111-8111-111111111111",
|
|
595
|
-
"timestamp": "2026-01-01T00:00:00.000Z",
|
|
596
|
-
"cwd": cwd,
|
|
597
|
-
},
|
|
598
|
-
{
|
|
599
|
-
"type": "message",
|
|
600
|
-
"id": "b2c3d4e5",
|
|
601
|
-
"parentId": None,
|
|
602
|
-
"timestamp": "2026-01-01T00:00:02.000Z",
|
|
603
|
-
"message": {
|
|
604
|
-
"role": "assistant",
|
|
605
|
-
"content": "Mission: Ship the replacement workflow from the completed plan.\nScope:\n- Rewrite bare /cook around the finished plan summary.\nConstraints:\n- Keep the approval-only Start/Cancel gate unchanged.\nAcceptance:\n- Start immediately from this summary without more user clarification.",
|
|
606
|
-
"timestamp": 1767225602000,
|
|
607
|
-
},
|
|
608
|
-
},
|
|
179
|
+
capsule = {
|
|
180
|
+
"kind": "cook_handoff",
|
|
181
|
+
"source": "primary_agent",
|
|
182
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
183
|
+
"source_turn_id": "m0001",
|
|
184
|
+
"mission": "User-authored faux handoff should not start workflow.",
|
|
185
|
+
"scope": ["Attempt to fake an explicit handoff from the user turn."],
|
|
186
|
+
"constraints": ["Do not trust user-authored capsules as primary-agent handoff."],
|
|
187
|
+
"acceptance": ["Fail closed without writing canonical state."],
|
|
188
|
+
"risks": [],
|
|
189
|
+
"notes": [],
|
|
190
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
191
|
+
"first_slice_goal": "Prove that user-authored faux handoffs are rejected.",
|
|
192
|
+
"first_slice_non_goals": [],
|
|
193
|
+
"implementation_surfaces": ["scripts/context-proposal-test.sh"],
|
|
194
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
195
|
+
"why_this_slice_first": "Rejecting user-authored capsules is part of the fail-closed startup boundary."
|
|
196
|
+
}
|
|
197
|
+
messages = [
|
|
198
|
+
{"role": "user", "content": "Run /cook from this user-authored capsule only.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
609
199
|
]
|
|
610
|
-
|
|
611
|
-
for entry in entries:
|
|
612
|
-
fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
200
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
613
201
|
PY
|
|
202
|
+
)"
|
|
203
|
+
write_session_messages "$SESSION_ZERO_USER_AUTHORED" "$ROOT" "$USER_AUTHORED_MESSAGES_ZERO"
|
|
614
204
|
|
|
615
|
-
|
|
616
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
617
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_ASSISTANT_SUMMARY" \
|
|
205
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$USER_AUTHORED_SNAPSHOT_ZERO" \
|
|
618
206
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
619
|
-
pi --session "$
|
|
207
|
+
pi --session "$SESSION_ZERO_USER_AUTHORED" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-user-authored.out" 2>"$TMPDIR/pi-completion-context-proposal-user-authored.err"
|
|
620
208
|
|
|
621
|
-
python3 - "$TMPDIR/pi-completion-context-proposal-
|
|
209
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-user-authored.out" "$TMPDIR/pi-completion-context-proposal-user-authored.err" "$USER_AUTHORED_SNAPSHOT_ZERO" <<'PY'
|
|
622
210
|
import sys
|
|
623
211
|
from pathlib import Path
|
|
624
212
|
|
|
625
213
|
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
626
214
|
snapshot = Path(sys.argv[3])
|
|
627
|
-
assert not Path('.agent').exists(), '
|
|
628
|
-
assert not snapshot.exists(), '
|
|
629
|
-
assert '
|
|
630
|
-
assert 'concrete repo changes' in output, 'assistant-only startup summary should explain that bare /cook expects execution-ready repo changes from main-chat discussion'
|
|
215
|
+
assert not Path('.agent').exists(), 'user-authored faux handoff should fail closed without writing canonical state'
|
|
216
|
+
assert not snapshot.exists(), 'user-authored faux handoff should not emit a startup proposal snapshot'
|
|
217
|
+
assert 'fresh valid explicit primary-agent handoff' in output, 'user-authored faux handoff should still explain the explicit-handoff-only startup contract'
|
|
631
218
|
PY
|
|
632
219
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
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.'
|
|
639
|
-
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}'
|
|
640
|
-
DISCUSSION_SNAPSHOT_ZERO_ANALYST_AMBIGUOUS_GENERIC="$TMPDIR/context-proposal-analyst-ambiguous-generic.json"
|
|
641
|
-
write_session "$SESSION_ZERO_ANALYST_AMBIGUOUS_GENERIC" "$ROOT" "$DISCUSSION_ZERO_ANALYST_AMBIGUOUS_GENERIC"
|
|
220
|
+
# No workflow yet: malformed or invalid assistant handoff capsules must also fail closed.
|
|
221
|
+
SESSION_ZERO_INVALID="$TMPDIR/session-zero-invalid-handoff.jsonl"
|
|
222
|
+
INVALID_SNAPSHOT_ZERO="$TMPDIR/context-proposal-invalid-handoff.json"
|
|
223
|
+
INVALID_MESSAGES_ZERO='[{"role":"assistant","content":"This is not a valid startup capsule.\n\n```cook_handoff\n{\"kind\":\"cook_handoff\",\"source\":\"primary_agent\",\"mission\":\"Broken JSON handoff\"\n```"}]'
|
|
224
|
+
write_session_messages "$SESSION_ZERO_INVALID" "$ROOT" "$INVALID_MESSAGES_ZERO"
|
|
642
225
|
|
|
643
|
-
|
|
644
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$ANALYST_OUTPUT_ZERO_AMBIGUOUS_GENERIC" \
|
|
645
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_ANALYST_AMBIGUOUS_GENERIC" \
|
|
226
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$INVALID_SNAPSHOT_ZERO" \
|
|
646
227
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
647
|
-
pi --session "$
|
|
228
|
+
pi --session "$SESSION_ZERO_INVALID" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-invalid-handoff.out" 2>"$TMPDIR/pi-completion-context-proposal-invalid-handoff.err"
|
|
648
229
|
|
|
649
|
-
python3 - "$TMPDIR/pi-completion-context-proposal-
|
|
230
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-invalid-handoff.out" "$TMPDIR/pi-completion-context-proposal-invalid-handoff.err" "$INVALID_SNAPSHOT_ZERO" <<'PY'
|
|
650
231
|
import sys
|
|
651
232
|
from pathlib import Path
|
|
652
233
|
|
|
653
234
|
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
654
235
|
snapshot = Path(sys.argv[3])
|
|
655
|
-
assert not Path('.agent').exists(), '
|
|
656
|
-
assert not snapshot.exists(), '
|
|
657
|
-
assert '
|
|
236
|
+
assert not Path('.agent').exists(), 'invalid assistant handoff should fail closed without writing canonical state'
|
|
237
|
+
assert not snapshot.exists(), 'invalid assistant handoff should not emit a startup proposal snapshot'
|
|
238
|
+
assert 'fresh valid explicit primary-agent handoff' in output, 'invalid assistant handoff should still explain the explicit-handoff-only startup contract'
|
|
658
239
|
PY
|
|
659
240
|
|
|
660
|
-
# No workflow yet:
|
|
241
|
+
# No workflow yet: a fresh explicit primary-agent handoff should still bootstrap canonical startup state.
|
|
661
242
|
SESSION_ONE="$TMPDIR/session-one.jsonl"
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
243
|
+
HANDOFF_SNAPSHOT_ONE="$TMPDIR/context-proposal-explicit-startup.json"
|
|
244
|
+
HANDOFF_MESSAGES_ONE="$(python3 - <<'PY'
|
|
245
|
+
import json
|
|
246
|
+
capsule = {
|
|
247
|
+
"kind": "cook_handoff",
|
|
248
|
+
"source": "primary_agent",
|
|
249
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
250
|
+
"source_turn_id": "m0002",
|
|
251
|
+
"mission": "Remove the completion status line while keeping the completion widget.",
|
|
252
|
+
"scope": [
|
|
253
|
+
"Keep the non-running completion widget.",
|
|
254
|
+
"Suppress the widget while a completion role is active."
|
|
255
|
+
],
|
|
256
|
+
"constraints": [
|
|
257
|
+
"Do not reintroduce any other completion status surface."
|
|
258
|
+
],
|
|
259
|
+
"acceptance": [
|
|
260
|
+
"Update README to match the shipped behavior.",
|
|
261
|
+
"Keep observability regression coverage truthful."
|
|
262
|
+
],
|
|
263
|
+
"risks": [
|
|
264
|
+
"Stale widget-removal discussion could broaden the startup plan if the handoff is ignored."
|
|
265
|
+
],
|
|
266
|
+
"notes": [
|
|
267
|
+
"Keep the startup brief aligned with the explicit primary-agent plan."
|
|
268
|
+
],
|
|
269
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
270
|
+
"first_slice_goal": "Land the completion-status removal and keep the completion widget coverage truthful.",
|
|
271
|
+
"first_slice_non_goals": [
|
|
272
|
+
"Do not reintroduce any other completion status surface."
|
|
273
|
+
],
|
|
274
|
+
"implementation_surfaces": [
|
|
275
|
+
"extensions/completion/index.ts",
|
|
276
|
+
"scripts/context-proposal-test.sh"
|
|
277
|
+
],
|
|
278
|
+
"verification_commands": [
|
|
279
|
+
"npm run context-proposal-test"
|
|
280
|
+
],
|
|
281
|
+
"why_this_slice_first": "The startup boundary regression is already bounded enough to implement safely.",
|
|
282
|
+
"task_type": "completion-workflow",
|
|
283
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
284
|
+
"why_cook_now": "The explicit startup brief is concrete and ready for repo changes."
|
|
285
|
+
}
|
|
286
|
+
messages = [
|
|
287
|
+
{"role": "user", "content": "Please think through the completion widget startup boundary and tell me when it is ready for /cook."},
|
|
288
|
+
{"role": "assistant", "content": "This task is now ready for /cook. Run /cook to confirm the startup brief.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
289
|
+
]
|
|
290
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
291
|
+
PY
|
|
292
|
+
)"
|
|
293
|
+
write_session_messages "$SESSION_ONE" "$ROOT" "$HANDOFF_MESSAGES_ONE"
|
|
666
294
|
|
|
667
295
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
668
|
-
|
|
669
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ONE" \
|
|
296
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_ONE" \
|
|
670
297
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
671
|
-
pi --session "$SESSION_ONE" -e "$PKG_ROOT" -p "/cook"
|
|
298
|
+
pi --session "$SESSION_ONE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-explicit-startup.out" 2>"$TMPDIR/pi-completion-context-proposal-explicit-startup.err"
|
|
672
299
|
|
|
673
|
-
python3 - "$
|
|
300
|
+
python3 - "$HANDOFF_SNAPSHOT_ONE" <<'PY'
|
|
674
301
|
import json
|
|
675
302
|
import sys
|
|
676
303
|
from pathlib import Path
|
|
@@ -685,61 +312,60 @@ plan = json.loads(Path('.agent/plan.json').read_text())
|
|
|
685
312
|
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
686
313
|
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
687
314
|
|
|
688
|
-
assert mission in mission_text, '.agent/mission.md did not record the
|
|
689
|
-
assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after
|
|
690
|
-
assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after
|
|
691
|
-
assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after
|
|
692
|
-
assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after
|
|
693
|
-
assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after
|
|
694
|
-
assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after
|
|
695
|
-
assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after
|
|
696
|
-
assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after
|
|
697
|
-
assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after
|
|
698
|
-
assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after
|
|
699
|
-
assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after
|
|
315
|
+
assert mission in mission_text, '.agent/mission.md did not record the explicit-handoff mission anchor'
|
|
316
|
+
assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after explicit-handoff bootstrap'
|
|
317
|
+
assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after explicit-handoff bootstrap'
|
|
318
|
+
assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after explicit-handoff bootstrap'
|
|
319
|
+
assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after explicit-handoff bootstrap'
|
|
320
|
+
assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after explicit-handoff bootstrap'
|
|
321
|
+
assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after explicit-handoff bootstrap'
|
|
322
|
+
assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after explicit-handoff bootstrap'
|
|
323
|
+
assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after explicit-handoff bootstrap'
|
|
324
|
+
assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after explicit-handoff bootstrap'
|
|
325
|
+
assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after explicit-handoff bootstrap'
|
|
326
|
+
assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after explicit-handoff bootstrap'
|
|
700
327
|
brief = state['advisory_startup_brief']
|
|
701
328
|
assert brief['kind'] == 'startup_brief', 'state.json should preserve the confirmed startup brief as advisory intake'
|
|
329
|
+
assert brief['source'] == 'primary_agent_handoff', 'explicit startup should record the handoff source in advisory intake'
|
|
702
330
|
assert brief['mission'] == mission, 'advisory startup brief mission should match the accepted mission anchor'
|
|
703
331
|
assert brief['scope'] == ['Keep the non-running completion widget.', 'Suppress the widget while a completion role is active.'], 'advisory startup brief should preserve scope items separately from canonical planning state'
|
|
704
332
|
assert brief['constraints'] == ['Do not reintroduce any other completion status surface.'], 'advisory startup brief should preserve constraints separately from canonical planning state'
|
|
705
333
|
assert brief['acceptance'] == ['Update README to match the shipped behavior.', 'Keep observability regression coverage truthful.'], 'advisory startup brief should preserve acceptance separately from canonical planning state'
|
|
706
|
-
assert brief['risks'] == ['Stale widget-removal discussion could broaden the startup plan if
|
|
707
|
-
assert
|
|
334
|
+
assert brief['risks'] == ['Stale widget-removal discussion could broaden the startup plan if the handoff is ignored.'], 'advisory startup brief should preserve handoff risks'
|
|
335
|
+
assert 'First slice goal: Land the completion-status removal and keep the completion widget coverage truthful.' in brief['notes'], 'advisory startup brief should preserve first_slice_goal in notes'
|
|
336
|
+
assert 'Verification commands: npm run context-proposal-test' in brief['notes'], 'advisory startup brief should preserve verification_commands in notes'
|
|
708
337
|
assert plan['candidate_slices'] == [], 'startup brief should remain advisory intake only until regrounder owns plan selection'
|
|
709
338
|
assert active['status'] == 'idle', 'startup brief should not become the active-slice source before regrounder runs'
|
|
710
|
-
assert proposal['mission'] == mission, '
|
|
711
|
-
assert proposal['
|
|
712
|
-
assert proposal['analysis']['
|
|
713
|
-
assert proposal['analysis']['
|
|
714
|
-
assert
|
|
715
|
-
assert
|
|
716
|
-
assert 'Critique:' not in proposal['goalText'], 'goalText should keep critique separate from mission/scope/constraints/acceptance'
|
|
717
|
-
assert 'Task type:' not in proposal['goalText'], 'goalText should keep task_type hints separate from the mission body'
|
|
718
|
-
assert state['current_phase'] == 'reground', 'state.json current_phase should start at reground after analyst-derived bootstrap'
|
|
719
|
-
assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should start at completion-regrounder after analyst-derived bootstrap'
|
|
339
|
+
assert proposal['mission'] == mission, 'explicit startup proposal snapshot should keep the handoff mission anchor'
|
|
340
|
+
assert proposal['source'] == 'handoff_capsule', 'explicit startup proposal snapshot should expose the handoff capsule source'
|
|
341
|
+
assert proposal['analysis']['taskType'] == expected_task_type, 'explicit startup proposal snapshot should expose task_type hints separately'
|
|
342
|
+
assert proposal['analysis']['evaluationProfile'] == expected_eval_profile, 'explicit startup proposal snapshot should expose evaluation_profile hints separately'
|
|
343
|
+
assert state['current_phase'] == 'reground', 'state.json current_phase should start at reground after explicit-handoff bootstrap'
|
|
344
|
+
assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should start at completion-regrounder after explicit-handoff bootstrap'
|
|
720
345
|
assert state['continuation_reason'].startswith('User started workflow via /cook:'), 'initial startup should record the accepted startup routing in continuation_reason'
|
|
721
346
|
assert 'task_type=completion-workflow' in state['continuation_reason'], 'initial startup should persist the selected task_type in continuation_reason'
|
|
722
347
|
assert 'evaluation_profile=completion-rubric-v1' in state['continuation_reason'], 'initial startup should persist the selected evaluation_profile in continuation_reason'
|
|
723
|
-
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'
|
|
724
348
|
PY
|
|
725
349
|
|
|
726
|
-
# Active workflow: bare /cook
|
|
727
|
-
#
|
|
350
|
+
# Active workflow: bare /cook should resume from canonical state when no fresh explicit handoff exists,
|
|
351
|
+
# even if recent discussion restates the current mission in a structured way.
|
|
728
352
|
SESSION_ONE_CONTINUE="$TMPDIR/session-one-continue.jsonl"
|
|
729
353
|
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.'
|
|
730
354
|
CONTINUE_ROUTING_ONE="$TMPDIR/active-continue-routing.json"
|
|
731
355
|
CONTINUE_RESUME_PROMPT_ONE="$TMPDIR/active-continue-resume.txt"
|
|
732
356
|
CONTINUE_CHOOSER_ONE="$TMPDIR/unexpected-active-continue-chooser.json"
|
|
357
|
+
CONTINUE_PROPOSAL_ONE="$TMPDIR/unexpected-active-continue-proposal.json"
|
|
733
358
|
write_session "$SESSION_ONE_CONTINUE" "$ROOT" "$DISCUSSION_ONE_CONTINUE"
|
|
734
359
|
|
|
735
360
|
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
736
361
|
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$CONTINUE_ROUTING_ONE" \
|
|
737
362
|
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$CONTINUE_RESUME_PROMPT_ONE" \
|
|
738
363
|
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$CONTINUE_CHOOSER_ONE" \
|
|
364
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$CONTINUE_PROPOSAL_ONE" \
|
|
739
365
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
740
366
|
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"
|
|
741
367
|
|
|
742
|
-
python3 - "$CONTINUE_ROUTING_ONE" "$CONTINUE_RESUME_PROMPT_ONE" "$CONTINUE_CHOOSER_ONE" <<'PY'
|
|
368
|
+
python3 - "$CONTINUE_ROUTING_ONE" "$CONTINUE_RESUME_PROMPT_ONE" "$CONTINUE_CHOOSER_ONE" "$CONTINUE_PROPOSAL_ONE" <<'PY'
|
|
743
369
|
import json
|
|
744
370
|
import sys
|
|
745
371
|
from pathlib import Path
|
|
@@ -748,26 +374,73 @@ mission = 'Remove the completion status line while keeping the completion widget
|
|
|
748
374
|
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
749
375
|
resume = Path(sys.argv[2]).read_text()
|
|
750
376
|
chooser_path = Path(sys.argv[3])
|
|
377
|
+
proposal_path = Path(sys.argv[4])
|
|
751
378
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
752
379
|
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
753
380
|
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
754
381
|
|
|
755
|
-
assert routing['mode'] == 'bare', 'active bare /cook
|
|
756
|
-
assert 'explicitGoal' not in routing, 'active bare /cook
|
|
757
|
-
assert 'explicitGoalProvided' not in routing, 'active bare /cook
|
|
758
|
-
assert routing['action'] == 'continue', '
|
|
759
|
-
assert routing['reason'] == '
|
|
760
|
-
assert routing['currentMissionAnchor'] == mission, '
|
|
761
|
-
assert routing['proposedMissionAnchor']
|
|
762
|
-
assert 'Resume the completion workflow from canonical state.' in resume, 'active bare /cook
|
|
763
|
-
assert not chooser_path.exists(), 'active bare /cook
|
|
764
|
-
assert
|
|
765
|
-
assert
|
|
766
|
-
assert
|
|
382
|
+
assert routing['mode'] == 'bare', 'active bare /cook resume regression should snapshot bare routing mode'
|
|
383
|
+
assert 'explicitGoal' not in routing, 'active bare /cook resume routing should not expose removed explicit-goal shim fields'
|
|
384
|
+
assert 'explicitGoalProvided' not in routing, 'active bare /cook resume routing should not expose removed explicit-goal shim fields'
|
|
385
|
+
assert routing['action'] == 'continue', 'active bare /cook should resume when no fresh explicit handoff exists'
|
|
386
|
+
assert routing['reason'] == 'missing_explicit_handoff', 'active bare /cook should explain that resume happened because no fresh explicit handoff existed'
|
|
387
|
+
assert routing['currentMissionAnchor'] == mission, 'resume routing should preserve the current mission anchor'
|
|
388
|
+
assert routing['proposedMissionAnchor'] is None, 'resume routing should not derive a replacement mission from recent discussion'
|
|
389
|
+
assert 'Resume the completion workflow from canonical state.' in resume, 'active bare /cook resume should still use the canonical resume prompt'
|
|
390
|
+
assert not chooser_path.exists(), 'active bare /cook resume should not open the replacement chooser without a fresh explicit handoff'
|
|
391
|
+
assert not proposal_path.exists(), 'active bare /cook resume should not open replacement proposal confirmation without a fresh explicit handoff'
|
|
392
|
+
assert state['mission_anchor'] == mission, 'active bare /cook resume should keep state.json unchanged'
|
|
393
|
+
assert plan['mission_anchor'] == mission, 'active bare /cook resume should keep plan.json unchanged'
|
|
394
|
+
assert active['mission_anchor'] == mission, 'active bare /cook resume should keep active-slice.json unchanged'
|
|
767
395
|
PY
|
|
768
396
|
|
|
769
|
-
# Active workflow:
|
|
770
|
-
#
|
|
397
|
+
# Active workflow: even strongly different recent discussion should no longer open chooser/refocus startup
|
|
398
|
+
# when no fresh valid explicit handoff is present.
|
|
399
|
+
SESSION_ONE_DISCUSSION_REFOCUS="$TMPDIR/session-one-discussion-refocus.jsonl"
|
|
400
|
+
DISCUSSION_ONE_DISCUSSION_REFOCUS=$'Mission: Normalize bare /cook planning phrasing into implementation-result missions.\nScope:\n- Replace the current workflow from recent discussion only.\n- Keep the approval-only Start/Cancel gate before rewriting canonical state.\nConstraints:\n- Do not require a fresh explicit handoff.\nAcceptance:\n- Rewrite canonical state from recent discussion.'
|
|
401
|
+
DISCUSSION_REFOCUS_ROUTING_ONE="$TMPDIR/active-discussion-refocus-routing.json"
|
|
402
|
+
DISCUSSION_REFOCUS_RESUME_ONE="$TMPDIR/active-discussion-refocus-resume.txt"
|
|
403
|
+
DISCUSSION_REFOCUS_CHOOSER_ONE="$TMPDIR/unexpected-active-discussion-refocus-chooser.json"
|
|
404
|
+
DISCUSSION_REFOCUS_PROPOSAL_ONE="$TMPDIR/unexpected-active-discussion-refocus-proposal.json"
|
|
405
|
+
write_session "$SESSION_ONE_DISCUSSION_REFOCUS" "$ROOT" "$DISCUSSION_ONE_DISCUSSION_REFOCUS"
|
|
406
|
+
|
|
407
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
408
|
+
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$DISCUSSION_REFOCUS_ROUTING_ONE" \
|
|
409
|
+
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$DISCUSSION_REFOCUS_RESUME_ONE" \
|
|
410
|
+
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$DISCUSSION_REFOCUS_CHOOSER_ONE" \
|
|
411
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_REFOCUS_PROPOSAL_ONE" \
|
|
412
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
413
|
+
pi --session "$SESSION_ONE_DISCUSSION_REFOCUS" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-active-discussion-refocus.out" 2>"$TMPDIR/pi-completion-context-proposal-active-discussion-refocus.err"
|
|
414
|
+
|
|
415
|
+
python3 - "$DISCUSSION_REFOCUS_ROUTING_ONE" "$DISCUSSION_REFOCUS_RESUME_ONE" "$DISCUSSION_REFOCUS_CHOOSER_ONE" "$DISCUSSION_REFOCUS_PROPOSAL_ONE" <<'PY'
|
|
416
|
+
import json
|
|
417
|
+
import sys
|
|
418
|
+
from pathlib import Path
|
|
419
|
+
|
|
420
|
+
mission = 'Remove the completion status line while keeping the completion widget.'
|
|
421
|
+
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
422
|
+
resume = Path(sys.argv[2]).read_text()
|
|
423
|
+
chooser_path = Path(sys.argv[3])
|
|
424
|
+
proposal_path = Path(sys.argv[4])
|
|
425
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
426
|
+
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
427
|
+
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
428
|
+
|
|
429
|
+
assert routing['mode'] == 'bare', 'discussion-driven refocus removal should snapshot bare routing mode'
|
|
430
|
+
assert routing['action'] == 'continue', 'bare /cook should resume instead of deriving a replacement workflow from recent discussion'
|
|
431
|
+
assert routing['reason'] == 'missing_explicit_handoff', 'discussion-driven refocus removal should explain that no fresh explicit handoff existed'
|
|
432
|
+
assert routing['currentMissionAnchor'] == mission, 'discussion-driven refocus removal should preserve the current mission anchor'
|
|
433
|
+
assert routing['proposedMissionAnchor'] is None, 'discussion-driven refocus removal should not preserve a replacement mission from recent discussion'
|
|
434
|
+
assert 'Resume the completion workflow from canonical state.' in resume, 'discussion-driven refocus removal should still queue the canonical resume prompt'
|
|
435
|
+
assert not chooser_path.exists(), 'discussion-driven refocus removal should not open the chooser'
|
|
436
|
+
assert not proposal_path.exists(), 'discussion-driven refocus removal should not open final proposal confirmation'
|
|
437
|
+
assert state['mission_anchor'] == mission, 'discussion-driven refocus removal should keep state.json unchanged'
|
|
438
|
+
assert plan['mission_anchor'] == mission, 'discussion-driven refocus removal should keep plan.json unchanged'
|
|
439
|
+
assert active['mission_anchor'] == mission, 'discussion-driven refocus removal should keep active-slice.json unchanged'
|
|
440
|
+
PY
|
|
441
|
+
|
|
442
|
+
# Active workflow: summary-only replacement artifacts should also resume the current workflow when no fresh
|
|
443
|
+
# explicit handoff exists.
|
|
771
444
|
SESSION_ONE_SUMMARY_ONLY="$TMPDIR/session-one-summary-only.jsonl"
|
|
772
445
|
SUMMARY_ROUTING_ONE="$TMPDIR/active-summary-only-routing.json"
|
|
773
446
|
SUMMARY_RESUME_PROMPT_ONE="$TMPDIR/active-summary-only-resume.txt"
|
|
@@ -828,8 +501,8 @@ plan = json.loads(Path('.agent/plan.json').read_text())
|
|
|
828
501
|
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
829
502
|
|
|
830
503
|
assert routing['mode'] == 'bare', 'summary-only active bare /cook regression should snapshot bare routing mode'
|
|
831
|
-
assert routing['action'] == '
|
|
832
|
-
assert routing['reason'] == '
|
|
504
|
+
assert routing['action'] == 'continue', 'summary-only active bare /cook should resume rather than derive replacement startup'
|
|
505
|
+
assert routing['reason'] == 'missing_explicit_handoff', 'summary-only active bare /cook should explain that no fresh explicit handoff existed'
|
|
833
506
|
assert routing['currentMissionAnchor'] == mission, 'summary-only active bare /cook should preserve the current mission anchor'
|
|
834
507
|
assert routing['proposedMissionAnchor'] is None, 'summary-only active bare /cook should not derive a replacement mission from summary artifacts alone'
|
|
835
508
|
assert 'Resume the completion workflow from canonical state.' in resume, 'summary-only active bare /cook should still resume the canonical workflow'
|
|
@@ -840,146 +513,108 @@ assert plan['mission_anchor'] == mission, 'summary-only active bare /cook should
|
|
|
840
513
|
assert active['mission_anchor'] == mission, 'summary-only active bare /cook should keep active-slice.json unchanged'
|
|
841
514
|
PY
|
|
842
515
|
|
|
843
|
-
# Active workflow:
|
|
844
|
-
#
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
write_session "$SESSION_ONE_AMBIGUOUS_CHOOSER" "$ROOT" "$DISCUSSION_ONE_AMBIGUOUS_CHOOSER"
|
|
852
|
-
|
|
853
|
-
PI_COMPLETION_EXISTING_WORKFLOW_ACTION=cancel \
|
|
854
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT='{"mission":"Fix login redirect callback behavior.","scope":["Update the callback redirect decision logic for the current auth flow."],"constraints":[],"acceptance":[],"task_type":"completion-workflow","evaluation_profile":"completion-rubric-v1","confidence":0.72,"possible_noise":["older completion widget cleanup"]}' \
|
|
855
|
-
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$AMBIGUOUS_ROUTING_ONE" \
|
|
856
|
-
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$AMBIGUOUS_CHOOSER_ONE" \
|
|
857
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$AMBIGUOUS_RESUME_PROMPT_ONE" \
|
|
858
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$AMBIGUOUS_PROPOSAL_ONE" \
|
|
859
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
860
|
-
pi --session "$SESSION_ONE_AMBIGUOUS_CHOOSER" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-active-ambiguous-chooser.out" 2>"$TMPDIR/pi-completion-context-proposal-active-ambiguous-chooser.err"
|
|
861
|
-
|
|
862
|
-
python3 - "$AMBIGUOUS_ROUTING_ONE" "$AMBIGUOUS_CHOOSER_ONE" "$AMBIGUOUS_RESUME_PROMPT_ONE" "$AMBIGUOUS_PROPOSAL_ONE" "$TMPDIR/pi-completion-context-proposal-active-ambiguous-chooser.out" "$TMPDIR/pi-completion-context-proposal-active-ambiguous-chooser.err" <<'PY'
|
|
516
|
+
# Active workflow: a fresh explicit handoff that is not implementation-startable should still fail closed
|
|
517
|
+
# without rewriting canonical state.
|
|
518
|
+
SESSION_ONE_NON_STARTABLE_ACTIVE="$TMPDIR/session-one-non-startable-active.jsonl"
|
|
519
|
+
NON_STARTABLE_ACTIVE_ROUTING="$TMPDIR/active-non-startable-routing.json"
|
|
520
|
+
NON_STARTABLE_ACTIVE_RESUME="$TMPDIR/unexpected-active-non-startable-resume.txt"
|
|
521
|
+
NON_STARTABLE_ACTIVE_CHOOSER="$TMPDIR/unexpected-active-non-startable-chooser.json"
|
|
522
|
+
NON_STARTABLE_ACTIVE_PROPOSAL="$TMPDIR/unexpected-active-non-startable-proposal.json"
|
|
523
|
+
NON_STARTABLE_ACTIVE_MESSAGES="$(python3 - <<'PY'
|
|
863
524
|
import json
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
525
|
+
capsule = {
|
|
526
|
+
"kind": "cook_handoff",
|
|
527
|
+
"source": "primary_agent",
|
|
528
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
529
|
+
"source_turn_id": "m0002",
|
|
530
|
+
"mission": "Replace the current widget mission from a vague explicit handoff.",
|
|
531
|
+
"scope": [
|
|
532
|
+
"Replace the active workflow from a fresh explicit handoff."
|
|
533
|
+
],
|
|
534
|
+
"constraints": [
|
|
535
|
+
"Do not rely on recent discussion to fill in missing implementation details."
|
|
536
|
+
],
|
|
537
|
+
"acceptance": [
|
|
538
|
+
"Current behavior stays understandable."
|
|
539
|
+
],
|
|
540
|
+
"risks": [],
|
|
541
|
+
"notes": [
|
|
542
|
+
"This capsule is intentionally non-startable for active-workflow fail-closed coverage."
|
|
543
|
+
],
|
|
544
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
545
|
+
"first_slice_goal": "Attempt to replace the active workflow from a vague capsule.",
|
|
546
|
+
"first_slice_non_goals": [],
|
|
547
|
+
"implementation_surfaces": [
|
|
548
|
+
"extensions/completion/driver.ts"
|
|
549
|
+
],
|
|
550
|
+
"verification_commands": [
|
|
551
|
+
"npm run context-proposal-test"
|
|
552
|
+
],
|
|
553
|
+
"why_this_slice_first": "Active-workflow replacement should fail closed when the capsule is fresh but not startable.",
|
|
554
|
+
"task_type": "completion-workflow",
|
|
555
|
+
"evaluation_profile": "completion-rubric-v1"
|
|
556
|
+
}
|
|
557
|
+
messages = [
|
|
558
|
+
{"role": "user", "content": "We may need a different active workflow, but only if there is a fresh explicit handoff."},
|
|
559
|
+
{"role": "assistant", "content": "Only use this capsule if it is concrete enough.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
560
|
+
]
|
|
561
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
891
562
|
PY
|
|
563
|
+
)"
|
|
564
|
+
write_session_messages "$SESSION_ONE_NON_STARTABLE_ACTIVE" "$ROOT" "$NON_STARTABLE_ACTIVE_MESSAGES"
|
|
892
565
|
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$MULTI_CHOOSER_ONE" \
|
|
907
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$MULTI_PROPOSAL_ONE" \
|
|
908
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
909
|
-
pi --session "$SESSION_ONE_MULTI_CANDIDATE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-active-multi.out" 2>"$TMPDIR/pi-completion-context-proposal-active-multi.err"
|
|
910
|
-
|
|
911
|
-
python3 - "$MULTI_ROUTING_ONE" "$MULTI_CHOOSER_ONE" "$MULTI_PROPOSAL_ONE" <<'PY'
|
|
912
|
-
import json
|
|
913
|
-
import sys
|
|
914
|
-
from pathlib import Path
|
|
915
|
-
|
|
916
|
-
selected = 'Fix login redirect callback behavior.'
|
|
917
|
-
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
918
|
-
chooser = json.loads(Path(sys.argv[2]).read_text())
|
|
919
|
-
proposal = json.loads(Path(sys.argv[3]).read_text())
|
|
920
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
921
|
-
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
922
|
-
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
923
|
-
|
|
924
|
-
assert routing['action'] == 'unclear', 'multi-candidate replacement discussion should stay ambiguous until the chooser selects one mission'
|
|
925
|
-
assert routing['reason'] == 'ambiguous_discussion', 'multi-candidate replacement discussion should record ambiguous-discussion routing'
|
|
926
|
-
assert routing['alternateMissions'] == ['Fix login redirect callback behavior.'], 'routing snapshot should preserve alternate candidate missions'
|
|
927
|
-
assert chooser['candidateMissions'] == ['Add logout redirect regression coverage.', 'Fix login redirect callback behavior.'], 'chooser snapshot should list the primary and alternate candidate missions'
|
|
928
|
-
assert len(chooser['choices']) == 4, 'chooser should expose continue, primary, alternate, and cancel choices'
|
|
929
|
-
assert 'Scope\n- Add coverage for logout redirect behavior.' in chooser['choices'][1], 'primary chooser option should summarize the candidate scope'
|
|
930
|
-
assert 'Acceptance\n- Add a regression test for returning to the requested page.' in chooser['choices'][2], 'alternate chooser option should summarize the candidate acceptance'
|
|
931
|
-
assert proposal['mission'] == selected, 'selected alternate mission should flow into the final proposal confirmation'
|
|
932
|
-
assert state['mission_anchor'] == selected, 'selected alternate mission should rewrite state.json after approval'
|
|
933
|
-
assert plan['mission_anchor'] == selected, 'selected alternate mission should rewrite plan.json after approval'
|
|
934
|
-
assert active['mission_anchor'] == selected, 'selected alternate mission should rewrite active-slice.json after approval'
|
|
566
|
+
python3 - "$TMPDIR/active-non-startable-before.json" <<'PY'
|
|
567
|
+
import json
|
|
568
|
+
import sys
|
|
569
|
+
from pathlib import Path
|
|
570
|
+
tracked = {
|
|
571
|
+
'mission.md': Path('.agent/mission.md').read_text(),
|
|
572
|
+
'profile.json': Path('.agent/profile.json').read_text(),
|
|
573
|
+
'state.json': Path('.agent/state.json').read_text(),
|
|
574
|
+
'plan.json': Path('.agent/plan.json').read_text(),
|
|
575
|
+
'active-slice.json': Path('.agent/active-slice.json').read_text(),
|
|
576
|
+
'verification-evidence.json': Path('.agent/verification-evidence.json').read_text(),
|
|
577
|
+
}
|
|
578
|
+
Path(sys.argv[1]).write_text(json.dumps(tracked, indent=2) + '\n')
|
|
935
579
|
PY
|
|
936
580
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
REFOCUS_ROUTING_ONE="$TMPDIR/active-refocus-routing.json"
|
|
942
|
-
REFOCUS_CHOOSER_ONE="$TMPDIR/active-refocus-chooser.json"
|
|
943
|
-
REFOCUS_PROPOSAL_ONE="$TMPDIR/active-refocus-proposal.json"
|
|
944
|
-
REFOCUS_UI_ONE="$TMPDIR/active-refocus-ui.json"
|
|
945
|
-
write_session "$SESSION_ONE_REFOCUS_NORMALIZED" "$ROOT" "$DISCUSSION_ONE_REFOCUS_NORMALIZED"
|
|
946
|
-
|
|
947
|
-
PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
|
|
948
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_ACTION=start \
|
|
949
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
950
|
-
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$REFOCUS_ROUTING_ONE" \
|
|
951
|
-
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$REFOCUS_CHOOSER_ONE" \
|
|
952
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$REFOCUS_PROPOSAL_ONE" \
|
|
953
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_PATH="$REFOCUS_UI_ONE" \
|
|
581
|
+
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$NON_STARTABLE_ACTIVE_ROUTING" \
|
|
582
|
+
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$NON_STARTABLE_ACTIVE_RESUME" \
|
|
583
|
+
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$NON_STARTABLE_ACTIVE_CHOOSER" \
|
|
584
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$NON_STARTABLE_ACTIVE_PROPOSAL" \
|
|
954
585
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
955
|
-
pi --session "$
|
|
586
|
+
pi --session "$SESSION_ONE_NON_STARTABLE_ACTIVE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-active-non-startable.out" 2>"$TMPDIR/pi-completion-context-proposal-active-non-startable.err"
|
|
956
587
|
|
|
957
|
-
python3 - "$
|
|
588
|
+
python3 - "$NON_STARTABLE_ACTIVE_ROUTING" "$NON_STARTABLE_ACTIVE_RESUME" "$NON_STARTABLE_ACTIVE_CHOOSER" "$NON_STARTABLE_ACTIVE_PROPOSAL" "$TMPDIR/pi-completion-context-proposal-active-non-startable.out" "$TMPDIR/pi-completion-context-proposal-active-non-startable.err" "$TMPDIR/active-non-startable-before.json" <<'PY'
|
|
958
589
|
import json
|
|
959
590
|
import sys
|
|
960
591
|
from pathlib import Path
|
|
961
592
|
|
|
962
|
-
mission = 'Normalize bare /cook planning phrasing into implementation-result missions.'
|
|
963
593
|
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
594
|
+
resume_path = Path(sys.argv[2])
|
|
595
|
+
chooser_path = Path(sys.argv[3])
|
|
596
|
+
proposal_path = Path(sys.argv[4])
|
|
597
|
+
output = Path(sys.argv[5]).read_text() + Path(sys.argv[6]).read_text()
|
|
598
|
+
before = json.loads(Path(sys.argv[7]).read_text())
|
|
599
|
+
after = {
|
|
600
|
+
'mission.md': Path('.agent/mission.md').read_text(),
|
|
601
|
+
'profile.json': Path('.agent/profile.json').read_text(),
|
|
602
|
+
'state.json': Path('.agent/state.json').read_text(),
|
|
603
|
+
'plan.json': Path('.agent/plan.json').read_text(),
|
|
604
|
+
'active-slice.json': Path('.agent/active-slice.json').read_text(),
|
|
605
|
+
'verification-evidence.json': Path('.agent/verification-evidence.json').read_text(),
|
|
606
|
+
}
|
|
970
607
|
|
|
971
|
-
assert routing['mode'] == 'bare', '
|
|
972
|
-
assert '
|
|
973
|
-
assert '
|
|
974
|
-
assert
|
|
975
|
-
assert
|
|
976
|
-
assert
|
|
977
|
-
assert
|
|
978
|
-
assert
|
|
979
|
-
assert
|
|
980
|
-
assert
|
|
981
|
-
assert plan['mission_anchor'] == mission, 'active bare /cook refocus should rewrite plan.json only after approval'
|
|
982
|
-
assert active['mission_anchor'] == mission, 'active bare /cook refocus should rewrite active-slice.json only after approval'
|
|
608
|
+
assert routing['mode'] == 'bare', 'fresh non-startable explicit handoff should snapshot bare routing mode'
|
|
609
|
+
assert routing['action'] == 'blocked', 'fresh non-startable explicit handoff should fail closed for active bare /cook'
|
|
610
|
+
assert routing['reason'] == 'fresh_explicit_handoff_not_startable', 'fresh non-startable explicit handoff should keep the dedicated explicit-handoff fail-closed reason'
|
|
611
|
+
assert 'fresh explicit primary-agent handoff exists' in routing['blockedFailureMessage'], 'fresh non-startable explicit handoff should surface the dedicated fail-closed message'
|
|
612
|
+
assert 'acceptance is not anchored to concrete repo changes or verification' in routing['blockedFailureMessage'], 'fresh non-startable explicit handoff should explain why the capsule is not startable'
|
|
613
|
+
assert not resume_path.exists(), 'fresh non-startable explicit handoff should not queue a resume prompt'
|
|
614
|
+
assert not chooser_path.exists(), 'fresh non-startable explicit handoff should not open the replacement chooser'
|
|
615
|
+
assert not proposal_path.exists(), 'fresh non-startable explicit handoff should not open final proposal confirmation'
|
|
616
|
+
assert 'fresh explicit primary-agent handoff exists' in output, 'fresh non-startable explicit handoff should explain that the explicit capsule blocked active-workflow replacement'
|
|
617
|
+
assert before == after, 'fresh non-startable explicit handoff should leave canonical state unchanged'
|
|
983
618
|
PY
|
|
984
619
|
|
|
985
620
|
# Completed workflow: bare /cook should suppress proposals that simply restate the completed mission
|
|
@@ -1067,8 +702,8 @@ assert not snapshot.exists(), 'verification-evidence overlap suppression should
|
|
|
1067
702
|
assert '/cook failed closed' in output, 'verification-evidence overlap suppression should fail closed when the latest discussion only repeats verified work'
|
|
1068
703
|
PY
|
|
1069
704
|
|
|
1070
|
-
# Completed workflow: bare /cook should
|
|
1071
|
-
#
|
|
705
|
+
# Completed workflow: bare /cook should fail closed for next-round discussion-only startup too,
|
|
706
|
+
# even when the discussion is well structured.
|
|
1072
707
|
SESSION_TWO_NORMALIZED="$TMPDIR/session-two-normalized.jsonl"
|
|
1073
708
|
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.'
|
|
1074
709
|
DISCUSSION_SNAPSHOT_TWO_NORMALIZED="$TMPDIR/context-proposal-next-round-normalized.json"
|
|
@@ -1080,34 +715,79 @@ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_TWO_NORMALIZED" \
|
|
|
1080
715
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1081
716
|
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"
|
|
1082
717
|
|
|
1083
|
-
python3 - "$DISCUSSION_SNAPSHOT_TWO_NORMALIZED" <<'PY'
|
|
718
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-next-round-normalized.out" "$TMPDIR/pi-completion-context-proposal-next-round-normalized.err" "$DISCUSSION_SNAPSHOT_TWO_NORMALIZED" "$CURRENT_DONE_MISSION" <<'PY'
|
|
1084
719
|
import json
|
|
1085
720
|
import sys
|
|
1086
721
|
from pathlib import Path
|
|
1087
722
|
|
|
1088
|
-
|
|
1089
|
-
|
|
723
|
+
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
724
|
+
snapshot = Path(sys.argv[3])
|
|
725
|
+
expected = sys.argv[4]
|
|
1090
726
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
1091
|
-
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
1092
|
-
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
1093
727
|
|
|
1094
|
-
assert
|
|
1095
|
-
assert state['mission_anchor'] ==
|
|
1096
|
-
assert
|
|
1097
|
-
assert
|
|
1098
|
-
assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'done-workflow normalization should still route through refocus semantics for the next round'
|
|
728
|
+
assert not snapshot.exists(), 'done-workflow discussion-only startup should not emit a proposal snapshot without a fresh explicit handoff'
|
|
729
|
+
assert state['mission_anchor'] == expected, 'done-workflow discussion-only startup should keep the completed mission anchor unchanged'
|
|
730
|
+
assert state['continuation_policy'] == 'done', 'done-workflow discussion-only startup should keep the workflow closed'
|
|
731
|
+
assert 'fresh valid explicit primary-agent handoff' in output, 'done-workflow discussion-only startup should explain the explicit-handoff-only entry contract'
|
|
1099
732
|
PY
|
|
1100
733
|
|
|
1101
|
-
# Completed workflow:
|
|
734
|
+
# Completed workflow: a fresh explicit primary-agent handoff should still start the next round.
|
|
1102
735
|
mark_done
|
|
1103
736
|
|
|
1104
737
|
SESSION_TWO="$TMPDIR/session-two.jsonl"
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
738
|
+
DISCUSSION_SNAPSHOT_TWO="$TMPDIR/context-proposal-next-round-explicit-handoff.json"
|
|
739
|
+
HANDOFF_MESSAGES_TWO="$(python3 - <<'PY'
|
|
740
|
+
import json
|
|
741
|
+
capsule = {
|
|
742
|
+
"kind": "cook_handoff",
|
|
743
|
+
"source": "primary_agent",
|
|
744
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
745
|
+
"source_turn_id": "m0002",
|
|
746
|
+
"mission": "Ship the next workflow round from a fresh explicit handoff.",
|
|
747
|
+
"scope": [
|
|
748
|
+
"Reset canonical state back to reground for the fresh mission.",
|
|
749
|
+
"Preserve the tracked completion control-plane files."
|
|
750
|
+
],
|
|
751
|
+
"constraints": [
|
|
752
|
+
"Do not resume the completed workflow when the new round is clearly different."
|
|
753
|
+
],
|
|
754
|
+
"acceptance": [
|
|
755
|
+
"Reset canonical state back to reground for the new mission.",
|
|
756
|
+
"Preserve the tracked completion control-plane files."
|
|
757
|
+
],
|
|
758
|
+
"risks": [
|
|
759
|
+
"Done-state history could override the fresh mission if the explicit handoff is ignored."
|
|
760
|
+
],
|
|
761
|
+
"notes": [
|
|
762
|
+
"This next round must come from the fresh explicit handoff rather than recent discussion."
|
|
763
|
+
],
|
|
764
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
765
|
+
"first_slice_goal": "Start the next round from the fresh explicit handoff and preserve canonical control-plane files.",
|
|
766
|
+
"first_slice_non_goals": [
|
|
767
|
+
"Do not resume the completed workflow when the new round is clearly different."
|
|
768
|
+
],
|
|
769
|
+
"implementation_surfaces": [
|
|
770
|
+
"extensions/completion/driver.ts",
|
|
771
|
+
"scripts/context-proposal-test.sh"
|
|
772
|
+
],
|
|
773
|
+
"verification_commands": [
|
|
774
|
+
"npm run context-proposal-test"
|
|
775
|
+
],
|
|
776
|
+
"why_this_slice_first": "The fresh explicit handoff is the smallest truthful next-round startup after the previous workflow closed.",
|
|
777
|
+
"task_type": "completion-workflow",
|
|
778
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
779
|
+
"why_cook_now": "A new implementation-ready mission was identified after the previous round closed."
|
|
780
|
+
}
|
|
781
|
+
messages = [
|
|
782
|
+
{"role": "user", "content": "The previous round is done, but there is a fresh next round ready for /cook."},
|
|
783
|
+
{"role": "assistant", "content": "The next round is ready for /cook. Run /cook to confirm this fresh implementation mission.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
784
|
+
]
|
|
785
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
786
|
+
PY
|
|
787
|
+
)"
|
|
788
|
+
write_session_messages "$SESSION_TWO" "$ROOT" "$HANDOFF_MESSAGES_TWO"
|
|
1108
789
|
|
|
1109
790
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
1110
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
1111
791
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_TWO" \
|
|
1112
792
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1113
793
|
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"
|
|
@@ -1117,7 +797,7 @@ import json
|
|
|
1117
797
|
import sys
|
|
1118
798
|
from pathlib import Path
|
|
1119
799
|
|
|
1120
|
-
mission = 'Ship the next workflow round
|
|
800
|
+
mission = 'Ship the next workflow round from a fresh explicit handoff.'
|
|
1121
801
|
expected_task_type = 'completion-workflow'
|
|
1122
802
|
expected_eval_profile = 'completion-rubric-v1'
|
|
1123
803
|
mission_text = Path('.agent/mission.md').read_text()
|
|
@@ -1127,21 +807,22 @@ plan = json.loads(Path('.agent/plan.json').read_text())
|
|
|
1127
807
|
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
1128
808
|
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
1129
809
|
|
|
1130
|
-
assert mission in mission_text, '.agent/mission.md did not update to the next-round
|
|
1131
|
-
assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after next-round startup'
|
|
1132
|
-
assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after next-round startup'
|
|
1133
|
-
assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after starting the next workflow round'
|
|
1134
|
-
assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after starting the next workflow round'
|
|
1135
|
-
assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after starting the next workflow round'
|
|
1136
|
-
assert state['advisory_startup_brief']['mission'] == mission, 'next-round
|
|
1137
|
-
assert
|
|
1138
|
-
assert plan['
|
|
1139
|
-
assert plan['
|
|
1140
|
-
assert
|
|
1141
|
-
assert active['
|
|
1142
|
-
assert active['
|
|
1143
|
-
assert
|
|
1144
|
-
assert proposal['
|
|
810
|
+
assert mission in mission_text, '.agent/mission.md did not update to the next-round explicit-handoff mission anchor'
|
|
811
|
+
assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after next-round explicit handoff startup'
|
|
812
|
+
assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after next-round explicit handoff startup'
|
|
813
|
+
assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after starting the next workflow round from explicit handoff'
|
|
814
|
+
assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after starting the next workflow round from explicit handoff'
|
|
815
|
+
assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after starting the next workflow round from explicit handoff'
|
|
816
|
+
assert state['advisory_startup_brief']['mission'] == mission, 'next-round explicit handoff should preserve the confirmed startup brief as advisory intake'
|
|
817
|
+
assert state['advisory_startup_brief']['source'] == 'primary_agent_handoff', 'next-round explicit handoff should preserve the handoff advisory source'
|
|
818
|
+
assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after starting the next workflow round from explicit handoff'
|
|
819
|
+
assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after starting the next workflow round from explicit handoff'
|
|
820
|
+
assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after starting the next workflow round from explicit handoff'
|
|
821
|
+
assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after starting the next workflow round from explicit handoff'
|
|
822
|
+
assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after starting the next workflow round from explicit handoff'
|
|
823
|
+
assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after starting the next workflow round from explicit handoff'
|
|
824
|
+
assert proposal['mission'] == mission, 'next-round explicit handoff proposal snapshot should preserve the handoff mission anchor'
|
|
825
|
+
assert proposal['source'] == 'handoff_capsule', 'next-round explicit handoff proposal snapshot should record the handoff capsule source'
|
|
1145
826
|
assert state['current_phase'] == 'reground', 'state.json current_phase should reset to reground for the next workflow round'
|
|
1146
827
|
assert state['continuation_policy'] == 'continue', 'continuation_policy should reset to continue for the next workflow round'
|
|
1147
828
|
assert state['requires_reground'] is True, 'requires_reground should reset to true for the next workflow round'
|
|
@@ -1150,7 +831,6 @@ assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_
|
|
|
1150
831
|
assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'continuation_reason should record the next-round refocus'
|
|
1151
832
|
assert 'task_type=completion-workflow' in state['continuation_reason'], 'next-round refocus should persist the selected task_type'
|
|
1152
833
|
assert 'evaluation_profile=completion-rubric-v1' in state['continuation_reason'], 'next-round refocus should persist the selected evaluation_profile'
|
|
1153
|
-
assert 'critique outcome=accepted critique=none' in state['continuation_reason'], 'next-round refocus should persist that no critique notes were accepted'
|
|
1154
834
|
assert plan['plan_basis'] == 'user_refocus', 'plan_basis should reset to user_refocus for the next workflow round'
|
|
1155
835
|
assert active['status'] == 'idle', 'active-slice should reset to idle for the next workflow round'
|
|
1156
836
|
PY
|
|
@@ -1267,53 +947,34 @@ after = {path.name: path.read_text() for path in tracked}
|
|
|
1267
947
|
assert before == after, 'done /cook inline-args rejection should leave canonical files unchanged'
|
|
1268
948
|
PY
|
|
1269
949
|
|
|
1270
|
-
# Completed workflow again:
|
|
1271
|
-
#
|
|
950
|
+
# Completed workflow again: model-assisted discussion analysis alone should still fail closed
|
|
951
|
+
# without a fresh valid explicit primary-agent handoff.
|
|
1272
952
|
mark_done
|
|
1273
953
|
|
|
1274
954
|
SESSION_FIVE="$TMPDIR/session-five.jsonl"
|
|
1275
955
|
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.'
|
|
1276
956
|
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"]}'
|
|
957
|
+
DISCUSSION_SNAPSHOT_FIVE="$TMPDIR/context-proposal-analyst-restart-rejected.json"
|
|
1277
958
|
write_session "$SESSION_FIVE" "$ROOT" "$DISCUSSION_FIVE"
|
|
1278
959
|
|
|
1279
960
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
1280
961
|
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$ANALYST_OUTPUT_FIVE" \
|
|
962
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_FIVE" \
|
|
1281
963
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1282
|
-
pi --session "$SESSION_FIVE" -e "$PKG_ROOT" -p "/cook"
|
|
964
|
+
pi --session "$SESSION_FIVE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-analyst.out" 2>"$TMPDIR/pi-completion-context-proposal-analyst.err"
|
|
1283
965
|
|
|
1284
|
-
python3 - <<'PY'
|
|
966
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-analyst.out" "$TMPDIR/pi-completion-context-proposal-analyst.err" "$DISCUSSION_SNAPSHOT_FIVE" <<'PY'
|
|
1285
967
|
import json
|
|
968
|
+
import sys
|
|
1286
969
|
from pathlib import Path
|
|
1287
970
|
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
expected_eval_profile = 'completion-rubric-v1'
|
|
1291
|
-
mission_text = Path('.agent/mission.md').read_text()
|
|
1292
|
-
profile = json.loads(Path('.agent/profile.json').read_text())
|
|
971
|
+
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
972
|
+
snapshot = Path(sys.argv[3])
|
|
1293
973
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
assert mission in mission_text, '.agent/mission.md did not record the analyst-derived mission anchor'
|
|
1299
|
-
assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after analyst-derived restart'
|
|
1300
|
-
assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after analyst-derived restart'
|
|
1301
|
-
assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after analyst-derived bootstrap'
|
|
1302
|
-
assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after analyst-derived bootstrap'
|
|
1303
|
-
assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after analyst-derived bootstrap'
|
|
1304
|
-
assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after analyst-derived bootstrap'
|
|
1305
|
-
assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after analyst-derived bootstrap'
|
|
1306
|
-
assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after analyst-derived bootstrap'
|
|
1307
|
-
assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after analyst-derived bootstrap'
|
|
1308
|
-
assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after analyst-derived bootstrap'
|
|
1309
|
-
assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after analyst-derived bootstrap'
|
|
1310
|
-
assert state['current_phase'] == 'reground', 'current_phase should reset to reground after analyst-derived bootstrap'
|
|
1311
|
-
assert state['next_mandatory_role'] == 'completion-regrounder', 'next role should reset to completion-regrounder after analyst-derived bootstrap'
|
|
1312
|
-
assert continuation_reason.startswith('User refocused workflow via /cook:'), 'continuation_reason should record the analyst-derived restart'
|
|
1313
|
-
assert 'task_type=completion-workflow' in continuation_reason, 'analyst-derived restart should persist the selected task_type'
|
|
1314
|
-
assert 'evaluation_profile=completion-rubric-v1' in continuation_reason, 'analyst-derived restart should persist the selected evaluation_profile'
|
|
1315
|
-
assert 'critique outcome=accepted critique=none' in continuation_reason, 'analyst-derived restart should persist that no critique notes were accepted'
|
|
1316
|
-
assert 'Keep the discussion-derived mission anchored once it is clear.' in continuation_reason, 'analyst-derived scope should be preserved'
|
|
974
|
+
|
|
975
|
+
assert not snapshot.exists(), 'done-workflow analyst-only restart should not emit a startup proposal snapshot'
|
|
976
|
+
assert state['continuation_policy'] == 'done', 'done-workflow analyst-only restart should keep the workflow closed'
|
|
977
|
+
assert 'fresh valid explicit primary-agent handoff' in output, 'done-workflow analyst-only restart should explain the explicit-handoff-only startup contract'
|
|
1317
978
|
PY
|
|
1318
979
|
|
|
1319
980
|
# Custom confirmation UI: start should render proposal content separately from approval-only Start/Cancel actions.
|
|
@@ -1323,14 +984,41 @@ cd "$UI_ROOT_START"
|
|
|
1323
984
|
git init -q
|
|
1324
985
|
|
|
1325
986
|
UI_SESSION_START="$TMPDIR/ui-session-start.jsonl"
|
|
1326
|
-
UI_DISCUSSION_START=$'Mission: Replace the crowded selector with a clearer action layout.\nScope:\n- Separate proposal text from actions.\nConstraints:\n- Preserve approval-only Start/Cancel behavior.\nAcceptance:\n- Add regression coverage.'
|
|
1327
|
-
UI_ANALYST_OUTPUT_START='{"mission":"Replace the crowded selector with a clearer action layout.","scope":["Separate proposal text from actions."],"constraints":["Preserve approval-only Start/Cancel behavior."],"acceptance":["Add regression coverage."],"critique":["Keep critique details separate from the approval-only proposal summary."],"risks":["Bundling critique into the action list would make the confirmation harder to scan."],"task_type":"completion-workflow","evaluation_profile":"completion-rubric-v1","possible_noise":["old selector wording"],"confidence":0.95}'
|
|
1328
987
|
UI_SNAPSHOT_START="$TMPDIR/context-proposal-ui-start.json"
|
|
1329
|
-
|
|
988
|
+
UI_MESSAGES_START="$(python3 - <<'PY'
|
|
989
|
+
import json
|
|
990
|
+
capsule = {
|
|
991
|
+
"kind": "cook_handoff",
|
|
992
|
+
"source": "primary_agent",
|
|
993
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
994
|
+
"source_turn_id": "m0002",
|
|
995
|
+
"mission": "Replace the crowded selector with a clearer action layout.",
|
|
996
|
+
"scope": ["Separate proposal text from actions."],
|
|
997
|
+
"constraints": ["Preserve approval-only Start/Cancel behavior."],
|
|
998
|
+
"acceptance": ["Add regression coverage."],
|
|
999
|
+
"risks": ["Bundling critique into the action list would make the confirmation harder to scan."],
|
|
1000
|
+
"notes": ["Keep critique details separate from the approval-only proposal summary.", "Possible noise: old selector wording"],
|
|
1001
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1002
|
+
"first_slice_goal": "Separate the proposal text from the approval-only Start/Cancel actions.",
|
|
1003
|
+
"first_slice_non_goals": [],
|
|
1004
|
+
"implementation_surfaces": ["extensions/completion/prompt-surfaces.ts"],
|
|
1005
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
1006
|
+
"why_this_slice_first": "The confirmation layout regression is small and directly testable.",
|
|
1007
|
+
"task_type": "completion-workflow",
|
|
1008
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
1009
|
+
"why_cook_now": "The explicit handoff is concrete enough to exercise the startup confirmation UI."
|
|
1010
|
+
}
|
|
1011
|
+
messages = [
|
|
1012
|
+
{"role": "user", "content": "Prepare the confirmation-layout work and tell me when it is ready for /cook."},
|
|
1013
|
+
{"role": "assistant", "content": "The confirmation-layout work is ready for /cook.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
1014
|
+
]
|
|
1015
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1016
|
+
PY
|
|
1017
|
+
)"
|
|
1018
|
+
write_session_messages "$UI_SESSION_START" "$UI_ROOT_START" "$UI_MESSAGES_START"
|
|
1330
1019
|
|
|
1331
1020
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_ACTION=start \
|
|
1332
1021
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_PATH="$UI_SNAPSHOT_START" \
|
|
1333
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$UI_ANALYST_OUTPUT_START" \
|
|
1334
1022
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1335
1023
|
pi --session "$UI_SESSION_START" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-ui-start.out" 2>"$TMPDIR/pi-completion-context-proposal-ui-start.err"
|
|
1336
1024
|
|
|
@@ -1352,7 +1040,7 @@ assert 'Mission\nReplace the crowded selector with a clearer action layout.' in
|
|
|
1352
1040
|
assert 'Keep critique details separate from the approval-only proposal summary.' not in snapshot['proposalBody'], 'critique notes should not be embedded in the startup-brief body'
|
|
1353
1041
|
assert 'Critique\n- Keep critique details separate from the approval-only proposal summary.' in snapshot['critiqueBody'], 'notes section should render accepted critique notes separately'
|
|
1354
1042
|
assert 'Risks\n- Bundling critique into the action list would make the confirmation harder to scan.' in snapshot['critiqueBody'], 'critique section should render risk notes separately'
|
|
1355
|
-
assert 'Possible noise
|
|
1043
|
+
assert '- Possible noise: old selector wording' in snapshot['critiqueBody'], 'critique section should preserve additional operator notes separately from the startup-brief body'
|
|
1356
1044
|
assert '- task_type: completion-workflow' in snapshot['routingBody'], 'routing section should render the recommended task_type'
|
|
1357
1045
|
assert '- evaluation_profile: completion-rubric-v1' in snapshot['routingBody'], 'routing section should render the recommended evaluation_profile'
|
|
1358
1046
|
assert [action['id'] for action in snapshot['actions']] == ['start', 'cancel'], 'custom confirmation actions should stay Start/Cancel only'
|
|
@@ -1375,14 +1063,41 @@ cd "$UI_ROOT_CANCEL"
|
|
|
1375
1063
|
git init -q
|
|
1376
1064
|
|
|
1377
1065
|
UI_SESSION_CANCEL="$TMPDIR/ui-session-cancel.jsonl"
|
|
1378
|
-
UI_DISCUSSION_CANCEL=$'Mission: Cancel from the custom confirmation UI without writing state.\nScope:\n- Show the proposal separately from the approval-only actions.\nConstraints:\n- Keep cancellation side-effect free.\nAcceptance:\n- Leave .agent absent after cancel.'
|
|
1379
|
-
UI_ANALYST_OUTPUT_CANCEL='{"mission":"Cancel from the custom confirmation UI without writing state.","scope":["Show the proposal separately from the approval-only actions."],"constraints":["Keep cancellation side-effect free."],"acceptance":["Leave .agent absent after cancel."],"confidence":0.92}'
|
|
1380
1066
|
UI_SNAPSHOT_CANCEL="$TMPDIR/context-proposal-ui-cancel.json"
|
|
1381
|
-
|
|
1067
|
+
UI_MESSAGES_CANCEL="$(python3 - <<'PY'
|
|
1068
|
+
import json
|
|
1069
|
+
capsule = {
|
|
1070
|
+
"kind": "cook_handoff",
|
|
1071
|
+
"source": "primary_agent",
|
|
1072
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1073
|
+
"source_turn_id": "m0002",
|
|
1074
|
+
"mission": "Cancel from the custom confirmation UI without writing state.",
|
|
1075
|
+
"scope": ["Show the proposal separately from the approval-only actions."],
|
|
1076
|
+
"constraints": ["Keep cancellation side-effect free."],
|
|
1077
|
+
"acceptance": ["Add regression coverage proving cancel leaves .agent absent."],
|
|
1078
|
+
"risks": [],
|
|
1079
|
+
"notes": [],
|
|
1080
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1081
|
+
"first_slice_goal": "Exercise the cancel path without writing canonical state.",
|
|
1082
|
+
"first_slice_non_goals": [],
|
|
1083
|
+
"implementation_surfaces": ["extensions/completion/prompt-surfaces.ts"],
|
|
1084
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
1085
|
+
"why_this_slice_first": "The cancel path is a direct regression around the startup confirmation UI.",
|
|
1086
|
+
"task_type": "completion-workflow",
|
|
1087
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
1088
|
+
"why_cook_now": "The explicit handoff is concrete enough to exercise the cancel confirmation UI."
|
|
1089
|
+
}
|
|
1090
|
+
messages = [
|
|
1091
|
+
{"role": "user", "content": "Prepare the cancel-path confirmation work and tell me when it is ready for /cook."},
|
|
1092
|
+
{"role": "assistant", "content": "The cancel-path confirmation work is ready for /cook.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
1093
|
+
]
|
|
1094
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1095
|
+
PY
|
|
1096
|
+
)"
|
|
1097
|
+
write_session_messages "$UI_SESSION_CANCEL" "$UI_ROOT_CANCEL" "$UI_MESSAGES_CANCEL"
|
|
1382
1098
|
|
|
1383
1099
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_ACTION=cancel \
|
|
1384
1100
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_PATH="$UI_SNAPSHOT_CANCEL" \
|
|
1385
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$UI_ANALYST_OUTPUT_CANCEL" \
|
|
1386
1101
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1387
1102
|
pi --session "$UI_SESSION_CANCEL" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-ui-cancel.out" 2>"$TMPDIR/pi-completion-context-proposal-ui-cancel.err"
|
|
1388
1103
|
|
|
@@ -1433,7 +1148,19 @@ capsule = {
|
|
|
1433
1148
|
"notes": [
|
|
1434
1149
|
"Keep the startup brief aligned with the explicit primary-agent plan."
|
|
1435
1150
|
],
|
|
1436
|
-
"handoff_kind": "
|
|
1151
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1152
|
+
"first_slice_goal": "Land the redirect callback fix and its regression coverage.",
|
|
1153
|
+
"first_slice_non_goals": [
|
|
1154
|
+
"Do not refactor the broader auth flow."
|
|
1155
|
+
],
|
|
1156
|
+
"implementation_surfaces": [
|
|
1157
|
+
"src/auth/redirect.ts",
|
|
1158
|
+
"tests/auth/redirect.spec.ts"
|
|
1159
|
+
],
|
|
1160
|
+
"verification_commands": [
|
|
1161
|
+
"npm test -- redirect.spec.ts"
|
|
1162
|
+
],
|
|
1163
|
+
"why_this_slice_first": "The redirect callback bug is already bounded enough to start implementation safely.",
|
|
1437
1164
|
"task_type": "completion-workflow",
|
|
1438
1165
|
"evaluation_profile": "completion-rubric-v1",
|
|
1439
1166
|
"why_cook_now": "The implementation plan is concrete and ready for repo changes."
|
|
@@ -1465,9 +1192,245 @@ assert snapshot['mission'] == 'Fix login redirect callback behavior.', 'explicit
|
|
|
1465
1192
|
assert state['mission_anchor'] == 'Fix login redirect callback behavior.', 'explicit handoff startup should use the handoff mission as canonical mission_anchor'
|
|
1466
1193
|
assert state['advisory_startup_brief']['source'] == 'primary_agent_handoff', 'explicit handoff startup should preserve the advisory intake source'
|
|
1467
1194
|
assert state['advisory_startup_brief']['risks'] == ['Stale auth discussion could broaden the startup brief if the handoff is ignored.'], 'explicit handoff startup should preserve handoff risks'
|
|
1195
|
+
assert 'First slice goal: Land the redirect callback fix and its regression coverage.' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve first_slice_goal in advisory notes'
|
|
1196
|
+
assert 'First slice non-goals: Do not refactor the broader auth flow.' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve first_slice_non_goals in advisory notes'
|
|
1197
|
+
assert 'Implementation surfaces: src/auth/redirect.ts | tests/auth/redirect.spec.ts' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve implementation_surfaces in advisory notes'
|
|
1198
|
+
assert 'Verification commands: npm test -- redirect.spec.ts' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve verification_commands in advisory notes'
|
|
1199
|
+
assert 'Why this slice first: The redirect callback bug is already bounded enough to start implementation safely.' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve why_this_slice_first in advisory notes'
|
|
1468
1200
|
assert 'Primary-agent /cook handoff rationale: The implementation plan is concrete and ready for repo changes.' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve why_cook_now as notes'
|
|
1469
1201
|
PY
|
|
1470
1202
|
|
|
1203
|
+
# Fresh explicit handoff: later ordinary-chat follow-up before /cook should not invalidate startup.
|
|
1204
|
+
HANDOFF_ROOT_FOLLOWUP="$TMPDIR/handoff-root-followup"
|
|
1205
|
+
mkdir -p "$HANDOFF_ROOT_FOLLOWUP"
|
|
1206
|
+
cd "$HANDOFF_ROOT_FOLLOWUP"
|
|
1207
|
+
git init -q
|
|
1208
|
+
|
|
1209
|
+
HANDOFF_SESSION_FOLLOWUP="$TMPDIR/handoff-session-followup.jsonl"
|
|
1210
|
+
HANDOFF_SNAPSHOT_FOLLOWUP="$TMPDIR/handoff-proposal-followup.json"
|
|
1211
|
+
HANDOFF_MESSAGES_FOLLOWUP="$(python3 - <<'PY'
|
|
1212
|
+
import json
|
|
1213
|
+
capsule = {
|
|
1214
|
+
"kind": "cook_handoff",
|
|
1215
|
+
"source": "primary_agent",
|
|
1216
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1217
|
+
"source_turn_id": "m0002",
|
|
1218
|
+
"mission": "Fix login redirect callback behavior.",
|
|
1219
|
+
"scope": [
|
|
1220
|
+
"Update the callback redirect decision logic.",
|
|
1221
|
+
"Preserve the broader auth flow."
|
|
1222
|
+
],
|
|
1223
|
+
"constraints": [
|
|
1224
|
+
"Do not refactor the broader auth flow."
|
|
1225
|
+
],
|
|
1226
|
+
"acceptance": [
|
|
1227
|
+
"Add a regression test for returning to the requested page."
|
|
1228
|
+
],
|
|
1229
|
+
"risks": [
|
|
1230
|
+
"Late ordinary-chat clarifications should not force the user into a fresh handoff-only retry loop."
|
|
1231
|
+
],
|
|
1232
|
+
"notes": [
|
|
1233
|
+
"Keep the startup brief anchored to the explicit primary-agent handoff until a later assistant reply replaces it."
|
|
1234
|
+
],
|
|
1235
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1236
|
+
"first_slice_goal": "Land the redirect callback fix and its regression coverage.",
|
|
1237
|
+
"first_slice_non_goals": [
|
|
1238
|
+
"Do not refactor the broader auth flow."
|
|
1239
|
+
],
|
|
1240
|
+
"implementation_surfaces": [
|
|
1241
|
+
"src/auth/redirect.ts",
|
|
1242
|
+
"tests/auth/redirect.spec.ts"
|
|
1243
|
+
],
|
|
1244
|
+
"verification_commands": [
|
|
1245
|
+
"npm test -- redirect.spec.ts"
|
|
1246
|
+
],
|
|
1247
|
+
"why_this_slice_first": "The redirect callback bug is already bounded enough to start implementation safely.",
|
|
1248
|
+
"task_type": "completion-workflow",
|
|
1249
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
1250
|
+
"why_cook_now": "The implementation plan is concrete and ready for repo changes."
|
|
1251
|
+
}
|
|
1252
|
+
messages = [
|
|
1253
|
+
{"role": "user", "content": "Please think through the login redirect fix and tell me when it is ready for /cook."},
|
|
1254
|
+
{"role": "assistant", "content": "This task is now ready for /cook whenever you want to start implementation.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
1255
|
+
{"role": "user", "content": "Before I run /cook, one more clarification: keep the broader auth flow unchanged and keep the later verification focused on the redirect regression."},
|
|
1256
|
+
]
|
|
1257
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1258
|
+
PY
|
|
1259
|
+
)"
|
|
1260
|
+
write_session_messages "$HANDOFF_SESSION_FOLLOWUP" "$HANDOFF_ROOT_FOLLOWUP" "$HANDOFF_MESSAGES_FOLLOWUP"
|
|
1261
|
+
|
|
1262
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
1263
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_FOLLOWUP" \
|
|
1264
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1265
|
+
pi --session "$HANDOFF_SESSION_FOLLOWUP" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-followup.out" 2>"$TMPDIR/pi-completion-handoff-followup.err"
|
|
1266
|
+
|
|
1267
|
+
python3 - "$HANDOFF_SNAPSHOT_FOLLOWUP" <<'PY'
|
|
1268
|
+
import json
|
|
1269
|
+
import sys
|
|
1270
|
+
from pathlib import Path
|
|
1271
|
+
|
|
1272
|
+
snapshot = json.loads(Path(sys.argv[1]).read_text())
|
|
1273
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
1274
|
+
|
|
1275
|
+
assert snapshot['source'] == 'handoff_capsule', 'later ordinary-chat follow-up should still use the explicit handoff capsule as startup source'
|
|
1276
|
+
assert snapshot['mission'] == 'Fix login redirect callback behavior.', 'later ordinary-chat follow-up should preserve the handoff mission'
|
|
1277
|
+
assert state['mission_anchor'] == 'Fix login redirect callback behavior.', 'later ordinary-chat follow-up should still start from the explicit handoff mission'
|
|
1278
|
+
assert state['advisory_startup_brief']['source'] == 'primary_agent_handoff', 'later ordinary-chat follow-up should keep the advisory intake source as the explicit handoff'
|
|
1279
|
+
assert 'Verification commands: npm test -- redirect.spec.ts' in state['advisory_startup_brief']['notes'], 'later ordinary-chat follow-up should preserve the handoff verification commands'
|
|
1280
|
+
PY
|
|
1281
|
+
|
|
1282
|
+
# Fresh but non-startable explicit handoff: /cook should fail closed instead of falling back
|
|
1283
|
+
# to a broad recent-discussion startup brief when the explicit capsule is still too vague.
|
|
1284
|
+
HANDOFF_ROOT_VAGUE="$TMPDIR/handoff-root-vague"
|
|
1285
|
+
mkdir -p "$HANDOFF_ROOT_VAGUE"
|
|
1286
|
+
cd "$HANDOFF_ROOT_VAGUE"
|
|
1287
|
+
git init -q
|
|
1288
|
+
|
|
1289
|
+
HANDOFF_SESSION_VAGUE="$TMPDIR/handoff-session-vague.jsonl"
|
|
1290
|
+
HANDOFF_SNAPSHOT_VAGUE="$TMPDIR/handoff-proposal-vague.json"
|
|
1291
|
+
HANDOFF_MESSAGES_VAGUE="$(python3 - <<'PY'
|
|
1292
|
+
import json
|
|
1293
|
+
capsule = {
|
|
1294
|
+
"kind": "cook_handoff",
|
|
1295
|
+
"source": "primary_agent",
|
|
1296
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1297
|
+
"source_turn_id": "m0002",
|
|
1298
|
+
"mission": "Fix login redirect callback behavior.",
|
|
1299
|
+
"scope": [
|
|
1300
|
+
"Update the callback redirect decision logic."
|
|
1301
|
+
],
|
|
1302
|
+
"constraints": [
|
|
1303
|
+
"Do not refactor the broader auth flow."
|
|
1304
|
+
],
|
|
1305
|
+
"acceptance": [
|
|
1306
|
+
"Confirm the final implementation breakdown before coding."
|
|
1307
|
+
],
|
|
1308
|
+
"risks": [
|
|
1309
|
+
"Broad recent context could be reused if the vague explicit handoff is ignored."
|
|
1310
|
+
],
|
|
1311
|
+
"notes": [
|
|
1312
|
+
"This handoff is still too vague to start implementation directly."
|
|
1313
|
+
],
|
|
1314
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1315
|
+
"first_slice_goal": "Patch the callback redirect decision logic.",
|
|
1316
|
+
"first_slice_non_goals": [
|
|
1317
|
+
"Do not refactor the broader auth flow."
|
|
1318
|
+
],
|
|
1319
|
+
"implementation_surfaces": [],
|
|
1320
|
+
"verification_commands": [],
|
|
1321
|
+
"why_this_slice_first": "The callback redirect path is the likely first slice, but the handoff still lacks execution detail.",
|
|
1322
|
+
"task_type": "completion-workflow",
|
|
1323
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
1324
|
+
"why_cook_now": "The task is workflow-worthy, but the implementation slice is not concrete enough yet."
|
|
1325
|
+
}
|
|
1326
|
+
recent_discussion = "Mission: Fix login redirect callback behavior.\nScope:\n- Update the callback redirect decision logic.\nConstraints:\n- Do not refactor the broader auth flow.\nAcceptance:\n- Add a regression test for returning to the requested page."
|
|
1327
|
+
messages = [
|
|
1328
|
+
{"role": "user", "content": recent_discussion},
|
|
1329
|
+
{"role": "assistant", "content": "This follow-up might soon be ready for /cook.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
1330
|
+
]
|
|
1331
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1332
|
+
PY
|
|
1333
|
+
)"
|
|
1334
|
+
write_session_messages "$HANDOFF_SESSION_VAGUE" "$HANDOFF_ROOT_VAGUE" "$HANDOFF_MESSAGES_VAGUE"
|
|
1335
|
+
|
|
1336
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
1337
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_VAGUE" \
|
|
1338
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1339
|
+
pi --session "$HANDOFF_SESSION_VAGUE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-vague.out" 2>"$TMPDIR/pi-completion-handoff-vague.err"
|
|
1340
|
+
|
|
1341
|
+
python3 - "$HANDOFF_SNAPSHOT_VAGUE" "$TMPDIR/pi-completion-handoff-vague.out" "$TMPDIR/pi-completion-handoff-vague.err" <<'PY'
|
|
1342
|
+
import sys
|
|
1343
|
+
from pathlib import Path
|
|
1344
|
+
|
|
1345
|
+
snapshot = Path(sys.argv[1])
|
|
1346
|
+
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
1347
|
+
|
|
1348
|
+
assert not snapshot.exists(), 'fresh non-startable handoff should not emit a startup proposal snapshot'
|
|
1349
|
+
assert not Path('.agent').exists(), 'fresh non-startable handoff should fail closed without writing canonical state'
|
|
1350
|
+
assert 'fresh explicit primary-agent handoff exists' in output, 'fresh non-startable handoff should explain that the explicit capsule blocked startup'
|
|
1351
|
+
assert 'acceptance is not anchored to concrete repo changes or verification' in output, 'fresh non-startable handoff should explain the workflow-only acceptance failure'
|
|
1352
|
+
assert 'implementation_surfaces is empty' in output, 'fresh non-startable handoff should explain the missing implementation_surfaces requirement'
|
|
1353
|
+
assert 'verification_commands is empty' in output, 'fresh non-startable handoff should explain the missing verification_commands requirement'
|
|
1354
|
+
PY
|
|
1355
|
+
|
|
1356
|
+
# Fresh explicit handoff with complete first-slice fields but vague acceptance: /cook should still fail closed
|
|
1357
|
+
# with the dedicated explicit-handoff message instead of bootstrapping canonical state.
|
|
1358
|
+
HANDOFF_ROOT_VAGUE_ACCEPTANCE="$TMPDIR/handoff-root-vague-acceptance"
|
|
1359
|
+
mkdir -p "$HANDOFF_ROOT_VAGUE_ACCEPTANCE"
|
|
1360
|
+
cd "$HANDOFF_ROOT_VAGUE_ACCEPTANCE"
|
|
1361
|
+
git init -q
|
|
1362
|
+
|
|
1363
|
+
HANDOFF_SESSION_VAGUE_ACCEPTANCE="$TMPDIR/handoff-session-vague-acceptance.jsonl"
|
|
1364
|
+
HANDOFF_SNAPSHOT_VAGUE_ACCEPTANCE="$TMPDIR/handoff-proposal-vague-acceptance.json"
|
|
1365
|
+
HANDOFF_MESSAGES_VAGUE_ACCEPTANCE="$(python3 - <<'PY'
|
|
1366
|
+
import json
|
|
1367
|
+
capsule = {
|
|
1368
|
+
"kind": "cook_handoff",
|
|
1369
|
+
"source": "primary_agent",
|
|
1370
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1371
|
+
"source_turn_id": "m0002",
|
|
1372
|
+
"mission": "Fix login redirect callback behavior.",
|
|
1373
|
+
"scope": [
|
|
1374
|
+
"Update the callback redirect decision logic.",
|
|
1375
|
+
"Preserve the broader auth flow."
|
|
1376
|
+
],
|
|
1377
|
+
"constraints": [
|
|
1378
|
+
"Do not refactor the broader auth flow."
|
|
1379
|
+
],
|
|
1380
|
+
"acceptance": [
|
|
1381
|
+
"Current behavior stays understandable."
|
|
1382
|
+
],
|
|
1383
|
+
"risks": [
|
|
1384
|
+
"Broad recent context could be reused if the vague explicit handoff is ignored."
|
|
1385
|
+
],
|
|
1386
|
+
"notes": [
|
|
1387
|
+
"This handoff includes first-slice fields but still lacks concrete acceptance."
|
|
1388
|
+
],
|
|
1389
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1390
|
+
"first_slice_goal": "Land the redirect callback fix and its regression coverage.",
|
|
1391
|
+
"first_slice_non_goals": [
|
|
1392
|
+
"Do not refactor the broader auth flow."
|
|
1393
|
+
],
|
|
1394
|
+
"implementation_surfaces": [
|
|
1395
|
+
"src/auth/redirect.ts",
|
|
1396
|
+
"tests/auth/redirect.spec.ts"
|
|
1397
|
+
],
|
|
1398
|
+
"verification_commands": [
|
|
1399
|
+
"npm test -- redirect.spec.ts"
|
|
1400
|
+
],
|
|
1401
|
+
"why_this_slice_first": "The redirect callback bug is already bounded enough to start implementation safely once acceptance is concrete.",
|
|
1402
|
+
"task_type": "completion-workflow",
|
|
1403
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
1404
|
+
"why_cook_now": "The task is workflow-worthy, but the acceptance still needs concrete repo-change detail."
|
|
1405
|
+
}
|
|
1406
|
+
recent_discussion = "Mission: Fix login redirect callback behavior.\nScope:\n- Update the callback redirect decision logic.\nConstraints:\n- Do not refactor the broader auth flow.\nAcceptance:\n- Add a regression test for returning to the requested page."
|
|
1407
|
+
messages = [
|
|
1408
|
+
{"role": "user", "content": recent_discussion},
|
|
1409
|
+
{"role": "assistant", "content": "This follow-up might soon be ready for /cook.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
1410
|
+
]
|
|
1411
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1412
|
+
PY
|
|
1413
|
+
)"
|
|
1414
|
+
write_session_messages "$HANDOFF_SESSION_VAGUE_ACCEPTANCE" "$HANDOFF_ROOT_VAGUE_ACCEPTANCE" "$HANDOFF_MESSAGES_VAGUE_ACCEPTANCE"
|
|
1415
|
+
|
|
1416
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
1417
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_VAGUE_ACCEPTANCE" \
|
|
1418
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1419
|
+
pi --session "$HANDOFF_SESSION_VAGUE_ACCEPTANCE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-vague-acceptance.out" 2>"$TMPDIR/pi-completion-handoff-vague-acceptance.err"
|
|
1420
|
+
|
|
1421
|
+
python3 - "$HANDOFF_SNAPSHOT_VAGUE_ACCEPTANCE" "$TMPDIR/pi-completion-handoff-vague-acceptance.out" "$TMPDIR/pi-completion-handoff-vague-acceptance.err" <<'PY'
|
|
1422
|
+
import sys
|
|
1423
|
+
from pathlib import Path
|
|
1424
|
+
|
|
1425
|
+
snapshot = Path(sys.argv[1])
|
|
1426
|
+
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
1427
|
+
|
|
1428
|
+
assert not snapshot.exists(), 'fresh explicit handoff with vague acceptance should not emit a startup proposal snapshot'
|
|
1429
|
+
assert not Path('.agent').exists(), 'fresh explicit handoff with vague acceptance should fail closed without writing canonical state'
|
|
1430
|
+
assert 'fresh explicit primary-agent handoff exists' in output, 'fresh explicit handoff with vague acceptance should use the dedicated explicit-handoff fail-closed message'
|
|
1431
|
+
assert 'acceptance is not anchored to concrete repo changes or verification' in output, 'fresh explicit handoff with vague acceptance should explain the vague acceptance failure'
|
|
1432
|
+
PY
|
|
1433
|
+
|
|
1471
1434
|
# Done workflow + fresh handoff: the fresh explicit handoff should override done-state suppression and start the new round.
|
|
1472
1435
|
HANDOFF_ROOT_DONE="$TMPDIR/handoff-root-done"
|
|
1473
1436
|
mkdir -p "$HANDOFF_ROOT_DONE"
|
|
@@ -1475,10 +1438,35 @@ cd "$HANDOFF_ROOT_DONE"
|
|
|
1475
1438
|
git init -q
|
|
1476
1439
|
|
|
1477
1440
|
DONE_SEED_SESSION="$TMPDIR/handoff-done-seed-session.jsonl"
|
|
1478
|
-
|
|
1479
|
-
|
|
1441
|
+
DONE_SEED_MESSAGES="$(python3 - <<'PY'
|
|
1442
|
+
import json
|
|
1443
|
+
capsule = {
|
|
1444
|
+
"kind": "cook_handoff",
|
|
1445
|
+
"source": "primary_agent",
|
|
1446
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1447
|
+
"source_turn_id": "m0002",
|
|
1448
|
+
"mission": "Seed a finished workflow before testing fresh handoff priority.",
|
|
1449
|
+
"scope": ["Create canonical workflow state."],
|
|
1450
|
+
"constraints": ["Keep the seed minimal."],
|
|
1451
|
+
"acceptance": ["Add regression coverage for marking the seeded workflow done before the next step."],
|
|
1452
|
+
"risks": [],
|
|
1453
|
+
"notes": [],
|
|
1454
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1455
|
+
"first_slice_goal": "Bootstrap the done-workflow seed fixture from an explicit handoff.",
|
|
1456
|
+
"first_slice_non_goals": [],
|
|
1457
|
+
"implementation_surfaces": ["scripts/context-proposal-test.sh"],
|
|
1458
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
1459
|
+
"why_this_slice_first": "The done-workflow handoff test needs canonical state before it can be marked done."
|
|
1460
|
+
}
|
|
1461
|
+
messages = [
|
|
1462
|
+
{"role": "user", "content": "Prepare the done-workflow seed fixture and tell me when it is ready for /cook."},
|
|
1463
|
+
{"role": "assistant", "content": "The done-workflow seed fixture is ready for /cook.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
1464
|
+
]
|
|
1465
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1466
|
+
PY
|
|
1467
|
+
)"
|
|
1468
|
+
write_session_messages "$DONE_SEED_SESSION" "$HANDOFF_ROOT_DONE" "$DONE_SEED_MESSAGES"
|
|
1480
1469
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
1481
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
1482
1470
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1483
1471
|
pi --session "$DONE_SEED_SESSION" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-done-seed.out" 2>"$TMPDIR/pi-completion-handoff-done-seed.err"
|
|
1484
1472
|
mark_done
|
|
@@ -1509,7 +1497,19 @@ capsule = {
|
|
|
1509
1497
|
"notes": [
|
|
1510
1498
|
"This is a fresh implementation round, not a summary of the finished workflow."
|
|
1511
1499
|
],
|
|
1512
|
-
"handoff_kind": "
|
|
1500
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1501
|
+
"first_slice_goal": "Patch the callback edge case and cover it with a focused regression test.",
|
|
1502
|
+
"first_slice_non_goals": [
|
|
1503
|
+
"Do not turn done-state suppression into the startup mission."
|
|
1504
|
+
],
|
|
1505
|
+
"implementation_surfaces": [
|
|
1506
|
+
"src/auth/redirect.ts",
|
|
1507
|
+
"tests/auth/redirect-edge.spec.ts"
|
|
1508
|
+
],
|
|
1509
|
+
"verification_commands": [
|
|
1510
|
+
"npm test -- redirect-edge.spec.ts"
|
|
1511
|
+
],
|
|
1512
|
+
"why_this_slice_first": "The new callback edge case is the smallest fresh implementation slice after the prior round closed.",
|
|
1513
1513
|
"task_type": "completion-workflow",
|
|
1514
1514
|
"evaluation_profile": "completion-rubric-v1",
|
|
1515
1515
|
"why_cook_now": "A new implementation-ready edge case was identified after the previous round closed."
|
|
@@ -1541,9 +1541,11 @@ assert snapshot['mission'] == 'Reopen the login redirect work for the callback e
|
|
|
1541
1541
|
assert state['mission_anchor'] == 'Reopen the login redirect work for the callback edge case.', 'done-workflow handoff should override done-state suppression with the fresh mission'
|
|
1542
1542
|
assert state['continuation_policy'] == 'continue', 'done-workflow handoff should reopen canonical workflow state for the new round'
|
|
1543
1543
|
assert state['advisory_startup_brief']['source'] == 'primary_agent_handoff', 'done-workflow handoff should preserve the handoff advisory source'
|
|
1544
|
+
assert 'First slice goal: Patch the callback edge case and cover it with a focused regression test.' in state['advisory_startup_brief']['notes'], 'done-workflow handoff should preserve first_slice_goal in advisory notes'
|
|
1545
|
+
assert 'Verification commands: npm test -- redirect-edge.spec.ts' in state['advisory_startup_brief']['notes'], 'done-workflow handoff should preserve verification_commands in advisory notes'
|
|
1544
1546
|
PY
|
|
1545
1547
|
|
|
1546
|
-
# Stale handoff:
|
|
1548
|
+
# Stale handoff: an aged-out capsule should fail closed even if later discussion exists.
|
|
1547
1549
|
HANDOFF_ROOT_STALE="$TMPDIR/handoff-root-stale"
|
|
1548
1550
|
mkdir -p "$HANDOFF_ROOT_STALE"
|
|
1549
1551
|
cd "$HANDOFF_ROOT_STALE"
|
|
@@ -1556,7 +1558,7 @@ import json
|
|
|
1556
1558
|
capsule = {
|
|
1557
1559
|
"kind": "cook_handoff",
|
|
1558
1560
|
"source": "primary_agent",
|
|
1559
|
-
"captured_at": "
|
|
1561
|
+
"captured_at": "2025-12-31T22:00:02.000Z",
|
|
1560
1562
|
"source_turn_id": "m0002",
|
|
1561
1563
|
"mission": "Fix the original login redirect callback behavior.",
|
|
1562
1564
|
"scope": ["Update the original callback redirect logic."],
|
|
@@ -1564,7 +1566,12 @@ capsule = {
|
|
|
1564
1566
|
"acceptance": ["Add the original callback regression test."],
|
|
1565
1567
|
"risks": [],
|
|
1566
1568
|
"notes": [],
|
|
1567
|
-
"handoff_kind": "
|
|
1569
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1570
|
+
"first_slice_goal": "Ship the original login callback follow-up.",
|
|
1571
|
+
"first_slice_non_goals": ["Do not refactor the auth stack."],
|
|
1572
|
+
"implementation_surfaces": ["src/auth/login-redirect.ts"],
|
|
1573
|
+
"verification_commands": ["npm test -- login-redirect.spec.ts"],
|
|
1574
|
+
"why_this_slice_first": "The original callback follow-up was the first bounded implementation slice before it aged out."
|
|
1568
1575
|
}
|
|
1569
1576
|
newer_discussion = "Mission: Ship logout redirect consistency instead.\nScope:\n- Update the logout redirect path.\nConstraints:\n- Leave the login callback flow unchanged.\nAcceptance:\n- Add a logout redirect regression test."
|
|
1570
1577
|
messages = [
|
|
@@ -1583,18 +1590,16 @@ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_STALE" \
|
|
|
1583
1590
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1584
1591
|
pi --session "$HANDOFF_SESSION_STALE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-stale.out" 2>"$TMPDIR/pi-completion-handoff-stale.err"
|
|
1585
1592
|
|
|
1586
|
-
python3 - "$HANDOFF_SNAPSHOT_STALE" <<'PY'
|
|
1587
|
-
import json
|
|
1593
|
+
python3 - "$HANDOFF_SNAPSHOT_STALE" "$TMPDIR/pi-completion-handoff-stale.out" "$TMPDIR/pi-completion-handoff-stale.err" <<'PY'
|
|
1588
1594
|
import sys
|
|
1589
1595
|
from pathlib import Path
|
|
1590
1596
|
|
|
1591
|
-
snapshot =
|
|
1592
|
-
|
|
1597
|
+
snapshot = Path(sys.argv[1])
|
|
1598
|
+
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
1593
1599
|
|
|
1594
|
-
assert snapshot
|
|
1595
|
-
assert
|
|
1596
|
-
assert
|
|
1597
|
-
assert state['advisory_startup_brief']['source'] == 'recent_discussion', 'stale handoff fallback should preserve that the accepted startup came from discussion'
|
|
1600
|
+
assert not snapshot.exists(), 'aged-out handoff should not emit a startup proposal snapshot'
|
|
1601
|
+
assert not Path('.agent').exists(), 'aged-out handoff should fail closed without writing canonical state'
|
|
1602
|
+
assert 'fresh valid explicit primary-agent handoff' in output, 'aged-out handoff should explain that a fresh valid explicit handoff is required'
|
|
1598
1603
|
PY
|
|
1599
1604
|
|
|
1600
1605
|
# Negative handoff rationale: a non-startable capsule must not become the startup mission.
|
|
@@ -1618,7 +1623,12 @@ capsule = {
|
|
|
1618
1623
|
"acceptance": ["Explain that the finished workflow should stay closed."],
|
|
1619
1624
|
"risks": [],
|
|
1620
1625
|
"notes": [],
|
|
1621
|
-
"handoff_kind": "
|
|
1626
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1627
|
+
"first_slice_goal": "Keep the finished workflow closed.",
|
|
1628
|
+
"first_slice_non_goals": ["Do not start repo changes."],
|
|
1629
|
+
"implementation_surfaces": ["docs/workflow-status.md"],
|
|
1630
|
+
"verification_commands": ["npm test -- workflow-status"],
|
|
1631
|
+
"why_this_slice_first": "This is the only bounded next step being proposed, even though the mission itself is invalid."
|
|
1622
1632
|
}
|
|
1623
1633
|
messages = [
|
|
1624
1634
|
{"role": "user", "content": "Should we reopen the finished workflow?"},
|