@linimin/pi-letscook 0.1.66 → 0.1.68
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 +4 -0
- package/.agent/verify_completion_control_plane.sh +34 -0
- package/CHANGELOG.md +17 -0
- package/README.md +26 -21
- package/agents/completion-bootstrapper.md +2 -1
- package/agents/completion-regrounder.md +16 -10
- package/extensions/completion/driver.ts +85 -37
- package/extensions/completion/index.ts +54 -41
- package/extensions/completion/prompt-surfaces.ts +158 -17
- package/extensions/completion/proposal.ts +61 -26
- package/extensions/completion/role-runner.ts +15 -10
- package/extensions/completion/state-store.ts +87 -2
- package/extensions/completion/types.ts +3 -0
- package/package.json +1 -1
- package/scripts/active-slice-contract-test.sh +7 -12
- package/scripts/canonical-evidence-artifact-test.sh +55 -15
- package/scripts/context-proposal-test.sh +307 -1427
- package/scripts/refocus-test.sh +185 -459
- package/scripts/release-check.sh +13 -11
- package/scripts/role-runner-contract-test.sh +2 -2
- package/scripts/smoke-test.sh +76 -21
- package/skills/completion-protocol/SKILL.md +9 -3
- package/skills/completion-protocol/references/completion.md +37 -2
- package/skills/cook-handoff-boundary/SKILL.md +18 -17
|
@@ -90,6 +90,22 @@ 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
|
+
|
|
93
109
|
mark_done() {
|
|
94
110
|
python3 - <<'PY'
|
|
95
111
|
import json
|
|
@@ -142,25 +158,29 @@ active_path.write_text(json.dumps(active, indent=2) + '\n')
|
|
|
142
158
|
PY
|
|
143
159
|
}
|
|
144
160
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
161
|
+
snapshot_tracked() {
|
|
162
|
+
local baseline_path="$1"
|
|
163
|
+
python3 - "$baseline_path" <<'PY'
|
|
164
|
+
import json
|
|
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
|
+
}
|
|
149
181
|
|
|
150
|
-
|
|
151
|
-
|
|
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'
|
|
182
|
+
STARTUP_DISCUSSION=$'Remove the completion status line while keeping the completion widget and keep the startup confirm-first.'
|
|
183
|
+
STARTUP_HANDOFF="$(python3 - <<'PY'
|
|
164
184
|
import json
|
|
165
185
|
capsule = {
|
|
166
186
|
"kind": "cook_handoff",
|
|
@@ -171,1522 +191,382 @@ capsule = {
|
|
|
171
191
|
"Suppress the widget while a completion role is active."
|
|
172
192
|
],
|
|
173
193
|
"constraints": [
|
|
174
|
-
"Do not reintroduce
|
|
194
|
+
"Do not reintroduce another completion status surface."
|
|
175
195
|
],
|
|
176
196
|
"acceptance": [
|
|
177
197
|
"Update README to match the shipped behavior.",
|
|
178
198
|
"Keep observability regression coverage truthful."
|
|
179
199
|
],
|
|
180
200
|
"risks": [],
|
|
181
|
-
"notes": ["Generated by
|
|
201
|
+
"notes": ["Generated by same-entry primary-agent startup-plan synthesis."],
|
|
182
202
|
"handoff_kind": "implementation_workflow_handoff",
|
|
183
|
-
"first_slice_goal": "Remove the completion status line while preserving
|
|
184
|
-
"first_slice_non_goals": ["Do not
|
|
203
|
+
"first_slice_goal": "Remove the completion status line while preserving widget behavior.",
|
|
204
|
+
"first_slice_non_goals": ["Do not add a replacement status surface."],
|
|
185
205
|
"implementation_surfaces": ["extensions/completion/index.ts", "README.md"],
|
|
186
206
|
"verification_commands": ["npm run smoke-test"],
|
|
187
|
-
"why_this_slice_first": "
|
|
207
|
+
"why_this_slice_first": "The startup mission is already concrete and bounded enough to begin workflow planning.",
|
|
188
208
|
"task_type": "completion-workflow",
|
|
189
209
|
"evaluation_profile": "completion-rubric-v1",
|
|
190
|
-
"why_cook_now": "The user explicitly chose workflow mode for this bounded implementation
|
|
210
|
+
"why_cook_now": "The user explicitly chose workflow mode for this bounded implementation mission."
|
|
191
211
|
}
|
|
192
212
|
print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
|
|
193
213
|
PY
|
|
194
214
|
)"
|
|
195
|
-
|
|
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'
|
|
200
|
-
import json
|
|
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'
|
|
213
|
-
PY
|
|
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'
|
|
215
|
+
MATCHING_HANDOFF="$(python3 - <<'PY'
|
|
221
216
|
import json
|
|
222
217
|
capsule = {
|
|
223
218
|
"kind": "cook_handoff",
|
|
224
219
|
"source": "primary_agent",
|
|
225
|
-
"
|
|
226
|
-
"
|
|
227
|
-
"
|
|
228
|
-
"
|
|
229
|
-
|
|
230
|
-
|
|
220
|
+
"mission": "Remove the completion status line while keeping the completion widget.",
|
|
221
|
+
"scope": ["Keep the widget behavior aligned with the current mission."],
|
|
222
|
+
"constraints": ["Do not replace the current mission."],
|
|
223
|
+
"acceptance": [
|
|
224
|
+
"Keep the non-running completion widget visible while no role is active.",
|
|
225
|
+
"Verify with npm run smoke-test that active-role suppression still works."
|
|
226
|
+
],
|
|
231
227
|
"risks": [],
|
|
232
228
|
"notes": [],
|
|
233
229
|
"handoff_kind": "implementation_workflow_handoff",
|
|
234
|
-
"first_slice_goal": "
|
|
230
|
+
"first_slice_goal": "Continue the current mission without changing it.",
|
|
235
231
|
"first_slice_non_goals": [],
|
|
236
|
-
"implementation_surfaces": ["
|
|
237
|
-
"verification_commands": ["npm run
|
|
238
|
-
"why_this_slice_first": "
|
|
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."
|
|
239
238
|
}
|
|
240
|
-
|
|
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))
|
|
239
|
+
print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
|
|
244
240
|
PY
|
|
245
241
|
)"
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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'
|
|
242
|
+
NON_STARTABLE_HANDOFF="$(python3 - <<'PY'
|
|
243
|
+
import json
|
|
244
|
+
capsule = {
|
|
245
|
+
"kind": "cook_handoff",
|
|
246
|
+
"source": "primary_agent",
|
|
247
|
+
"mission": "Maybe think about workflow status later.",
|
|
248
|
+
"scope": ["Consider status changes eventually."],
|
|
249
|
+
"constraints": ["Do not commit to concrete repo work yet."],
|
|
250
|
+
"acceptance": ["Discuss possible approaches."],
|
|
251
|
+
"risks": [],
|
|
252
|
+
"notes": [],
|
|
253
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
254
|
+
"task_type": "completion-workflow",
|
|
255
|
+
"evaluation_profile": "completion-rubric-v1"
|
|
256
|
+
}
|
|
257
|
+
print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
|
|
284
258
|
PY
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
SESSION_ONE="$TMPDIR/session-one.jsonl"
|
|
288
|
-
HANDOFF_SNAPSHOT_ONE="$TMPDIR/context-proposal-explicit-startup.json"
|
|
289
|
-
HANDOFF_MESSAGES_ONE="$(python3 - <<'PY'
|
|
259
|
+
)"
|
|
260
|
+
NEXT_ROUND_HANDOFF="$(python3 - <<'PY'
|
|
290
261
|
import json
|
|
291
262
|
capsule = {
|
|
292
263
|
"kind": "cook_handoff",
|
|
293
264
|
"source": "primary_agent",
|
|
294
|
-
"
|
|
295
|
-
"source_turn_id": "m0002",
|
|
296
|
-
"mission": "Remove the completion status line while keeping the completion widget.",
|
|
265
|
+
"mission": "Start the next workflow round for widget follow-up docs.",
|
|
297
266
|
"scope": [
|
|
298
|
-
"
|
|
299
|
-
"
|
|
300
|
-
],
|
|
301
|
-
"constraints": [
|
|
302
|
-
"Do not reintroduce any other completion status surface."
|
|
267
|
+
"Document the widget-only status behavior for the next round.",
|
|
268
|
+
"Keep the next round confirm-first."
|
|
303
269
|
],
|
|
270
|
+
"constraints": ["Do not reopen the previous mission."],
|
|
304
271
|
"acceptance": [
|
|
305
|
-
"
|
|
306
|
-
"Keep
|
|
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."
|
|
272
|
+
"Record the next round as a new canonical mission.",
|
|
273
|
+
"Keep startup-plan persistence truthful for the next round."
|
|
313
274
|
],
|
|
275
|
+
"risks": [],
|
|
276
|
+
"notes": [],
|
|
314
277
|
"handoff_kind": "implementation_workflow_handoff",
|
|
315
|
-
"first_slice_goal": "
|
|
316
|
-
"first_slice_non_goals": [
|
|
317
|
-
|
|
318
|
-
],
|
|
319
|
-
"
|
|
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.",
|
|
278
|
+
"first_slice_goal": "Reset canonical state for the next widget-docs mission.",
|
|
279
|
+
"first_slice_non_goals": [],
|
|
280
|
+
"implementation_surfaces": ["README.md", ".agent/startup-plan.json"],
|
|
281
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
282
|
+
"why_this_slice_first": "The next round must be anchored before later slice derivation.",
|
|
327
283
|
"task_type": "completion-workflow",
|
|
328
284
|
"evaluation_profile": "completion-rubric-v1",
|
|
329
|
-
"why_cook_now": "The
|
|
285
|
+
"why_cook_now": "The previous workflow is done and the next round is concrete enough to start."
|
|
330
286
|
}
|
|
331
|
-
|
|
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))
|
|
287
|
+
print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
|
|
336
288
|
PY
|
|
337
289
|
)"
|
|
338
|
-
write_session_messages "$SESSION_ONE" "$ROOT" "$HANDOFF_MESSAGES_ONE"
|
|
339
290
|
|
|
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"
|
|
340
299
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
341
|
-
|
|
300
|
+
PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$STARTUP_HANDOFF" \
|
|
301
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$PROPOSAL_ONE" \
|
|
342
302
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
343
|
-
pi --session "$SESSION_ONE" -e "$PKG_ROOT" -p "/cook"
|
|
303
|
+
pi --session "$SESSION_ONE" -e "$PKG_ROOT" -p "/cook" \
|
|
304
|
+
>"$TMPDIR/startup-success.out" 2>"$TMPDIR/startup-success.err"
|
|
344
305
|
|
|
345
|
-
python3 - "$
|
|
306
|
+
python3 - "$PROPOSAL_ONE" "$TMPDIR/startup-success.out" "$TMPDIR/startup-success.err" <<'PY'
|
|
346
307
|
import json
|
|
347
308
|
import sys
|
|
348
309
|
from pathlib import Path
|
|
349
310
|
|
|
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())
|
|
358
311
|
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
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])
|
|
424
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
425
|
-
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
426
|
-
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
427
|
-
|
|
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'
|
|
441
|
-
PY
|
|
442
|
-
|
|
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])
|
|
312
|
+
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
547
313
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
548
|
-
|
|
549
|
-
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
314
|
+
startup_plan = json.loads(Path('.agent/startup-plan.json').read_text())
|
|
550
315
|
|
|
551
|
-
assert
|
|
552
|
-
assert
|
|
553
|
-
assert
|
|
554
|
-
assert
|
|
555
|
-
assert
|
|
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'
|
|
316
|
+
assert Path('.agent').exists(), 'same-entry primary-agent synthesis should scaffold canonical workflow state'
|
|
317
|
+
assert proposal['source'] == 'deferred_primary_agent_handoff', proposal
|
|
318
|
+
assert state['advisory_startup_brief']['source'] == 'deferred_primary_agent_handoff', state
|
|
319
|
+
assert startup_plan['source'] == 'deferred_primary_agent_handoff', startup_plan
|
|
320
|
+
assert 'Initialized completion control plane' in output, output
|
|
562
321
|
PY
|
|
563
322
|
|
|
564
|
-
#
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
323
|
+
# Case 2: assistant preview alone is ignored when same-entry synthesis is unavailable.
|
|
324
|
+
ROOT_TWO="$TMPDIR/repo-two"
|
|
325
|
+
SESSION_TWO="$TMPDIR/session-two.jsonl"
|
|
326
|
+
PROPOSAL_TWO="$TMPDIR/proposal-two.json"
|
|
327
|
+
mkdir -p "$ROOT_TWO"
|
|
328
|
+
cd "$ROOT_TWO"
|
|
329
|
+
git init -q
|
|
330
|
+
PREVIEW_MESSAGES="$(python3 - <<'PY'
|
|
572
331
|
import json
|
|
573
332
|
capsule = {
|
|
574
333
|
"kind": "cook_handoff",
|
|
575
334
|
"source": "primary_agent",
|
|
576
335
|
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
577
336
|
"source_turn_id": "m0002",
|
|
578
|
-
"mission": "
|
|
579
|
-
"scope": [
|
|
580
|
-
|
|
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
|
-
],
|
|
337
|
+
"mission": "Assistant preview should not start workflow by itself.",
|
|
338
|
+
"scope": ["Do not trust the old preview as approval-ready startup state."],
|
|
339
|
+
"constraints": ["Require same-entry synthesis."],
|
|
340
|
+
"acceptance": ["Fail closed without canonical state."],
|
|
588
341
|
"risks": [],
|
|
589
|
-
"notes": [
|
|
590
|
-
"This capsule is intentionally non-startable for active-workflow fail-closed coverage."
|
|
591
|
-
],
|
|
342
|
+
"notes": [],
|
|
592
343
|
"handoff_kind": "implementation_workflow_handoff",
|
|
593
|
-
"first_slice_goal": "
|
|
344
|
+
"first_slice_goal": "Prove preview-only startup is ignored.",
|
|
594
345
|
"first_slice_non_goals": [],
|
|
595
|
-
"implementation_surfaces": [
|
|
596
|
-
|
|
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"
|
|
346
|
+
"implementation_surfaces": ["scripts/context-proposal-test.sh"],
|
|
347
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
348
|
+
"why_this_slice_first": "Preview-only startup should no longer bootstrap workflow by itself."
|
|
604
349
|
}
|
|
605
350
|
messages = [
|
|
606
|
-
{"role": "user", "content": "
|
|
607
|
-
{"role": "assistant", "content": "
|
|
351
|
+
{"role": "user", "content": "Should this preview be enough to start workflow on its own?"},
|
|
352
|
+
{"role": "assistant", "content": "Preview only.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
608
353
|
]
|
|
609
354
|
print(json.dumps(messages, ensure_ascii=False))
|
|
610
355
|
PY
|
|
611
356
|
)"
|
|
612
|
-
write_session_messages "$
|
|
357
|
+
write_session_messages "$SESSION_TWO" "$ROOT_TWO" "$PREVIEW_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"
|
|
613
363
|
|
|
614
|
-
python3 - "$TMPDIR/
|
|
615
|
-
import json
|
|
364
|
+
python3 - "$PROPOSAL_TWO" "$TMPDIR/preview-ignored.out" "$TMPDIR/preview-ignored.err" <<'PY'
|
|
616
365
|
import sys
|
|
617
366
|
from pathlib import Path
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
}
|
|
626
|
-
Path(sys.argv[1]).write_text(json.dumps(tracked, indent=2) + '\n')
|
|
367
|
+
|
|
368
|
+
proposal = Path(sys.argv[1])
|
|
369
|
+
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
370
|
+
|
|
371
|
+
assert not Path('.agent').exists(), 'preview-only startup must fail closed without same-entry synthesis'
|
|
372
|
+
assert not proposal.exists(), 'preview-only startup must not emit a proposal snapshot'
|
|
373
|
+
assert '/cook failed closed because the startup-plan step could not prepare a concrete workflow startup plan from the current task context.' in output, output
|
|
627
374
|
PY
|
|
628
375
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
376
|
+
# Case 3: structured discussion alone no longer falls back when synthesis is unavailable.
|
|
377
|
+
ROOT_THREE="$TMPDIR/repo-three"
|
|
378
|
+
SESSION_THREE="$TMPDIR/session-three.jsonl"
|
|
379
|
+
PROPOSAL_THREE="$TMPDIR/proposal-three.json"
|
|
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" \
|
|
633
387
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
634
|
-
pi --session "$
|
|
388
|
+
pi --session "$SESSION_THREE" -e "$PKG_ROOT" -p "/cook" \
|
|
389
|
+
>"$TMPDIR/discussion-ignored.out" 2>"$TMPDIR/discussion-ignored.err"
|
|
635
390
|
|
|
636
|
-
python3 - "$
|
|
637
|
-
import json
|
|
391
|
+
python3 - "$PROPOSAL_THREE" "$TMPDIR/discussion-ignored.out" "$TMPDIR/discussion-ignored.err" <<'PY'
|
|
638
392
|
import sys
|
|
639
393
|
from pathlib import Path
|
|
640
394
|
|
|
641
|
-
|
|
642
|
-
|
|
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
|
-
}
|
|
395
|
+
proposal = Path(sys.argv[1])
|
|
396
|
+
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
655
397
|
|
|
656
|
-
assert
|
|
657
|
-
assert
|
|
658
|
-
assert
|
|
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'
|
|
398
|
+
assert not Path('.agent').exists(), 'structured discussion alone must fail closed without same-entry synthesis'
|
|
399
|
+
assert not proposal.exists(), 'structured discussion alone must not emit a proposal snapshot'
|
|
400
|
+
assert '/cook failed closed because the startup-plan step could not prepare a concrete workflow startup plan from the current task context.' in output, output
|
|
666
401
|
PY
|
|
667
402
|
|
|
668
|
-
#
|
|
669
|
-
|
|
670
|
-
|
|
403
|
+
# Case 4: non-startable synthesized startup plans fail closed with the dedicated same-entry message.
|
|
404
|
+
ROOT_FOUR="$TMPDIR/repo-four"
|
|
405
|
+
SESSION_FOUR="$TMPDIR/session-four.jsonl"
|
|
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"
|
|
671
416
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
import json
|
|
417
|
+
python3 - "$PROPOSAL_FOUR" "$TMPDIR/non-startable-startup.out" "$TMPDIR/non-startable-startup.err" <<'PY'
|
|
418
|
+
import sys
|
|
675
419
|
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"
|
|
688
420
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
421
|
+
proposal = Path(sys.argv[1])
|
|
422
|
+
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
423
|
+
|
|
424
|
+
assert not Path('.agent').exists(), 'non-startable synthesized startup must fail closed'
|
|
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" \
|
|
693
444
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
694
|
-
pi --session "$
|
|
445
|
+
pi --session "$SESSION_FIVE_MATCHING" -e "$PKG_ROOT" -p "/cook" \
|
|
446
|
+
>"$TMPDIR/matching-active.out" 2>"$TMPDIR/matching-active.err"
|
|
695
447
|
|
|
696
|
-
python3 - "$
|
|
448
|
+
python3 - "$ROUTING_FIVE" "$CHOOSER_FIVE" "$BASELINE_FIVE" <<'PY'
|
|
697
449
|
import json
|
|
698
450
|
import sys
|
|
699
451
|
from pathlib import Path
|
|
700
452
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
453
|
+
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
454
|
+
chooser = Path(sys.argv[2])
|
|
455
|
+
before = json.loads(Path(sys.argv[3]).read_text())
|
|
456
|
+
tracked = [
|
|
457
|
+
Path('.agent/mission.md'),
|
|
458
|
+
Path('.agent/profile.json'),
|
|
459
|
+
Path('.agent/state.json'),
|
|
460
|
+
Path('.agent/startup-plan.json'),
|
|
461
|
+
Path('.agent/startup-plan.md'),
|
|
462
|
+
Path('.agent/plan.json'),
|
|
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"
|
|
711
496
|
|
|
712
|
-
|
|
713
|
-
# verification evidence for already verified work.
|
|
714
|
-
python3 - <<'PY'
|
|
497
|
+
python3 - "$ROUTING_SIX" "$CHOOSER_SIX" "$PROPOSAL_SIX" "$BASELINE_SIX" "$TMPDIR/blocked-active.out" "$TMPDIR/blocked-active.err" <<'PY'
|
|
715
498
|
import json
|
|
499
|
+
import sys
|
|
716
500
|
from pathlib import Path
|
|
717
501
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
Path(
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
'
|
|
725
|
-
'
|
|
726
|
-
'
|
|
727
|
-
'
|
|
728
|
-
'
|
|
729
|
-
|
|
730
|
-
Path('.agent/
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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"
|
|
502
|
+
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
503
|
+
chooser = Path(sys.argv[2])
|
|
504
|
+
proposal = Path(sys.argv[3])
|
|
505
|
+
before = json.loads(Path(sys.argv[4]).read_text())
|
|
506
|
+
output = Path(sys.argv[5]).read_text() + Path(sys.argv[6]).read_text()
|
|
507
|
+
tracked = [
|
|
508
|
+
Path('.agent/mission.md'),
|
|
509
|
+
Path('.agent/profile.json'),
|
|
510
|
+
Path('.agent/state.json'),
|
|
511
|
+
Path('.agent/startup-plan.json'),
|
|
512
|
+
Path('.agent/startup-plan.md'),
|
|
513
|
+
Path('.agent/plan.json'),
|
|
514
|
+
Path('.agent/active-slice.json'),
|
|
515
|
+
Path('.agent/verification-evidence.json'),
|
|
516
|
+
]
|
|
517
|
+
after = {path.name: path.read_text() for path in tracked}
|
|
737
518
|
|
|
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.'
|
|
738
537
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_TWO_VERIFIED_SUPPRESS" \
|
|
538
|
+
PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$NEXT_ROUND_HANDOFF" \
|
|
539
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$PROPOSAL_SEVEN" \
|
|
742
540
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
743
|
-
pi --session "$
|
|
541
|
+
pi --session "$SESSION_SEVEN_NEXT" -e "$PKG_ROOT" -p "/cook" \
|
|
542
|
+
>"$TMPDIR/next-round.out" 2>"$TMPDIR/next-round.err"
|
|
744
543
|
|
|
745
|
-
python3 - "$TMPDIR/
|
|
544
|
+
python3 - "$PROPOSAL_SEVEN" "$TMPDIR/next-round.out" "$TMPDIR/next-round.err" <<'PY'
|
|
545
|
+
import json
|
|
746
546
|
import sys
|
|
747
547
|
from pathlib import Path
|
|
748
548
|
|
|
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
549
|
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
550
|
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
551
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
552
|
+
startup_plan = json.loads(Path('.agent/startup-plan.json').read_text())
|
|
553
|
+
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
554
|
+
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
555
|
+
mission = 'Start the next workflow round for widget follow-up docs.'
|
|
1564
556
|
|
|
1565
|
-
assert
|
|
1566
|
-
assert
|
|
1567
|
-
assert state['
|
|
1568
|
-
assert state['
|
|
1569
|
-
assert
|
|
1570
|
-
assert '
|
|
1571
|
-
assert
|
|
1572
|
-
|
|
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()
|
|
1680
|
-
|
|
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'
|
|
557
|
+
assert proposal['source'] == 'deferred_primary_agent_handoff', proposal
|
|
558
|
+
assert state['mission_anchor'] == mission, state
|
|
559
|
+
assert state['current_phase'] == 'reground', state
|
|
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
|
|
1684
566
|
PY
|
|
1685
567
|
|
|
568
|
+
grep -q 'export function assessCookHandoffText' "$PKG_ROOT/extensions/completion/proposal.ts"
|
|
1686
569
|
grep -q 'export async function deriveCookContextProposalFromRecentDiscussion' "$PKG_ROOT/extensions/completion/proposal.ts"
|
|
1687
|
-
grep -q 'export function
|
|
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"
|
|
570
|
+
grep -q 'export async function analyzeContextProposalWithAgent' "$PKG_ROOT/extensions/completion/role-runner.ts"
|
|
1691
571
|
|
|
1692
|
-
echo "context proposal test passed: $
|
|
572
|
+
echo "context proposal test passed: $PKG_ROOT"
|