@linimin/pi-letscook 0.1.68 → 0.1.70
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 +2 -3
- package/.agent/verify_completion_control_plane.sh +21 -34
- package/CHANGELOG.md +16 -0
- package/README.md +23 -26
- package/agents/completion-bootstrapper.md +1 -2
- package/agents/completion-regrounder.md +10 -16
- package/extensions/completion/driver.ts +69 -136
- package/extensions/completion/index.ts +94 -81
- package/extensions/completion/policy-guards.ts +1 -1
- package/extensions/completion/prompt-surfaces.ts +63 -161
- package/extensions/completion/proposal.ts +26 -61
- package/extensions/completion/role-runner.ts +161 -57
- package/extensions/completion/state-store.ts +43 -85
- package/extensions/completion/types.ts +2 -3
- package/package.json +1 -1
- package/scripts/active-slice-contract-test.sh +21 -0
- package/scripts/canonical-evidence-artifact-test.sh +57 -65
- package/scripts/context-proposal-test.sh +1430 -310
- package/scripts/legacy-cleanup-test.sh +1 -1
- package/scripts/refocus-test.sh +459 -185
- package/scripts/release-check.sh +14 -15
- package/scripts/role-runner-contract-test.sh +4 -2
- package/scripts/smoke-test.sh +36 -78
- package/skills/completion-protocol/SKILL.md +8 -9
- package/skills/completion-protocol/references/completion.md +2 -37
- package/skills/cook-handoff-boundary/SKILL.md +19 -18
|
@@ -90,22 +90,6 @@ with session_path.open('w', encoding='utf-8') as fh:
|
|
|
90
90
|
PY
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
bootstrap_workflow() {
|
|
94
|
-
local repo_root="$1"
|
|
95
|
-
local session_path="$2"
|
|
96
|
-
local discussion="$3"
|
|
97
|
-
local generated_handoff="$4"
|
|
98
|
-
mkdir -p "$repo_root"
|
|
99
|
-
cd "$repo_root"
|
|
100
|
-
git init -q
|
|
101
|
-
write_session "$session_path" "$repo_root" "$discussion"
|
|
102
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
103
|
-
PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$generated_handoff" \
|
|
104
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
105
|
-
pi --session "$session_path" -e "$PKG_ROOT" -p "/cook" \
|
|
106
|
-
>"$TMPDIR/bootstrap.out" 2>"$TMPDIR/bootstrap.err"
|
|
107
|
-
}
|
|
108
|
-
|
|
109
93
|
mark_done() {
|
|
110
94
|
python3 - <<'PY'
|
|
111
95
|
import json
|
|
@@ -158,29 +142,25 @@ active_path.write_text(json.dumps(active, indent=2) + '\n')
|
|
|
158
142
|
PY
|
|
159
143
|
}
|
|
160
144
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
import sys
|
|
166
|
-
from pathlib import Path
|
|
167
|
-
|
|
168
|
-
tracked = [
|
|
169
|
-
Path('.agent/mission.md'),
|
|
170
|
-
Path('.agent/profile.json'),
|
|
171
|
-
Path('.agent/state.json'),
|
|
172
|
-
Path('.agent/startup-plan.json'),
|
|
173
|
-
Path('.agent/startup-plan.md'),
|
|
174
|
-
Path('.agent/plan.json'),
|
|
175
|
-
Path('.agent/active-slice.json'),
|
|
176
|
-
Path('.agent/verification-evidence.json'),
|
|
177
|
-
]
|
|
178
|
-
Path(sys.argv[1]).write_text(json.dumps({path.name: path.read_text() for path in tracked}, indent=2) + '\n')
|
|
179
|
-
PY
|
|
180
|
-
}
|
|
145
|
+
ROOT="$TMPDIR/repo"
|
|
146
|
+
mkdir -p "$ROOT"
|
|
147
|
+
cd "$ROOT"
|
|
148
|
+
git init -q
|
|
181
149
|
|
|
182
|
-
|
|
183
|
-
|
|
150
|
+
# No workflow yet: bare /cook should be able to generate a primary-agent handoff in the same entry,
|
|
151
|
+
# then continue directly to startup confirmation.
|
|
152
|
+
SESSION_ZERO="$TMPDIR/session-zero.jsonl"
|
|
153
|
+
DISCUSSION_ZERO=$'Mission: Remove the completion status line while keeping the completion widget.
|
|
154
|
+
Scope:
|
|
155
|
+
- Keep the non-running completion widget.
|
|
156
|
+
- Suppress the widget while a completion role is active.
|
|
157
|
+
Constraints:
|
|
158
|
+
- Do not reintroduce any other completion status surface.
|
|
159
|
+
Acceptance:
|
|
160
|
+
- Update README to match the shipped behavior.
|
|
161
|
+
- Keep observability regression coverage truthful.'
|
|
162
|
+
DISCUSSION_SNAPSHOT_ZERO="$TMPDIR/context-proposal-structured-fallback.json"
|
|
163
|
+
GENERATED_HANDOFF_ZERO="$(python3 - <<'PY'
|
|
184
164
|
import json
|
|
185
165
|
capsule = {
|
|
186
166
|
"kind": "cook_handoff",
|
|
@@ -191,382 +171,1522 @@ capsule = {
|
|
|
191
171
|
"Suppress the widget while a completion role is active."
|
|
192
172
|
],
|
|
193
173
|
"constraints": [
|
|
194
|
-
"Do not reintroduce
|
|
174
|
+
"Do not reintroduce any other completion status surface."
|
|
195
175
|
],
|
|
196
176
|
"acceptance": [
|
|
197
177
|
"Update README to match the shipped behavior.",
|
|
198
178
|
"Keep observability regression coverage truthful."
|
|
199
179
|
],
|
|
200
180
|
"risks": [],
|
|
201
|
-
"notes": ["Generated by
|
|
181
|
+
"notes": ["Generated by the primary-agent handoff step triggered from /cook."],
|
|
202
182
|
"handoff_kind": "implementation_workflow_handoff",
|
|
203
|
-
"first_slice_goal": "Remove the completion status line while preserving widget behavior.",
|
|
204
|
-
"first_slice_non_goals": ["Do not
|
|
183
|
+
"first_slice_goal": "Remove the completion status line while preserving the widget behavior.",
|
|
184
|
+
"first_slice_non_goals": ["Do not reintroduce another status surface."],
|
|
205
185
|
"implementation_surfaces": ["extensions/completion/index.ts", "README.md"],
|
|
206
186
|
"verification_commands": ["npm run smoke-test"],
|
|
207
|
-
"why_this_slice_first": "
|
|
187
|
+
"why_this_slice_first": "This slice is already concrete and bounded enough to start workflow safely.",
|
|
208
188
|
"task_type": "completion-workflow",
|
|
209
189
|
"evaluation_profile": "completion-rubric-v1",
|
|
210
|
-
"why_cook_now": "The user explicitly chose workflow mode for this bounded implementation
|
|
190
|
+
"why_cook_now": "The user explicitly chose workflow mode for this bounded implementation slice."
|
|
211
191
|
}
|
|
212
192
|
print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
|
|
213
193
|
PY
|
|
214
194
|
)"
|
|
215
|
-
|
|
195
|
+
write_session "$SESSION_ZERO" "$ROOT" "$DISCUSSION_ZERO"
|
|
196
|
+
|
|
197
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$GENERATED_HANDOFF_ZERO" PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO" PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 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"
|
|
198
|
+
|
|
199
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-structured-fallback.out" "$TMPDIR/pi-completion-context-proposal-structured-fallback.err" "$DISCUSSION_SNAPSHOT_ZERO" <<'PY'
|
|
216
200
|
import json
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
"handoff_kind": "implementation_workflow_handoff",
|
|
230
|
-
"first_slice_goal": "Continue the current mission without changing it.",
|
|
231
|
-
"first_slice_non_goals": [],
|
|
232
|
-
"implementation_surfaces": ["extensions/completion/index.ts"],
|
|
233
|
-
"verification_commands": ["npm run smoke-test"],
|
|
234
|
-
"why_this_slice_first": "The current mission already matches the latest startup plan.",
|
|
235
|
-
"task_type": "completion-workflow",
|
|
236
|
-
"evaluation_profile": "completion-rubric-v1",
|
|
237
|
-
"why_cook_now": "The current workflow should continue without a mission change."
|
|
238
|
-
}
|
|
239
|
-
print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
|
|
201
|
+
import sys
|
|
202
|
+
from pathlib import Path
|
|
203
|
+
|
|
204
|
+
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
205
|
+
snapshot = Path(sys.argv[3])
|
|
206
|
+
assert Path('.agent').exists(), 'primary-agent handoff generation should scaffold canonical state in the same /cook entry'
|
|
207
|
+
assert snapshot.exists(), 'primary-agent handoff generation should emit a startup proposal snapshot'
|
|
208
|
+
proposal = json.loads(snapshot.read_text())
|
|
209
|
+
brief = json.loads(Path('.agent/state.json').read_text())['advisory_startup_brief']
|
|
210
|
+
assert proposal['source'] == 'handoff_capsule', 'generated primary-agent handoff should be consumed as handoff capsule startup source'
|
|
211
|
+
assert brief['source'] == 'primary_agent_handoff', 'generated primary-agent handoff should record primary_agent_handoff advisory intake'
|
|
212
|
+
assert 'Initialized completion control plane' in output, 'same-entry primary-agent handoff generation should initialize canonical workflow state'
|
|
240
213
|
PY
|
|
241
|
-
|
|
242
|
-
|
|
214
|
+
|
|
215
|
+
rm -rf .agent
|
|
216
|
+
|
|
217
|
+
# No workflow yet: user-authored faux handoffs must not bootstrap canonical workflow state.
|
|
218
|
+
SESSION_ZERO_USER_AUTHORED="$TMPDIR/session-zero-user-authored.jsonl"
|
|
219
|
+
USER_AUTHORED_SNAPSHOT_ZERO="$TMPDIR/context-proposal-user-authored-handoff.json"
|
|
220
|
+
USER_AUTHORED_MESSAGES_ZERO="$(python3 - <<'PY'
|
|
243
221
|
import json
|
|
244
222
|
capsule = {
|
|
245
223
|
"kind": "cook_handoff",
|
|
246
224
|
"source": "primary_agent",
|
|
247
|
-
"
|
|
248
|
-
"
|
|
249
|
-
"
|
|
250
|
-
"
|
|
225
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
226
|
+
"source_turn_id": "m0001",
|
|
227
|
+
"mission": "User-authored faux handoff should not start workflow.",
|
|
228
|
+
"scope": ["Attempt to fake an explicit handoff from the user turn."],
|
|
229
|
+
"constraints": ["Do not trust user-authored capsules as primary-agent handoff."],
|
|
230
|
+
"acceptance": ["Fail closed without writing canonical state."],
|
|
251
231
|
"risks": [],
|
|
252
232
|
"notes": [],
|
|
253
233
|
"handoff_kind": "implementation_workflow_handoff",
|
|
254
|
-
"
|
|
255
|
-
"
|
|
234
|
+
"first_slice_goal": "Prove that user-authored faux handoffs are rejected.",
|
|
235
|
+
"first_slice_non_goals": [],
|
|
236
|
+
"implementation_surfaces": ["scripts/context-proposal-test.sh"],
|
|
237
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
238
|
+
"why_this_slice_first": "Rejecting user-authored capsules is part of the fail-closed startup boundary."
|
|
256
239
|
}
|
|
257
|
-
|
|
240
|
+
messages = [
|
|
241
|
+
{"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```"},
|
|
242
|
+
]
|
|
243
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
258
244
|
PY
|
|
259
245
|
)"
|
|
260
|
-
|
|
246
|
+
write_session_messages "$SESSION_ZERO_USER_AUTHORED" "$ROOT" "$USER_AUTHORED_MESSAGES_ZERO"
|
|
247
|
+
|
|
248
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$USER_AUTHORED_SNAPSHOT_ZERO" \
|
|
249
|
+
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
250
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
251
|
+
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"
|
|
252
|
+
|
|
253
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-user-authored.out" "$TMPDIR/pi-completion-context-proposal-user-authored.err" "$USER_AUTHORED_SNAPSHOT_ZERO" <<'PY'
|
|
254
|
+
import sys
|
|
255
|
+
from pathlib import Path
|
|
256
|
+
|
|
257
|
+
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
258
|
+
snapshot = Path(sys.argv[3])
|
|
259
|
+
assert not Path('.agent').exists(), 'user-authored faux handoff without supporting discussion should still fail closed without writing canonical state'
|
|
260
|
+
assert not snapshot.exists(), 'user-authored faux handoff should not emit a startup proposal snapshot'
|
|
261
|
+
assert 'primary-agent handoff step could not prepare a concrete startup handoff' in output, 'user-authored faux handoff should fail closed when primary-agent handoff generation yields no handoff'
|
|
262
|
+
PY
|
|
263
|
+
|
|
264
|
+
# No workflow yet: malformed or invalid assistant handoff capsules must also fail closed.
|
|
265
|
+
SESSION_ZERO_INVALID="$TMPDIR/session-zero-invalid-handoff.jsonl"
|
|
266
|
+
INVALID_SNAPSHOT_ZERO="$TMPDIR/context-proposal-invalid-handoff.json"
|
|
267
|
+
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```"}]'
|
|
268
|
+
write_session_messages "$SESSION_ZERO_INVALID" "$ROOT" "$INVALID_MESSAGES_ZERO"
|
|
269
|
+
|
|
270
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$INVALID_SNAPSHOT_ZERO" \
|
|
271
|
+
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
272
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
273
|
+
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"
|
|
274
|
+
|
|
275
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-invalid-handoff.out" "$TMPDIR/pi-completion-context-proposal-invalid-handoff.err" "$INVALID_SNAPSHOT_ZERO" <<'PY'
|
|
276
|
+
import sys
|
|
277
|
+
from pathlib import Path
|
|
278
|
+
|
|
279
|
+
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
280
|
+
snapshot = Path(sys.argv[3])
|
|
281
|
+
assert not Path('.agent').exists(), 'invalid assistant handoff without supporting discussion should fail closed without writing canonical state'
|
|
282
|
+
assert not snapshot.exists(), 'invalid assistant handoff should not emit a startup proposal snapshot'
|
|
283
|
+
assert 'primary-agent handoff step could not prepare a concrete startup handoff' in output, 'invalid assistant handoff should fail closed when no valid handoff can be prepared'
|
|
284
|
+
PY
|
|
285
|
+
|
|
286
|
+
# No workflow yet: a fresh explicit primary-agent handoff should still bootstrap canonical startup state.
|
|
287
|
+
SESSION_ONE="$TMPDIR/session-one.jsonl"
|
|
288
|
+
HANDOFF_SNAPSHOT_ONE="$TMPDIR/context-proposal-explicit-startup.json"
|
|
289
|
+
HANDOFF_MESSAGES_ONE="$(python3 - <<'PY'
|
|
261
290
|
import json
|
|
262
291
|
capsule = {
|
|
263
292
|
"kind": "cook_handoff",
|
|
264
293
|
"source": "primary_agent",
|
|
265
|
-
"
|
|
294
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
295
|
+
"source_turn_id": "m0002",
|
|
296
|
+
"mission": "Remove the completion status line while keeping the completion widget.",
|
|
266
297
|
"scope": [
|
|
267
|
-
"
|
|
268
|
-
"
|
|
298
|
+
"Keep the non-running completion widget.",
|
|
299
|
+
"Suppress the widget while a completion role is active."
|
|
300
|
+
],
|
|
301
|
+
"constraints": [
|
|
302
|
+
"Do not reintroduce any other completion status surface."
|
|
269
303
|
],
|
|
270
|
-
"constraints": ["Do not reopen the previous mission."],
|
|
271
304
|
"acceptance": [
|
|
272
|
-
"
|
|
273
|
-
"Keep
|
|
305
|
+
"Update README to match the shipped behavior.",
|
|
306
|
+
"Keep observability regression coverage truthful."
|
|
307
|
+
],
|
|
308
|
+
"risks": [
|
|
309
|
+
"Stale widget-removal discussion could broaden the startup plan if the handoff is ignored."
|
|
310
|
+
],
|
|
311
|
+
"notes": [
|
|
312
|
+
"Keep the startup brief aligned with the explicit primary-agent plan."
|
|
274
313
|
],
|
|
275
|
-
"risks": [],
|
|
276
|
-
"notes": [],
|
|
277
314
|
"handoff_kind": "implementation_workflow_handoff",
|
|
278
|
-
"first_slice_goal": "
|
|
279
|
-
"first_slice_non_goals": [
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
"
|
|
315
|
+
"first_slice_goal": "Land the completion-status removal and keep the completion widget coverage truthful.",
|
|
316
|
+
"first_slice_non_goals": [
|
|
317
|
+
"Do not reintroduce any other completion status surface."
|
|
318
|
+
],
|
|
319
|
+
"implementation_surfaces": [
|
|
320
|
+
"extensions/completion/index.ts",
|
|
321
|
+
"scripts/context-proposal-test.sh"
|
|
322
|
+
],
|
|
323
|
+
"verification_commands": [
|
|
324
|
+
"npm run context-proposal-test"
|
|
325
|
+
],
|
|
326
|
+
"why_this_slice_first": "The startup boundary regression is already bounded enough to implement safely.",
|
|
283
327
|
"task_type": "completion-workflow",
|
|
284
328
|
"evaluation_profile": "completion-rubric-v1",
|
|
285
|
-
"why_cook_now": "The
|
|
329
|
+
"why_cook_now": "The explicit startup brief is concrete and ready for repo changes."
|
|
286
330
|
}
|
|
287
|
-
|
|
331
|
+
messages = [
|
|
332
|
+
{"role": "user", "content": "Please think through the completion widget startup boundary and tell me when it is ready for /cook."},
|
|
333
|
+
{"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```"},
|
|
334
|
+
]
|
|
335
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
288
336
|
PY
|
|
289
337
|
)"
|
|
338
|
+
write_session_messages "$SESSION_ONE" "$ROOT" "$HANDOFF_MESSAGES_ONE"
|
|
290
339
|
|
|
291
|
-
# Case 1: startup succeeds only through same-entry primary-agent synthesis.
|
|
292
|
-
ROOT_ONE="$TMPDIR/repo-one"
|
|
293
|
-
SESSION_ONE="$TMPDIR/session-one.jsonl"
|
|
294
|
-
PROPOSAL_ONE="$TMPDIR/proposal-one.json"
|
|
295
|
-
mkdir -p "$ROOT_ONE"
|
|
296
|
-
cd "$ROOT_ONE"
|
|
297
|
-
git init -q
|
|
298
|
-
write_session "$SESSION_ONE" "$ROOT_ONE" "$STARTUP_DISCUSSION"
|
|
299
340
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
300
|
-
|
|
301
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$PROPOSAL_ONE" \
|
|
341
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_ONE" \
|
|
302
342
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
303
|
-
pi --session "$SESSION_ONE" -e "$PKG_ROOT" -p "/cook"
|
|
304
|
-
>"$TMPDIR/startup-success.out" 2>"$TMPDIR/startup-success.err"
|
|
343
|
+
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"
|
|
305
344
|
|
|
306
|
-
python3 - "$
|
|
345
|
+
python3 - "$HANDOFF_SNAPSHOT_ONE" <<'PY'
|
|
307
346
|
import json
|
|
308
347
|
import sys
|
|
309
348
|
from pathlib import Path
|
|
310
349
|
|
|
350
|
+
mission = 'Remove the completion status line while keeping the completion widget.'
|
|
351
|
+
expected_task_type = 'completion-workflow'
|
|
352
|
+
expected_eval_profile = 'completion-rubric-v1'
|
|
353
|
+
mission_text = Path('.agent/mission.md').read_text()
|
|
354
|
+
profile = json.loads(Path('.agent/profile.json').read_text())
|
|
355
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
356
|
+
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
357
|
+
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
311
358
|
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
312
|
-
|
|
359
|
+
|
|
360
|
+
assert mission in mission_text, '.agent/mission.md did not record the explicit-handoff mission anchor'
|
|
361
|
+
assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after explicit-handoff bootstrap'
|
|
362
|
+
assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after explicit-handoff bootstrap'
|
|
363
|
+
assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after explicit-handoff bootstrap'
|
|
364
|
+
assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after explicit-handoff bootstrap'
|
|
365
|
+
assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after explicit-handoff bootstrap'
|
|
366
|
+
assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after explicit-handoff bootstrap'
|
|
367
|
+
assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after explicit-handoff bootstrap'
|
|
368
|
+
assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after explicit-handoff bootstrap'
|
|
369
|
+
assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after explicit-handoff bootstrap'
|
|
370
|
+
assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after explicit-handoff bootstrap'
|
|
371
|
+
assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after explicit-handoff bootstrap'
|
|
372
|
+
brief = state['advisory_startup_brief']
|
|
373
|
+
assert brief['kind'] == 'startup_brief', 'state.json should preserve the confirmed startup brief as advisory intake'
|
|
374
|
+
assert brief['source'] == 'primary_agent_handoff', 'explicit startup should record the handoff source in advisory intake'
|
|
375
|
+
assert brief['mission'] == mission, 'advisory startup brief mission should match the accepted mission anchor'
|
|
376
|
+
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'
|
|
377
|
+
assert brief['constraints'] == ['Do not reintroduce any other completion status surface.'], 'advisory startup brief should preserve constraints separately from canonical planning state'
|
|
378
|
+
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'
|
|
379
|
+
assert brief['risks'] == ['Stale widget-removal discussion could broaden the startup plan if the handoff is ignored.'], 'advisory startup brief should preserve handoff risks'
|
|
380
|
+
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'
|
|
381
|
+
assert 'Verification commands: npm run context-proposal-test' in brief['notes'], 'advisory startup brief should preserve verification_commands in notes'
|
|
382
|
+
assert plan['candidate_slices'] == [], 'startup brief should remain advisory intake only until regrounder owns plan selection'
|
|
383
|
+
assert active['status'] == 'idle', 'startup brief should not become the active-slice source before regrounder runs'
|
|
384
|
+
assert proposal['mission'] == mission, 'explicit startup proposal snapshot should keep the handoff mission anchor'
|
|
385
|
+
assert proposal['source'] == 'handoff_capsule', 'explicit startup proposal snapshot should expose the handoff capsule source'
|
|
386
|
+
assert proposal['analysis']['taskType'] == expected_task_type, 'explicit startup proposal snapshot should expose task_type hints separately'
|
|
387
|
+
assert proposal['analysis']['evaluationProfile'] == expected_eval_profile, 'explicit startup proposal snapshot should expose evaluation_profile hints separately'
|
|
388
|
+
assert state['current_phase'] == 'reground', 'state.json current_phase should start at reground after explicit-handoff bootstrap'
|
|
389
|
+
assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should start at completion-regrounder after explicit-handoff bootstrap'
|
|
390
|
+
assert state['continuation_reason'].startswith('User started workflow via /cook:'), 'initial startup should record the accepted startup routing in continuation_reason'
|
|
391
|
+
assert 'task_type=completion-workflow' in state['continuation_reason'], 'initial startup should persist the selected task_type in continuation_reason'
|
|
392
|
+
assert 'evaluation_profile=completion-rubric-v1' in state['continuation_reason'], 'initial startup should persist the selected evaluation_profile in continuation_reason'
|
|
393
|
+
PY
|
|
394
|
+
|
|
395
|
+
# Active workflow: bare /cook should resume from canonical state when no fresh explicit handoff exists,
|
|
396
|
+
# even if recent discussion restates the current mission in a structured way.
|
|
397
|
+
SESSION_ONE_CONTINUE="$TMPDIR/session-one-continue.jsonl"
|
|
398
|
+
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.'
|
|
399
|
+
CONTINUE_ROUTING_ONE="$TMPDIR/active-continue-routing.json"
|
|
400
|
+
CONTINUE_RESUME_PROMPT_ONE="$TMPDIR/active-continue-resume.txt"
|
|
401
|
+
CONTINUE_CHOOSER_ONE="$TMPDIR/unexpected-active-continue-chooser.json"
|
|
402
|
+
CONTINUE_PROPOSAL_ONE="$TMPDIR/unexpected-active-continue-proposal.json"
|
|
403
|
+
write_session "$SESSION_ONE_CONTINUE" "$ROOT" "$DISCUSSION_ONE_CONTINUE"
|
|
404
|
+
|
|
405
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
406
|
+
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
407
|
+
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$CONTINUE_ROUTING_ONE" \
|
|
408
|
+
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$CONTINUE_RESUME_PROMPT_ONE" \
|
|
409
|
+
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$CONTINUE_CHOOSER_ONE" \
|
|
410
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$CONTINUE_PROPOSAL_ONE" \
|
|
411
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
412
|
+
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"
|
|
413
|
+
|
|
414
|
+
python3 - "$CONTINUE_ROUTING_ONE" "$CONTINUE_RESUME_PROMPT_ONE" "$CONTINUE_CHOOSER_ONE" "$CONTINUE_PROPOSAL_ONE" <<'PY'
|
|
415
|
+
import json
|
|
416
|
+
import sys
|
|
417
|
+
from pathlib import Path
|
|
418
|
+
|
|
419
|
+
mission = 'Remove the completion status line while keeping the completion widget.'
|
|
420
|
+
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
421
|
+
resume = Path(sys.argv[2]).read_text()
|
|
422
|
+
chooser_path = Path(sys.argv[3])
|
|
423
|
+
proposal_path = Path(sys.argv[4])
|
|
313
424
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
314
|
-
|
|
425
|
+
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
426
|
+
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
315
427
|
|
|
316
|
-
assert
|
|
317
|
-
assert
|
|
318
|
-
assert
|
|
319
|
-
assert
|
|
320
|
-
assert '
|
|
428
|
+
assert routing['mode'] == 'bare', 'active bare /cook resume regression should snapshot bare routing mode'
|
|
429
|
+
assert 'explicitGoal' not in routing, 'active bare /cook resume routing should not expose removed explicit-goal shim fields'
|
|
430
|
+
assert 'explicitGoalProvided' not in routing, 'active bare /cook resume routing should not expose removed explicit-goal shim fields'
|
|
431
|
+
assert routing['action'] == 'continue', 'active bare /cook should resume when no fresh explicit handoff exists'
|
|
432
|
+
assert routing['reason'] == 'missing_explicit_handoff', 'active bare /cook should explain that resume happened because no fresh explicit handoff existed'
|
|
433
|
+
assert routing['currentMissionAnchor'] == mission, 'resume routing should preserve the current mission anchor'
|
|
434
|
+
assert routing['proposedMissionAnchor'] is None, 'resume routing should not derive a replacement mission from recent discussion'
|
|
435
|
+
assert 'Resume the completion workflow from canonical state.' in resume, 'active bare /cook resume should still use the canonical resume prompt'
|
|
436
|
+
assert not chooser_path.exists(), 'active bare /cook resume should not open the replacement chooser without a fresh explicit handoff'
|
|
437
|
+
assert not proposal_path.exists(), 'active bare /cook resume should not open replacement proposal confirmation without a fresh explicit handoff'
|
|
438
|
+
assert state['mission_anchor'] == mission, 'active bare /cook resume should keep state.json unchanged'
|
|
439
|
+
assert plan['mission_anchor'] == mission, 'active bare /cook resume should keep plan.json unchanged'
|
|
440
|
+
assert active['mission_anchor'] == mission, 'active bare /cook resume should keep active-slice.json unchanged'
|
|
321
441
|
PY
|
|
322
442
|
|
|
323
|
-
#
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
443
|
+
# Active workflow: even strongly different recent discussion should no longer open chooser/refocus startup
|
|
444
|
+
# when no fresh valid explicit handoff is present.
|
|
445
|
+
SESSION_ONE_DISCUSSION_REFOCUS="$TMPDIR/session-one-discussion-refocus.jsonl"
|
|
446
|
+
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.'
|
|
447
|
+
DISCUSSION_REFOCUS_ROUTING_ONE="$TMPDIR/active-discussion-refocus-routing.json"
|
|
448
|
+
DISCUSSION_REFOCUS_RESUME_ONE="$TMPDIR/active-discussion-refocus-resume.txt"
|
|
449
|
+
DISCUSSION_REFOCUS_CHOOSER_ONE="$TMPDIR/unexpected-active-discussion-refocus-chooser.json"
|
|
450
|
+
DISCUSSION_REFOCUS_PROPOSAL_ONE="$TMPDIR/unexpected-active-discussion-refocus-proposal.json"
|
|
451
|
+
write_session "$SESSION_ONE_DISCUSSION_REFOCUS" "$ROOT" "$DISCUSSION_ONE_DISCUSSION_REFOCUS"
|
|
452
|
+
|
|
453
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
454
|
+
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
455
|
+
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$DISCUSSION_REFOCUS_ROUTING_ONE" \
|
|
456
|
+
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$DISCUSSION_REFOCUS_RESUME_ONE" \
|
|
457
|
+
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$DISCUSSION_REFOCUS_CHOOSER_ONE" \
|
|
458
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_REFOCUS_PROPOSAL_ONE" \
|
|
459
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
460
|
+
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"
|
|
461
|
+
|
|
462
|
+
python3 - "$DISCUSSION_REFOCUS_ROUTING_ONE" "$DISCUSSION_REFOCUS_RESUME_ONE" "$DISCUSSION_REFOCUS_CHOOSER_ONE" "$DISCUSSION_REFOCUS_PROPOSAL_ONE" <<'PY'
|
|
463
|
+
import json
|
|
464
|
+
import sys
|
|
465
|
+
from pathlib import Path
|
|
466
|
+
|
|
467
|
+
mission = 'Remove the completion status line while keeping the completion widget.'
|
|
468
|
+
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
469
|
+
resume = Path(sys.argv[2]).read_text()
|
|
470
|
+
chooser_path = Path(sys.argv[3])
|
|
471
|
+
proposal_path = Path(sys.argv[4])
|
|
472
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
473
|
+
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
474
|
+
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
475
|
+
|
|
476
|
+
assert routing['mode'] == 'bare', 'discussion-driven refocus removal should snapshot bare routing mode'
|
|
477
|
+
assert routing['action'] == 'continue', 'bare /cook should resume instead of deriving a replacement workflow from recent discussion'
|
|
478
|
+
assert routing['reason'] == 'missing_explicit_handoff', 'discussion-driven refocus removal should explain that no fresh explicit handoff existed'
|
|
479
|
+
assert routing['currentMissionAnchor'] == mission, 'discussion-driven refocus removal should preserve the current mission anchor'
|
|
480
|
+
assert routing['proposedMissionAnchor'] is None, 'discussion-driven refocus removal should not preserve a replacement mission from recent discussion'
|
|
481
|
+
assert 'Resume the completion workflow from canonical state.' in resume, 'discussion-driven refocus removal should still queue the canonical resume prompt'
|
|
482
|
+
assert not chooser_path.exists(), 'discussion-driven refocus removal should not open the chooser'
|
|
483
|
+
assert not proposal_path.exists(), 'discussion-driven refocus removal should not open final proposal confirmation'
|
|
484
|
+
assert state['mission_anchor'] == mission, 'discussion-driven refocus removal should keep state.json unchanged'
|
|
485
|
+
assert plan['mission_anchor'] == mission, 'discussion-driven refocus removal should keep plan.json unchanged'
|
|
486
|
+
assert active['mission_anchor'] == mission, 'discussion-driven refocus removal should keep active-slice.json unchanged'
|
|
487
|
+
PY
|
|
488
|
+
|
|
489
|
+
# Active workflow: summary-only replacement artifacts should also resume the current workflow when no fresh
|
|
490
|
+
# explicit handoff exists.
|
|
491
|
+
SESSION_ONE_SUMMARY_ONLY="$TMPDIR/session-one-summary-only.jsonl"
|
|
492
|
+
SUMMARY_ROUTING_ONE="$TMPDIR/active-summary-only-routing.json"
|
|
493
|
+
SUMMARY_RESUME_PROMPT_ONE="$TMPDIR/active-summary-only-resume.txt"
|
|
494
|
+
SUMMARY_CHOOSER_ONE="$TMPDIR/unexpected-active-summary-only-chooser.json"
|
|
495
|
+
SUMMARY_PROPOSAL_ONE="$TMPDIR/unexpected-active-summary-only-proposal.json"
|
|
496
|
+
python3 - "$SESSION_ONE_SUMMARY_ONLY" "$ROOT" <<'PY'
|
|
497
|
+
import json
|
|
498
|
+
import sys
|
|
499
|
+
from pathlib import Path
|
|
500
|
+
|
|
501
|
+
session_path = Path(sys.argv[1])
|
|
502
|
+
cwd = sys.argv[2]
|
|
503
|
+
session_path.parent.mkdir(parents=True, exist_ok=True)
|
|
504
|
+
entries = [
|
|
505
|
+
{
|
|
506
|
+
"type": "session",
|
|
507
|
+
"version": 3,
|
|
508
|
+
"id": "11111111-1111-4111-8111-111111111111",
|
|
509
|
+
"timestamp": "2026-01-01T00:00:00.000Z",
|
|
510
|
+
"cwd": cwd,
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
"type": "message",
|
|
514
|
+
"id": "c3d4e5f6",
|
|
515
|
+
"parentId": None,
|
|
516
|
+
"timestamp": "2026-01-01T00:00:03.000Z",
|
|
517
|
+
"message": {
|
|
518
|
+
"role": "branchSummary",
|
|
519
|
+
"summary": "Mission: Replace the current workflow from the completed plan summary.\nScope:\n- Refocus to a different mission from this summary artifact alone.\nConstraints:\n- Keep the approval-only Start/Cancel gate unchanged.\nAcceptance:\n- Rewrite canonical state from the summary without new user discussion.",
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
]
|
|
523
|
+
with session_path.open('w', encoding='utf-8') as fh:
|
|
524
|
+
for entry in entries:
|
|
525
|
+
fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
526
|
+
PY
|
|
527
|
+
|
|
528
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
529
|
+
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
530
|
+
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$SUMMARY_ROUTING_ONE" \
|
|
531
|
+
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$SUMMARY_RESUME_PROMPT_ONE" \
|
|
532
|
+
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$SUMMARY_CHOOSER_ONE" \
|
|
533
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$SUMMARY_PROPOSAL_ONE" \
|
|
534
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
535
|
+
pi --session "$SESSION_ONE_SUMMARY_ONLY" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-active-summary-only.out" 2>"$TMPDIR/pi-completion-context-proposal-active-summary-only.err"
|
|
536
|
+
|
|
537
|
+
python3 - "$SUMMARY_ROUTING_ONE" "$SUMMARY_RESUME_PROMPT_ONE" "$SUMMARY_CHOOSER_ONE" "$SUMMARY_PROPOSAL_ONE" <<'PY'
|
|
538
|
+
import json
|
|
539
|
+
import sys
|
|
540
|
+
from pathlib import Path
|
|
541
|
+
|
|
542
|
+
mission = 'Remove the completion status line while keeping the completion widget.'
|
|
543
|
+
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
544
|
+
resume = Path(sys.argv[2]).read_text()
|
|
545
|
+
chooser_path = Path(sys.argv[3])
|
|
546
|
+
proposal_path = Path(sys.argv[4])
|
|
547
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
548
|
+
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
549
|
+
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
550
|
+
|
|
551
|
+
assert routing['mode'] == 'bare', 'summary-only active bare /cook regression should snapshot bare routing mode'
|
|
552
|
+
assert routing['action'] == 'continue', 'summary-only active bare /cook should resume rather than derive replacement startup'
|
|
553
|
+
assert routing['reason'] == 'missing_explicit_handoff', 'summary-only active bare /cook should explain that no fresh explicit handoff existed'
|
|
554
|
+
assert routing['currentMissionAnchor'] == mission, 'summary-only active bare /cook should preserve the current mission anchor'
|
|
555
|
+
assert routing['proposedMissionAnchor'] is None, 'summary-only active bare /cook should not derive a replacement mission from summary artifacts alone'
|
|
556
|
+
assert 'Resume the completion workflow from canonical state.' in resume, 'summary-only active bare /cook should still resume the canonical workflow'
|
|
557
|
+
assert not chooser_path.exists(), 'summary-only active bare /cook should not open the refocus chooser'
|
|
558
|
+
assert not proposal_path.exists(), 'summary-only active bare /cook should not open replacement proposal confirmation'
|
|
559
|
+
assert state['mission_anchor'] == mission, 'summary-only active bare /cook should keep state.json unchanged'
|
|
560
|
+
assert plan['mission_anchor'] == mission, 'summary-only active bare /cook should keep plan.json unchanged'
|
|
561
|
+
assert active['mission_anchor'] == mission, 'summary-only active bare /cook should keep active-slice.json unchanged'
|
|
562
|
+
PY
|
|
563
|
+
|
|
564
|
+
# Active workflow: a fresh explicit handoff that is not implementation-startable should still fail closed
|
|
565
|
+
# without rewriting canonical state.
|
|
566
|
+
SESSION_ONE_NON_STARTABLE_ACTIVE="$TMPDIR/session-one-non-startable-active.jsonl"
|
|
567
|
+
NON_STARTABLE_ACTIVE_ROUTING="$TMPDIR/active-non-startable-routing.json"
|
|
568
|
+
NON_STARTABLE_ACTIVE_RESUME="$TMPDIR/unexpected-active-non-startable-resume.txt"
|
|
569
|
+
NON_STARTABLE_ACTIVE_CHOOSER="$TMPDIR/unexpected-active-non-startable-chooser.json"
|
|
570
|
+
NON_STARTABLE_ACTIVE_PROPOSAL="$TMPDIR/unexpected-active-non-startable-proposal.json"
|
|
571
|
+
NON_STARTABLE_ACTIVE_MESSAGES="$(python3 - <<'PY'
|
|
331
572
|
import json
|
|
332
573
|
capsule = {
|
|
333
574
|
"kind": "cook_handoff",
|
|
334
575
|
"source": "primary_agent",
|
|
335
576
|
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
336
577
|
"source_turn_id": "m0002",
|
|
337
|
-
"mission": "
|
|
338
|
-
"scope": [
|
|
339
|
-
|
|
340
|
-
|
|
578
|
+
"mission": "Replace the current widget mission from a vague explicit handoff.",
|
|
579
|
+
"scope": [
|
|
580
|
+
"Replace the active workflow from a fresh explicit handoff."
|
|
581
|
+
],
|
|
582
|
+
"constraints": [
|
|
583
|
+
"Do not rely on recent discussion to fill in missing implementation details."
|
|
584
|
+
],
|
|
585
|
+
"acceptance": [
|
|
586
|
+
"Current behavior stays understandable."
|
|
587
|
+
],
|
|
341
588
|
"risks": [],
|
|
342
|
-
"notes": [
|
|
589
|
+
"notes": [
|
|
590
|
+
"This capsule is intentionally non-startable for active-workflow fail-closed coverage."
|
|
591
|
+
],
|
|
343
592
|
"handoff_kind": "implementation_workflow_handoff",
|
|
344
|
-
"first_slice_goal": "
|
|
593
|
+
"first_slice_goal": "Attempt to replace the active workflow from a vague capsule.",
|
|
345
594
|
"first_slice_non_goals": [],
|
|
346
|
-
"implementation_surfaces": [
|
|
347
|
-
|
|
348
|
-
|
|
595
|
+
"implementation_surfaces": [
|
|
596
|
+
"extensions/completion/driver.ts"
|
|
597
|
+
],
|
|
598
|
+
"verification_commands": [
|
|
599
|
+
"npm run context-proposal-test"
|
|
600
|
+
],
|
|
601
|
+
"why_this_slice_first": "Active-workflow replacement should fail closed when the capsule is fresh but not startable.",
|
|
602
|
+
"task_type": "completion-workflow",
|
|
603
|
+
"evaluation_profile": "completion-rubric-v1"
|
|
349
604
|
}
|
|
350
605
|
messages = [
|
|
351
|
-
{"role": "user", "content": "
|
|
352
|
-
{"role": "assistant", "content": "
|
|
606
|
+
{"role": "user", "content": "We may need a different active workflow, but only if there is a fresh explicit handoff."},
|
|
607
|
+
{"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```"},
|
|
353
608
|
]
|
|
354
609
|
print(json.dumps(messages, ensure_ascii=False))
|
|
355
610
|
PY
|
|
356
611
|
)"
|
|
357
|
-
write_session_messages "$
|
|
358
|
-
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
359
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$PROPOSAL_TWO" \
|
|
360
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
361
|
-
pi --session "$SESSION_TWO" -e "$PKG_ROOT" -p "/cook" \
|
|
362
|
-
>"$TMPDIR/preview-ignored.out" 2>"$TMPDIR/preview-ignored.err"
|
|
612
|
+
write_session_messages "$SESSION_ONE_NON_STARTABLE_ACTIVE" "$ROOT" "$NON_STARTABLE_ACTIVE_MESSAGES"
|
|
363
613
|
|
|
364
|
-
python3 - "$
|
|
614
|
+
python3 - "$TMPDIR/active-non-startable-before.json" <<'PY'
|
|
615
|
+
import json
|
|
365
616
|
import sys
|
|
366
617
|
from pathlib import Path
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
618
|
+
tracked = {
|
|
619
|
+
'mission.md': Path('.agent/mission.md').read_text(),
|
|
620
|
+
'profile.json': Path('.agent/profile.json').read_text(),
|
|
621
|
+
'state.json': Path('.agent/state.json').read_text(),
|
|
622
|
+
'plan.json': Path('.agent/plan.json').read_text(),
|
|
623
|
+
'active-slice.json': Path('.agent/active-slice.json').read_text(),
|
|
624
|
+
'verification-evidence.json': Path('.agent/verification-evidence.json').read_text(),
|
|
625
|
+
}
|
|
626
|
+
Path(sys.argv[1]).write_text(json.dumps(tracked, indent=2) + '\n')
|
|
374
627
|
PY
|
|
375
628
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
mkdir -p "$ROOT_THREE"
|
|
381
|
-
cd "$ROOT_THREE"
|
|
382
|
-
git init -q
|
|
383
|
-
DISCUSSION_THREE=$'Mission: Rewrite startup flow from structured discussion only.\nScope:\n- Do not run same-entry synthesis.\nAcceptance:\n- Pretend transcript inference is enough.'
|
|
384
|
-
write_session "$SESSION_THREE" "$ROOT_THREE" "$DISCUSSION_THREE"
|
|
385
|
-
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
386
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$PROPOSAL_THREE" \
|
|
629
|
+
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$NON_STARTABLE_ACTIVE_ROUTING" \
|
|
630
|
+
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$NON_STARTABLE_ACTIVE_RESUME" \
|
|
631
|
+
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$NON_STARTABLE_ACTIVE_CHOOSER" \
|
|
632
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$NON_STARTABLE_ACTIVE_PROPOSAL" \
|
|
387
633
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
388
|
-
pi --session "$
|
|
389
|
-
>"$TMPDIR/discussion-ignored.out" 2>"$TMPDIR/discussion-ignored.err"
|
|
634
|
+
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"
|
|
390
635
|
|
|
391
|
-
python3 - "$
|
|
636
|
+
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'
|
|
637
|
+
import json
|
|
392
638
|
import sys
|
|
393
639
|
from pathlib import Path
|
|
394
640
|
|
|
395
|
-
|
|
396
|
-
|
|
641
|
+
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
642
|
+
resume_path = Path(sys.argv[2])
|
|
643
|
+
chooser_path = Path(sys.argv[3])
|
|
644
|
+
proposal_path = Path(sys.argv[4])
|
|
645
|
+
output = Path(sys.argv[5]).read_text() + Path(sys.argv[6]).read_text()
|
|
646
|
+
before = json.loads(Path(sys.argv[7]).read_text())
|
|
647
|
+
after = {
|
|
648
|
+
'mission.md': Path('.agent/mission.md').read_text(),
|
|
649
|
+
'profile.json': Path('.agent/profile.json').read_text(),
|
|
650
|
+
'state.json': Path('.agent/state.json').read_text(),
|
|
651
|
+
'plan.json': Path('.agent/plan.json').read_text(),
|
|
652
|
+
'active-slice.json': Path('.agent/active-slice.json').read_text(),
|
|
653
|
+
'verification-evidence.json': Path('.agent/verification-evidence.json').read_text(),
|
|
654
|
+
}
|
|
397
655
|
|
|
398
|
-
assert
|
|
399
|
-
assert
|
|
400
|
-
assert '
|
|
656
|
+
assert routing['mode'] == 'bare', 'fresh non-startable explicit handoff should snapshot bare routing mode'
|
|
657
|
+
assert routing['action'] == 'blocked', 'fresh non-startable explicit handoff should fail closed for active bare /cook'
|
|
658
|
+
assert routing['reason'] == 'fresh_explicit_handoff_not_startable', 'fresh non-startable explicit handoff should keep the dedicated explicit-handoff fail-closed reason'
|
|
659
|
+
assert 'fresh explicit primary-agent handoff exists' in routing['blockedFailureMessage'], 'fresh non-startable explicit handoff should surface the dedicated fail-closed message'
|
|
660
|
+
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'
|
|
661
|
+
assert not resume_path.exists(), 'fresh non-startable explicit handoff should not queue a resume prompt'
|
|
662
|
+
assert not chooser_path.exists(), 'fresh non-startable explicit handoff should not open the replacement chooser'
|
|
663
|
+
assert not proposal_path.exists(), 'fresh non-startable explicit handoff should not open final proposal confirmation'
|
|
664
|
+
assert 'fresh explicit primary-agent handoff exists' in output, 'fresh non-startable explicit handoff should explain that the explicit capsule blocked active-workflow replacement'
|
|
665
|
+
assert before == after, 'fresh non-startable explicit handoff should leave canonical state unchanged'
|
|
401
666
|
PY
|
|
402
667
|
|
|
403
|
-
#
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
PROPOSAL_FOUR="$TMPDIR/proposal-four.json"
|
|
407
|
-
mkdir -p "$ROOT_FOUR"
|
|
408
|
-
cd "$ROOT_FOUR"
|
|
409
|
-
git init -q
|
|
410
|
-
write_session "$SESSION_FOUR" "$ROOT_FOUR" "$STARTUP_DISCUSSION"
|
|
411
|
-
PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$NON_STARTABLE_HANDOFF" \
|
|
412
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$PROPOSAL_FOUR" \
|
|
413
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
414
|
-
pi --session "$SESSION_FOUR" -e "$PKG_ROOT" -p "/cook" \
|
|
415
|
-
>"$TMPDIR/non-startable-startup.out" 2>"$TMPDIR/non-startable-startup.err"
|
|
668
|
+
# Completed workflow: bare /cook should suppress proposals that simply restate the completed mission
|
|
669
|
+
# without a clear reopen or next-round signal.
|
|
670
|
+
mark_done
|
|
416
671
|
|
|
417
|
-
|
|
418
|
-
|
|
672
|
+
SESSION_TWO_COMPLETED_SUPPRESS="$TMPDIR/session-two-completed-suppress.jsonl"
|
|
673
|
+
CURRENT_DONE_MISSION="$(python3 - <<'PY'
|
|
674
|
+
import json
|
|
419
675
|
from pathlib import Path
|
|
676
|
+
print(json.loads(Path('.agent/state.json').read_text())['mission_anchor'])
|
|
677
|
+
PY
|
|
678
|
+
)"
|
|
679
|
+
DISCUSSION_TWO_COMPLETED_SUPPRESS="Mission: ${CURRENT_DONE_MISSION}
|
|
680
|
+
Scope:
|
|
681
|
+
- Keep the current completed mission exactly as-is.
|
|
682
|
+
Constraints:
|
|
683
|
+
- Do not start a different workflow from this discussion.
|
|
684
|
+
Acceptance:
|
|
685
|
+
- Keep the finished mission closed and unchanged."
|
|
686
|
+
DISCUSSION_SNAPSHOT_TWO_COMPLETED_SUPPRESS="$TMPDIR/context-proposal-next-round-completed-suppress.json"
|
|
687
|
+
write_session "$SESSION_TWO_COMPLETED_SUPPRESS" "$ROOT" "$DISCUSSION_TWO_COMPLETED_SUPPRESS"
|
|
420
688
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
assert not proposal.exists(), 'non-startable synthesized startup must not emit a proposal snapshot'
|
|
426
|
-
assert 'same-entry primary-agent startup-plan synthesis step returned a startup plan that is still not concrete enough' in output, output
|
|
427
|
-
assert 'acceptance is not anchored to concrete repo changes or verification' in output, output
|
|
428
|
-
PY
|
|
429
|
-
|
|
430
|
-
# Case 5: active workflow resumes when same-entry synthesis matches the current mission.
|
|
431
|
-
ROOT_FIVE="$TMPDIR/repo-five"
|
|
432
|
-
SESSION_FIVE_BOOTSTRAP="$TMPDIR/session-five-bootstrap.jsonl"
|
|
433
|
-
SESSION_FIVE_MATCHING="$TMPDIR/session-five-matching.jsonl"
|
|
434
|
-
ROUTING_FIVE="$TMPDIR/routing-five.json"
|
|
435
|
-
CHOOSER_FIVE="$TMPDIR/chooser-five.json"
|
|
436
|
-
BASELINE_FIVE="$TMPDIR/baseline-five.json"
|
|
437
|
-
bootstrap_workflow "$ROOT_FIVE" "$SESSION_FIVE_BOOTSTRAP" "$STARTUP_DISCUSSION" "$STARTUP_HANDOFF"
|
|
438
|
-
cd "$ROOT_FIVE"
|
|
439
|
-
snapshot_tracked "$BASELINE_FIVE"
|
|
440
|
-
write_session "$SESSION_FIVE_MATCHING" "$ROOT_FIVE" $'Continue the widget mission and keep the workflow aligned.'
|
|
441
|
-
PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$MATCHING_HANDOFF" \
|
|
442
|
-
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$ROUTING_FIVE" \
|
|
443
|
-
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$CHOOSER_FIVE" \
|
|
689
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
690
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
691
|
+
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
692
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_TWO_COMPLETED_SUPPRESS" \
|
|
444
693
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
445
|
-
pi --session "$
|
|
446
|
-
>"$TMPDIR/matching-active.out" 2>"$TMPDIR/matching-active.err"
|
|
694
|
+
pi --session "$SESSION_TWO_COMPLETED_SUPPRESS" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-next-round-completed-suppress.out" 2>"$TMPDIR/pi-completion-context-proposal-next-round-completed-suppress.err"
|
|
447
695
|
|
|
448
|
-
python3 - "$
|
|
696
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-next-round-completed-suppress.out" "$TMPDIR/pi-completion-context-proposal-next-round-completed-suppress.err" "$DISCUSSION_SNAPSHOT_TWO_COMPLETED_SUPPRESS" "$CURRENT_DONE_MISSION" <<'PY'
|
|
449
697
|
import json
|
|
450
698
|
import sys
|
|
451
699
|
from pathlib import Path
|
|
452
700
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
Path('.agent/active-slice.json'),
|
|
464
|
-
Path('.agent/verification-evidence.json'),
|
|
465
|
-
]
|
|
466
|
-
after = {path.name: path.read_text() for path in tracked}
|
|
467
|
-
state = json.loads(after['state.json'])
|
|
468
|
-
|
|
469
|
-
assert routing['action'] == 'continue', routing
|
|
470
|
-
assert routing['reason'] == 'matching_generated_startup_plan', routing
|
|
471
|
-
assert routing['currentMissionAnchor'] == state['mission_anchor'], routing
|
|
472
|
-
assert routing['proposedMissionAnchor'] == state['mission_anchor'], routing
|
|
473
|
-
assert not chooser.exists(), 'matching synthesized mission should not open the chooser'
|
|
474
|
-
assert before == after, 'matching synthesized mission should leave canonical state unchanged before resume'
|
|
475
|
-
PY
|
|
476
|
-
|
|
477
|
-
# Case 6: non-startable synthesized active replacement blocks without rewriting canonical state.
|
|
478
|
-
ROOT_SIX="$TMPDIR/repo-six"
|
|
479
|
-
SESSION_SIX_BOOTSTRAP="$TMPDIR/session-six-bootstrap.jsonl"
|
|
480
|
-
SESSION_SIX_BLOCKED="$TMPDIR/session-six-blocked.jsonl"
|
|
481
|
-
ROUTING_SIX="$TMPDIR/routing-six.json"
|
|
482
|
-
CHOOSER_SIX="$TMPDIR/chooser-six.json"
|
|
483
|
-
PROPOSAL_SIX="$TMPDIR/proposal-six.json"
|
|
484
|
-
BASELINE_SIX="$TMPDIR/baseline-six.json"
|
|
485
|
-
bootstrap_workflow "$ROOT_SIX" "$SESSION_SIX_BOOTSTRAP" "$STARTUP_DISCUSSION" "$STARTUP_HANDOFF"
|
|
486
|
-
cd "$ROOT_SIX"
|
|
487
|
-
snapshot_tracked "$BASELINE_SIX"
|
|
488
|
-
write_session "$SESSION_SIX_BLOCKED" "$ROOT_SIX" $'Maybe replace the current mission, but the new intent is still vague.'
|
|
489
|
-
PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$NON_STARTABLE_HANDOFF" \
|
|
490
|
-
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$ROUTING_SIX" \
|
|
491
|
-
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$CHOOSER_SIX" \
|
|
492
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$PROPOSAL_SIX" \
|
|
493
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
494
|
-
pi --session "$SESSION_SIX_BLOCKED" -e "$PKG_ROOT" -p "/cook" \
|
|
495
|
-
>"$TMPDIR/blocked-active.out" 2>"$TMPDIR/blocked-active.err"
|
|
701
|
+
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
702
|
+
snapshot = Path(sys.argv[3])
|
|
703
|
+
expected = sys.argv[4]
|
|
704
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
705
|
+
|
|
706
|
+
assert state['mission_anchor'] == expected, 'completed-topic suppression should keep the done workflow mission anchor unchanged'
|
|
707
|
+
assert state['continuation_policy'] == 'done', 'completed-topic suppression should keep the workflow closed'
|
|
708
|
+
assert not snapshot.exists(), 'completed-topic suppression should not emit a proposal snapshot when the latest discussion only repeats finished work'
|
|
709
|
+
assert 'primary-agent handoff step could not prepare a concrete startup handoff' in output, 'completed-topic suppression should fail closed when no concrete primary-agent handoff can be prepared'
|
|
710
|
+
PY
|
|
496
711
|
|
|
497
|
-
|
|
712
|
+
# Completed workflow: bare /cook should also suppress proposals that merely restate canonical
|
|
713
|
+
# verification evidence for already verified work.
|
|
714
|
+
python3 - <<'PY'
|
|
498
715
|
import json
|
|
499
|
-
import sys
|
|
500
716
|
from pathlib import Path
|
|
501
717
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
718
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
719
|
+
state['latest_verified_slice'] = 'verified-logout-redirect'
|
|
720
|
+
Path('.agent/state.json').write_text(json.dumps(state, indent=2) + '\n')
|
|
721
|
+
|
|
722
|
+
evidence = json.loads(Path('.agent/verification-evidence.json').read_text())
|
|
723
|
+
evidence.update({
|
|
724
|
+
'subject_type': 'selected_slice',
|
|
725
|
+
'slice_id': 'verified-logout-redirect',
|
|
726
|
+
'goal': 'Add logout redirect regression coverage.',
|
|
727
|
+
'summary': 'Verified logout redirect regression coverage already matches the selected slice and current HEAD.',
|
|
728
|
+
'outcome': 'pass',
|
|
729
|
+
})
|
|
730
|
+
Path('.agent/verification-evidence.json').write_text(json.dumps(evidence, indent=2) + '\n')
|
|
731
|
+
PY
|
|
732
|
+
|
|
733
|
+
SESSION_TWO_VERIFIED_SUPPRESS="$TMPDIR/session-two-verified-suppress.jsonl"
|
|
734
|
+
DISCUSSION_TWO_VERIFIED_SUPPRESS=$'Mission: Add logout redirect regression coverage.\nScope:\n- Add coverage for logout redirect behavior.\nConstraints:\n- Do not change the verified logout redirect work.\nAcceptance:\n- Keep the verified logout redirect regression coverage unchanged.'
|
|
735
|
+
DISCUSSION_SNAPSHOT_TWO_VERIFIED_SUPPRESS="$TMPDIR/context-proposal-next-round-verified-suppress.json"
|
|
736
|
+
write_session "$SESSION_TWO_VERIFIED_SUPPRESS" "$ROOT" "$DISCUSSION_TWO_VERIFIED_SUPPRESS"
|
|
518
737
|
|
|
519
|
-
assert routing['action'] == 'blocked', routing
|
|
520
|
-
assert routing['reason'] == 'generated_startup_plan_not_startable', routing
|
|
521
|
-
assert 'same-entry primary-agent startup-plan synthesis step returned a startup plan that is still not concrete enough' in routing['blockedFailureMessage'], routing
|
|
522
|
-
assert not chooser.exists(), 'blocked replacement should not open the chooser'
|
|
523
|
-
assert not proposal.exists(), 'blocked replacement should not open final proposal confirmation'
|
|
524
|
-
assert before == after, 'blocked replacement should leave canonical state unchanged'
|
|
525
|
-
assert 'same-entry primary-agent startup-plan synthesis step returned a startup plan that is still not concrete enough' in output, output
|
|
526
|
-
PY
|
|
527
|
-
|
|
528
|
-
# Case 7: done workflow starts the next round from same-entry synthesis.
|
|
529
|
-
ROOT_SEVEN="$TMPDIR/repo-seven"
|
|
530
|
-
SESSION_SEVEN_BOOTSTRAP="$TMPDIR/session-seven-bootstrap.jsonl"
|
|
531
|
-
SESSION_SEVEN_NEXT="$TMPDIR/session-seven-next.jsonl"
|
|
532
|
-
PROPOSAL_SEVEN="$TMPDIR/proposal-seven.json"
|
|
533
|
-
bootstrap_workflow "$ROOT_SEVEN" "$SESSION_SEVEN_BOOTSTRAP" "$STARTUP_DISCUSSION" "$STARTUP_HANDOFF"
|
|
534
|
-
cd "$ROOT_SEVEN"
|
|
535
|
-
mark_done
|
|
536
|
-
write_session "$SESSION_SEVEN_NEXT" "$ROOT_SEVEN" $'Start the next workflow round for widget follow-up docs.'
|
|
537
738
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
538
|
-
|
|
539
|
-
|
|
739
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
740
|
+
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
741
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_TWO_VERIFIED_SUPPRESS" \
|
|
540
742
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
541
|
-
pi --session "$
|
|
542
|
-
>"$TMPDIR/next-round.out" 2>"$TMPDIR/next-round.err"
|
|
743
|
+
pi --session "$SESSION_TWO_VERIFIED_SUPPRESS" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-next-round-verified-suppress.out" 2>"$TMPDIR/pi-completion-context-proposal-next-round-verified-suppress.err"
|
|
543
744
|
|
|
544
|
-
python3 - "$
|
|
545
|
-
import json
|
|
745
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-next-round-verified-suppress.out" "$TMPDIR/pi-completion-context-proposal-next-round-verified-suppress.err" "$DISCUSSION_SNAPSHOT_TWO_VERIFIED_SUPPRESS" <<'PY'
|
|
546
746
|
import sys
|
|
547
747
|
from pathlib import Path
|
|
548
748
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
749
|
+
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
750
|
+
snapshot = Path(sys.argv[3])
|
|
751
|
+
assert not snapshot.exists(), 'verification-evidence overlap suppression should not emit a proposal snapshot for already verified work'
|
|
752
|
+
assert 'primary-agent handoff step could not prepare a concrete startup handoff' in output, 'verification-evidence overlap suppression should fail closed when no concrete primary-agent handoff can be prepared'
|
|
753
|
+
PY
|
|
754
|
+
|
|
755
|
+
# Completed workflow: bare /cook should fail closed for next-round discussion-only startup too,
|
|
756
|
+
# even when the discussion is well structured.
|
|
757
|
+
SESSION_TWO_NORMALIZED="$TMPDIR/session-two-normalized.jsonl"
|
|
758
|
+
DISCUSSION_TWO_NORMALIZED=$'Mission: 開始實作這個方案\nScope:\n- Normalize bare /cook planning phrasing for the next workflow round.\n- Reset canonical state for the new implementation mission.\nConstraints:\n- Do not resume the completed workflow when the new round is clearly different.\nAcceptance:\n- Start a new round with the normalized mission anchor.'
|
|
759
|
+
DISCUSSION_SNAPSHOT_TWO_NORMALIZED="$TMPDIR/context-proposal-next-round-normalized.json"
|
|
760
|
+
write_session "$SESSION_TWO_NORMALIZED" "$ROOT" "$DISCUSSION_TWO_NORMALIZED"
|
|
761
|
+
|
|
762
|
+
GENERATED_HANDOFF_TWO_NORMALIZED="$(python3 - <<'PY'
|
|
763
|
+
import json
|
|
764
|
+
capsule = {
|
|
765
|
+
"kind": "cook_handoff",
|
|
766
|
+
"source": "primary_agent",
|
|
767
|
+
"mission": "Start the next workflow round with a normalized implementation mission.",
|
|
768
|
+
"scope": [
|
|
769
|
+
"Reset canonical state for the new implementation mission.",
|
|
770
|
+
"Keep the next round distinct from the completed workflow."
|
|
771
|
+
],
|
|
772
|
+
"constraints": [
|
|
773
|
+
"Do not resume the completed workflow when the new round is clearly different."
|
|
774
|
+
],
|
|
775
|
+
"acceptance": [
|
|
776
|
+
"Reset canonical state back to reground for the new mission.",
|
|
777
|
+
"Preserve the tracked completion control-plane files."
|
|
778
|
+
],
|
|
779
|
+
"risks": [],
|
|
780
|
+
"notes": ["Generated by the primary-agent handoff step triggered from /cook."],
|
|
781
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
782
|
+
"first_slice_goal": "Bootstrap the next workflow round from the normalized implementation mission.",
|
|
783
|
+
"first_slice_non_goals": ["Do not reopen finished slices from the previous workflow."],
|
|
784
|
+
"implementation_surfaces": ["extensions/completion/driver.ts", "scripts/context-proposal-test.sh"],
|
|
785
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
786
|
+
"why_this_slice_first": "The user explicitly chose workflow mode for a bounded next-round restart.",
|
|
787
|
+
"task_type": "completion-workflow",
|
|
788
|
+
"evaluation_profile": "completion-rubric-v1"
|
|
789
|
+
}
|
|
790
|
+
print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
|
|
791
|
+
PY
|
|
792
|
+
)"
|
|
793
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$GENERATED_HANDOFF_TWO_NORMALIZED" PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_TWO_NORMALIZED" PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 pi --session "$SESSION_TWO_NORMALIZED" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-next-round-normalized.out" 2>"$TMPDIR/pi-completion-context-proposal-next-round-normalized.err"
|
|
794
|
+
|
|
795
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-next-round-normalized.out" "$TMPDIR/pi-completion-context-proposal-next-round-normalized.err" "$DISCUSSION_SNAPSHOT_TWO_NORMALIZED" "$CURRENT_DONE_MISSION" <<'PY'
|
|
796
|
+
import json
|
|
797
|
+
import sys
|
|
798
|
+
from pathlib import Path
|
|
799
|
+
|
|
800
|
+
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
801
|
+
snapshot = Path(sys.argv[3])
|
|
802
|
+
previous = sys.argv[4]
|
|
803
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
804
|
+
if snapshot.exists():
|
|
805
|
+
proposal = json.loads(snapshot.read_text())
|
|
806
|
+
assert proposal['source'] == 'handoff_capsule', 'done-workflow generated startup should snapshot the primary-agent handoff source'
|
|
807
|
+
assert state['mission_anchor'] != previous, 'done-workflow discussion-only startup should advance to the new mission anchor'
|
|
808
|
+
assert state['continuation_policy'] == 'continue', 'done-workflow discussion-only startup should reopen workflow state'
|
|
809
|
+
assert 'Started a new completion workflow round from explicit primary-agent handoff' in output, 'done-workflow generated startup should report explicit primary-agent handoff startup'
|
|
810
|
+
PY
|
|
811
|
+
|
|
812
|
+
# Completed workflow: a fresh explicit primary-agent handoff should still start the next round.
|
|
813
|
+
mark_done
|
|
814
|
+
|
|
815
|
+
SESSION_TWO="$TMPDIR/session-two.jsonl"
|
|
816
|
+
DISCUSSION_SNAPSHOT_TWO="$TMPDIR/context-proposal-next-round-explicit-handoff.json"
|
|
817
|
+
HANDOFF_MESSAGES_TWO="$(python3 - <<'PY'
|
|
818
|
+
import json
|
|
819
|
+
capsule = {
|
|
820
|
+
"kind": "cook_handoff",
|
|
821
|
+
"source": "primary_agent",
|
|
822
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
823
|
+
"source_turn_id": "m0002",
|
|
824
|
+
"mission": "Ship the next workflow round from a fresh explicit handoff.",
|
|
825
|
+
"scope": [
|
|
826
|
+
"Reset canonical state back to reground for the fresh mission.",
|
|
827
|
+
"Preserve the tracked completion control-plane files."
|
|
828
|
+
],
|
|
829
|
+
"constraints": [
|
|
830
|
+
"Do not resume the completed workflow when the new round is clearly different."
|
|
831
|
+
],
|
|
832
|
+
"acceptance": [
|
|
833
|
+
"Reset canonical state back to reground for the new mission.",
|
|
834
|
+
"Preserve the tracked completion control-plane files."
|
|
835
|
+
],
|
|
836
|
+
"risks": [
|
|
837
|
+
"Done-state history could override the fresh mission if the explicit handoff is ignored."
|
|
838
|
+
],
|
|
839
|
+
"notes": [
|
|
840
|
+
"This next round must come from the fresh explicit handoff rather than recent discussion."
|
|
841
|
+
],
|
|
842
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
843
|
+
"first_slice_goal": "Start the next round from the fresh explicit handoff and preserve canonical control-plane files.",
|
|
844
|
+
"first_slice_non_goals": [
|
|
845
|
+
"Do not resume the completed workflow when the new round is clearly different."
|
|
846
|
+
],
|
|
847
|
+
"implementation_surfaces": [
|
|
848
|
+
"extensions/completion/driver.ts",
|
|
849
|
+
"scripts/context-proposal-test.sh"
|
|
850
|
+
],
|
|
851
|
+
"verification_commands": [
|
|
852
|
+
"npm run context-proposal-test"
|
|
853
|
+
],
|
|
854
|
+
"why_this_slice_first": "The fresh explicit handoff is the smallest truthful next-round startup after the previous workflow closed.",
|
|
855
|
+
"task_type": "completion-workflow",
|
|
856
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
857
|
+
"why_cook_now": "A new implementation-ready mission was identified after the previous round closed."
|
|
858
|
+
}
|
|
859
|
+
messages = [
|
|
860
|
+
{"role": "user", "content": "The previous round is done, but there is a fresh next round ready for /cook."},
|
|
861
|
+
{"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```"},
|
|
862
|
+
]
|
|
863
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
864
|
+
PY
|
|
865
|
+
)"
|
|
866
|
+
write_session_messages "$SESSION_TWO" "$ROOT" "$HANDOFF_MESSAGES_TWO"
|
|
867
|
+
|
|
868
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
869
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_TWO" \
|
|
870
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
871
|
+
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"
|
|
872
|
+
|
|
873
|
+
python3 - "$DISCUSSION_SNAPSHOT_TWO" <<'PY'
|
|
874
|
+
import json
|
|
875
|
+
import sys
|
|
876
|
+
from pathlib import Path
|
|
877
|
+
|
|
878
|
+
mission = 'Ship the next workflow round from a fresh explicit handoff.'
|
|
879
|
+
expected_task_type = 'completion-workflow'
|
|
880
|
+
expected_eval_profile = 'completion-rubric-v1'
|
|
881
|
+
mission_text = Path('.agent/mission.md').read_text()
|
|
882
|
+
profile = json.loads(Path('.agent/profile.json').read_text())
|
|
883
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
884
|
+
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
885
|
+
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
886
|
+
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
887
|
+
|
|
888
|
+
assert mission in mission_text, '.agent/mission.md did not update to the next-round explicit-handoff mission anchor'
|
|
889
|
+
assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after next-round explicit handoff startup'
|
|
890
|
+
assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after next-round explicit handoff startup'
|
|
891
|
+
assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after starting the next workflow round from explicit handoff'
|
|
892
|
+
assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after starting the next workflow round from explicit handoff'
|
|
893
|
+
assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after starting the next workflow round from explicit handoff'
|
|
894
|
+
assert state['advisory_startup_brief']['mission'] == mission, 'next-round explicit handoff should preserve the confirmed startup brief as advisory intake'
|
|
895
|
+
assert state['advisory_startup_brief']['source'] == 'primary_agent_handoff', 'next-round explicit handoff should preserve the handoff advisory source'
|
|
896
|
+
assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after starting the next workflow round from explicit handoff'
|
|
897
|
+
assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after starting the next workflow round from explicit handoff'
|
|
898
|
+
assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after starting the next workflow round from explicit handoff'
|
|
899
|
+
assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after starting the next workflow round from explicit handoff'
|
|
900
|
+
assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after starting the next workflow round from explicit handoff'
|
|
901
|
+
assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after starting the next workflow round from explicit handoff'
|
|
902
|
+
assert proposal['mission'] == mission, 'next-round explicit handoff proposal snapshot should preserve the handoff mission anchor'
|
|
903
|
+
assert proposal['source'] == 'handoff_capsule', 'next-round explicit handoff proposal snapshot should record the handoff capsule source'
|
|
904
|
+
assert state['current_phase'] == 'reground', 'state.json current_phase should reset to reground for the next workflow round'
|
|
905
|
+
assert state['continuation_policy'] == 'continue', 'continuation_policy should reset to continue for the next workflow round'
|
|
906
|
+
assert state['requires_reground'] is True, 'requires_reground should reset to true for the next workflow round'
|
|
907
|
+
assert state['project_done'] is False, 'project_done should reset to false for the next workflow round'
|
|
908
|
+
assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should reset to completion-regrounder for the next workflow round'
|
|
909
|
+
assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'continuation_reason should record the next-round refocus'
|
|
910
|
+
assert 'task_type=completion-workflow' in state['continuation_reason'], 'next-round refocus should persist the selected task_type'
|
|
911
|
+
assert 'evaluation_profile=completion-rubric-v1' in state['continuation_reason'], 'next-round refocus should persist the selected evaluation_profile'
|
|
912
|
+
assert plan['plan_basis'] == 'user_refocus', 'plan_basis should reset to user_refocus for the next workflow round'
|
|
913
|
+
assert active['status'] == 'idle', 'active-slice should reset to idle for the next workflow round'
|
|
914
|
+
PY
|
|
915
|
+
|
|
916
|
+
# Active workflow: inline `/cook` arguments should fail closed immediately and leave canonical state unchanged.
|
|
917
|
+
ACTIVE_INLINE_REJECTION_ROUTING="$TMPDIR/context-proposal-active-inline-arg-routing.json"
|
|
918
|
+
ACTIVE_INLINE_REJECTION_PROPOSAL="$TMPDIR/context-proposal-active-inline-arg-proposal.json"
|
|
919
|
+
ACTIVE_INLINE_REJECTION_CHOOSER="$TMPDIR/context-proposal-active-inline-arg-chooser.json"
|
|
920
|
+
ACTIVE_INLINE_REJECTION_BASELINE="$TMPDIR/context-proposal-active-inline-before.json"
|
|
921
|
+
python3 - "$ACTIVE_INLINE_REJECTION_BASELINE" <<'PY'
|
|
922
|
+
import json
|
|
923
|
+
import sys
|
|
924
|
+
from pathlib import Path
|
|
925
|
+
|
|
926
|
+
tracked = [
|
|
927
|
+
Path('.agent/mission.md'),
|
|
928
|
+
Path('.agent/profile.json'),
|
|
929
|
+
Path('.agent/state.json'),
|
|
930
|
+
Path('.agent/plan.json'),
|
|
931
|
+
Path('.agent/active-slice.json'),
|
|
932
|
+
Path('.agent/verification-evidence.json'),
|
|
933
|
+
]
|
|
934
|
+
Path(sys.argv[1]).write_text(json.dumps({path.name: path.read_text() for path in tracked}, indent=2) + '\n')
|
|
935
|
+
PY
|
|
936
|
+
|
|
937
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$ACTIVE_INLINE_REJECTION_PROPOSAL" \
|
|
938
|
+
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$ACTIVE_INLINE_REJECTION_ROUTING" \
|
|
939
|
+
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$ACTIVE_INLINE_REJECTION_CHOOSER" \
|
|
940
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
941
|
+
pi -e "$PKG_ROOT" -p "/cook Replacement mission for the active workflow" >"$TMPDIR/pi-completion-context-proposal-active-inline-arg.out" 2>"$TMPDIR/pi-completion-context-proposal-active-inline-arg.err"
|
|
942
|
+
|
|
943
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-active-inline-arg.out" "$TMPDIR/pi-completion-context-proposal-active-inline-arg.err" "$ACTIVE_INLINE_REJECTION_ROUTING" "$ACTIVE_INLINE_REJECTION_PROPOSAL" "$ACTIVE_INLINE_REJECTION_CHOOSER" "$ACTIVE_INLINE_REJECTION_BASELINE" <<'PY'
|
|
944
|
+
import json
|
|
945
|
+
import sys
|
|
946
|
+
from pathlib import Path
|
|
947
|
+
|
|
948
|
+
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
949
|
+
routing = Path(sys.argv[3])
|
|
950
|
+
proposal = Path(sys.argv[4])
|
|
951
|
+
chooser = Path(sys.argv[5])
|
|
952
|
+
before = json.loads(Path(sys.argv[6]).read_text())
|
|
953
|
+
tracked = [
|
|
954
|
+
Path('.agent/mission.md'),
|
|
955
|
+
Path('.agent/profile.json'),
|
|
956
|
+
Path('.agent/state.json'),
|
|
957
|
+
Path('.agent/plan.json'),
|
|
958
|
+
Path('.agent/active-slice.json'),
|
|
959
|
+
Path('.agent/verification-evidence.json'),
|
|
960
|
+
]
|
|
961
|
+
|
|
962
|
+
assert not routing.exists(), 'active /cook inline-args rejection should not run active-workflow routing'
|
|
963
|
+
assert not proposal.exists(), 'active /cook inline-args rejection should not emit a replacement startup-brief proposal'
|
|
964
|
+
assert not chooser.exists(), 'active /cook inline-args rejection should not open the existing-workflow chooser'
|
|
965
|
+
assert '/cook no longer accepts inline arguments.' in output, 'active /cook inline-args rejection should explain the bare-only entry contract'
|
|
966
|
+
after = {path.name: path.read_text() for path in tracked}
|
|
967
|
+
assert before == after, 'active /cook inline-args rejection should leave canonical files unchanged'
|
|
968
|
+
PY
|
|
969
|
+
|
|
970
|
+
# Completed workflow: inline `/cook` arguments should also fail closed before any next-round proposal derivation.
|
|
971
|
+
mark_done
|
|
972
|
+
|
|
973
|
+
DONE_INLINE_REJECTION_ROUTING="$TMPDIR/context-proposal-done-inline-arg-routing.json"
|
|
974
|
+
DONE_INLINE_REJECTION_PROPOSAL="$TMPDIR/context-proposal-done-inline-arg-proposal.json"
|
|
975
|
+
DONE_INLINE_REJECTION_CHOOSER="$TMPDIR/context-proposal-done-inline-arg-chooser.json"
|
|
976
|
+
DONE_INLINE_REJECTION_BASELINE="$TMPDIR/context-proposal-done-inline-before.json"
|
|
977
|
+
python3 - "$DONE_INLINE_REJECTION_BASELINE" <<'PY'
|
|
978
|
+
import json
|
|
979
|
+
import sys
|
|
980
|
+
from pathlib import Path
|
|
981
|
+
|
|
982
|
+
tracked = [
|
|
983
|
+
Path('.agent/mission.md'),
|
|
984
|
+
Path('.agent/profile.json'),
|
|
985
|
+
Path('.agent/state.json'),
|
|
986
|
+
Path('.agent/plan.json'),
|
|
987
|
+
Path('.agent/active-slice.json'),
|
|
988
|
+
Path('.agent/verification-evidence.json'),
|
|
989
|
+
]
|
|
990
|
+
Path(sys.argv[1]).write_text(json.dumps({path.name: path.read_text() for path in tracked}, indent=2) + '\n')
|
|
991
|
+
PY
|
|
992
|
+
|
|
993
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DONE_INLINE_REJECTION_PROPOSAL" \
|
|
994
|
+
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$DONE_INLINE_REJECTION_ROUTING" \
|
|
995
|
+
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$DONE_INLINE_REJECTION_CHOOSER" \
|
|
996
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
997
|
+
pi -e "$PKG_ROOT" -p "/cook done-workflow replacement mission" >"$TMPDIR/pi-completion-context-proposal-done-inline-arg.out" 2>"$TMPDIR/pi-completion-context-proposal-done-inline-arg.err"
|
|
998
|
+
|
|
999
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-done-inline-arg.out" "$TMPDIR/pi-completion-context-proposal-done-inline-arg.err" "$DONE_INLINE_REJECTION_ROUTING" "$DONE_INLINE_REJECTION_PROPOSAL" "$DONE_INLINE_REJECTION_CHOOSER" "$DONE_INLINE_REJECTION_BASELINE" <<'PY'
|
|
1000
|
+
import json
|
|
1001
|
+
import sys
|
|
1002
|
+
from pathlib import Path
|
|
1003
|
+
|
|
1004
|
+
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
1005
|
+
routing = Path(sys.argv[3])
|
|
1006
|
+
proposal = Path(sys.argv[4])
|
|
1007
|
+
chooser = Path(sys.argv[5])
|
|
1008
|
+
before = json.loads(Path(sys.argv[6]).read_text())
|
|
1009
|
+
tracked = [
|
|
1010
|
+
Path('.agent/mission.md'),
|
|
1011
|
+
Path('.agent/profile.json'),
|
|
1012
|
+
Path('.agent/state.json'),
|
|
1013
|
+
Path('.agent/plan.json'),
|
|
1014
|
+
Path('.agent/active-slice.json'),
|
|
1015
|
+
Path('.agent/verification-evidence.json'),
|
|
1016
|
+
]
|
|
1017
|
+
state_before = json.loads(before['state.json'])
|
|
1018
|
+
assert state_before['current_phase'] == 'done', 'done /cook inline-args rejection should start from a completed workflow'
|
|
1019
|
+
assert state_before['project_done'] is True, 'done /cook inline-args rejection should start from project_done=true'
|
|
1020
|
+
assert not routing.exists(), 'done /cook inline-args rejection should not run active-workflow routing while starting the next round'
|
|
1021
|
+
assert not proposal.exists(), 'done /cook inline-args rejection should not emit a next-round startup-brief proposal'
|
|
1022
|
+
assert not chooser.exists(), 'done /cook inline-args rejection should not open the existing-workflow chooser when starting the next round'
|
|
1023
|
+
assert '/cook no longer accepts inline arguments.' in output, 'done /cook inline-args rejection should explain the bare-only entry contract'
|
|
1024
|
+
after = {path.name: path.read_text() for path in tracked}
|
|
1025
|
+
assert before == after, 'done /cook inline-args rejection should leave canonical files unchanged'
|
|
1026
|
+
PY
|
|
1027
|
+
|
|
1028
|
+
# Completed workflow again: model-assisted discussion analysis alone should still fail closed
|
|
1029
|
+
# without a fresh explicit primary-agent handoff.
|
|
1030
|
+
mark_done
|
|
1031
|
+
|
|
1032
|
+
SESSION_FIVE="$TMPDIR/session-five.jsonl"
|
|
1033
|
+
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.'
|
|
1034
|
+
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"]}'
|
|
1035
|
+
DISCUSSION_SNAPSHOT_FIVE="$TMPDIR/context-proposal-analyst-restart-rejected.json"
|
|
1036
|
+
write_session "$SESSION_FIVE" "$ROOT" "$DISCUSSION_FIVE"
|
|
1037
|
+
|
|
1038
|
+
GENERATED_HANDOFF_FIVE="$(python3 - <<'PY'
|
|
1039
|
+
import json
|
|
1040
|
+
capsule = {
|
|
1041
|
+
"kind": "cook_handoff",
|
|
1042
|
+
"source": "primary_agent",
|
|
1043
|
+
"mission": "Use the analyst-backed parser follow-up as the next workflow round.",
|
|
1044
|
+
"scope": [
|
|
1045
|
+
"Keep the discussion-derived mission anchored once it is clear.",
|
|
1046
|
+
"Drop stale scope from earlier turns."
|
|
1047
|
+
],
|
|
1048
|
+
"constraints": [
|
|
1049
|
+
"Do not rewrite the parser."
|
|
1050
|
+
],
|
|
1051
|
+
"acceptance": [
|
|
1052
|
+
"Add a regression test."
|
|
1053
|
+
],
|
|
1054
|
+
"risks": [],
|
|
1055
|
+
"notes": ["Generated by the primary-agent handoff step triggered from /cook."],
|
|
1056
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1057
|
+
"first_slice_goal": "Land the regression-test-backed parser follow-up without rewriting the parser.",
|
|
1058
|
+
"first_slice_non_goals": ["Do not broaden the mission with stale scope."],
|
|
1059
|
+
"implementation_surfaces": ["scripts/context-proposal-test.sh"],
|
|
1060
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
1061
|
+
"why_this_slice_first": "The user explicitly chose workflow mode and the primary agent can already bound the first slice.",
|
|
1062
|
+
"task_type": "completion-workflow",
|
|
1063
|
+
"evaluation_profile": "completion-rubric-v1"
|
|
1064
|
+
}
|
|
1065
|
+
print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
|
|
1066
|
+
PY
|
|
1067
|
+
)"
|
|
1068
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$ANALYST_OUTPUT_FIVE" PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$GENERATED_HANDOFF_FIVE" PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_FIVE" PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 pi --session "$SESSION_FIVE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-analyst.out" 2>"$TMPDIR/pi-completion-context-proposal-analyst.err"
|
|
1069
|
+
|
|
1070
|
+
python3 - "$TMPDIR/pi-completion-context-proposal-analyst.out" "$TMPDIR/pi-completion-context-proposal-analyst.err" "$DISCUSSION_SNAPSHOT_FIVE" <<'PY'
|
|
1071
|
+
import json
|
|
1072
|
+
import sys
|
|
1073
|
+
from pathlib import Path
|
|
1074
|
+
|
|
1075
|
+
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
1076
|
+
snapshot = Path(sys.argv[3])
|
|
1077
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
1078
|
+
|
|
1079
|
+
if snapshot.exists():
|
|
1080
|
+
pass
|
|
1081
|
+
assert state['continuation_policy'] == 'continue', 'done-workflow analyst-backed primary-agent handoff should reopen the workflow'
|
|
1082
|
+
assert 'Started a new completion workflow round from explicit primary-agent handoff' in output, 'done-workflow analyst-backed startup should report explicit primary-agent handoff startup'
|
|
1083
|
+
PY
|
|
1084
|
+
|
|
1085
|
+
# Custom confirmation UI: start should render proposal content separately from approval-only Start/Cancel actions.
|
|
1086
|
+
UI_ROOT_START="$TMPDIR/ui-root-start"
|
|
1087
|
+
mkdir -p "$UI_ROOT_START"
|
|
1088
|
+
cd "$UI_ROOT_START"
|
|
1089
|
+
git init -q
|
|
1090
|
+
|
|
1091
|
+
UI_SESSION_START="$TMPDIR/ui-session-start.jsonl"
|
|
1092
|
+
UI_SNAPSHOT_START="$TMPDIR/context-proposal-ui-start.json"
|
|
1093
|
+
UI_MESSAGES_START="$(python3 - <<'PY'
|
|
1094
|
+
import json
|
|
1095
|
+
capsule = {
|
|
1096
|
+
"kind": "cook_handoff",
|
|
1097
|
+
"source": "primary_agent",
|
|
1098
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1099
|
+
"source_turn_id": "m0002",
|
|
1100
|
+
"mission": "Replace the crowded selector with a clearer action layout.",
|
|
1101
|
+
"scope": ["Separate proposal text from actions."],
|
|
1102
|
+
"constraints": ["Preserve approval-only Start/Cancel behavior."],
|
|
1103
|
+
"acceptance": ["Add regression coverage."],
|
|
1104
|
+
"risks": ["Bundling critique into the action list would make the confirmation harder to scan."],
|
|
1105
|
+
"notes": ["Keep critique details separate from the approval-only proposal summary.", "Possible noise: old selector wording"],
|
|
1106
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1107
|
+
"first_slice_goal": "Separate the proposal text from the approval-only Start/Cancel actions.",
|
|
1108
|
+
"first_slice_non_goals": [],
|
|
1109
|
+
"implementation_surfaces": ["extensions/completion/prompt-surfaces.ts"],
|
|
1110
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
1111
|
+
"why_this_slice_first": "The confirmation layout regression is small and directly testable.",
|
|
1112
|
+
"task_type": "completion-workflow",
|
|
1113
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
1114
|
+
"why_cook_now": "The explicit handoff is concrete enough to exercise the startup confirmation UI."
|
|
1115
|
+
}
|
|
1116
|
+
messages = [
|
|
1117
|
+
{"role": "user", "content": "Prepare the confirmation-layout work and tell me when it is ready for /cook."},
|
|
1118
|
+
{"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```"},
|
|
1119
|
+
]
|
|
1120
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1121
|
+
PY
|
|
1122
|
+
)"
|
|
1123
|
+
write_session_messages "$UI_SESSION_START" "$UI_ROOT_START" "$UI_MESSAGES_START"
|
|
1124
|
+
|
|
1125
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_ACTION=start \
|
|
1126
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_PATH="$UI_SNAPSHOT_START" \
|
|
1127
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1128
|
+
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"
|
|
1129
|
+
|
|
1130
|
+
python3 - "$UI_SNAPSHOT_START" <<'PY'
|
|
1131
|
+
import json
|
|
1132
|
+
import sys
|
|
1133
|
+
from pathlib import Path
|
|
1134
|
+
|
|
1135
|
+
snapshot = json.loads(Path(sys.argv[1]).read_text())
|
|
1136
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
1137
|
+
|
|
1138
|
+
assert snapshot['proposalHeading'] == 'Startup brief', 'custom confirmation snapshot should expose a dedicated startup-brief section'
|
|
1139
|
+
assert snapshot['critiqueHeading'] == 'Notes and risks', 'custom confirmation snapshot should expose notes separately from the startup-brief body'
|
|
1140
|
+
assert snapshot['routingHeading'] == 'Routing recommendations', 'custom confirmation snapshot should expose routing recommendations separately from the proposal body'
|
|
1141
|
+
assert 'approval-only' in snapshot['intro'], 'custom confirmation intro should explain the approval-only gate'
|
|
1142
|
+
assert state['task_type'] == 'completion-workflow', 'start action should preserve canonical task_type'
|
|
1143
|
+
assert state['evaluation_profile'] == 'completion-rubric-v1', 'start action should preserve canonical evaluation_profile'
|
|
1144
|
+
assert 'Mission\nReplace the crowded selector with a clearer action layout.' in snapshot['proposalBody'], 'proposal body should be captured separately from the action list'
|
|
1145
|
+
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'
|
|
1146
|
+
assert 'Critique\n- Keep critique details separate from the approval-only proposal summary.' in snapshot['critiqueBody'], 'notes section should render accepted critique notes separately'
|
|
1147
|
+
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'
|
|
1148
|
+
assert '- Possible noise: old selector wording' in snapshot['critiqueBody'], 'critique section should preserve additional operator notes separately from the startup-brief body'
|
|
1149
|
+
assert '- task_type: completion-workflow' in snapshot['routingBody'], 'routing section should render the recommended task_type'
|
|
1150
|
+
assert '- evaluation_profile: completion-rubric-v1' in snapshot['routingBody'], 'routing section should render the recommended evaluation_profile'
|
|
1151
|
+
assert [action['id'] for action in snapshot['actions']] == ['start', 'cancel'], 'custom confirmation actions should stay Start/Cancel only'
|
|
1152
|
+
assert [action['label'] for action in snapshot['actions']] == ['Start', 'Cancel'], 'custom confirmation action labels should be concise'
|
|
1153
|
+
assert 'Discuss changes in the main chat and rerun /cook.' in snapshot['actions'][1]['description'], 'cancel action should redirect users back to the main chat and rerun /cook'
|
|
1154
|
+
for action in snapshot['actions']:
|
|
1155
|
+
assert 'Replace the crowded selector with a clearer action layout.' not in action['label'], 'proposal mission should not be embedded in action labels'
|
|
1156
|
+
assert 'Separate proposal text from actions.' not in action['description'], 'proposal scope should not be embedded in action descriptions'
|
|
1157
|
+
assert state['mission_anchor'] == 'Replace the crowded selector with a clearer action layout.', 'start action should still accept the proposed mission'
|
|
1158
|
+
assert state['advisory_startup_brief']['mission'] == 'Replace the crowded selector with a clearer action layout.', 'start action should preserve the confirmed startup brief canonically'
|
|
1159
|
+
assert state['continuation_reason'].startswith('User started workflow via /cook:'), 'start action should persist the startup routing outcome in continuation_reason'
|
|
1160
|
+
assert 'Keep critique details separate from the approval-only proposal summary.' in state['continuation_reason'], 'start action should persist the accepted critique outcome canonically'
|
|
1161
|
+
PY
|
|
1162
|
+
|
|
1163
|
+
# Custom confirmation UI: cancel should exit without writing canonical state and should tell the user
|
|
1164
|
+
# to discuss changes in the main chat before rerunning /cook.
|
|
1165
|
+
UI_ROOT_CANCEL="$TMPDIR/ui-root-cancel"
|
|
1166
|
+
mkdir -p "$UI_ROOT_CANCEL"
|
|
1167
|
+
cd "$UI_ROOT_CANCEL"
|
|
1168
|
+
git init -q
|
|
1169
|
+
|
|
1170
|
+
UI_SESSION_CANCEL="$TMPDIR/ui-session-cancel.jsonl"
|
|
1171
|
+
UI_SNAPSHOT_CANCEL="$TMPDIR/context-proposal-ui-cancel.json"
|
|
1172
|
+
UI_MESSAGES_CANCEL="$(python3 - <<'PY'
|
|
1173
|
+
import json
|
|
1174
|
+
capsule = {
|
|
1175
|
+
"kind": "cook_handoff",
|
|
1176
|
+
"source": "primary_agent",
|
|
1177
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1178
|
+
"source_turn_id": "m0002",
|
|
1179
|
+
"mission": "Cancel from the custom confirmation UI without writing state.",
|
|
1180
|
+
"scope": ["Show the proposal separately from the approval-only actions."],
|
|
1181
|
+
"constraints": ["Keep cancellation side-effect free."],
|
|
1182
|
+
"acceptance": ["Add regression coverage proving cancel leaves .agent absent."],
|
|
1183
|
+
"risks": [],
|
|
1184
|
+
"notes": [],
|
|
1185
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1186
|
+
"first_slice_goal": "Exercise the cancel path without writing canonical state.",
|
|
1187
|
+
"first_slice_non_goals": [],
|
|
1188
|
+
"implementation_surfaces": ["extensions/completion/prompt-surfaces.ts"],
|
|
1189
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
1190
|
+
"why_this_slice_first": "The cancel path is a direct regression around the startup confirmation UI.",
|
|
1191
|
+
"task_type": "completion-workflow",
|
|
1192
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
1193
|
+
"why_cook_now": "The explicit handoff is concrete enough to exercise the cancel confirmation UI."
|
|
1194
|
+
}
|
|
1195
|
+
messages = [
|
|
1196
|
+
{"role": "user", "content": "Prepare the cancel-path confirmation work and tell me when it is ready for /cook."},
|
|
1197
|
+
{"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```"},
|
|
1198
|
+
]
|
|
1199
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1200
|
+
PY
|
|
1201
|
+
)"
|
|
1202
|
+
write_session_messages "$UI_SESSION_CANCEL" "$UI_ROOT_CANCEL" "$UI_MESSAGES_CANCEL"
|
|
1203
|
+
|
|
1204
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_ACTION=cancel \
|
|
1205
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_PATH="$UI_SNAPSHOT_CANCEL" \
|
|
1206
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1207
|
+
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"
|
|
1208
|
+
|
|
1209
|
+
python3 - "$UI_SNAPSHOT_CANCEL" "$TMPDIR/pi-completion-context-proposal-ui-cancel.out" "$TMPDIR/pi-completion-context-proposal-ui-cancel.err" <<'PY'
|
|
1210
|
+
import json
|
|
1211
|
+
import sys
|
|
1212
|
+
from pathlib import Path
|
|
1213
|
+
|
|
1214
|
+
snapshot = json.loads(Path(sys.argv[1]).read_text())
|
|
1215
|
+
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
1216
|
+
assert 'approval-only' in snapshot['intro'], 'cancel snapshot should keep the approval-only intro'
|
|
1217
|
+
assert [action['id'] for action in snapshot['actions']] == ['start', 'cancel'], 'cancel snapshot should expose Start/Cancel actions only'
|
|
1218
|
+
assert [action['label'] for action in snapshot['actions']] == ['Start', 'Cancel'], 'cancel snapshot should keep concise Start/Cancel labels'
|
|
1219
|
+
assert 'Discuss changes in the main chat and rerun /cook.' in snapshot['actions'][1]['description'], 'cancel action copy should redirect users back to the main chat and rerun /cook'
|
|
1220
|
+
assert 'Discuss changes in the main chat and rerun /cook.' in output, 'cancel command output should redirect users back to the main chat and rerun /cook'
|
|
1221
|
+
assert not Path('.agent').exists(), 'cancel action should not write canonical workflow state'
|
|
1222
|
+
PY
|
|
1223
|
+
|
|
1224
|
+
# Explicit primary-agent handoff: /cook should prefer the structured handoff capsule over broad context re-inference.
|
|
1225
|
+
HANDOFF_ROOT_START="$TMPDIR/handoff-root-start"
|
|
1226
|
+
mkdir -p "$HANDOFF_ROOT_START"
|
|
1227
|
+
cd "$HANDOFF_ROOT_START"
|
|
1228
|
+
git init -q
|
|
1229
|
+
|
|
1230
|
+
HANDOFF_SESSION_START="$TMPDIR/handoff-session-start.jsonl"
|
|
1231
|
+
HANDOFF_SNAPSHOT_START="$TMPDIR/handoff-proposal-start.json"
|
|
1232
|
+
HANDOFF_MESSAGES_START="$(python3 - <<'PY'
|
|
1233
|
+
import json
|
|
1234
|
+
capsule = {
|
|
1235
|
+
"kind": "cook_handoff",
|
|
1236
|
+
"source": "primary_agent",
|
|
1237
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1238
|
+
"source_turn_id": "m0002",
|
|
1239
|
+
"mission": "Fix login redirect callback behavior.",
|
|
1240
|
+
"scope": [
|
|
1241
|
+
"Update the callback redirect decision logic.",
|
|
1242
|
+
"Preserve the broader auth flow."
|
|
1243
|
+
],
|
|
1244
|
+
"constraints": [
|
|
1245
|
+
"Do not refactor the broader auth flow."
|
|
1246
|
+
],
|
|
1247
|
+
"acceptance": [
|
|
1248
|
+
"Add a regression test for returning to the requested page."
|
|
1249
|
+
],
|
|
1250
|
+
"risks": [
|
|
1251
|
+
"Stale auth discussion could broaden the startup brief if the handoff is ignored."
|
|
1252
|
+
],
|
|
1253
|
+
"notes": [
|
|
1254
|
+
"Keep the startup brief aligned with the explicit primary-agent plan."
|
|
1255
|
+
],
|
|
1256
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1257
|
+
"first_slice_goal": "Land the redirect callback fix and its regression coverage.",
|
|
1258
|
+
"first_slice_non_goals": [
|
|
1259
|
+
"Do not refactor the broader auth flow."
|
|
1260
|
+
],
|
|
1261
|
+
"implementation_surfaces": [
|
|
1262
|
+
"src/auth/redirect.ts",
|
|
1263
|
+
"tests/auth/redirect.spec.ts"
|
|
1264
|
+
],
|
|
1265
|
+
"verification_commands": [
|
|
1266
|
+
"npm test -- redirect.spec.ts"
|
|
1267
|
+
],
|
|
1268
|
+
"why_this_slice_first": "The redirect callback bug is already bounded enough to start implementation safely.",
|
|
1269
|
+
"task_type": "completion-workflow",
|
|
1270
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
1271
|
+
"why_cook_now": "The implementation plan is concrete and ready for repo changes."
|
|
1272
|
+
}
|
|
1273
|
+
messages = [
|
|
1274
|
+
{"role": "user", "content": "Please think through the login redirect fix and tell me when it is ready for /cook."},
|
|
1275
|
+
{"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```"},
|
|
1276
|
+
]
|
|
1277
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1278
|
+
PY
|
|
1279
|
+
)"
|
|
1280
|
+
write_session_messages "$HANDOFF_SESSION_START" "$HANDOFF_ROOT_START" "$HANDOFF_MESSAGES_START"
|
|
1281
|
+
|
|
1282
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
1283
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_START" \
|
|
1284
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1285
|
+
pi --session "$HANDOFF_SESSION_START" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-start.out" 2>"$TMPDIR/pi-completion-handoff-start.err"
|
|
1286
|
+
|
|
1287
|
+
python3 - "$HANDOFF_SNAPSHOT_START" <<'PY'
|
|
1288
|
+
import json
|
|
1289
|
+
import sys
|
|
1290
|
+
from pathlib import Path
|
|
1291
|
+
|
|
1292
|
+
snapshot = json.loads(Path(sys.argv[1]).read_text())
|
|
1293
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
1294
|
+
|
|
1295
|
+
assert snapshot['source'] == 'handoff_capsule', 'explicit handoff startup should snapshot the handoff capsule as the proposal source'
|
|
1296
|
+
assert snapshot['mission'] == 'Fix login redirect callback behavior.', 'explicit handoff startup should preserve the primary-agent mission'
|
|
1297
|
+
assert state['mission_anchor'] == 'Fix login redirect callback behavior.', 'explicit handoff startup should use the handoff mission as canonical mission_anchor'
|
|
1298
|
+
assert state['advisory_startup_brief']['source'] == 'primary_agent_handoff', 'explicit handoff startup should preserve the advisory intake source'
|
|
1299
|
+
assert state['advisory_startup_brief']['risks'] == ['Stale auth discussion could broaden the startup brief if the handoff is ignored.'], 'explicit handoff startup should preserve handoff risks'
|
|
1300
|
+
assert 'First slice goal: Land the redirect callback fix and its regression coverage.' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve first_slice_goal in advisory notes'
|
|
1301
|
+
assert 'First slice non-goals: Do not refactor the broader auth flow.' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve first_slice_non_goals in advisory notes'
|
|
1302
|
+
assert 'Implementation surfaces: src/auth/redirect.ts | tests/auth/redirect.spec.ts' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve implementation_surfaces in advisory notes'
|
|
1303
|
+
assert 'Verification commands: npm test -- redirect.spec.ts' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve verification_commands in advisory notes'
|
|
1304
|
+
assert 'Why this slice first: The redirect callback bug is already bounded enough to start implementation safely.' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve why_this_slice_first in advisory notes'
|
|
1305
|
+
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'
|
|
1306
|
+
PY
|
|
1307
|
+
|
|
1308
|
+
# Fresh but non-startable explicit handoff: /cook should fail closed instead of falling back
|
|
1309
|
+
# to a broad recent-discussion startup brief when the explicit capsule is still too vague.
|
|
1310
|
+
HANDOFF_ROOT_VAGUE="$TMPDIR/handoff-root-vague"
|
|
1311
|
+
mkdir -p "$HANDOFF_ROOT_VAGUE"
|
|
1312
|
+
cd "$HANDOFF_ROOT_VAGUE"
|
|
1313
|
+
git init -q
|
|
1314
|
+
|
|
1315
|
+
HANDOFF_SESSION_VAGUE="$TMPDIR/handoff-session-vague.jsonl"
|
|
1316
|
+
HANDOFF_SNAPSHOT_VAGUE="$TMPDIR/handoff-proposal-vague.json"
|
|
1317
|
+
HANDOFF_MESSAGES_VAGUE="$(python3 - <<'PY'
|
|
1318
|
+
import json
|
|
1319
|
+
capsule = {
|
|
1320
|
+
"kind": "cook_handoff",
|
|
1321
|
+
"source": "primary_agent",
|
|
1322
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1323
|
+
"source_turn_id": "m0002",
|
|
1324
|
+
"mission": "Fix login redirect callback behavior.",
|
|
1325
|
+
"scope": [
|
|
1326
|
+
"Update the callback redirect decision logic."
|
|
1327
|
+
],
|
|
1328
|
+
"constraints": [
|
|
1329
|
+
"Do not refactor the broader auth flow."
|
|
1330
|
+
],
|
|
1331
|
+
"acceptance": [
|
|
1332
|
+
"Confirm the final implementation breakdown before coding."
|
|
1333
|
+
],
|
|
1334
|
+
"risks": [
|
|
1335
|
+
"Broad recent context could be reused if the vague explicit handoff is ignored."
|
|
1336
|
+
],
|
|
1337
|
+
"notes": [
|
|
1338
|
+
"This handoff is still too vague to start implementation directly."
|
|
1339
|
+
],
|
|
1340
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1341
|
+
"first_slice_goal": "Patch the callback redirect decision logic.",
|
|
1342
|
+
"first_slice_non_goals": [
|
|
1343
|
+
"Do not refactor the broader auth flow."
|
|
1344
|
+
],
|
|
1345
|
+
"implementation_surfaces": [],
|
|
1346
|
+
"verification_commands": [],
|
|
1347
|
+
"why_this_slice_first": "The callback redirect path is the likely first slice, but the handoff still lacks execution detail.",
|
|
1348
|
+
"task_type": "completion-workflow",
|
|
1349
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
1350
|
+
"why_cook_now": "The task is workflow-worthy, but the implementation slice is not concrete enough yet."
|
|
1351
|
+
}
|
|
1352
|
+
recent_discussion = "Mission: Fix login redirect callback behavior.\nScope:\n- Update the callback redirect decision logic.\nConstraints:\n- Do not refactor the broader auth flow.\nAcceptance:\n- Add a regression test for returning to the requested page."
|
|
1353
|
+
messages = [
|
|
1354
|
+
{"role": "user", "content": recent_discussion},
|
|
1355
|
+
{"role": "assistant", "content": "This follow-up might soon be ready for /cook.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
1356
|
+
]
|
|
1357
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1358
|
+
PY
|
|
1359
|
+
)"
|
|
1360
|
+
write_session_messages "$HANDOFF_SESSION_VAGUE" "$HANDOFF_ROOT_VAGUE" "$HANDOFF_MESSAGES_VAGUE"
|
|
1361
|
+
|
|
1362
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
1363
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_VAGUE" \
|
|
1364
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1365
|
+
pi --session "$HANDOFF_SESSION_VAGUE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-vague.out" 2>"$TMPDIR/pi-completion-handoff-vague.err"
|
|
1366
|
+
|
|
1367
|
+
python3 - "$HANDOFF_SNAPSHOT_VAGUE" "$TMPDIR/pi-completion-handoff-vague.out" "$TMPDIR/pi-completion-handoff-vague.err" <<'PY'
|
|
1368
|
+
import sys
|
|
1369
|
+
from pathlib import Path
|
|
1370
|
+
|
|
1371
|
+
snapshot = Path(sys.argv[1])
|
|
1372
|
+
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
1373
|
+
|
|
1374
|
+
assert not snapshot.exists(), 'fresh non-startable handoff should not emit a startup proposal snapshot'
|
|
1375
|
+
assert not Path('.agent').exists(), 'fresh non-startable handoff should fail closed without writing canonical state'
|
|
1376
|
+
assert 'fresh explicit primary-agent handoff exists' in output, 'fresh non-startable handoff should explain that the explicit capsule blocked startup'
|
|
1377
|
+
assert 'acceptance is not anchored to concrete repo changes or verification' in output, 'fresh non-startable handoff should explain the workflow-only acceptance failure'
|
|
1378
|
+
assert 'implementation_surfaces is empty' in output, 'fresh non-startable handoff should explain the missing implementation_surfaces requirement'
|
|
1379
|
+
assert 'verification_commands is empty' in output, 'fresh non-startable handoff should explain the missing verification_commands requirement'
|
|
1380
|
+
PY
|
|
1381
|
+
|
|
1382
|
+
# Fresh explicit handoff with complete first-slice fields but vague acceptance: /cook should still fail closed
|
|
1383
|
+
# with the dedicated explicit-handoff message instead of bootstrapping canonical state.
|
|
1384
|
+
HANDOFF_ROOT_VAGUE_ACCEPTANCE="$TMPDIR/handoff-root-vague-acceptance"
|
|
1385
|
+
mkdir -p "$HANDOFF_ROOT_VAGUE_ACCEPTANCE"
|
|
1386
|
+
cd "$HANDOFF_ROOT_VAGUE_ACCEPTANCE"
|
|
1387
|
+
git init -q
|
|
1388
|
+
|
|
1389
|
+
HANDOFF_SESSION_VAGUE_ACCEPTANCE="$TMPDIR/handoff-session-vague-acceptance.jsonl"
|
|
1390
|
+
HANDOFF_SNAPSHOT_VAGUE_ACCEPTANCE="$TMPDIR/handoff-proposal-vague-acceptance.json"
|
|
1391
|
+
HANDOFF_MESSAGES_VAGUE_ACCEPTANCE="$(python3 - <<'PY'
|
|
1392
|
+
import json
|
|
1393
|
+
capsule = {
|
|
1394
|
+
"kind": "cook_handoff",
|
|
1395
|
+
"source": "primary_agent",
|
|
1396
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1397
|
+
"source_turn_id": "m0002",
|
|
1398
|
+
"mission": "Fix login redirect callback behavior.",
|
|
1399
|
+
"scope": [
|
|
1400
|
+
"Update the callback redirect decision logic.",
|
|
1401
|
+
"Preserve the broader auth flow."
|
|
1402
|
+
],
|
|
1403
|
+
"constraints": [
|
|
1404
|
+
"Do not refactor the broader auth flow."
|
|
1405
|
+
],
|
|
1406
|
+
"acceptance": [
|
|
1407
|
+
"Current behavior stays understandable."
|
|
1408
|
+
],
|
|
1409
|
+
"risks": [
|
|
1410
|
+
"Broad recent context could be reused if the vague explicit handoff is ignored."
|
|
1411
|
+
],
|
|
1412
|
+
"notes": [
|
|
1413
|
+
"This handoff includes first-slice fields but still lacks concrete acceptance."
|
|
1414
|
+
],
|
|
1415
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1416
|
+
"first_slice_goal": "Land the redirect callback fix and its regression coverage.",
|
|
1417
|
+
"first_slice_non_goals": [
|
|
1418
|
+
"Do not refactor the broader auth flow."
|
|
1419
|
+
],
|
|
1420
|
+
"implementation_surfaces": [
|
|
1421
|
+
"src/auth/redirect.ts",
|
|
1422
|
+
"tests/auth/redirect.spec.ts"
|
|
1423
|
+
],
|
|
1424
|
+
"verification_commands": [
|
|
1425
|
+
"npm test -- redirect.spec.ts"
|
|
1426
|
+
],
|
|
1427
|
+
"why_this_slice_first": "The redirect callback bug is already bounded enough to start implementation safely once acceptance is concrete.",
|
|
1428
|
+
"task_type": "completion-workflow",
|
|
1429
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
1430
|
+
"why_cook_now": "The task is workflow-worthy, but the acceptance still needs concrete repo-change detail."
|
|
1431
|
+
}
|
|
1432
|
+
recent_discussion = "Mission: Fix login redirect callback behavior.\nScope:\n- Update the callback redirect decision logic.\nConstraints:\n- Do not refactor the broader auth flow.\nAcceptance:\n- Add a regression test for returning to the requested page."
|
|
1433
|
+
messages = [
|
|
1434
|
+
{"role": "user", "content": recent_discussion},
|
|
1435
|
+
{"role": "assistant", "content": "This follow-up might soon be ready for /cook.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
1436
|
+
]
|
|
1437
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1438
|
+
PY
|
|
1439
|
+
)"
|
|
1440
|
+
write_session_messages "$HANDOFF_SESSION_VAGUE_ACCEPTANCE" "$HANDOFF_ROOT_VAGUE_ACCEPTANCE" "$HANDOFF_MESSAGES_VAGUE_ACCEPTANCE"
|
|
1441
|
+
|
|
1442
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
1443
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_VAGUE_ACCEPTANCE" \
|
|
1444
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1445
|
+
pi --session "$HANDOFF_SESSION_VAGUE_ACCEPTANCE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-vague-acceptance.out" 2>"$TMPDIR/pi-completion-handoff-vague-acceptance.err"
|
|
1446
|
+
|
|
1447
|
+
python3 - "$HANDOFF_SNAPSHOT_VAGUE_ACCEPTANCE" "$TMPDIR/pi-completion-handoff-vague-acceptance.out" "$TMPDIR/pi-completion-handoff-vague-acceptance.err" <<'PY'
|
|
1448
|
+
import sys
|
|
1449
|
+
from pathlib import Path
|
|
1450
|
+
|
|
1451
|
+
snapshot = Path(sys.argv[1])
|
|
1452
|
+
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
1453
|
+
|
|
1454
|
+
assert not snapshot.exists(), 'fresh explicit handoff with vague acceptance should not emit a startup proposal snapshot'
|
|
1455
|
+
assert not Path('.agent').exists(), 'fresh explicit handoff with vague acceptance should fail closed without writing canonical state'
|
|
1456
|
+
assert 'fresh explicit primary-agent handoff exists' in output, 'fresh explicit handoff with vague acceptance should use the dedicated explicit-handoff fail-closed message'
|
|
1457
|
+
assert 'acceptance is not anchored to concrete repo changes or verification' in output, 'fresh explicit handoff with vague acceptance should explain the vague acceptance failure'
|
|
1458
|
+
PY
|
|
1459
|
+
|
|
1460
|
+
# Done workflow + fresh handoff: the fresh explicit handoff should override done-state suppression and start the new round.
|
|
1461
|
+
HANDOFF_ROOT_DONE="$TMPDIR/handoff-root-done"
|
|
1462
|
+
mkdir -p "$HANDOFF_ROOT_DONE"
|
|
1463
|
+
cd "$HANDOFF_ROOT_DONE"
|
|
1464
|
+
git init -q
|
|
1465
|
+
|
|
1466
|
+
DONE_SEED_SESSION="$TMPDIR/handoff-done-seed-session.jsonl"
|
|
1467
|
+
DONE_SEED_MESSAGES="$(python3 - <<'PY'
|
|
1468
|
+
import json
|
|
1469
|
+
capsule = {
|
|
1470
|
+
"kind": "cook_handoff",
|
|
1471
|
+
"source": "primary_agent",
|
|
1472
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1473
|
+
"source_turn_id": "m0002",
|
|
1474
|
+
"mission": "Seed a finished workflow before testing fresh handoff priority.",
|
|
1475
|
+
"scope": ["Create canonical workflow state."],
|
|
1476
|
+
"constraints": ["Keep the seed minimal."],
|
|
1477
|
+
"acceptance": ["Add regression coverage for marking the seeded workflow done before the next step."],
|
|
1478
|
+
"risks": [],
|
|
1479
|
+
"notes": [],
|
|
1480
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1481
|
+
"first_slice_goal": "Bootstrap the done-workflow seed fixture from an explicit handoff.",
|
|
1482
|
+
"first_slice_non_goals": [],
|
|
1483
|
+
"implementation_surfaces": ["scripts/context-proposal-test.sh"],
|
|
1484
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
1485
|
+
"why_this_slice_first": "The done-workflow handoff test needs canonical state before it can be marked done."
|
|
1486
|
+
}
|
|
1487
|
+
messages = [
|
|
1488
|
+
{"role": "user", "content": "Prepare the done-workflow seed fixture and tell me when it is ready for /cook."},
|
|
1489
|
+
{"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```"},
|
|
1490
|
+
]
|
|
1491
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1492
|
+
PY
|
|
1493
|
+
)"
|
|
1494
|
+
write_session_messages "$DONE_SEED_SESSION" "$HANDOFF_ROOT_DONE" "$DONE_SEED_MESSAGES"
|
|
1495
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
1496
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1497
|
+
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"
|
|
1498
|
+
mark_done
|
|
1499
|
+
|
|
1500
|
+
HANDOFF_SESSION_DONE="$TMPDIR/handoff-session-done.jsonl"
|
|
1501
|
+
HANDOFF_SNAPSHOT_DONE="$TMPDIR/handoff-proposal-done.json"
|
|
1502
|
+
HANDOFF_MESSAGES_DONE="$(python3 - <<'PY'
|
|
1503
|
+
import json
|
|
1504
|
+
capsule = {
|
|
1505
|
+
"kind": "cook_handoff",
|
|
1506
|
+
"source": "primary_agent",
|
|
1507
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1508
|
+
"source_turn_id": "m0002",
|
|
1509
|
+
"mission": "Reopen the login redirect work for the callback edge case.",
|
|
1510
|
+
"scope": [
|
|
1511
|
+
"Handle the callback edge case in the redirect logic.",
|
|
1512
|
+
"Keep the finished workflow as historical context only."
|
|
1513
|
+
],
|
|
1514
|
+
"constraints": [
|
|
1515
|
+
"Do not turn done-state suppression into the startup mission."
|
|
1516
|
+
],
|
|
1517
|
+
"acceptance": [
|
|
1518
|
+
"Add a regression test for the callback edge case."
|
|
1519
|
+
],
|
|
1520
|
+
"risks": [
|
|
1521
|
+
"Done-state context could override the new mission if the handoff is ignored."
|
|
1522
|
+
],
|
|
1523
|
+
"notes": [
|
|
1524
|
+
"This is a fresh implementation round, not a summary of the finished workflow."
|
|
1525
|
+
],
|
|
1526
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1527
|
+
"first_slice_goal": "Patch the callback edge case and cover it with a focused regression test.",
|
|
1528
|
+
"first_slice_non_goals": [
|
|
1529
|
+
"Do not turn done-state suppression into the startup mission."
|
|
1530
|
+
],
|
|
1531
|
+
"implementation_surfaces": [
|
|
1532
|
+
"src/auth/redirect.ts",
|
|
1533
|
+
"tests/auth/redirect-edge.spec.ts"
|
|
1534
|
+
],
|
|
1535
|
+
"verification_commands": [
|
|
1536
|
+
"npm test -- redirect-edge.spec.ts"
|
|
1537
|
+
],
|
|
1538
|
+
"why_this_slice_first": "The new callback edge case is the smallest fresh implementation slice after the prior round closed.",
|
|
1539
|
+
"task_type": "completion-workflow",
|
|
1540
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
1541
|
+
"why_cook_now": "A new implementation-ready edge case was identified after the previous round closed."
|
|
1542
|
+
}
|
|
1543
|
+
messages = [
|
|
1544
|
+
{"role": "user", "content": "The previous round is done, but there is a fresh callback edge case to implement."},
|
|
1545
|
+
{"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```"},
|
|
1546
|
+
]
|
|
1547
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1548
|
+
PY
|
|
1549
|
+
)"
|
|
1550
|
+
write_session_messages "$HANDOFF_SESSION_DONE" "$HANDOFF_ROOT_DONE" "$HANDOFF_MESSAGES_DONE"
|
|
1551
|
+
|
|
1552
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
1553
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_DONE" \
|
|
1554
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1555
|
+
pi --session "$HANDOFF_SESSION_DONE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-done.out" 2>"$TMPDIR/pi-completion-handoff-done.err"
|
|
1556
|
+
|
|
1557
|
+
python3 - "$HANDOFF_SNAPSHOT_DONE" <<'PY'
|
|
1558
|
+
import json
|
|
1559
|
+
import sys
|
|
1560
|
+
from pathlib import Path
|
|
1561
|
+
|
|
1562
|
+
snapshot = json.loads(Path(sys.argv[1]).read_text())
|
|
1563
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
1564
|
+
|
|
1565
|
+
assert snapshot['source'] == 'handoff_capsule', 'done-workflow handoff should still use the explicit handoff capsule'
|
|
1566
|
+
assert snapshot['mission'] == 'Reopen the login redirect work for the callback edge case.', 'done-workflow handoff should preserve the fresh mission'
|
|
1567
|
+
assert state['mission_anchor'] == 'Reopen the login redirect work for the callback edge case.', 'done-workflow handoff should override done-state suppression with the fresh mission'
|
|
1568
|
+
assert state['continuation_policy'] == 'continue', 'done-workflow handoff should reopen canonical workflow state for the new round'
|
|
1569
|
+
assert state['advisory_startup_brief']['source'] == 'primary_agent_handoff', 'done-workflow handoff should preserve the handoff advisory source'
|
|
1570
|
+
assert 'First slice goal: Patch the callback edge case and cover it with a focused regression test.' in state['advisory_startup_brief']['notes'], 'done-workflow handoff should preserve first_slice_goal in advisory notes'
|
|
1571
|
+
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'
|
|
1572
|
+
PY
|
|
1573
|
+
|
|
1574
|
+
# Stale handoff: later discussion should invalidate the older handoff capsule and fail closed instead of falling back to newer discussion.
|
|
1575
|
+
HANDOFF_ROOT_STALE="$TMPDIR/handoff-root-stale"
|
|
1576
|
+
mkdir -p "$HANDOFF_ROOT_STALE"
|
|
1577
|
+
cd "$HANDOFF_ROOT_STALE"
|
|
1578
|
+
git init -q
|
|
1579
|
+
|
|
1580
|
+
HANDOFF_SESSION_STALE="$TMPDIR/handoff-session-stale.jsonl"
|
|
1581
|
+
HANDOFF_SNAPSHOT_STALE="$TMPDIR/handoff-proposal-stale.json"
|
|
1582
|
+
HANDOFF_MESSAGES_STALE="$(python3 - <<'PY'
|
|
1583
|
+
import json
|
|
1584
|
+
capsule = {
|
|
1585
|
+
"kind": "cook_handoff",
|
|
1586
|
+
"source": "primary_agent",
|
|
1587
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1588
|
+
"source_turn_id": "m0002",
|
|
1589
|
+
"mission": "Fix the original login redirect callback behavior.",
|
|
1590
|
+
"scope": ["Update the original callback redirect logic."],
|
|
1591
|
+
"constraints": ["Do not refactor the auth stack."],
|
|
1592
|
+
"acceptance": ["Add the original callback regression test."],
|
|
1593
|
+
"risks": [],
|
|
1594
|
+
"notes": [],
|
|
1595
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1596
|
+
"first_slice_goal": "Ship the original login callback follow-up.",
|
|
1597
|
+
"first_slice_non_goals": ["Do not refactor the auth stack."],
|
|
1598
|
+
"implementation_surfaces": ["src/auth/login-redirect.ts"],
|
|
1599
|
+
"verification_commands": ["npm test -- login-redirect.spec.ts"],
|
|
1600
|
+
"why_this_slice_first": "The original callback follow-up was the first bounded implementation slice before later discussion replaced it."
|
|
1601
|
+
}
|
|
1602
|
+
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."
|
|
1603
|
+
messages = [
|
|
1604
|
+
{"role": "user", "content": "Please plan the login redirect follow-up."},
|
|
1605
|
+
{"role": "assistant", "content": "Run /cook if you want to start the original follow-up.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
1606
|
+
{"role": "user", "content": newer_discussion},
|
|
1607
|
+
]
|
|
1608
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1609
|
+
PY
|
|
1610
|
+
)"
|
|
1611
|
+
write_session_messages "$HANDOFF_SESSION_STALE" "$HANDOFF_ROOT_STALE" "$HANDOFF_MESSAGES_STALE"
|
|
1612
|
+
|
|
1613
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
1614
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
1615
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_STALE" \
|
|
1616
|
+
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
1617
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1618
|
+
pi --session "$HANDOFF_SESSION_STALE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-stale.out" 2>"$TMPDIR/pi-completion-handoff-stale.err"
|
|
1619
|
+
|
|
1620
|
+
python3 - "$HANDOFF_SNAPSHOT_STALE" "$TMPDIR/pi-completion-handoff-stale.out" "$TMPDIR/pi-completion-handoff-stale.err" <<'PY'
|
|
1621
|
+
import sys
|
|
1622
|
+
from pathlib import Path
|
|
1623
|
+
|
|
1624
|
+
snapshot = Path(sys.argv[1])
|
|
1625
|
+
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
1626
|
+
|
|
1627
|
+
assert not snapshot.exists(), 'stale handoff should not emit a startup proposal snapshot'
|
|
1628
|
+
assert not Path('.agent').exists(), 'stale handoff should fail closed without writing canonical state'
|
|
1629
|
+
assert 'primary-agent handoff step could not prepare a concrete startup handoff' in output, 'stale handoff should fail closed when the synthesized handoff step produces nothing'
|
|
1630
|
+
PY
|
|
1631
|
+
|
|
1632
|
+
# Negative handoff rationale: a non-startable capsule must not become the startup mission.
|
|
1633
|
+
HANDOFF_ROOT_NEGATIVE="$TMPDIR/handoff-root-negative"
|
|
1634
|
+
mkdir -p "$HANDOFF_ROOT_NEGATIVE"
|
|
1635
|
+
cd "$HANDOFF_ROOT_NEGATIVE"
|
|
1636
|
+
git init -q
|
|
1637
|
+
|
|
1638
|
+
HANDOFF_SESSION_NEGATIVE="$TMPDIR/handoff-session-negative.jsonl"
|
|
1639
|
+
HANDOFF_SNAPSHOT_NEGATIVE="$TMPDIR/handoff-proposal-negative.json"
|
|
1640
|
+
HANDOFF_MESSAGES_NEGATIVE="$(python3 - <<'PY'
|
|
1641
|
+
import json
|
|
1642
|
+
capsule = {
|
|
1643
|
+
"kind": "cook_handoff",
|
|
1644
|
+
"source": "primary_agent",
|
|
1645
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1646
|
+
"source_turn_id": "m0002",
|
|
1647
|
+
"mission": "Do not reopen implementation for the finished workflow.",
|
|
1648
|
+
"scope": ["Keep the old workflow closed."],
|
|
1649
|
+
"constraints": ["Do not start repo changes."],
|
|
1650
|
+
"acceptance": ["Explain that the finished workflow should stay closed."],
|
|
1651
|
+
"risks": [],
|
|
1652
|
+
"notes": [],
|
|
1653
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1654
|
+
"first_slice_goal": "Keep the finished workflow closed.",
|
|
1655
|
+
"first_slice_non_goals": ["Do not start repo changes."],
|
|
1656
|
+
"implementation_surfaces": ["docs/workflow-status.md"],
|
|
1657
|
+
"verification_commands": ["npm test -- workflow-status"],
|
|
1658
|
+
"why_this_slice_first": "This is the only bounded next step being proposed, even though the mission itself is invalid."
|
|
1659
|
+
}
|
|
1660
|
+
messages = [
|
|
1661
|
+
{"role": "user", "content": "Should we reopen the finished workflow?"},
|
|
1662
|
+
{"role": "assistant", "content": "Do not reopen it directly.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
1663
|
+
]
|
|
1664
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1665
|
+
PY
|
|
1666
|
+
)"
|
|
1667
|
+
write_session_messages "$HANDOFF_SESSION_NEGATIVE" "$HANDOFF_ROOT_NEGATIVE" "$HANDOFF_MESSAGES_NEGATIVE"
|
|
1668
|
+
|
|
1669
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
1670
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_NEGATIVE" \
|
|
1671
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1672
|
+
pi --session "$HANDOFF_SESSION_NEGATIVE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-negative.out" 2>"$TMPDIR/pi-completion-handoff-negative.err"
|
|
1673
|
+
|
|
1674
|
+
python3 - "$HANDOFF_SNAPSHOT_NEGATIVE" "$TMPDIR/pi-completion-handoff-negative.out" "$TMPDIR/pi-completion-handoff-negative.err" <<'PY'
|
|
1675
|
+
import sys
|
|
1676
|
+
from pathlib import Path
|
|
1677
|
+
|
|
1678
|
+
snapshot = Path(sys.argv[1])
|
|
1679
|
+
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
556
1680
|
|
|
557
|
-
assert
|
|
558
|
-
assert
|
|
559
|
-
assert
|
|
560
|
-
assert state['next_mandatory_role'] == 'completion-regrounder', state
|
|
561
|
-
assert startup_plan['mission_anchor'] == mission, startup_plan
|
|
562
|
-
assert startup_plan['source'] == 'deferred_primary_agent_handoff', startup_plan
|
|
563
|
-
assert plan['mission_anchor'] == mission, plan
|
|
564
|
-
assert active['mission_anchor'] == mission, active
|
|
565
|
-
assert 'Started a new completion workflow round and recorded the approved startup plan' in output, output
|
|
1681
|
+
assert not snapshot.exists(), 'negative handoff rationale should not emit a startup proposal snapshot'
|
|
1682
|
+
assert not Path('.agent').exists(), 'negative handoff rationale should fail closed without writing canonical state'
|
|
1683
|
+
assert '/cook failed closed' in output, 'negative handoff rationale should fail closed instead of becoming the startup mission'
|
|
566
1684
|
PY
|
|
567
1685
|
|
|
568
|
-
grep -q 'export function assessCookHandoffText' "$PKG_ROOT/extensions/completion/proposal.ts"
|
|
569
1686
|
grep -q 'export async function deriveCookContextProposalFromRecentDiscussion' "$PKG_ROOT/extensions/completion/proposal.ts"
|
|
570
|
-
grep -q 'export
|
|
1687
|
+
grep -q 'export function extractLatestCookHandoffProposal' "$PKG_ROOT/extensions/completion/proposal.ts"
|
|
1688
|
+
grep -q 'export function parseContextProposalAnalystOutput' "$PKG_ROOT/extensions/completion/proposal.ts"
|
|
1689
|
+
grep -q 'export function buildContextProposalConfirmationLayout' "$PKG_ROOT/extensions/completion/prompt-surfaces.ts"
|
|
1690
|
+
grep -q 'export function buildEvaluationRoleContextLines' "$PKG_ROOT/extensions/completion/prompt-surfaces.ts"
|
|
571
1691
|
|
|
572
|
-
echo "context proposal test passed: $
|
|
1692
|
+
echo "context proposal test passed: $ROOT"
|