@linimin/pi-letscook 0.1.58 → 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 +10 -0
- package/README.md +23 -19
- package/extensions/completion/driver.ts +47 -41
- package/extensions/completion/index.ts +29 -21
- package/extensions/completion/prompt-surfaces.ts +8 -6
- package/extensions/completion/proposal.ts +0 -15
- 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 +553 -736
- package/scripts/refocus-test.sh +196 -28
- package/scripts/release-check.sh +50 -28
- package/scripts/smoke-test.sh +113 -10
- package/skills/cook-handoff-boundary/SKILL.md +11 -6
|
@@ -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'
|
|
163
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-structured-fallback.out" "$TMPDIR/pi-completion-context-proposal-structured-fallback.err" "$DISCUSSION_SNAPSHOT_ZERO" <<'PY'
|
|
296
164
|
import sys
|
|
297
165
|
from pathlib import Path
|
|
298
166
|
|
|
299
167
|
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
300
168
|
snapshot = Path(sys.argv[3])
|
|
301
|
-
assert not Path('.agent').exists(), 'missing
|
|
302
|
-
assert not snapshot.exists(), 'missing
|
|
303
|
-
assert '
|
|
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'
|
|
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'
|
|
332
172
|
PY
|
|
333
173
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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'
|
|
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'
|
|
350
178
|
import json
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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'
|
|
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```"},
|
|
199
|
+
]
|
|
200
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
398
201
|
PY
|
|
202
|
+
)"
|
|
203
|
+
write_session_messages "$SESSION_ZERO_USER_AUTHORED" "$ROOT" "$USER_AUTHORED_MESSAGES_ZERO"
|
|
399
204
|
|
|
400
|
-
|
|
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" \
|
|
205
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$USER_AUTHORED_SNAPSHOT_ZERO" \
|
|
412
206
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
413
|
-
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"
|
|
414
208
|
|
|
415
|
-
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'
|
|
416
210
|
import sys
|
|
417
211
|
from pathlib import Path
|
|
418
212
|
|
|
419
213
|
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
420
214
|
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'
|
|
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'
|
|
426
218
|
PY
|
|
427
219
|
|
|
428
|
-
# No workflow yet:
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
write_session "$SESSION_ZERO_SUPPORT_DOCS_ONLY" "$ROOT" "$DISCUSSION_ZERO_SUPPORT_DOCS_ONLY"
|
|
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"
|
|
434
225
|
|
|
435
|
-
|
|
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'
|
|
514
|
-
PY
|
|
515
|
-
|
|
516
|
-
rm -rf .agent
|
|
517
|
-
|
|
518
|
-
# No workflow yet: explicit `Docs only:` tracked-doc scope wording should still
|
|
519
|
-
# normalize placeholder planning missions into concrete repo-change missions.
|
|
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'
|
|
532
|
-
import json
|
|
533
|
-
import sys
|
|
534
|
-
from pathlib import Path
|
|
535
|
-
|
|
536
|
-
mission = 'Update README and CHANGELOG for the /cook mission-normalization behavior.'
|
|
537
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
538
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
539
|
-
|
|
540
|
-
assert proposal['mission'] == mission, 'explicit docs only scope should strip the docs-only qualifier while normalizing to the tracked-doc repo change'
|
|
541
|
-
assert state['mission_anchor'] == mission, 'explicit docs only scope should write the stripped tracked-doc mission into canonical state'
|
|
542
|
-
assert proposal['scope'] == ['Docs only: Update README and CHANGELOG for the /cook mission-normalization behavior.'], 'explicit docs only scope should preserve the original scope wording in the proposal body'
|
|
543
|
-
assert proposal['acceptance'] == ['Keep the operator-facing refocus flow guidance truthful.'], 'explicit docs only scope should preserve the non-mission acceptance item'
|
|
544
|
-
PY
|
|
545
|
-
|
|
546
|
-
rm -rf .agent
|
|
547
|
-
|
|
548
|
-
# No workflow yet: explicit `Documentation only:` tracked-doc acceptance wording should also
|
|
549
|
-
# normalize placeholder planning missions into concrete repo-change missions.
|
|
550
|
-
SESSION_ZERO_EXPLICIT_DOCUMENTATION_ONLY_ACCEPTANCE="$TMPDIR/session-zero-explicit-documentation-only-acceptance.jsonl"
|
|
551
|
-
DISCUSSION_ZERO_EXPLICIT_DOCUMENTATION_ONLY_ACCEPTANCE=$'Mission: 開始實作這個方案\nScope:\n- README and CHANGELOG guidance for bare /cook.\nConstraints:\n- Keep the approval-only Start/Cancel gate unchanged.\nAcceptance:\n- Documentation only: Write README and CHANGELOG notes for the bare /cook fail-closed clarification path.'
|
|
552
|
-
DISCUSSION_SNAPSHOT_ZERO_EXPLICIT_DOCUMENTATION_ONLY_ACCEPTANCE="$TMPDIR/context-proposal-explicit-documentation-only-acceptance.json"
|
|
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
|
-
},
|
|
609
|
-
]
|
|
610
|
-
with session_path.open('w', encoding='utf-8') as fh:
|
|
611
|
-
for entry in entries:
|
|
612
|
-
fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
613
|
-
PY
|
|
614
|
-
|
|
615
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
616
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
617
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_ASSISTANT_SUMMARY" \
|
|
226
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$INVALID_SNAPSHOT_ZERO" \
|
|
618
227
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
619
|
-
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"
|
|
620
229
|
|
|
621
|
-
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'
|
|
622
231
|
import sys
|
|
623
232
|
from pathlib import Path
|
|
624
233
|
|
|
625
234
|
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
626
235
|
snapshot = Path(sys.argv[3])
|
|
627
|
-
assert not Path('.agent').exists(), 'assistant
|
|
628
|
-
assert not snapshot.exists(), 'assistant
|
|
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'
|
|
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'
|
|
631
239
|
PY
|
|
632
240
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
241
|
+
# No workflow yet: a fresh explicit primary-agent handoff should still bootstrap canonical startup state.
|
|
242
|
+
SESSION_ONE="$TMPDIR/session-one.jsonl"
|
|
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))
|
|
658
291
|
PY
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
SESSION_ONE="$TMPDIR/session-one.jsonl"
|
|
662
|
-
DISCUSSION_ONE="$DISCUSSION_ZERO"
|
|
663
|
-
ANALYST_OUTPUT_ONE='{"mission":"Remove the completion status line while keeping the completion widget.","scope":["Keep the non-running completion widget.","Suppress the widget while a completion role is active."],"constraints":["Do not reintroduce any other completion status surface."],"acceptance":["Update README to match the shipped behavior.","Keep observability regression coverage truthful."],"critique":["Keep critique separate from the mission anchor so startup analysis does not rewrite the workflow goal."],"risks":["Stale widget-removal discussion could broaden the startup plan if it gets treated as mission text."],"task_type":"completion-workflow","evaluation_profile":"completion-rubric-v1","possible_noise":["older widget restyle ideas"],"confidence":0.94}'
|
|
664
|
-
DISCUSSION_SNAPSHOT_ONE="$TMPDIR/context-proposal-discussion-hints.json"
|
|
665
|
-
write_session "$SESSION_ONE" "$ROOT" "$DISCUSSION_ONE"
|
|
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,107 @@ 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'
|
|
369
|
+
import json
|
|
370
|
+
import sys
|
|
371
|
+
from pathlib import Path
|
|
372
|
+
|
|
373
|
+
mission = 'Remove the completion status line while keeping the completion widget.'
|
|
374
|
+
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
375
|
+
resume = Path(sys.argv[2]).read_text()
|
|
376
|
+
chooser_path = Path(sys.argv[3])
|
|
377
|
+
proposal_path = Path(sys.argv[4])
|
|
378
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
379
|
+
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
380
|
+
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
381
|
+
|
|
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'
|
|
395
|
+
PY
|
|
396
|
+
|
|
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'
|
|
743
416
|
import json
|
|
744
417
|
import sys
|
|
745
418
|
from pathlib import Path
|
|
@@ -748,26 +421,26 @@ mission = 'Remove the completion status line while keeping the completion widget
|
|
|
748
421
|
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
749
422
|
resume = Path(sys.argv[2]).read_text()
|
|
750
423
|
chooser_path = Path(sys.argv[3])
|
|
424
|
+
proposal_path = Path(sys.argv[4])
|
|
751
425
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
752
426
|
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
753
427
|
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
754
428
|
|
|
755
|
-
assert routing['mode'] == 'bare', '
|
|
756
|
-
assert '
|
|
757
|
-
assert '
|
|
758
|
-
assert routing['
|
|
759
|
-
assert routing['
|
|
760
|
-
assert
|
|
761
|
-
assert
|
|
762
|
-
assert
|
|
763
|
-
assert
|
|
764
|
-
assert
|
|
765
|
-
assert
|
|
766
|
-
assert active['mission_anchor'] == mission, 'active bare /cook continue should keep active-slice.json unchanged'
|
|
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'
|
|
767
440
|
PY
|
|
768
441
|
|
|
769
|
-
# Active workflow: summary-only replacement artifacts should
|
|
770
|
-
#
|
|
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
|
-
# surface all candidates and allow the chooser to select a non-primary alternate mission.
|
|
895
|
-
SESSION_ONE_MULTI_CANDIDATE="$TMPDIR/session-one-multi-candidate.jsonl"
|
|
896
|
-
DISCUSSION_ONE_MULTI_CANDIDATE=$'Mission: Fix login redirect callback behavior.\nScope:\n- Update the callback redirect decision logic for the current auth flow.\nConstraints:\n- Do not refactor the broader auth flow.\nAcceptance:\n- Add a regression test for returning to the requested page.\nMission: Add logout redirect regression coverage.\nScope:\n- Add coverage for logout redirect behavior.\nConstraints:\n- Do not change login redirect behavior in this pass.\nAcceptance:\n- Land a dedicated logout redirect regression test.'
|
|
897
|
-
MULTI_ROUTING_ONE="$TMPDIR/active-multi-routing.json"
|
|
898
|
-
MULTI_CHOOSER_ONE="$TMPDIR/active-multi-chooser.json"
|
|
899
|
-
MULTI_PROPOSAL_ONE="$TMPDIR/active-multi-proposal.json"
|
|
900
|
-
write_session "$SESSION_ONE_MULTI_CANDIDATE" "$ROOT" "$DISCUSSION_ONE_MULTI_CANDIDATE"
|
|
901
|
-
|
|
902
|
-
PI_COMPLETION_EXISTING_WORKFLOW_MISSION='Fix login redirect callback behavior.' \
|
|
903
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
904
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
905
|
-
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$MULTI_ROUTING_ONE" \
|
|
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'
|
|
566
|
+
python3 - "$TMPDIR/active-non-startable-before.json" <<'PY'
|
|
912
567
|
import json
|
|
913
568
|
import sys
|
|
914
569
|
from pathlib import Path
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
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'
|
|
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
|
|
|
@@ -1485,6 +1200,85 @@ assert 'Why this slice first: The redirect callback bug is already bounded enoug
|
|
|
1485
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'
|
|
1486
1201
|
PY
|
|
1487
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
|
+
|
|
1488
1282
|
# Fresh but non-startable explicit handoff: /cook should fail closed instead of falling back
|
|
1489
1283
|
# to a broad recent-discussion startup brief when the explicit capsule is still too vague.
|
|
1490
1284
|
HANDOFF_ROOT_VAGUE="$TMPDIR/handoff-root-vague"
|
|
@@ -1644,10 +1438,35 @@ cd "$HANDOFF_ROOT_DONE"
|
|
|
1644
1438
|
git init -q
|
|
1645
1439
|
|
|
1646
1440
|
DONE_SEED_SESSION="$TMPDIR/handoff-done-seed-session.jsonl"
|
|
1647
|
-
|
|
1648
|
-
|
|
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"
|
|
1649
1469
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
1650
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
1651
1470
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1652
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"
|
|
1653
1472
|
mark_done
|
|
@@ -1726,7 +1545,7 @@ assert 'First slice goal: Patch the callback edge case and cover it with a focus
|
|
|
1726
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'
|
|
1727
1546
|
PY
|
|
1728
1547
|
|
|
1729
|
-
# Stale handoff:
|
|
1548
|
+
# Stale handoff: an aged-out capsule should fail closed even if later discussion exists.
|
|
1730
1549
|
HANDOFF_ROOT_STALE="$TMPDIR/handoff-root-stale"
|
|
1731
1550
|
mkdir -p "$HANDOFF_ROOT_STALE"
|
|
1732
1551
|
cd "$HANDOFF_ROOT_STALE"
|
|
@@ -1739,7 +1558,7 @@ import json
|
|
|
1739
1558
|
capsule = {
|
|
1740
1559
|
"kind": "cook_handoff",
|
|
1741
1560
|
"source": "primary_agent",
|
|
1742
|
-
"captured_at": "
|
|
1561
|
+
"captured_at": "2025-12-31T22:00:02.000Z",
|
|
1743
1562
|
"source_turn_id": "m0002",
|
|
1744
1563
|
"mission": "Fix the original login redirect callback behavior.",
|
|
1745
1564
|
"scope": ["Update the original callback redirect logic."],
|
|
@@ -1752,7 +1571,7 @@ capsule = {
|
|
|
1752
1571
|
"first_slice_non_goals": ["Do not refactor the auth stack."],
|
|
1753
1572
|
"implementation_surfaces": ["src/auth/login-redirect.ts"],
|
|
1754
1573
|
"verification_commands": ["npm test -- login-redirect.spec.ts"],
|
|
1755
|
-
"why_this_slice_first": "The original callback follow-up was the first bounded implementation slice before
|
|
1574
|
+
"why_this_slice_first": "The original callback follow-up was the first bounded implementation slice before it aged out."
|
|
1756
1575
|
}
|
|
1757
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."
|
|
1758
1577
|
messages = [
|
|
@@ -1771,18 +1590,16 @@ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_STALE" \
|
|
|
1771
1590
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1772
1591
|
pi --session "$HANDOFF_SESSION_STALE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-stale.out" 2>"$TMPDIR/pi-completion-handoff-stale.err"
|
|
1773
1592
|
|
|
1774
|
-
python3 - "$HANDOFF_SNAPSHOT_STALE" <<'PY'
|
|
1775
|
-
import json
|
|
1593
|
+
python3 - "$HANDOFF_SNAPSHOT_STALE" "$TMPDIR/pi-completion-handoff-stale.out" "$TMPDIR/pi-completion-handoff-stale.err" <<'PY'
|
|
1776
1594
|
import sys
|
|
1777
1595
|
from pathlib import Path
|
|
1778
1596
|
|
|
1779
|
-
snapshot =
|
|
1780
|
-
|
|
1597
|
+
snapshot = Path(sys.argv[1])
|
|
1598
|
+
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
1781
1599
|
|
|
1782
|
-
assert snapshot
|
|
1783
|
-
assert
|
|
1784
|
-
assert
|
|
1785
|
-
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'
|
|
1786
1603
|
PY
|
|
1787
1604
|
|
|
1788
1605
|
# Negative handoff rationale: a non-startable capsule must not become the startup mission.
|