@linimin/pi-letscook 0.1.58 → 0.1.60
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 +12 -0
- package/README.md +28 -22
- package/extensions/completion/driver.ts +51 -39
- package/extensions/completion/index.ts +29 -21
- package/extensions/completion/prompt-surfaces.ts +12 -10
- 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 +577 -727
- package/scripts/refocus-test.sh +196 -28
- package/scripts/release-check.sh +57 -34
- package/scripts/smoke-test.sh +73 -14
- package/skills/cook-handoff-boundary/SKILL.md +16 -10
|
@@ -147,530 +147,163 @@ 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 synthesize a startup brief from recent discussion,
|
|
151
|
+
# and Cancel should leave canonical workflow state untouched.
|
|
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"
|
|
154
155
|
write_session "$SESSION_ZERO" "$ROOT" "$DISCUSSION_ZERO"
|
|
155
156
|
|
|
156
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=
|
|
157
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=cancel \
|
|
157
158
|
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
158
159
|
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'
|
|
163
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-structured-fallback.out" "$TMPDIR/pi-completion-context-proposal-structured-fallback.err" "$DISCUSSION_SNAPSHOT_ZERO" <<'PY'
|
|
263
164
|
import json
|
|
264
165
|
import sys
|
|
265
166
|
from pathlib import Path
|
|
266
167
|
|
|
267
|
-
mission = 'Fix login redirect callback behavior.'
|
|
268
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
269
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
270
|
-
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
271
|
-
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
272
|
-
|
|
273
|
-
assert proposal['mission'] == mission, 'latest structured discussion should win over older structured context'
|
|
274
|
-
assert proposal['scope'] == ['Update the callback redirect decision logic.'], 'latest structured discussion should preserve the latest scope only'
|
|
275
|
-
assert proposal['analysis']['suppressedNegatedTopics'] == ['Do not refactor the broader auth flow.'], 'latest structured discussion should preserve negated implementation topics separately from the mission'
|
|
276
|
-
assert state['mission_anchor'] == mission, 'latest structured discussion should initialize state.json with the latest mission'
|
|
277
|
-
assert plan['mission_anchor'] == mission, 'latest structured discussion should initialize plan.json with the latest mission'
|
|
278
|
-
assert active['mission_anchor'] == mission, 'latest structured discussion should initialize active-slice.json with the latest mission'
|
|
279
|
-
PY
|
|
280
|
-
|
|
281
|
-
rm -rf .agent
|
|
282
|
-
|
|
283
|
-
# No workflow yet: bare /cook should fail closed when a required structured section is missing and analyst output is unavailable.
|
|
284
|
-
SESSION_ZERO_MISSING="$TMPDIR/session-zero-missing-section.jsonl"
|
|
285
|
-
DISCUSSION_ZERO_MISSING=$'Mission: Remove the completion status line while keeping the completion widget.\nScope:\n- Keep the non-running completion widget.\n- Suppress the widget while a completion role is active.\nConstraints:\n- Do not reintroduce any other completion status surface.'
|
|
286
|
-
DISCUSSION_SNAPSHOT_ZERO_MISSING="$TMPDIR/context-proposal-missing-section.json"
|
|
287
|
-
write_session "$SESSION_ZERO_MISSING" "$ROOT" "$DISCUSSION_ZERO_MISSING"
|
|
288
|
-
|
|
289
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
290
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
291
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_MISSING" \
|
|
292
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
293
|
-
pi --session "$SESSION_ZERO_MISSING" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-missing-section.out" 2>"$TMPDIR/pi-completion-context-proposal-missing-section.err"
|
|
294
|
-
|
|
295
|
-
python3 - "$TMPDIR/pi-completion-context-proposal-missing-section.out" "$TMPDIR/pi-completion-context-proposal-missing-section.err" "$DISCUSSION_SNAPSHOT_ZERO_MISSING" <<'PY'
|
|
296
|
-
import sys
|
|
297
|
-
from pathlib import Path
|
|
298
|
-
|
|
299
168
|
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
300
169
|
snapshot = Path(sys.argv[3])
|
|
301
|
-
|
|
302
|
-
assert not
|
|
303
|
-
assert '
|
|
304
|
-
assert '
|
|
170
|
+
proposal = json.loads(snapshot.read_text())
|
|
171
|
+
assert not Path('.agent').exists(), 'recent-discussion Cancel should leave canonical state untouched before workflow bootstrap'
|
|
172
|
+
assert snapshot.exists(), 'recent-discussion startup synthesis should emit a proposal snapshot before the Start/Cancel gate'
|
|
173
|
+
assert proposal['mission'] == 'Remove the completion status line while keeping the completion widget.', 'recent-discussion startup synthesis should preserve the structured mission anchor'
|
|
174
|
+
assert proposal['source'] == 'session', 'recent-discussion startup synthesis should snapshot the structured-session proposal source'
|
|
175
|
+
assert 'Cancelled recent-discussion workflow proposal' in output, 'recent-discussion Cancel should report that canonical state was left unchanged'
|
|
305
176
|
PY
|
|
306
177
|
|
|
307
|
-
# No workflow yet:
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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'
|
|
178
|
+
# No workflow yet: user-authored faux handoffs must not bootstrap canonical workflow state.
|
|
179
|
+
SESSION_ZERO_USER_AUTHORED="$TMPDIR/session-zero-user-authored.jsonl"
|
|
180
|
+
USER_AUTHORED_SNAPSHOT_ZERO="$TMPDIR/context-proposal-user-authored-handoff.json"
|
|
181
|
+
USER_AUTHORED_MESSAGES_ZERO="$(python3 - <<'PY'
|
|
321
182
|
import json
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
345
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_NORMALIZED" \
|
|
346
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
347
|
-
pi --session "$SESSION_ZERO_NORMALIZED" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-normalized-fallback.out" 2>"$TMPDIR/pi-completion-context-proposal-normalized-fallback.err"
|
|
348
|
-
|
|
349
|
-
python3 - "$DISCUSSION_SNAPSHOT_ZERO_NORMALIZED" <<'PY'
|
|
350
|
-
import json
|
|
351
|
-
import sys
|
|
352
|
-
from pathlib import Path
|
|
353
|
-
|
|
354
|
-
mission = 'Normalize bare /cook planning phrasing into shipped implementation missions.'
|
|
355
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
356
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
357
|
-
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
358
|
-
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
359
|
-
mission_text = Path('.agent/mission.md').read_text()
|
|
360
|
-
|
|
361
|
-
assert mission in mission_text, 'normalized structured-fallback startup should update .agent/mission.md to the implementation mission'
|
|
362
|
-
assert proposal['mission'] == mission, 'structured-fallback startup should normalize the placeholder planning mission'
|
|
363
|
-
assert state['mission_anchor'] == mission, 'state.json mission_anchor should use the normalized implementation mission'
|
|
364
|
-
assert plan['mission_anchor'] == mission, 'plan.json mission_anchor should use the normalized implementation mission'
|
|
365
|
-
assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor should use the normalized implementation mission'
|
|
366
|
-
assert proposal['source'] == 'session', 'normalized structured-fallback startup should still record session fallback as the proposal source'
|
|
367
|
-
assert proposal['scope'][0] == mission, 'normalized structured-fallback startup should derive the mission from shipped-work scope'
|
|
368
|
-
PY
|
|
369
|
-
|
|
370
|
-
rm -rf .agent
|
|
371
|
-
|
|
372
|
-
# No workflow yet: analyst-derived and strict structured fallback proposals should converge on the same
|
|
373
|
-
# normalized implementation mission for the same planning-phrased discussion.
|
|
374
|
-
SESSION_ZERO_ANALYST_NORMALIZED="$TMPDIR/session-zero-analyst-normalized.jsonl"
|
|
375
|
-
ANALYST_OUTPUT_ZERO_NORMALIZED='{"mission":"開始實作這個方案","scope":["Normalize bare /cook planning phrasing into shipped implementation missions.","Keep analyst-derived and structured-fallback proposals aligned."],"constraints":["Do not rewrite the supported bare-discussion mission anchor once it is clear."],"acceptance":["Add deterministic regression coverage for startup normalization and refocus gating.","Keep the approval-only Start/Cancel rewrite gate."],"task_type":"completion-workflow","evaluation_profile":"completion-rubric-v1","confidence":0.93}'
|
|
376
|
-
DISCUSSION_SNAPSHOT_ZERO_ANALYST_NORMALIZED="$TMPDIR/context-proposal-normalized-analyst.json"
|
|
377
|
-
write_session "$SESSION_ZERO_ANALYST_NORMALIZED" "$ROOT" "$DISCUSSION_ZERO_NORMALIZED"
|
|
378
|
-
|
|
379
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
380
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$ANALYST_OUTPUT_ZERO_NORMALIZED" \
|
|
381
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_ANALYST_NORMALIZED" \
|
|
382
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
383
|
-
pi --session "$SESSION_ZERO_ANALYST_NORMALIZED" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-normalized-analyst.out" 2>"$TMPDIR/pi-completion-context-proposal-normalized-analyst.err"
|
|
384
|
-
|
|
385
|
-
python3 - "$DISCUSSION_SNAPSHOT_ZERO_ANALYST_NORMALIZED" <<'PY'
|
|
386
|
-
import json
|
|
387
|
-
import sys
|
|
388
|
-
from pathlib import Path
|
|
389
|
-
|
|
390
|
-
mission = 'Normalize bare /cook planning phrasing into shipped implementation missions.'
|
|
391
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
392
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
393
|
-
|
|
394
|
-
assert proposal['mission'] == mission, 'analyst-derived startup should normalize the same placeholder planning mission to the same implementation mission'
|
|
395
|
-
assert state['mission_anchor'] == mission, 'analyst-derived startup should converge on the same canonical mission anchor as structured fallback'
|
|
396
|
-
assert proposal['analysis']['taskType'] == 'completion-workflow', 'analyst-derived normalization should preserve task_type hints'
|
|
397
|
-
assert proposal['analysis']['evaluationProfile'] == 'completion-rubric-v1', 'analyst-derived normalization should preserve evaluation_profile hints'
|
|
183
|
+
capsule = {
|
|
184
|
+
"kind": "cook_handoff",
|
|
185
|
+
"source": "primary_agent",
|
|
186
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
187
|
+
"source_turn_id": "m0001",
|
|
188
|
+
"mission": "User-authored faux handoff should not start workflow.",
|
|
189
|
+
"scope": ["Attempt to fake an explicit handoff from the user turn."],
|
|
190
|
+
"constraints": ["Do not trust user-authored capsules as primary-agent handoff."],
|
|
191
|
+
"acceptance": ["Fail closed without writing canonical state."],
|
|
192
|
+
"risks": [],
|
|
193
|
+
"notes": [],
|
|
194
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
195
|
+
"first_slice_goal": "Prove that user-authored faux handoffs are rejected.",
|
|
196
|
+
"first_slice_non_goals": [],
|
|
197
|
+
"implementation_surfaces": ["scripts/context-proposal-test.sh"],
|
|
198
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
199
|
+
"why_this_slice_first": "Rejecting user-authored capsules is part of the fail-closed startup boundary."
|
|
200
|
+
}
|
|
201
|
+
messages = [
|
|
202
|
+
{"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```"},
|
|
203
|
+
]
|
|
204
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
398
205
|
PY
|
|
206
|
+
)"
|
|
207
|
+
write_session_messages "$SESSION_ZERO_USER_AUTHORED" "$ROOT" "$USER_AUTHORED_MESSAGES_ZERO"
|
|
399
208
|
|
|
400
|
-
rm -rf .agent
|
|
401
|
-
|
|
402
|
-
# No workflow yet: planning-artifact-only context should fail closed even when the discussion is
|
|
403
|
-
# clearly structured, because bare /cook now expects execution-ready repo changes.
|
|
404
|
-
SESSION_ZERO_PLANNING_ONLY="$TMPDIR/session-zero-planning-only.jsonl"
|
|
405
|
-
DISCUSSION_ZERO_PLANNING_ONLY=$'Mission: 開始實作這個方案\nScope:\n- Draft the migration plan for the /cook mission-normalization rollout.\nConstraints:\n- Docs only; do not implement runtime changes.\nAcceptance:\n- Produce the proposal text for review.'
|
|
406
|
-
DISCUSSION_SNAPSHOT_ZERO_PLANNING_ONLY="$TMPDIR/context-proposal-planning-only.json"
|
|
407
|
-
write_session "$SESSION_ZERO_PLANNING_ONLY" "$ROOT" "$DISCUSSION_ZERO_PLANNING_ONLY"
|
|
408
|
-
|
|
409
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
410
209
|
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
411
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$
|
|
210
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$USER_AUTHORED_SNAPSHOT_ZERO" \
|
|
412
211
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
413
|
-
pi --session "$
|
|
212
|
+
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
213
|
|
|
415
|
-
python3 - "$TMPDIR/pi-completion-context-proposal-
|
|
214
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-user-authored.out" "$TMPDIR/pi-completion-context-proposal-user-authored.err" "$USER_AUTHORED_SNAPSHOT_ZERO" <<'PY'
|
|
416
215
|
import sys
|
|
417
216
|
from pathlib import Path
|
|
418
217
|
|
|
419
218
|
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
420
219
|
snapshot = Path(sys.argv[3])
|
|
421
|
-
assert not Path('.agent').exists(), '
|
|
422
|
-
assert not snapshot.exists(), '
|
|
423
|
-
assert '
|
|
424
|
-
assert 'Mission/Scope/Constraints/Acceptance' in output, 'planning-only startup should still explain the structured discussion requirement'
|
|
425
|
-
assert 'concrete repo changes' in output, 'planning-only startup should explain that bare /cook now expects execution-ready repo changes'
|
|
426
|
-
PY
|
|
427
|
-
|
|
428
|
-
# No workflow yet: docs-only tracked deliverables such as README/CHANGELOG updates should
|
|
429
|
-
# normalize placeholder planning missions into concrete repo-change missions.
|
|
430
|
-
SESSION_ZERO_SUPPORT_DOCS_ONLY="$TMPDIR/session-zero-support-docs-only.jsonl"
|
|
431
|
-
DISCUSSION_ZERO_SUPPORT_DOCS_ONLY=$'Mission: 開始實作這個方案\nScope:\n- Update README and CHANGELOG for the /cook mission-normalization behavior.\nConstraints:\n- Keep the approval-only Start/Cancel gate unchanged.\nAcceptance:\n- Add documentation for the operator-facing refocus flow.'
|
|
432
|
-
DISCUSSION_SNAPSHOT_ZERO_SUPPORT_DOCS_ONLY="$TMPDIR/context-proposal-support-docs-only.json"
|
|
433
|
-
write_session "$SESSION_ZERO_SUPPORT_DOCS_ONLY" "$ROOT" "$DISCUSSION_ZERO_SUPPORT_DOCS_ONLY"
|
|
434
|
-
|
|
435
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
436
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
437
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_SUPPORT_DOCS_ONLY" \
|
|
438
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
439
|
-
pi --session "$SESSION_ZERO_SUPPORT_DOCS_ONLY" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-support-docs-only.out" 2>"$TMPDIR/pi-completion-context-proposal-support-docs-only.err"
|
|
440
|
-
|
|
441
|
-
python3 - "$DISCUSSION_SNAPSHOT_ZERO_SUPPORT_DOCS_ONLY" <<'PY'
|
|
442
|
-
import json
|
|
443
|
-
import sys
|
|
444
|
-
from pathlib import Path
|
|
445
|
-
|
|
446
|
-
mission = 'Update README and CHANGELOG for the /cook mission-normalization behavior.'
|
|
447
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
448
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
449
|
-
|
|
450
|
-
assert proposal['mission'] == mission, 'docs-only startup should normalize the placeholder planning mission to the tracked-doc repo change'
|
|
451
|
-
assert state['mission_anchor'] == mission, 'docs-only startup should write the normalized tracked-doc mission into canonical state'
|
|
452
|
-
assert proposal['scope'][0] == mission, 'docs-only startup should derive the mission from the tracked-doc scope item'
|
|
453
|
-
assert proposal['acceptance'] == ['Add documentation for the operator-facing refocus flow.'], 'docs-only startup should keep the documentation acceptance item'
|
|
454
|
-
PY
|
|
455
|
-
|
|
456
|
-
rm -rf .agent
|
|
457
|
-
|
|
458
|
-
# No workflow yet: reviewer-reproduced docs-only phrasing with edit/document wording should
|
|
459
|
-
# also normalize placeholder planning missions into concrete repo-change missions.
|
|
460
|
-
SESSION_ZERO_EDIT_DOCUMENT_DOCS_ONLY="$TMPDIR/session-zero-edit-document-docs-only.jsonl"
|
|
461
|
-
DISCUSSION_ZERO_EDIT_DOCUMENT_DOCS_ONLY=$'Mission: 開始實作這個方案\nScope:\n- Edit README to explain the /cook mission-normalization behavior.\nConstraints:\n- Keep the approval-only Start/Cancel gate unchanged.\nAcceptance:\n- Document the operator-facing refocus flow.'
|
|
462
|
-
DISCUSSION_SNAPSHOT_ZERO_EDIT_DOCUMENT_DOCS_ONLY="$TMPDIR/context-proposal-edit-document-docs-only.json"
|
|
463
|
-
write_session "$SESSION_ZERO_EDIT_DOCUMENT_DOCS_ONLY" "$ROOT" "$DISCUSSION_ZERO_EDIT_DOCUMENT_DOCS_ONLY"
|
|
464
|
-
|
|
465
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
466
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
467
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_EDIT_DOCUMENT_DOCS_ONLY" \
|
|
468
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
469
|
-
pi --session "$SESSION_ZERO_EDIT_DOCUMENT_DOCS_ONLY" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-edit-document-docs-only.out" 2>"$TMPDIR/pi-completion-context-proposal-edit-document-docs-only.err"
|
|
470
|
-
|
|
471
|
-
python3 - "$DISCUSSION_SNAPSHOT_ZERO_EDIT_DOCUMENT_DOCS_ONLY" <<'PY'
|
|
472
|
-
import json
|
|
473
|
-
import sys
|
|
474
|
-
from pathlib import Path
|
|
475
|
-
|
|
476
|
-
mission = 'Edit README to explain the /cook mission-normalization behavior.'
|
|
477
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
478
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
479
|
-
|
|
480
|
-
assert proposal['mission'] == mission, 'edit/document docs-only startup should normalize the placeholder planning mission to the tracked-doc repo change'
|
|
481
|
-
assert state['mission_anchor'] == mission, 'edit/document docs-only startup should write the normalized tracked-doc mission into canonical state'
|
|
482
|
-
assert proposal['scope'][0] == mission, 'edit/document docs-only startup should derive the mission from the tracked-doc scope item'
|
|
483
|
-
assert proposal['acceptance'] == ['Document the operator-facing refocus flow.'], 'edit/document docs-only startup should keep the documentation acceptance item'
|
|
484
|
-
PY
|
|
485
|
-
|
|
486
|
-
rm -rf .agent
|
|
487
|
-
|
|
488
|
-
# No workflow yet: acceptance-only docs deliverables using write phrasing should also
|
|
489
|
-
# normalize placeholder planning missions into concrete repo-change missions.
|
|
490
|
-
SESSION_ZERO_WRITE_DOCS_ONLY="$TMPDIR/session-zero-write-docs-only.jsonl"
|
|
491
|
-
DISCUSSION_ZERO_WRITE_DOCS_ONLY=$'Mission: 開始實作這個方案\nScope:\n- README and CHANGELOG guidance for bare /cook.\nConstraints:\n- Keep the approval-only Start/Cancel gate unchanged.\nAcceptance:\n- Write README and CHANGELOG notes for the bare /cook fail-closed clarification path.'
|
|
492
|
-
DISCUSSION_SNAPSHOT_ZERO_WRITE_DOCS_ONLY="$TMPDIR/context-proposal-write-docs-only.json"
|
|
493
|
-
write_session "$SESSION_ZERO_WRITE_DOCS_ONLY" "$ROOT" "$DISCUSSION_ZERO_WRITE_DOCS_ONLY"
|
|
494
|
-
|
|
495
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
496
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
497
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO_WRITE_DOCS_ONLY" \
|
|
498
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
499
|
-
pi --session "$SESSION_ZERO_WRITE_DOCS_ONLY" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-write-docs-only.out" 2>"$TMPDIR/pi-completion-context-proposal-write-docs-only.err"
|
|
500
|
-
|
|
501
|
-
python3 - "$DISCUSSION_SNAPSHOT_ZERO_WRITE_DOCS_ONLY" <<'PY'
|
|
502
|
-
import json
|
|
503
|
-
import sys
|
|
504
|
-
from pathlib import Path
|
|
505
|
-
|
|
506
|
-
mission = 'Write README and CHANGELOG notes for the bare /cook fail-closed clarification path.'
|
|
507
|
-
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
508
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
509
|
-
|
|
510
|
-
assert proposal['mission'] == mission, 'write docs-only startup should normalize the placeholder planning mission from acceptance-only tracked-doc work'
|
|
511
|
-
assert state['mission_anchor'] == mission, 'write docs-only startup should write the acceptance-derived tracked-doc mission into canonical state'
|
|
512
|
-
assert proposal['scope'] == ['README and CHANGELOG guidance for bare /cook.'], 'write docs-only startup should preserve the noun-only scope item while deriving the mission from acceptance'
|
|
513
|
-
assert proposal['acceptance'] == [mission], 'write docs-only startup should keep the acceptance-derived docs deliverable intact'
|
|
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'
|
|
220
|
+
assert not Path('.agent').exists(), 'user-authored faux handoff should fail closed without writing canonical state'
|
|
221
|
+
assert not snapshot.exists(), 'user-authored faux handoff should not emit a startup proposal snapshot'
|
|
222
|
+
assert 'recent discussion did not produce a clear execution-ready startup brief' in output, 'user-authored faux handoff should still fail closed unless ordinary discussion is concretely startable'
|
|
574
223
|
PY
|
|
575
224
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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
|
|
225
|
+
# No workflow yet: malformed or invalid assistant handoff capsules must also fail closed.
|
|
226
|
+
SESSION_ZERO_INVALID="$TMPDIR/session-zero-invalid-handoff.jsonl"
|
|
227
|
+
INVALID_SNAPSHOT_ZERO="$TMPDIR/context-proposal-invalid-handoff.json"
|
|
228
|
+
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```"}]'
|
|
229
|
+
write_session_messages "$SESSION_ZERO_INVALID" "$ROOT" "$INVALID_MESSAGES_ZERO"
|
|
586
230
|
|
|
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
231
|
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
617
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$
|
|
232
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$INVALID_SNAPSHOT_ZERO" \
|
|
618
233
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
619
|
-
pi --session "$
|
|
234
|
+
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
235
|
|
|
621
|
-
python3 - "$TMPDIR/pi-completion-context-proposal-
|
|
236
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-invalid-handoff.out" "$TMPDIR/pi-completion-context-proposal-invalid-handoff.err" "$INVALID_SNAPSHOT_ZERO" <<'PY'
|
|
622
237
|
import sys
|
|
623
238
|
from pathlib import Path
|
|
624
239
|
|
|
625
240
|
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
626
241
|
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'
|
|
242
|
+
assert not Path('.agent').exists(), 'invalid assistant handoff should fail closed without writing canonical state'
|
|
243
|
+
assert not snapshot.exists(), 'invalid assistant handoff should not emit a startup proposal snapshot'
|
|
244
|
+
assert 'recent discussion did not produce a clear execution-ready startup brief' in output, 'invalid assistant handoff should still fail closed when no clear recent-discussion startup brief exists'
|
|
631
245
|
PY
|
|
632
246
|
|
|
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
|
-
|
|
247
|
+
# No workflow yet: a fresh explicit primary-agent handoff should still bootstrap canonical startup state.
|
|
248
|
+
SESSION_ONE="$TMPDIR/session-one.jsonl"
|
|
249
|
+
HANDOFF_SNAPSHOT_ONE="$TMPDIR/context-proposal-explicit-startup.json"
|
|
250
|
+
HANDOFF_MESSAGES_ONE="$(python3 - <<'PY'
|
|
251
|
+
import json
|
|
252
|
+
capsule = {
|
|
253
|
+
"kind": "cook_handoff",
|
|
254
|
+
"source": "primary_agent",
|
|
255
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
256
|
+
"source_turn_id": "m0002",
|
|
257
|
+
"mission": "Remove the completion status line while keeping the completion widget.",
|
|
258
|
+
"scope": [
|
|
259
|
+
"Keep the non-running completion widget.",
|
|
260
|
+
"Suppress the widget while a completion role is active."
|
|
261
|
+
],
|
|
262
|
+
"constraints": [
|
|
263
|
+
"Do not reintroduce any other completion status surface."
|
|
264
|
+
],
|
|
265
|
+
"acceptance": [
|
|
266
|
+
"Update README to match the shipped behavior.",
|
|
267
|
+
"Keep observability regression coverage truthful."
|
|
268
|
+
],
|
|
269
|
+
"risks": [
|
|
270
|
+
"Stale widget-removal discussion could broaden the startup plan if the handoff is ignored."
|
|
271
|
+
],
|
|
272
|
+
"notes": [
|
|
273
|
+
"Keep the startup brief aligned with the explicit primary-agent plan."
|
|
274
|
+
],
|
|
275
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
276
|
+
"first_slice_goal": "Land the completion-status removal and keep the completion widget coverage truthful.",
|
|
277
|
+
"first_slice_non_goals": [
|
|
278
|
+
"Do not reintroduce any other completion status surface."
|
|
279
|
+
],
|
|
280
|
+
"implementation_surfaces": [
|
|
281
|
+
"extensions/completion/index.ts",
|
|
282
|
+
"scripts/context-proposal-test.sh"
|
|
283
|
+
],
|
|
284
|
+
"verification_commands": [
|
|
285
|
+
"npm run context-proposal-test"
|
|
286
|
+
],
|
|
287
|
+
"why_this_slice_first": "The startup boundary regression is already bounded enough to implement safely.",
|
|
288
|
+
"task_type": "completion-workflow",
|
|
289
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
290
|
+
"why_cook_now": "The explicit startup brief is concrete and ready for repo changes."
|
|
291
|
+
}
|
|
292
|
+
messages = [
|
|
293
|
+
{"role": "user", "content": "Please think through the completion widget startup boundary and tell me when it is ready for /cook."},
|
|
294
|
+
{"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```"},
|
|
295
|
+
]
|
|
296
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
658
297
|
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"
|
|
298
|
+
)"
|
|
299
|
+
write_session_messages "$SESSION_ONE" "$ROOT" "$HANDOFF_MESSAGES_ONE"
|
|
666
300
|
|
|
667
301
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
668
|
-
|
|
669
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ONE" \
|
|
302
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_ONE" \
|
|
670
303
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
671
|
-
pi --session "$SESSION_ONE" -e "$PKG_ROOT" -p "/cook"
|
|
304
|
+
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
305
|
|
|
673
|
-
python3 - "$
|
|
306
|
+
python3 - "$HANDOFF_SNAPSHOT_ONE" <<'PY'
|
|
674
307
|
import json
|
|
675
308
|
import sys
|
|
676
309
|
from pathlib import Path
|
|
@@ -685,61 +318,107 @@ plan = json.loads(Path('.agent/plan.json').read_text())
|
|
|
685
318
|
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
686
319
|
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
687
320
|
|
|
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
|
|
321
|
+
assert mission in mission_text, '.agent/mission.md did not record the explicit-handoff mission anchor'
|
|
322
|
+
assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after explicit-handoff bootstrap'
|
|
323
|
+
assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after explicit-handoff bootstrap'
|
|
324
|
+
assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after explicit-handoff bootstrap'
|
|
325
|
+
assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after explicit-handoff bootstrap'
|
|
326
|
+
assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after explicit-handoff bootstrap'
|
|
327
|
+
assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after explicit-handoff bootstrap'
|
|
328
|
+
assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after explicit-handoff bootstrap'
|
|
329
|
+
assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after explicit-handoff bootstrap'
|
|
330
|
+
assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after explicit-handoff bootstrap'
|
|
331
|
+
assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after explicit-handoff bootstrap'
|
|
332
|
+
assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after explicit-handoff bootstrap'
|
|
700
333
|
brief = state['advisory_startup_brief']
|
|
701
334
|
assert brief['kind'] == 'startup_brief', 'state.json should preserve the confirmed startup brief as advisory intake'
|
|
335
|
+
assert brief['source'] == 'primary_agent_handoff', 'explicit startup should record the handoff source in advisory intake'
|
|
702
336
|
assert brief['mission'] == mission, 'advisory startup brief mission should match the accepted mission anchor'
|
|
703
337
|
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
338
|
assert brief['constraints'] == ['Do not reintroduce any other completion status surface.'], 'advisory startup brief should preserve constraints separately from canonical planning state'
|
|
705
339
|
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
|
|
340
|
+
assert brief['risks'] == ['Stale widget-removal discussion could broaden the startup plan if the handoff is ignored.'], 'advisory startup brief should preserve handoff risks'
|
|
341
|
+
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'
|
|
342
|
+
assert 'Verification commands: npm run context-proposal-test' in brief['notes'], 'advisory startup brief should preserve verification_commands in notes'
|
|
708
343
|
assert plan['candidate_slices'] == [], 'startup brief should remain advisory intake only until regrounder owns plan selection'
|
|
709
344
|
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'
|
|
345
|
+
assert proposal['mission'] == mission, 'explicit startup proposal snapshot should keep the handoff mission anchor'
|
|
346
|
+
assert proposal['source'] == 'handoff_capsule', 'explicit startup proposal snapshot should expose the handoff capsule source'
|
|
347
|
+
assert proposal['analysis']['taskType'] == expected_task_type, 'explicit startup proposal snapshot should expose task_type hints separately'
|
|
348
|
+
assert proposal['analysis']['evaluationProfile'] == expected_eval_profile, 'explicit startup proposal snapshot should expose evaluation_profile hints separately'
|
|
349
|
+
assert state['current_phase'] == 'reground', 'state.json current_phase should start at reground after explicit-handoff bootstrap'
|
|
350
|
+
assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should start at completion-regrounder after explicit-handoff bootstrap'
|
|
720
351
|
assert state['continuation_reason'].startswith('User started workflow via /cook:'), 'initial startup should record the accepted startup routing in continuation_reason'
|
|
721
352
|
assert 'task_type=completion-workflow' in state['continuation_reason'], 'initial startup should persist the selected task_type in continuation_reason'
|
|
722
353
|
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
354
|
PY
|
|
725
355
|
|
|
726
|
-
# Active workflow: bare /cook
|
|
727
|
-
#
|
|
356
|
+
# Active workflow: bare /cook should resume from canonical state when no fresh explicit handoff exists,
|
|
357
|
+
# even if recent discussion restates the current mission in a structured way.
|
|
728
358
|
SESSION_ONE_CONTINUE="$TMPDIR/session-one-continue.jsonl"
|
|
729
359
|
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
360
|
CONTINUE_ROUTING_ONE="$TMPDIR/active-continue-routing.json"
|
|
731
361
|
CONTINUE_RESUME_PROMPT_ONE="$TMPDIR/active-continue-resume.txt"
|
|
732
362
|
CONTINUE_CHOOSER_ONE="$TMPDIR/unexpected-active-continue-chooser.json"
|
|
363
|
+
CONTINUE_PROPOSAL_ONE="$TMPDIR/unexpected-active-continue-proposal.json"
|
|
733
364
|
write_session "$SESSION_ONE_CONTINUE" "$ROOT" "$DISCUSSION_ONE_CONTINUE"
|
|
734
365
|
|
|
735
366
|
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
736
367
|
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$CONTINUE_ROUTING_ONE" \
|
|
737
368
|
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$CONTINUE_RESUME_PROMPT_ONE" \
|
|
738
369
|
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$CONTINUE_CHOOSER_ONE" \
|
|
370
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$CONTINUE_PROPOSAL_ONE" \
|
|
739
371
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
740
372
|
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
373
|
|
|
742
|
-
python3 - "$CONTINUE_ROUTING_ONE" "$CONTINUE_RESUME_PROMPT_ONE" "$CONTINUE_CHOOSER_ONE" <<'PY'
|
|
374
|
+
python3 - "$CONTINUE_ROUTING_ONE" "$CONTINUE_RESUME_PROMPT_ONE" "$CONTINUE_CHOOSER_ONE" "$CONTINUE_PROPOSAL_ONE" <<'PY'
|
|
375
|
+
import json
|
|
376
|
+
import sys
|
|
377
|
+
from pathlib import Path
|
|
378
|
+
|
|
379
|
+
mission = 'Remove the completion status line while keeping the completion widget.'
|
|
380
|
+
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
381
|
+
resume = Path(sys.argv[2]).read_text()
|
|
382
|
+
chooser_path = Path(sys.argv[3])
|
|
383
|
+
proposal_path = Path(sys.argv[4])
|
|
384
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
385
|
+
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
386
|
+
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
387
|
+
|
|
388
|
+
assert routing['mode'] == 'bare', 'active bare /cook resume regression should snapshot bare routing mode'
|
|
389
|
+
assert 'explicitGoal' not in routing, 'active bare /cook resume routing should not expose removed explicit-goal shim fields'
|
|
390
|
+
assert 'explicitGoalProvided' not in routing, 'active bare /cook resume routing should not expose removed explicit-goal shim fields'
|
|
391
|
+
assert routing['action'] == 'continue', 'active bare /cook should resume when no fresh explicit handoff exists'
|
|
392
|
+
assert routing['reason'] == 'missing_explicit_handoff', 'active bare /cook should explain that resume happened because no fresh explicit handoff existed'
|
|
393
|
+
assert routing['currentMissionAnchor'] == mission, 'resume routing should preserve the current mission anchor'
|
|
394
|
+
assert routing['proposedMissionAnchor'] is None, 'resume routing should not derive a replacement mission from recent discussion'
|
|
395
|
+
assert 'Resume the completion workflow from canonical state.' in resume, 'active bare /cook resume should still use the canonical resume prompt'
|
|
396
|
+
assert not chooser_path.exists(), 'active bare /cook resume should not open the replacement chooser without a fresh explicit handoff'
|
|
397
|
+
assert not proposal_path.exists(), 'active bare /cook resume should not open replacement proposal confirmation without a fresh explicit handoff'
|
|
398
|
+
assert state['mission_anchor'] == mission, 'active bare /cook resume should keep state.json unchanged'
|
|
399
|
+
assert plan['mission_anchor'] == mission, 'active bare /cook resume should keep plan.json unchanged'
|
|
400
|
+
assert active['mission_anchor'] == mission, 'active bare /cook resume should keep active-slice.json unchanged'
|
|
401
|
+
PY
|
|
402
|
+
|
|
403
|
+
# Active workflow: even strongly different recent discussion should no longer open chooser/refocus startup
|
|
404
|
+
# when no fresh valid explicit handoff is present.
|
|
405
|
+
SESSION_ONE_DISCUSSION_REFOCUS="$TMPDIR/session-one-discussion-refocus.jsonl"
|
|
406
|
+
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.'
|
|
407
|
+
DISCUSSION_REFOCUS_ROUTING_ONE="$TMPDIR/active-discussion-refocus-routing.json"
|
|
408
|
+
DISCUSSION_REFOCUS_RESUME_ONE="$TMPDIR/active-discussion-refocus-resume.txt"
|
|
409
|
+
DISCUSSION_REFOCUS_CHOOSER_ONE="$TMPDIR/unexpected-active-discussion-refocus-chooser.json"
|
|
410
|
+
DISCUSSION_REFOCUS_PROPOSAL_ONE="$TMPDIR/unexpected-active-discussion-refocus-proposal.json"
|
|
411
|
+
write_session "$SESSION_ONE_DISCUSSION_REFOCUS" "$ROOT" "$DISCUSSION_ONE_DISCUSSION_REFOCUS"
|
|
412
|
+
|
|
413
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
414
|
+
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$DISCUSSION_REFOCUS_ROUTING_ONE" \
|
|
415
|
+
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$DISCUSSION_REFOCUS_RESUME_ONE" \
|
|
416
|
+
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$DISCUSSION_REFOCUS_CHOOSER_ONE" \
|
|
417
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_REFOCUS_PROPOSAL_ONE" \
|
|
418
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
419
|
+
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"
|
|
420
|
+
|
|
421
|
+
python3 - "$DISCUSSION_REFOCUS_ROUTING_ONE" "$DISCUSSION_REFOCUS_RESUME_ONE" "$DISCUSSION_REFOCUS_CHOOSER_ONE" "$DISCUSSION_REFOCUS_PROPOSAL_ONE" <<'PY'
|
|
743
422
|
import json
|
|
744
423
|
import sys
|
|
745
424
|
from pathlib import Path
|
|
@@ -748,26 +427,26 @@ mission = 'Remove the completion status line while keeping the completion widget
|
|
|
748
427
|
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
749
428
|
resume = Path(sys.argv[2]).read_text()
|
|
750
429
|
chooser_path = Path(sys.argv[3])
|
|
430
|
+
proposal_path = Path(sys.argv[4])
|
|
751
431
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
752
432
|
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
753
433
|
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
754
434
|
|
|
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'
|
|
435
|
+
assert routing['mode'] == 'bare', 'discussion-driven refocus removal should snapshot bare routing mode'
|
|
436
|
+
assert routing['action'] == 'continue', 'bare /cook should resume instead of deriving a replacement workflow from recent discussion'
|
|
437
|
+
assert routing['reason'] == 'missing_explicit_handoff', 'discussion-driven refocus removal should explain that no fresh explicit handoff existed'
|
|
438
|
+
assert routing['currentMissionAnchor'] == mission, 'discussion-driven refocus removal should preserve the current mission anchor'
|
|
439
|
+
assert routing['proposedMissionAnchor'] is None, 'discussion-driven refocus removal should not preserve a replacement mission from recent discussion'
|
|
440
|
+
assert 'Resume the completion workflow from canonical state.' in resume, 'discussion-driven refocus removal should still queue the canonical resume prompt'
|
|
441
|
+
assert not chooser_path.exists(), 'discussion-driven refocus removal should not open the chooser'
|
|
442
|
+
assert not proposal_path.exists(), 'discussion-driven refocus removal should not open final proposal confirmation'
|
|
443
|
+
assert state['mission_anchor'] == mission, 'discussion-driven refocus removal should keep state.json unchanged'
|
|
444
|
+
assert plan['mission_anchor'] == mission, 'discussion-driven refocus removal should keep plan.json unchanged'
|
|
445
|
+
assert active['mission_anchor'] == mission, 'discussion-driven refocus removal should keep active-slice.json unchanged'
|
|
767
446
|
PY
|
|
768
447
|
|
|
769
|
-
# Active workflow: summary-only replacement artifacts should
|
|
770
|
-
#
|
|
448
|
+
# Active workflow: summary-only replacement artifacts should also resume the current workflow when no fresh
|
|
449
|
+
# explicit handoff exists.
|
|
771
450
|
SESSION_ONE_SUMMARY_ONLY="$TMPDIR/session-one-summary-only.jsonl"
|
|
772
451
|
SUMMARY_ROUTING_ONE="$TMPDIR/active-summary-only-routing.json"
|
|
773
452
|
SUMMARY_RESUME_PROMPT_ONE="$TMPDIR/active-summary-only-resume.txt"
|
|
@@ -828,8 +507,8 @@ plan = json.loads(Path('.agent/plan.json').read_text())
|
|
|
828
507
|
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
829
508
|
|
|
830
509
|
assert routing['mode'] == 'bare', 'summary-only active bare /cook regression should snapshot bare routing mode'
|
|
831
|
-
assert routing['action'] == '
|
|
832
|
-
assert routing['reason'] == '
|
|
510
|
+
assert routing['action'] == 'continue', 'summary-only active bare /cook should resume rather than derive replacement startup'
|
|
511
|
+
assert routing['reason'] == 'missing_explicit_handoff', 'summary-only active bare /cook should explain that no fresh explicit handoff existed'
|
|
833
512
|
assert routing['currentMissionAnchor'] == mission, 'summary-only active bare /cook should preserve the current mission anchor'
|
|
834
513
|
assert routing['proposedMissionAnchor'] is None, 'summary-only active bare /cook should not derive a replacement mission from summary artifacts alone'
|
|
835
514
|
assert 'Resume the completion workflow from canonical state.' in resume, 'summary-only active bare /cook should still resume the canonical workflow'
|
|
@@ -840,146 +519,108 @@ assert plan['mission_anchor'] == mission, 'summary-only active bare /cook should
|
|
|
840
519
|
assert active['mission_anchor'] == mission, 'summary-only active bare /cook should keep active-slice.json unchanged'
|
|
841
520
|
PY
|
|
842
521
|
|
|
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'
|
|
522
|
+
# Active workflow: a fresh explicit handoff that is not implementation-startable should still fail closed
|
|
523
|
+
# without rewriting canonical state.
|
|
524
|
+
SESSION_ONE_NON_STARTABLE_ACTIVE="$TMPDIR/session-one-non-startable-active.jsonl"
|
|
525
|
+
NON_STARTABLE_ACTIVE_ROUTING="$TMPDIR/active-non-startable-routing.json"
|
|
526
|
+
NON_STARTABLE_ACTIVE_RESUME="$TMPDIR/unexpected-active-non-startable-resume.txt"
|
|
527
|
+
NON_STARTABLE_ACTIVE_CHOOSER="$TMPDIR/unexpected-active-non-startable-chooser.json"
|
|
528
|
+
NON_STARTABLE_ACTIVE_PROPOSAL="$TMPDIR/unexpected-active-non-startable-proposal.json"
|
|
529
|
+
NON_STARTABLE_ACTIVE_MESSAGES="$(python3 - <<'PY'
|
|
863
530
|
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
|
-
|
|
531
|
+
capsule = {
|
|
532
|
+
"kind": "cook_handoff",
|
|
533
|
+
"source": "primary_agent",
|
|
534
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
535
|
+
"source_turn_id": "m0002",
|
|
536
|
+
"mission": "Replace the current widget mission from a vague explicit handoff.",
|
|
537
|
+
"scope": [
|
|
538
|
+
"Replace the active workflow from a fresh explicit handoff."
|
|
539
|
+
],
|
|
540
|
+
"constraints": [
|
|
541
|
+
"Do not rely on recent discussion to fill in missing implementation details."
|
|
542
|
+
],
|
|
543
|
+
"acceptance": [
|
|
544
|
+
"Current behavior stays understandable."
|
|
545
|
+
],
|
|
546
|
+
"risks": [],
|
|
547
|
+
"notes": [
|
|
548
|
+
"This capsule is intentionally non-startable for active-workflow fail-closed coverage."
|
|
549
|
+
],
|
|
550
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
551
|
+
"first_slice_goal": "Attempt to replace the active workflow from a vague capsule.",
|
|
552
|
+
"first_slice_non_goals": [],
|
|
553
|
+
"implementation_surfaces": [
|
|
554
|
+
"extensions/completion/driver.ts"
|
|
555
|
+
],
|
|
556
|
+
"verification_commands": [
|
|
557
|
+
"npm run context-proposal-test"
|
|
558
|
+
],
|
|
559
|
+
"why_this_slice_first": "Active-workflow replacement should fail closed when the capsule is fresh but not startable.",
|
|
560
|
+
"task_type": "completion-workflow",
|
|
561
|
+
"evaluation_profile": "completion-rubric-v1"
|
|
562
|
+
}
|
|
563
|
+
messages = [
|
|
564
|
+
{"role": "user", "content": "We may need a different active workflow, but only if there is a fresh explicit handoff."},
|
|
565
|
+
{"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```"},
|
|
566
|
+
]
|
|
567
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
891
568
|
PY
|
|
569
|
+
)"
|
|
570
|
+
write_session_messages "$SESSION_ONE_NON_STARTABLE_ACTIVE" "$ROOT" "$NON_STARTABLE_ACTIVE_MESSAGES"
|
|
892
571
|
|
|
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'
|
|
572
|
+
python3 - "$TMPDIR/active-non-startable-before.json" <<'PY'
|
|
912
573
|
import json
|
|
913
574
|
import sys
|
|
914
575
|
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'
|
|
576
|
+
tracked = {
|
|
577
|
+
'mission.md': Path('.agent/mission.md').read_text(),
|
|
578
|
+
'profile.json': Path('.agent/profile.json').read_text(),
|
|
579
|
+
'state.json': Path('.agent/state.json').read_text(),
|
|
580
|
+
'plan.json': Path('.agent/plan.json').read_text(),
|
|
581
|
+
'active-slice.json': Path('.agent/active-slice.json').read_text(),
|
|
582
|
+
'verification-evidence.json': Path('.agent/verification-evidence.json').read_text(),
|
|
583
|
+
}
|
|
584
|
+
Path(sys.argv[1]).write_text(json.dumps(tracked, indent=2) + '\n')
|
|
935
585
|
PY
|
|
936
586
|
|
|
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" \
|
|
587
|
+
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$NON_STARTABLE_ACTIVE_ROUTING" \
|
|
588
|
+
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$NON_STARTABLE_ACTIVE_RESUME" \
|
|
589
|
+
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$NON_STARTABLE_ACTIVE_CHOOSER" \
|
|
590
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$NON_STARTABLE_ACTIVE_PROPOSAL" \
|
|
954
591
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
955
|
-
pi --session "$
|
|
592
|
+
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
593
|
|
|
957
|
-
python3 - "$
|
|
594
|
+
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
595
|
import json
|
|
959
596
|
import sys
|
|
960
597
|
from pathlib import Path
|
|
961
598
|
|
|
962
|
-
mission = 'Normalize bare /cook planning phrasing into implementation-result missions.'
|
|
963
599
|
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
600
|
+
resume_path = Path(sys.argv[2])
|
|
601
|
+
chooser_path = Path(sys.argv[3])
|
|
602
|
+
proposal_path = Path(sys.argv[4])
|
|
603
|
+
output = Path(sys.argv[5]).read_text() + Path(sys.argv[6]).read_text()
|
|
604
|
+
before = json.loads(Path(sys.argv[7]).read_text())
|
|
605
|
+
after = {
|
|
606
|
+
'mission.md': Path('.agent/mission.md').read_text(),
|
|
607
|
+
'profile.json': Path('.agent/profile.json').read_text(),
|
|
608
|
+
'state.json': Path('.agent/state.json').read_text(),
|
|
609
|
+
'plan.json': Path('.agent/plan.json').read_text(),
|
|
610
|
+
'active-slice.json': Path('.agent/active-slice.json').read_text(),
|
|
611
|
+
'verification-evidence.json': Path('.agent/verification-evidence.json').read_text(),
|
|
612
|
+
}
|
|
970
613
|
|
|
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'
|
|
614
|
+
assert routing['mode'] == 'bare', 'fresh non-startable explicit handoff should snapshot bare routing mode'
|
|
615
|
+
assert routing['action'] == 'blocked', 'fresh non-startable explicit handoff should fail closed for active bare /cook'
|
|
616
|
+
assert routing['reason'] == 'fresh_explicit_handoff_not_startable', 'fresh non-startable explicit handoff should keep the dedicated explicit-handoff fail-closed reason'
|
|
617
|
+
assert 'fresh explicit primary-agent handoff exists' in routing['blockedFailureMessage'], 'fresh non-startable explicit handoff should surface the dedicated fail-closed message'
|
|
618
|
+
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'
|
|
619
|
+
assert not resume_path.exists(), 'fresh non-startable explicit handoff should not queue a resume prompt'
|
|
620
|
+
assert not chooser_path.exists(), 'fresh non-startable explicit handoff should not open the replacement chooser'
|
|
621
|
+
assert not proposal_path.exists(), 'fresh non-startable explicit handoff should not open final proposal confirmation'
|
|
622
|
+
assert 'fresh explicit primary-agent handoff exists' in output, 'fresh non-startable explicit handoff should explain that the explicit capsule blocked active-workflow replacement'
|
|
623
|
+
assert before == after, 'fresh non-startable explicit handoff should leave canonical state unchanged'
|
|
983
624
|
PY
|
|
984
625
|
|
|
985
626
|
# Completed workflow: bare /cook should suppress proposals that simply restate the completed mission
|
|
@@ -1067,10 +708,9 @@ assert not snapshot.exists(), 'verification-evidence overlap suppression should
|
|
|
1067
708
|
assert '/cook failed closed' in output, 'verification-evidence overlap suppression should fail closed when the latest discussion only repeats verified work'
|
|
1068
709
|
PY
|
|
1069
710
|
|
|
1070
|
-
# Completed workflow: bare /cook should
|
|
1071
|
-
# round too, not only for fresh startup.
|
|
711
|
+
# Completed workflow: bare /cook should synthesize the next-round startup brief from recent discussion.
|
|
1072
712
|
SESSION_TWO_NORMALIZED="$TMPDIR/session-two-normalized.jsonl"
|
|
1073
|
-
DISCUSSION_TWO_NORMALIZED=$'Mission:
|
|
713
|
+
DISCUSSION_TWO_NORMALIZED=$'Mission: Ship the next workflow round from recent discussion.\nScope:\n- Reset canonical state back to reground for the new mission.\n- Preserve the tracked completion control-plane files.\nConstraints:\n- Do not resume the completed workflow when the new round is clearly different.\nAcceptance:\n- Reset canonical state back to reground for the new mission.\n- Preserve the tracked completion control-plane files.'
|
|
1074
714
|
DISCUSSION_SNAPSHOT_TWO_NORMALIZED="$TMPDIR/context-proposal-next-round-normalized.json"
|
|
1075
715
|
write_session "$SESSION_TWO_NORMALIZED" "$ROOT" "$DISCUSSION_TWO_NORMALIZED"
|
|
1076
716
|
|
|
@@ -1085,29 +725,99 @@ import json
|
|
|
1085
725
|
import sys
|
|
1086
726
|
from pathlib import Path
|
|
1087
727
|
|
|
1088
|
-
mission = '
|
|
1089
|
-
|
|
728
|
+
mission = 'Ship the next workflow round from recent discussion.'
|
|
729
|
+
expected_task_type = 'completion-workflow'
|
|
730
|
+
expected_eval_profile = 'completion-rubric-v1'
|
|
731
|
+
mission_text = Path('.agent/mission.md').read_text()
|
|
732
|
+
profile = json.loads(Path('.agent/profile.json').read_text())
|
|
1090
733
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
1091
734
|
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
1092
735
|
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
736
|
+
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
1093
737
|
|
|
1094
|
-
assert
|
|
1095
|
-
assert
|
|
1096
|
-
assert
|
|
1097
|
-
assert
|
|
1098
|
-
assert state['
|
|
738
|
+
assert mission in mission_text, '.agent/mission.md did not update to the recent-discussion next-round mission anchor'
|
|
739
|
+
assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after recent-discussion next-round startup'
|
|
740
|
+
assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after recent-discussion next-round startup'
|
|
741
|
+
assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after starting the next workflow round from recent discussion'
|
|
742
|
+
assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after starting the next workflow round from recent discussion'
|
|
743
|
+
assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after starting the next workflow round from recent discussion'
|
|
744
|
+
assert state['advisory_startup_brief']['mission'] == mission, 'recent-discussion next round should preserve the confirmed startup brief as advisory intake'
|
|
745
|
+
assert state['advisory_startup_brief']['source'] == 'recent_discussion', 'recent-discussion next round should preserve the advisory source'
|
|
746
|
+
assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after starting the next workflow round from recent discussion'
|
|
747
|
+
assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after starting the next workflow round from recent discussion'
|
|
748
|
+
assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after starting the next workflow round from recent discussion'
|
|
749
|
+
assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after starting the next workflow round from recent discussion'
|
|
750
|
+
assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after starting the next workflow round from recent discussion'
|
|
751
|
+
assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after starting the next workflow round from recent discussion'
|
|
752
|
+
assert proposal['mission'] == mission, 'recent-discussion next-round proposal snapshot should preserve the synthesized mission anchor'
|
|
753
|
+
assert proposal['source'] == 'session', 'recent-discussion next-round proposal snapshot should record the structured-session source'
|
|
754
|
+
assert state['current_phase'] == 'reground', 'state.json current_phase should reset to reground for the recent-discussion next workflow round'
|
|
755
|
+
assert state['continuation_policy'] == 'continue', 'continuation_policy should reset to continue for the recent-discussion next workflow round'
|
|
756
|
+
assert state['requires_reground'] is True, 'requires_reground should reset to true for the recent-discussion next workflow round'
|
|
757
|
+
assert state['project_done'] is False, 'project_done should reset to false for the recent-discussion next workflow round'
|
|
758
|
+
assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should reset to completion-regrounder for the recent-discussion next workflow round'
|
|
759
|
+
assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'continuation_reason should record the recent-discussion next-round refocus'
|
|
760
|
+
assert plan['plan_basis'] == 'user_refocus', 'plan_basis should reset to user_refocus for the recent-discussion next workflow round'
|
|
761
|
+
assert active['status'] == 'idle', 'active-slice should reset to idle for the recent-discussion next workflow round'
|
|
1099
762
|
PY
|
|
1100
763
|
|
|
1101
|
-
# Completed workflow:
|
|
764
|
+
# Completed workflow: a fresh explicit primary-agent handoff should still start the next round.
|
|
1102
765
|
mark_done
|
|
1103
766
|
|
|
1104
767
|
SESSION_TWO="$TMPDIR/session-two.jsonl"
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
768
|
+
DISCUSSION_SNAPSHOT_TWO="$TMPDIR/context-proposal-next-round-explicit-handoff.json"
|
|
769
|
+
HANDOFF_MESSAGES_TWO="$(python3 - <<'PY'
|
|
770
|
+
import json
|
|
771
|
+
capsule = {
|
|
772
|
+
"kind": "cook_handoff",
|
|
773
|
+
"source": "primary_agent",
|
|
774
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
775
|
+
"source_turn_id": "m0002",
|
|
776
|
+
"mission": "Ship the next workflow round from a fresh explicit handoff.",
|
|
777
|
+
"scope": [
|
|
778
|
+
"Reset canonical state back to reground for the fresh mission.",
|
|
779
|
+
"Preserve the tracked completion control-plane files."
|
|
780
|
+
],
|
|
781
|
+
"constraints": [
|
|
782
|
+
"Do not resume the completed workflow when the new round is clearly different."
|
|
783
|
+
],
|
|
784
|
+
"acceptance": [
|
|
785
|
+
"Reset canonical state back to reground for the new mission.",
|
|
786
|
+
"Preserve the tracked completion control-plane files."
|
|
787
|
+
],
|
|
788
|
+
"risks": [
|
|
789
|
+
"Done-state history could override the fresh mission if the explicit handoff is ignored."
|
|
790
|
+
],
|
|
791
|
+
"notes": [
|
|
792
|
+
"This next round must come from the fresh explicit handoff rather than recent discussion."
|
|
793
|
+
],
|
|
794
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
795
|
+
"first_slice_goal": "Start the next round from the fresh explicit handoff and preserve canonical control-plane files.",
|
|
796
|
+
"first_slice_non_goals": [
|
|
797
|
+
"Do not resume the completed workflow when the new round is clearly different."
|
|
798
|
+
],
|
|
799
|
+
"implementation_surfaces": [
|
|
800
|
+
"extensions/completion/driver.ts",
|
|
801
|
+
"scripts/context-proposal-test.sh"
|
|
802
|
+
],
|
|
803
|
+
"verification_commands": [
|
|
804
|
+
"npm run context-proposal-test"
|
|
805
|
+
],
|
|
806
|
+
"why_this_slice_first": "The fresh explicit handoff is the smallest truthful next-round startup after the previous workflow closed.",
|
|
807
|
+
"task_type": "completion-workflow",
|
|
808
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
809
|
+
"why_cook_now": "A new implementation-ready mission was identified after the previous round closed."
|
|
810
|
+
}
|
|
811
|
+
messages = [
|
|
812
|
+
{"role": "user", "content": "The previous round is done, but there is a fresh next round ready for /cook."},
|
|
813
|
+
{"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```"},
|
|
814
|
+
]
|
|
815
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
816
|
+
PY
|
|
817
|
+
)"
|
|
818
|
+
write_session_messages "$SESSION_TWO" "$ROOT" "$HANDOFF_MESSAGES_TWO"
|
|
1108
819
|
|
|
1109
820
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
1110
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
1111
821
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_TWO" \
|
|
1112
822
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1113
823
|
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 +827,7 @@ import json
|
|
|
1117
827
|
import sys
|
|
1118
828
|
from pathlib import Path
|
|
1119
829
|
|
|
1120
|
-
mission = 'Ship the next workflow round
|
|
830
|
+
mission = 'Ship the next workflow round from a fresh explicit handoff.'
|
|
1121
831
|
expected_task_type = 'completion-workflow'
|
|
1122
832
|
expected_eval_profile = 'completion-rubric-v1'
|
|
1123
833
|
mission_text = Path('.agent/mission.md').read_text()
|
|
@@ -1127,21 +837,22 @@ plan = json.loads(Path('.agent/plan.json').read_text())
|
|
|
1127
837
|
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
1128
838
|
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
1129
839
|
|
|
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['
|
|
840
|
+
assert mission in mission_text, '.agent/mission.md did not update to the next-round explicit-handoff mission anchor'
|
|
841
|
+
assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after next-round explicit handoff startup'
|
|
842
|
+
assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after next-round explicit handoff startup'
|
|
843
|
+
assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after starting the next workflow round from explicit handoff'
|
|
844
|
+
assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after starting the next workflow round from explicit handoff'
|
|
845
|
+
assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after starting the next workflow round from explicit handoff'
|
|
846
|
+
assert state['advisory_startup_brief']['mission'] == mission, 'next-round explicit handoff should preserve the confirmed startup brief as advisory intake'
|
|
847
|
+
assert state['advisory_startup_brief']['source'] == 'primary_agent_handoff', 'next-round explicit handoff should preserve the handoff advisory source'
|
|
848
|
+
assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after starting the next workflow round from explicit handoff'
|
|
849
|
+
assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after starting the next workflow round from explicit handoff'
|
|
850
|
+
assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after starting the next workflow round from explicit handoff'
|
|
851
|
+
assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after starting the next workflow round from explicit handoff'
|
|
852
|
+
assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after starting the next workflow round from explicit handoff'
|
|
853
|
+
assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after starting the next workflow round from explicit handoff'
|
|
854
|
+
assert proposal['mission'] == mission, 'next-round explicit handoff proposal snapshot should preserve the handoff mission anchor'
|
|
855
|
+
assert proposal['source'] == 'handoff_capsule', 'next-round explicit handoff proposal snapshot should record the handoff capsule source'
|
|
1145
856
|
assert state['current_phase'] == 'reground', 'state.json current_phase should reset to reground for the next workflow round'
|
|
1146
857
|
assert state['continuation_policy'] == 'continue', 'continuation_policy should reset to continue for the next workflow round'
|
|
1147
858
|
assert state['requires_reground'] is True, 'requires_reground should reset to true for the next workflow round'
|
|
@@ -1150,7 +861,6 @@ assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_
|
|
|
1150
861
|
assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'continuation_reason should record the next-round refocus'
|
|
1151
862
|
assert 'task_type=completion-workflow' in state['continuation_reason'], 'next-round refocus should persist the selected task_type'
|
|
1152
863
|
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
864
|
assert plan['plan_basis'] == 'user_refocus', 'plan_basis should reset to user_refocus for the next workflow round'
|
|
1155
865
|
assert active['status'] == 'idle', 'active-slice should reset to idle for the next workflow round'
|
|
1156
866
|
PY
|
|
@@ -1267,53 +977,35 @@ after = {path.name: path.read_text() for path in tracked}
|
|
|
1267
977
|
assert before == after, 'done /cook inline-args rejection should leave canonical files unchanged'
|
|
1268
978
|
PY
|
|
1269
979
|
|
|
1270
|
-
# Completed workflow again:
|
|
1271
|
-
# analysis of natural discussion when discussion-only startup depends on analyst output.
|
|
980
|
+
# Completed workflow again: model-assisted discussion analysis should also synthesize the next-round startup brief.
|
|
1272
981
|
mark_done
|
|
1273
982
|
|
|
1274
983
|
SESSION_FIVE="$TMPDIR/session-five.jsonl"
|
|
1275
984
|
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
985
|
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"]}'
|
|
986
|
+
DISCUSSION_SNAPSHOT_FIVE="$TMPDIR/context-proposal-analyst-restart-rejected.json"
|
|
1277
987
|
write_session "$SESSION_FIVE" "$ROOT" "$DISCUSSION_FIVE"
|
|
1278
988
|
|
|
1279
989
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
1280
990
|
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$ANALYST_OUTPUT_FIVE" \
|
|
991
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_FIVE" \
|
|
1281
992
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1282
|
-
pi --session "$SESSION_FIVE" -e "$PKG_ROOT" -p "/cook"
|
|
993
|
+
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
994
|
|
|
1284
|
-
python3 - <<'PY'
|
|
995
|
+
python3 - "$DISCUSSION_SNAPSHOT_FIVE" <<'PY'
|
|
1285
996
|
import json
|
|
997
|
+
import sys
|
|
1286
998
|
from pathlib import Path
|
|
1287
999
|
|
|
1288
1000
|
mission = 'Use a proposal analyst to summarize natural discussion before /cook writes canonical state.'
|
|
1289
|
-
expected_task_type = 'completion-workflow'
|
|
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())
|
|
1293
1001
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
assert
|
|
1299
|
-
assert
|
|
1300
|
-
assert
|
|
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'
|
|
1002
|
+
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
1003
|
+
|
|
1004
|
+
assert proposal['mission'] == mission, 'analyst-driven restart should emit the synthesized mission in the proposal snapshot'
|
|
1005
|
+
assert proposal['source'] == 'analyst', 'analyst-driven restart should preserve the analyst proposal source'
|
|
1006
|
+
assert state['mission_anchor'] == mission, 'analyst-driven restart should promote the synthesized mission into canonical state after Start'
|
|
1007
|
+
assert state['continuation_policy'] == 'continue', 'analyst-driven restart should reopen the workflow after Start'
|
|
1008
|
+
assert state['advisory_startup_brief']['source'] == 'recent_discussion', 'analyst-driven restart should still record recent-discussion advisory intake'
|
|
1317
1009
|
PY
|
|
1318
1010
|
|
|
1319
1011
|
# Custom confirmation UI: start should render proposal content separately from approval-only Start/Cancel actions.
|
|
@@ -1323,14 +1015,41 @@ cd "$UI_ROOT_START"
|
|
|
1323
1015
|
git init -q
|
|
1324
1016
|
|
|
1325
1017
|
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
1018
|
UI_SNAPSHOT_START="$TMPDIR/context-proposal-ui-start.json"
|
|
1329
|
-
|
|
1019
|
+
UI_MESSAGES_START="$(python3 - <<'PY'
|
|
1020
|
+
import json
|
|
1021
|
+
capsule = {
|
|
1022
|
+
"kind": "cook_handoff",
|
|
1023
|
+
"source": "primary_agent",
|
|
1024
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1025
|
+
"source_turn_id": "m0002",
|
|
1026
|
+
"mission": "Replace the crowded selector with a clearer action layout.",
|
|
1027
|
+
"scope": ["Separate proposal text from actions."],
|
|
1028
|
+
"constraints": ["Preserve approval-only Start/Cancel behavior."],
|
|
1029
|
+
"acceptance": ["Add regression coverage."],
|
|
1030
|
+
"risks": ["Bundling critique into the action list would make the confirmation harder to scan."],
|
|
1031
|
+
"notes": ["Keep critique details separate from the approval-only proposal summary.", "Possible noise: old selector wording"],
|
|
1032
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1033
|
+
"first_slice_goal": "Separate the proposal text from the approval-only Start/Cancel actions.",
|
|
1034
|
+
"first_slice_non_goals": [],
|
|
1035
|
+
"implementation_surfaces": ["extensions/completion/prompt-surfaces.ts"],
|
|
1036
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
1037
|
+
"why_this_slice_first": "The confirmation layout regression is small and directly testable.",
|
|
1038
|
+
"task_type": "completion-workflow",
|
|
1039
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
1040
|
+
"why_cook_now": "The explicit handoff is concrete enough to exercise the startup confirmation UI."
|
|
1041
|
+
}
|
|
1042
|
+
messages = [
|
|
1043
|
+
{"role": "user", "content": "Prepare the confirmation-layout work and tell me when it is ready for /cook."},
|
|
1044
|
+
{"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```"},
|
|
1045
|
+
]
|
|
1046
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1047
|
+
PY
|
|
1048
|
+
)"
|
|
1049
|
+
write_session_messages "$UI_SESSION_START" "$UI_ROOT_START" "$UI_MESSAGES_START"
|
|
1330
1050
|
|
|
1331
1051
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_ACTION=start \
|
|
1332
1052
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_PATH="$UI_SNAPSHOT_START" \
|
|
1333
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$UI_ANALYST_OUTPUT_START" \
|
|
1334
1053
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1335
1054
|
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
1055
|
|
|
@@ -1352,7 +1071,7 @@ assert 'Mission\nReplace the crowded selector with a clearer action layout.' in
|
|
|
1352
1071
|
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
1072
|
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
1073
|
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
|
|
1074
|
+
assert '- Possible noise: old selector wording' in snapshot['critiqueBody'], 'critique section should preserve additional operator notes separately from the startup-brief body'
|
|
1356
1075
|
assert '- task_type: completion-workflow' in snapshot['routingBody'], 'routing section should render the recommended task_type'
|
|
1357
1076
|
assert '- evaluation_profile: completion-rubric-v1' in snapshot['routingBody'], 'routing section should render the recommended evaluation_profile'
|
|
1358
1077
|
assert [action['id'] for action in snapshot['actions']] == ['start', 'cancel'], 'custom confirmation actions should stay Start/Cancel only'
|
|
@@ -1375,14 +1094,41 @@ cd "$UI_ROOT_CANCEL"
|
|
|
1375
1094
|
git init -q
|
|
1376
1095
|
|
|
1377
1096
|
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
1097
|
UI_SNAPSHOT_CANCEL="$TMPDIR/context-proposal-ui-cancel.json"
|
|
1381
|
-
|
|
1098
|
+
UI_MESSAGES_CANCEL="$(python3 - <<'PY'
|
|
1099
|
+
import json
|
|
1100
|
+
capsule = {
|
|
1101
|
+
"kind": "cook_handoff",
|
|
1102
|
+
"source": "primary_agent",
|
|
1103
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1104
|
+
"source_turn_id": "m0002",
|
|
1105
|
+
"mission": "Cancel from the custom confirmation UI without writing state.",
|
|
1106
|
+
"scope": ["Show the proposal separately from the approval-only actions."],
|
|
1107
|
+
"constraints": ["Keep cancellation side-effect free."],
|
|
1108
|
+
"acceptance": ["Add regression coverage proving cancel leaves .agent absent."],
|
|
1109
|
+
"risks": [],
|
|
1110
|
+
"notes": [],
|
|
1111
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1112
|
+
"first_slice_goal": "Exercise the cancel path without writing canonical state.",
|
|
1113
|
+
"first_slice_non_goals": [],
|
|
1114
|
+
"implementation_surfaces": ["extensions/completion/prompt-surfaces.ts"],
|
|
1115
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
1116
|
+
"why_this_slice_first": "The cancel path is a direct regression around the startup confirmation UI.",
|
|
1117
|
+
"task_type": "completion-workflow",
|
|
1118
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
1119
|
+
"why_cook_now": "The explicit handoff is concrete enough to exercise the cancel confirmation UI."
|
|
1120
|
+
}
|
|
1121
|
+
messages = [
|
|
1122
|
+
{"role": "user", "content": "Prepare the cancel-path confirmation work and tell me when it is ready for /cook."},
|
|
1123
|
+
{"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```"},
|
|
1124
|
+
]
|
|
1125
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1126
|
+
PY
|
|
1127
|
+
)"
|
|
1128
|
+
write_session_messages "$UI_SESSION_CANCEL" "$UI_ROOT_CANCEL" "$UI_MESSAGES_CANCEL"
|
|
1382
1129
|
|
|
1383
1130
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_ACTION=cancel \
|
|
1384
1131
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_PATH="$UI_SNAPSHOT_CANCEL" \
|
|
1385
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$UI_ANALYST_OUTPUT_CANCEL" \
|
|
1386
1132
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1387
1133
|
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
1134
|
|
|
@@ -1485,6 +1231,85 @@ assert 'Why this slice first: The redirect callback bug is already bounded enoug
|
|
|
1485
1231
|
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
1232
|
PY
|
|
1487
1233
|
|
|
1234
|
+
# Fresh explicit handoff: later ordinary-chat follow-up before /cook should not invalidate startup.
|
|
1235
|
+
HANDOFF_ROOT_FOLLOWUP="$TMPDIR/handoff-root-followup"
|
|
1236
|
+
mkdir -p "$HANDOFF_ROOT_FOLLOWUP"
|
|
1237
|
+
cd "$HANDOFF_ROOT_FOLLOWUP"
|
|
1238
|
+
git init -q
|
|
1239
|
+
|
|
1240
|
+
HANDOFF_SESSION_FOLLOWUP="$TMPDIR/handoff-session-followup.jsonl"
|
|
1241
|
+
HANDOFF_SNAPSHOT_FOLLOWUP="$TMPDIR/handoff-proposal-followup.json"
|
|
1242
|
+
HANDOFF_MESSAGES_FOLLOWUP="$(python3 - <<'PY'
|
|
1243
|
+
import json
|
|
1244
|
+
capsule = {
|
|
1245
|
+
"kind": "cook_handoff",
|
|
1246
|
+
"source": "primary_agent",
|
|
1247
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1248
|
+
"source_turn_id": "m0002",
|
|
1249
|
+
"mission": "Fix login redirect callback behavior.",
|
|
1250
|
+
"scope": [
|
|
1251
|
+
"Update the callback redirect decision logic.",
|
|
1252
|
+
"Preserve the broader auth flow."
|
|
1253
|
+
],
|
|
1254
|
+
"constraints": [
|
|
1255
|
+
"Do not refactor the broader auth flow."
|
|
1256
|
+
],
|
|
1257
|
+
"acceptance": [
|
|
1258
|
+
"Add a regression test for returning to the requested page."
|
|
1259
|
+
],
|
|
1260
|
+
"risks": [
|
|
1261
|
+
"Late ordinary-chat clarifications should not force the user into a fresh handoff-only retry loop."
|
|
1262
|
+
],
|
|
1263
|
+
"notes": [
|
|
1264
|
+
"Keep the startup brief anchored to the explicit primary-agent handoff until a later assistant reply replaces it."
|
|
1265
|
+
],
|
|
1266
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1267
|
+
"first_slice_goal": "Land the redirect callback fix and its regression coverage.",
|
|
1268
|
+
"first_slice_non_goals": [
|
|
1269
|
+
"Do not refactor the broader auth flow."
|
|
1270
|
+
],
|
|
1271
|
+
"implementation_surfaces": [
|
|
1272
|
+
"src/auth/redirect.ts",
|
|
1273
|
+
"tests/auth/redirect.spec.ts"
|
|
1274
|
+
],
|
|
1275
|
+
"verification_commands": [
|
|
1276
|
+
"npm test -- redirect.spec.ts"
|
|
1277
|
+
],
|
|
1278
|
+
"why_this_slice_first": "The redirect callback bug is already bounded enough to start implementation safely.",
|
|
1279
|
+
"task_type": "completion-workflow",
|
|
1280
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
1281
|
+
"why_cook_now": "The implementation plan is concrete and ready for repo changes."
|
|
1282
|
+
}
|
|
1283
|
+
messages = [
|
|
1284
|
+
{"role": "user", "content": "Please think through the login redirect fix and tell me when it is ready for /cook."},
|
|
1285
|
+
{"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```"},
|
|
1286
|
+
{"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."},
|
|
1287
|
+
]
|
|
1288
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1289
|
+
PY
|
|
1290
|
+
)"
|
|
1291
|
+
write_session_messages "$HANDOFF_SESSION_FOLLOWUP" "$HANDOFF_ROOT_FOLLOWUP" "$HANDOFF_MESSAGES_FOLLOWUP"
|
|
1292
|
+
|
|
1293
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
1294
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_FOLLOWUP" \
|
|
1295
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1296
|
+
pi --session "$HANDOFF_SESSION_FOLLOWUP" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-followup.out" 2>"$TMPDIR/pi-completion-handoff-followup.err"
|
|
1297
|
+
|
|
1298
|
+
python3 - "$HANDOFF_SNAPSHOT_FOLLOWUP" <<'PY'
|
|
1299
|
+
import json
|
|
1300
|
+
import sys
|
|
1301
|
+
from pathlib import Path
|
|
1302
|
+
|
|
1303
|
+
snapshot = json.loads(Path(sys.argv[1]).read_text())
|
|
1304
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
1305
|
+
|
|
1306
|
+
assert snapshot['source'] == 'handoff_capsule', 'later ordinary-chat follow-up should still use the explicit handoff capsule as startup source'
|
|
1307
|
+
assert snapshot['mission'] == 'Fix login redirect callback behavior.', 'later ordinary-chat follow-up should preserve the handoff mission'
|
|
1308
|
+
assert state['mission_anchor'] == 'Fix login redirect callback behavior.', 'later ordinary-chat follow-up should still start from the explicit handoff mission'
|
|
1309
|
+
assert state['advisory_startup_brief']['source'] == 'primary_agent_handoff', 'later ordinary-chat follow-up should keep the advisory intake source as the explicit handoff'
|
|
1310
|
+
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'
|
|
1311
|
+
PY
|
|
1312
|
+
|
|
1488
1313
|
# Fresh but non-startable explicit handoff: /cook should fail closed instead of falling back
|
|
1489
1314
|
# to a broad recent-discussion startup brief when the explicit capsule is still too vague.
|
|
1490
1315
|
HANDOFF_ROOT_VAGUE="$TMPDIR/handoff-root-vague"
|
|
@@ -1644,10 +1469,35 @@ cd "$HANDOFF_ROOT_DONE"
|
|
|
1644
1469
|
git init -q
|
|
1645
1470
|
|
|
1646
1471
|
DONE_SEED_SESSION="$TMPDIR/handoff-done-seed-session.jsonl"
|
|
1647
|
-
|
|
1648
|
-
|
|
1472
|
+
DONE_SEED_MESSAGES="$(python3 - <<'PY'
|
|
1473
|
+
import json
|
|
1474
|
+
capsule = {
|
|
1475
|
+
"kind": "cook_handoff",
|
|
1476
|
+
"source": "primary_agent",
|
|
1477
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1478
|
+
"source_turn_id": "m0002",
|
|
1479
|
+
"mission": "Seed a finished workflow before testing fresh handoff priority.",
|
|
1480
|
+
"scope": ["Create canonical workflow state."],
|
|
1481
|
+
"constraints": ["Keep the seed minimal."],
|
|
1482
|
+
"acceptance": ["Add regression coverage for marking the seeded workflow done before the next step."],
|
|
1483
|
+
"risks": [],
|
|
1484
|
+
"notes": [],
|
|
1485
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1486
|
+
"first_slice_goal": "Bootstrap the done-workflow seed fixture from an explicit handoff.",
|
|
1487
|
+
"first_slice_non_goals": [],
|
|
1488
|
+
"implementation_surfaces": ["scripts/context-proposal-test.sh"],
|
|
1489
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
1490
|
+
"why_this_slice_first": "The done-workflow handoff test needs canonical state before it can be marked done."
|
|
1491
|
+
}
|
|
1492
|
+
messages = [
|
|
1493
|
+
{"role": "user", "content": "Prepare the done-workflow seed fixture and tell me when it is ready for /cook."},
|
|
1494
|
+
{"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```"},
|
|
1495
|
+
]
|
|
1496
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1497
|
+
PY
|
|
1498
|
+
)"
|
|
1499
|
+
write_session_messages "$DONE_SEED_SESSION" "$HANDOFF_ROOT_DONE" "$DONE_SEED_MESSAGES"
|
|
1649
1500
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
1650
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
1651
1501
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1652
1502
|
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
1503
|
mark_done
|
|
@@ -1726,7 +1576,7 @@ assert 'First slice goal: Patch the callback edge case and cover it with a focus
|
|
|
1726
1576
|
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
1577
|
PY
|
|
1728
1578
|
|
|
1729
|
-
# Stale handoff:
|
|
1579
|
+
# Stale handoff: an aged-out capsule should no longer block /cook from synthesizing a fresh startup brief from later discussion.
|
|
1730
1580
|
HANDOFF_ROOT_STALE="$TMPDIR/handoff-root-stale"
|
|
1731
1581
|
mkdir -p "$HANDOFF_ROOT_STALE"
|
|
1732
1582
|
cd "$HANDOFF_ROOT_STALE"
|
|
@@ -1739,7 +1589,7 @@ import json
|
|
|
1739
1589
|
capsule = {
|
|
1740
1590
|
"kind": "cook_handoff",
|
|
1741
1591
|
"source": "primary_agent",
|
|
1742
|
-
"captured_at": "
|
|
1592
|
+
"captured_at": "2025-12-31T22:00:02.000Z",
|
|
1743
1593
|
"source_turn_id": "m0002",
|
|
1744
1594
|
"mission": "Fix the original login redirect callback behavior.",
|
|
1745
1595
|
"scope": ["Update the original callback redirect logic."],
|
|
@@ -1752,7 +1602,7 @@ capsule = {
|
|
|
1752
1602
|
"first_slice_non_goals": ["Do not refactor the auth stack."],
|
|
1753
1603
|
"implementation_surfaces": ["src/auth/login-redirect.ts"],
|
|
1754
1604
|
"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
|
|
1605
|
+
"why_this_slice_first": "The original callback follow-up was the first bounded implementation slice before it aged out."
|
|
1756
1606
|
}
|
|
1757
1607
|
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
1608
|
messages = [
|
|
@@ -1779,10 +1629,10 @@ from pathlib import Path
|
|
|
1779
1629
|
snapshot = json.loads(Path(sys.argv[1]).read_text())
|
|
1780
1630
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
1781
1631
|
|
|
1782
|
-
assert snapshot['
|
|
1783
|
-
assert snapshot['
|
|
1784
|
-
assert state['mission_anchor'] == 'Ship logout redirect consistency instead.', '
|
|
1785
|
-
assert state['advisory_startup_brief']['source'] == 'recent_discussion', '
|
|
1632
|
+
assert snapshot['mission'] == 'Ship logout redirect consistency instead.', 'aged-out handoff should fall back to the newer recent-discussion mission'
|
|
1633
|
+
assert snapshot['source'] == 'session', 'aged-out handoff fallback should preserve the structured-session proposal source'
|
|
1634
|
+
assert state['mission_anchor'] == 'Ship logout redirect consistency instead.', 'aged-out handoff fallback should promote the newer recent-discussion mission after Start'
|
|
1635
|
+
assert state['advisory_startup_brief']['source'] == 'recent_discussion', 'aged-out handoff fallback should keep the advisory startup source non-canonical'
|
|
1786
1636
|
PY
|
|
1787
1637
|
|
|
1788
1638
|
# Negative handoff rationale: a non-startable capsule must not become the startup mission.
|