@linimin/pi-letscook 0.1.68 → 0.1.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/README.md +2 -3
- package/.agent/verify_completion_control_plane.sh +21 -34
- package/CHANGELOG.md +9 -0
- package/README.md +23 -26
- package/agents/completion-bootstrapper.md +1 -2
- package/agents/completion-regrounder.md +10 -16
- package/extensions/completion/driver.ts +69 -136
- package/extensions/completion/index.ts +94 -81
- package/extensions/completion/policy-guards.ts +1 -1
- package/extensions/completion/prompt-surfaces.ts +17 -158
- package/extensions/completion/proposal.ts +26 -61
- package/extensions/completion/role-runner.ts +10 -15
- package/extensions/completion/state-store.ts +43 -85
- package/extensions/completion/types.ts +2 -3
- package/package.json +1 -1
- package/scripts/active-slice-contract-test.sh +21 -0
- package/scripts/canonical-evidence-artifact-test.sh +57 -65
- package/scripts/context-proposal-test.sh +1430 -310
- package/scripts/refocus-test.sh +459 -185
- package/scripts/release-check.sh +14 -15
- package/scripts/role-runner-contract-test.sh +2 -2
- package/scripts/smoke-test.sh +36 -78
- package/skills/completion-protocol/SKILL.md +8 -9
- package/skills/completion-protocol/references/completion.md +2 -37
- package/skills/cook-handoff-boundary/SKILL.md +19 -18
package/scripts/refocus-test.sh
CHANGED
|
@@ -47,31 +47,130 @@ with session_path.open('w', encoding='utf-8') as fh:
|
|
|
47
47
|
PY
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
local
|
|
52
|
-
local
|
|
53
|
-
local
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
50
|
+
write_session_messages() {
|
|
51
|
+
local session_path="$1"
|
|
52
|
+
local cwd="$2"
|
|
53
|
+
local messages_json="$3"
|
|
54
|
+
python3 - "$session_path" "$cwd" "$messages_json" <<'PY'
|
|
55
|
+
import json
|
|
56
|
+
import sys
|
|
57
|
+
from pathlib import Path
|
|
58
|
+
|
|
59
|
+
session_path = Path(sys.argv[1])
|
|
60
|
+
cwd = sys.argv[2]
|
|
61
|
+
messages = json.loads(sys.argv[3])
|
|
62
|
+
session_path.parent.mkdir(parents=True, exist_ok=True)
|
|
63
|
+
entries = [
|
|
64
|
+
{
|
|
65
|
+
"type": "session",
|
|
66
|
+
"version": 3,
|
|
67
|
+
"id": "11111111-1111-4111-8111-111111111111",
|
|
68
|
+
"timestamp": "2026-01-01T00:00:00.000Z",
|
|
69
|
+
"cwd": cwd,
|
|
70
|
+
},
|
|
71
|
+
]
|
|
72
|
+
parent_id = None
|
|
73
|
+
for index, message in enumerate(messages, start=1):
|
|
74
|
+
entry_id = f"m{index:04d}"
|
|
75
|
+
entries.append({
|
|
76
|
+
"type": "message",
|
|
77
|
+
"id": entry_id,
|
|
78
|
+
"parentId": parent_id,
|
|
79
|
+
"timestamp": f"2026-01-01T00:00:{index:02d}.000Z",
|
|
80
|
+
"message": {
|
|
81
|
+
"role": message["role"],
|
|
82
|
+
"content": message["content"],
|
|
83
|
+
"timestamp": 1767225600000 + index * 1000,
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
parent_id = entry_id
|
|
87
|
+
with session_path.open('w', encoding='utf-8') as fh:
|
|
88
|
+
for entry in entries:
|
|
89
|
+
fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
90
|
+
PY
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
cd "$TMPDIR"
|
|
94
|
+
git init -q
|
|
95
|
+
|
|
96
|
+
BOOTSTRAP_SESSION="$TMPDIR/session-bootstrap.jsonl"
|
|
97
|
+
BOOTSTRAP_MESSAGES="$(python3 - <<'PY'
|
|
98
|
+
import json
|
|
99
|
+
capsule = {
|
|
100
|
+
"kind": "cook_handoff",
|
|
101
|
+
"source": "primary_agent",
|
|
102
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
103
|
+
"source_turn_id": "m0002",
|
|
104
|
+
"mission": "Smoke-test mission.",
|
|
105
|
+
"scope": [
|
|
106
|
+
"Bootstrap a completion workflow for the refocus regression fixture."
|
|
107
|
+
],
|
|
108
|
+
"constraints": [
|
|
109
|
+
"Keep active-workflow refocus behavior under the explicit-handoff startup contract."
|
|
110
|
+
],
|
|
111
|
+
"acceptance": [
|
|
112
|
+
"Bootstrap canonical refocus-fixture state for the active-workflow regression.",
|
|
113
|
+
"Verify the refocus regression with npm run refocus-test."
|
|
114
|
+
],
|
|
115
|
+
"risks": [],
|
|
116
|
+
"notes": [
|
|
117
|
+
"Use explicit primary-agent handoff startup for the refocus regression fixture."
|
|
118
|
+
],
|
|
119
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
120
|
+
"first_slice_goal": "Bootstrap the refocus regression fixture from a fresh explicit handoff.",
|
|
121
|
+
"first_slice_non_goals": [],
|
|
122
|
+
"implementation_surfaces": [
|
|
123
|
+
"scripts/refocus-test.sh"
|
|
124
|
+
],
|
|
125
|
+
"verification_commands": [
|
|
126
|
+
"npm run refocus-test"
|
|
127
|
+
],
|
|
128
|
+
"why_this_slice_first": "The refocus regression fixture needs canonical state before active-workflow routing can be exercised.",
|
|
129
|
+
"task_type": "completion-workflow",
|
|
130
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
131
|
+
"why_cook_now": "The active-workflow refocus regression needs a fresh explicit startup boundary."
|
|
70
132
|
}
|
|
133
|
+
messages = [
|
|
134
|
+
{"role": "user", "content": "Prepare the refocus regression fixture and tell me when it is ready for /cook."},
|
|
135
|
+
{"role": "assistant", "content": "The refocus regression fixture is ready for /cook.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
136
|
+
]
|
|
137
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
138
|
+
PY
|
|
139
|
+
)"
|
|
140
|
+
write_session_messages "$BOOTSTRAP_SESSION" "$TMPDIR" "$BOOTSTRAP_MESSAGES"
|
|
141
|
+
|
|
142
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
143
|
+
pi --session "$BOOTSTRAP_SESSION" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-refocus-bootstrap.out" 2>"$TMPDIR/pi-completion-refocus-bootstrap.err" &
|
|
144
|
+
PI_PID=$!
|
|
145
|
+
for _ in $(seq 1 60); do
|
|
146
|
+
if [[ -f .agent/profile.json && -f .agent/state.json && -f .agent/plan.json && -f .agent/active-slice.json ]]; then
|
|
147
|
+
break
|
|
148
|
+
fi
|
|
149
|
+
sleep 1
|
|
150
|
+
done
|
|
151
|
+
if [[ ! -f .agent/profile.json || ! -f .agent/state.json || ! -f .agent/plan.json || ! -f .agent/active-slice.json ]]; then
|
|
152
|
+
echo "completion bootstrap did not materialize canonical files in time" >&2
|
|
153
|
+
cat "$TMPDIR/pi-completion-refocus-bootstrap.err" >&2 || true
|
|
154
|
+
kill "$PI_PID" >/dev/null 2>&1 || true
|
|
155
|
+
wait "$PI_PID" >/dev/null 2>&1 || true
|
|
156
|
+
exit 1
|
|
157
|
+
fi
|
|
158
|
+
kill "$PI_PID" >/dev/null 2>&1 || true
|
|
159
|
+
wait "$PI_PID" >/dev/null 2>&1 || true
|
|
71
160
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
161
|
+
INITIAL_MISSION="$(python3 - <<'PY'
|
|
162
|
+
import json
|
|
163
|
+
from pathlib import Path
|
|
164
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
165
|
+
print(state['mission_anchor'])
|
|
166
|
+
PY
|
|
167
|
+
)"
|
|
168
|
+
|
|
169
|
+
INLINE_REJECTION_ROUTING="$TMPDIR/inline-arg-routing.json"
|
|
170
|
+
INLINE_REJECTION_PROPOSAL="$TMPDIR/inline-arg-proposal.json"
|
|
171
|
+
INLINE_REJECTION_CHOOSER="$TMPDIR/inline-arg-chooser.json"
|
|
172
|
+
INLINE_REJECTION_BASELINE="$TMPDIR/inline-arg-before.json"
|
|
173
|
+
python3 - "$INLINE_REJECTION_BASELINE" <<'PY'
|
|
75
174
|
import json
|
|
76
175
|
import sys
|
|
77
176
|
from pathlib import Path
|
|
@@ -80,246 +179,421 @@ tracked = [
|
|
|
80
179
|
Path('.agent/mission.md'),
|
|
81
180
|
Path('.agent/profile.json'),
|
|
82
181
|
Path('.agent/state.json'),
|
|
83
|
-
Path('.agent/startup-plan.json'),
|
|
84
|
-
Path('.agent/startup-plan.md'),
|
|
85
182
|
Path('.agent/plan.json'),
|
|
86
183
|
Path('.agent/active-slice.json'),
|
|
87
184
|
Path('.agent/verification-evidence.json'),
|
|
88
185
|
]
|
|
89
186
|
Path(sys.argv[1]).write_text(json.dumps({path.name: path.read_text() for path in tracked}, indent=2) + '\n')
|
|
90
187
|
PY
|
|
91
|
-
}
|
|
92
188
|
|
|
93
|
-
|
|
94
|
-
|
|
189
|
+
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$INLINE_REJECTION_ROUTING" \
|
|
190
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$INLINE_REJECTION_PROPOSAL" \
|
|
191
|
+
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$INLINE_REJECTION_CHOOSER" \
|
|
192
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
193
|
+
pi -e "$PKG_ROOT" -p "/cook replacement mission that should stay in the main chat" \
|
|
194
|
+
>"$TMPDIR/pi-completion-refocus-inline-arg.out" 2>"$TMPDIR/pi-completion-refocus-inline-arg.err"
|
|
195
|
+
|
|
196
|
+
python3 - "$TMPDIR/pi-completion-refocus-inline-arg.out" "$TMPDIR/pi-completion-refocus-inline-arg.err" "$INLINE_REJECTION_ROUTING" "$INLINE_REJECTION_PROPOSAL" "$INLINE_REJECTION_CHOOSER" "$INITIAL_MISSION" "$INLINE_REJECTION_BASELINE" <<'PY'
|
|
197
|
+
import json
|
|
198
|
+
import sys
|
|
199
|
+
from pathlib import Path
|
|
200
|
+
|
|
201
|
+
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
202
|
+
routing = Path(sys.argv[3])
|
|
203
|
+
proposal = Path(sys.argv[4])
|
|
204
|
+
chooser = Path(sys.argv[5])
|
|
205
|
+
initial_mission = sys.argv[6]
|
|
206
|
+
before = json.loads(Path(sys.argv[7]).read_text())
|
|
207
|
+
tracked = [
|
|
208
|
+
Path('.agent/mission.md'),
|
|
209
|
+
Path('.agent/profile.json'),
|
|
210
|
+
Path('.agent/state.json'),
|
|
211
|
+
Path('.agent/plan.json'),
|
|
212
|
+
Path('.agent/active-slice.json'),
|
|
213
|
+
Path('.agent/verification-evidence.json'),
|
|
214
|
+
]
|
|
215
|
+
current_state = json.loads(before['state.json'])
|
|
216
|
+
assert current_state['mission_anchor'] == initial_mission, 'active /cook inline-args rejection should start from the current mission anchor'
|
|
217
|
+
assert not routing.exists(), 'active /cook inline-args rejection should not run active-workflow routing'
|
|
218
|
+
assert not proposal.exists(), 'active /cook inline-args rejection should not open final startup-brief confirmation'
|
|
219
|
+
assert not chooser.exists(), 'active /cook inline-args rejection should not open the existing-workflow chooser'
|
|
220
|
+
assert '/cook no longer accepts inline arguments.' in output, 'active /cook inline-args rejection should explain the bare-only entry contract'
|
|
221
|
+
after = {path.name: path.read_text() for path in tracked}
|
|
222
|
+
assert before == after, 'active /cook inline-args rejection should leave canonical files unchanged'
|
|
223
|
+
PY
|
|
224
|
+
|
|
225
|
+
SESSION_INITIAL_REFOCUS="$TMPDIR/session-initial-bare-refocus.jsonl"
|
|
226
|
+
INITIAL_REFOCUS_ROUTING="$TMPDIR/initial-bare-refocus-routing.json"
|
|
227
|
+
INITIAL_REFOCUS_MESSAGES="$(python3 - <<'PY'
|
|
95
228
|
import json
|
|
96
229
|
capsule = {
|
|
97
230
|
"kind": "cook_handoff",
|
|
98
231
|
"source": "primary_agent",
|
|
99
|
-
"
|
|
232
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
233
|
+
"source_turn_id": "m0002",
|
|
234
|
+
"mission": "Remove completion status line, keep widget.",
|
|
100
235
|
"scope": [
|
|
101
|
-
"
|
|
102
|
-
"Keep the initial workflow ready for later replacement checks."
|
|
236
|
+
"Replace the initial smoke-test workflow with the widget mission."
|
|
103
237
|
],
|
|
104
238
|
"constraints": [
|
|
105
|
-
"
|
|
239
|
+
"Keep the approval-only Start/Cancel refocus gate."
|
|
106
240
|
],
|
|
107
241
|
"acceptance": [
|
|
108
|
-
"
|
|
109
|
-
"Keep scripts/refocus-test.sh aligned with the shipped startup behavior."
|
|
242
|
+
"Rewrite canonical state only after the replacement mission is approved."
|
|
110
243
|
],
|
|
111
244
|
"risks": [],
|
|
112
245
|
"notes": [
|
|
113
|
-
"
|
|
246
|
+
"Use a fresh explicit primary-agent handoff for the active-workflow replacement."
|
|
114
247
|
],
|
|
115
248
|
"handoff_kind": "implementation_workflow_handoff",
|
|
116
|
-
"first_slice_goal": "
|
|
249
|
+
"first_slice_goal": "Replace the initial smoke-test workflow with the widget mission.",
|
|
117
250
|
"first_slice_non_goals": [],
|
|
118
|
-
"implementation_surfaces": [
|
|
119
|
-
|
|
120
|
-
|
|
251
|
+
"implementation_surfaces": [
|
|
252
|
+
"scripts/refocus-test.sh"
|
|
253
|
+
],
|
|
254
|
+
"verification_commands": [
|
|
255
|
+
"npm run refocus-test"
|
|
256
|
+
],
|
|
257
|
+
"why_this_slice_first": "The fresh explicit handoff is the only supported replacement entry while a workflow is active.",
|
|
121
258
|
"task_type": "completion-workflow",
|
|
122
259
|
"evaluation_profile": "completion-rubric-v1",
|
|
123
|
-
"why_cook_now": "
|
|
260
|
+
"why_cook_now": "A different active workflow is ready and explicitly handed off by the primary agent."
|
|
124
261
|
}
|
|
125
|
-
|
|
262
|
+
messages = [
|
|
263
|
+
{"role": "user", "content": "The smoke-test workflow is active, but a different replacement workflow may now be ready."},
|
|
264
|
+
{"role": "assistant", "content": "Use this fresh explicit handoff if you want /cook to replace the active workflow.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
265
|
+
]
|
|
266
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
126
267
|
PY
|
|
127
268
|
)"
|
|
128
|
-
|
|
129
|
-
|
|
269
|
+
write_session_messages "$SESSION_INITIAL_REFOCUS" "$TMPDIR" "$INITIAL_REFOCUS_MESSAGES"
|
|
270
|
+
|
|
271
|
+
PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
|
|
272
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
273
|
+
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$INITIAL_REFOCUS_ROUTING" \
|
|
274
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
275
|
+
pi --session "$SESSION_INITIAL_REFOCUS" -e "$PKG_ROOT" -p "/cook" \
|
|
276
|
+
>"$TMPDIR/pi-completion-refocus.out" 2>"$TMPDIR/pi-completion-refocus.err"
|
|
277
|
+
|
|
278
|
+
python3 - "$INITIAL_REFOCUS_ROUTING" <<'PY'
|
|
279
|
+
import json
|
|
280
|
+
import sys
|
|
281
|
+
from pathlib import Path
|
|
282
|
+
|
|
283
|
+
new_anchor = 'Remove completion status line, keep widget.'
|
|
284
|
+
expected_task_type = 'completion-workflow'
|
|
285
|
+
expected_eval_profile = 'completion-rubric-v1'
|
|
286
|
+
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
287
|
+
mission_text = Path('.agent/mission.md').read_text()
|
|
288
|
+
profile = json.loads(Path('.agent/profile.json').read_text())
|
|
289
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
290
|
+
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
291
|
+
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
292
|
+
|
|
293
|
+
assert new_anchor in mission_text, '.agent/mission.md did not update to the refocused mission anchor'
|
|
294
|
+
assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after refocus'
|
|
295
|
+
assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after refocus'
|
|
296
|
+
assert state['mission_anchor'] == new_anchor, 'state.json mission_anchor mismatch after refocus'
|
|
297
|
+
assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after refocus'
|
|
298
|
+
assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after refocus'
|
|
299
|
+
assert state['advisory_startup_brief']['mission'] == new_anchor, 'refocus should preserve the confirmed startup brief as advisory intake'
|
|
300
|
+
assert plan['mission_anchor'] == new_anchor, 'plan.json mission_anchor mismatch after refocus'
|
|
301
|
+
assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after refocus'
|
|
302
|
+
assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after refocus'
|
|
303
|
+
assert active['mission_anchor'] == new_anchor, 'active-slice.json mission_anchor mismatch after refocus'
|
|
304
|
+
assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after refocus'
|
|
305
|
+
assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after refocus'
|
|
306
|
+
assert state['current_phase'] == 'reground', 'state.json current_phase should reset to reground after refocus'
|
|
307
|
+
assert state['requires_reground'] is True, 'state.json requires_reground should be true after refocus'
|
|
308
|
+
assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should reset to completion-regrounder'
|
|
309
|
+
assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'continuation_reason should record the refocus'
|
|
310
|
+
assert plan['plan_basis'] == 'user_refocus', 'plan.json plan_basis should be user_refocus after refocus'
|
|
311
|
+
assert active['status'] == 'idle', 'active-slice.json status should reset to idle after refocus'
|
|
312
|
+
assert routing['mode'] == 'bare', 'supported refocus should use bare active-workflow routing mode'
|
|
313
|
+
assert 'explicitGoal' not in routing, 'supported bare refocus should not expose removed explicit-goal shim fields'
|
|
314
|
+
assert 'explicitGoalProvided' not in routing, 'supported bare refocus should not expose removed explicit-goal shim fields'
|
|
315
|
+
assert routing['action'] == 'refocus', 'supported bare /cook should classify as refocus when a fresh explicit handoff proposes a different mission'
|
|
316
|
+
assert routing['reason'] == 'fresh_explicit_handoff', 'supported bare /cook should record the explicit-handoff replacement reason'
|
|
317
|
+
assert routing['proposedMissionAnchor'] == new_anchor, 'explicit handoff routing snapshot should expose the replacement mission anchor'
|
|
318
|
+
assert routing['proposalSource'] == 'handoff_capsule', 'explicit handoff routing snapshot should preserve the handoff source'
|
|
319
|
+
PY
|
|
320
|
+
|
|
321
|
+
UPDATED_MISSION="$(python3 - <<'PY'
|
|
322
|
+
import json
|
|
323
|
+
from pathlib import Path
|
|
324
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
325
|
+
print(state['mission_anchor'])
|
|
326
|
+
PY
|
|
327
|
+
)"
|
|
328
|
+
|
|
329
|
+
if [[ "$INITIAL_MISSION" == "$UPDATED_MISSION" ]]; then
|
|
330
|
+
echo "expected mission anchor to change during supported bare refocus" >&2
|
|
331
|
+
exit 1
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
# Fresh explicit handoff replacements must still reach the chooser and final Start/Cancel gate while the
|
|
335
|
+
# workflow is active.
|
|
336
|
+
BARE_REFOCUS_MISSION='Exercise explicit active-workflow replacement coverage.'
|
|
337
|
+
BARE_REFOCUS_MESSAGES="$(python3 - <<'PY'
|
|
130
338
|
import json
|
|
131
339
|
capsule = {
|
|
132
340
|
"kind": "cook_handoff",
|
|
133
341
|
"source": "primary_agent",
|
|
134
|
-
"
|
|
342
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
343
|
+
"source_turn_id": "m0002",
|
|
344
|
+
"mission": "Exercise explicit active-workflow replacement coverage.",
|
|
135
345
|
"scope": [
|
|
136
|
-
"
|
|
137
|
-
"Keep
|
|
346
|
+
"Treat the active bare /cook request as an explicit replacement workflow.",
|
|
347
|
+
"Keep the replacement behind the existing approval-only Start/Cancel gate."
|
|
138
348
|
],
|
|
139
349
|
"constraints": [
|
|
140
|
-
"Do not
|
|
350
|
+
"Do not rewrite canonical state before the final Start confirmation."
|
|
141
351
|
],
|
|
142
352
|
"acceptance": [
|
|
143
|
-
"
|
|
144
|
-
"Keep refocus regression coverage aligned with same-entry synthesis behavior."
|
|
145
|
-
],
|
|
146
|
-
"risks": [
|
|
147
|
-
"Do not leave the old mission partially active after refocus."
|
|
353
|
+
"Add deterministic coverage proving the chooser and final approval path for this explicit replacement mission."
|
|
148
354
|
],
|
|
355
|
+
"risks": [],
|
|
149
356
|
"notes": [
|
|
150
|
-
"This replacement
|
|
357
|
+
"This replacement should come only from the fresh explicit handoff, not recent discussion inference."
|
|
151
358
|
],
|
|
152
359
|
"handoff_kind": "implementation_workflow_handoff",
|
|
153
|
-
"first_slice_goal": "
|
|
154
|
-
"first_slice_non_goals": [
|
|
155
|
-
"implementation_surfaces": [
|
|
156
|
-
|
|
157
|
-
|
|
360
|
+
"first_slice_goal": "Exercise the active-workflow explicit-handoff replacement path.",
|
|
361
|
+
"first_slice_non_goals": [],
|
|
362
|
+
"implementation_surfaces": [
|
|
363
|
+
"scripts/refocus-test.sh"
|
|
364
|
+
],
|
|
365
|
+
"verification_commands": [
|
|
366
|
+
"npm run refocus-test"
|
|
367
|
+
],
|
|
368
|
+
"why_this_slice_first": "The active workflow should only replace from a fresh explicit handoff.",
|
|
158
369
|
"task_type": "completion-workflow",
|
|
159
370
|
"evaluation_profile": "completion-rubric-v1",
|
|
160
|
-
"why_cook_now": "
|
|
371
|
+
"why_cook_now": "The primary agent explicitly handed off a replacement workflow while the current one is active."
|
|
161
372
|
}
|
|
162
|
-
|
|
373
|
+
messages = [
|
|
374
|
+
{"role": "user", "content": "The current workflow is active, but there is a fresh explicit replacement handoff ready."},
|
|
375
|
+
{"role": "assistant", "content": "Use this fresh explicit handoff if you want /cook to replace the active workflow.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
376
|
+
]
|
|
377
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
163
378
|
PY
|
|
164
379
|
)"
|
|
165
380
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
CHOOSER_ONE="$TMPDIR/chooser-one.json"
|
|
172
|
-
PROPOSAL_ONE="$TMPDIR/proposal-one.json"
|
|
173
|
-
BASELINE_ONE="$TMPDIR/baseline-one.json"
|
|
174
|
-
bootstrap_active_workflow "$ROOT_ONE" "$SESSION_ONE_BOOTSTRAP" "$BOOTSTRAP_DISCUSSION" "$BOOTSTRAP_HANDOFF"
|
|
175
|
-
cd "$ROOT_ONE"
|
|
176
|
-
snapshot_tracked "$BASELINE_ONE"
|
|
177
|
-
write_session "$SESSION_ONE_REFOCUS" "$ROOT_ONE" "$REPLACEMENT_DISCUSSION"
|
|
178
|
-
|
|
179
|
-
PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$REPLACEMENT_HANDOFF" \
|
|
381
|
+
SESSION_BARE_CHOOSER_CANCEL="$TMPDIR/session-bare-chooser-cancel.jsonl"
|
|
382
|
+
BARE_CHOOSER_SNAPSHOT="$TMPDIR/bare-existing-workflow-chooser.json"
|
|
383
|
+
BARE_ROUTING_CHOOSER_CANCEL="$TMPDIR/bare-routing-chooser-cancel.json"
|
|
384
|
+
write_session_messages "$SESSION_BARE_CHOOSER_CANCEL" "$TMPDIR" "$BARE_REFOCUS_MESSAGES"
|
|
385
|
+
|
|
180
386
|
PI_COMPLETION_EXISTING_WORKFLOW_ACTION=cancel \
|
|
387
|
+
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$BARE_CHOOSER_SNAPSHOT" \
|
|
388
|
+
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$BARE_ROUTING_CHOOSER_CANCEL" \
|
|
181
389
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$PROPOSAL_ONE" \
|
|
185
|
-
pi --session "$SESSION_ONE_REFOCUS" -e "$PKG_ROOT" -p "/cook" \
|
|
186
|
-
>"$TMPDIR/refocus-one.out" 2>"$TMPDIR/refocus-one.err"
|
|
390
|
+
pi --session "$SESSION_BARE_CHOOSER_CANCEL" -e "$PKG_ROOT" -p "/cook" \
|
|
391
|
+
>"$TMPDIR/pi-completion-bare-chooser-cancel.out" 2>"$TMPDIR/pi-completion-bare-chooser-cancel.err"
|
|
187
392
|
|
|
188
|
-
python3 - "$
|
|
393
|
+
python3 - "$BARE_CHOOSER_SNAPSHOT" "$BARE_ROUTING_CHOOSER_CANCEL" "$TMPDIR/pi-completion-bare-chooser-cancel.out" "$TMPDIR/pi-completion-bare-chooser-cancel.err" "$UPDATED_MISSION" "$BARE_REFOCUS_MISSION" <<'PY'
|
|
189
394
|
import json
|
|
190
395
|
import sys
|
|
191
396
|
from pathlib import Path
|
|
192
397
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
Path('.agent/state.json'),
|
|
202
|
-
Path('.agent/startup-plan.json'),
|
|
203
|
-
Path('.agent/startup-plan.md'),
|
|
204
|
-
Path('.agent/plan.json'),
|
|
205
|
-
Path('.agent/active-slice.json'),
|
|
206
|
-
Path('.agent/verification-evidence.json'),
|
|
207
|
-
]
|
|
208
|
-
after = {path.name: path.read_text() for path in tracked}
|
|
398
|
+
chooser = json.loads(Path(sys.argv[1]).read_text())
|
|
399
|
+
routing = json.loads(Path(sys.argv[2]).read_text())
|
|
400
|
+
output = Path(sys.argv[3]).read_text() + Path(sys.argv[4]).read_text()
|
|
401
|
+
updated_mission = sys.argv[5]
|
|
402
|
+
replacement_mission = sys.argv[6]
|
|
403
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
404
|
+
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
405
|
+
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
209
406
|
|
|
210
|
-
assert
|
|
211
|
-
assert
|
|
212
|
-
assert
|
|
213
|
-
assert
|
|
214
|
-
assert not
|
|
215
|
-
assert
|
|
216
|
-
assert '
|
|
407
|
+
assert state['mission_anchor'] == updated_mission, 'chooser cancel should keep the current mission anchor'
|
|
408
|
+
assert plan['mission_anchor'] == updated_mission, 'chooser cancel should keep plan.json unchanged'
|
|
409
|
+
assert active['mission_anchor'] == updated_mission, 'chooser cancel should keep active-slice.json unchanged'
|
|
410
|
+
assert routing['mode'] == 'bare', 'bare /cook should snapshot bare active-workflow routing mode'
|
|
411
|
+
assert 'explicitGoal' not in routing, 'bare chooser routing should not expose removed explicit-goal shim fields'
|
|
412
|
+
assert 'explicitGoalProvided' not in routing, 'bare chooser routing should not expose removed explicit-goal shim fields'
|
|
413
|
+
assert routing['action'] == 'refocus', 'fresh explicit replacement handoff should classify active bare /cook as refocus'
|
|
414
|
+
assert routing['reason'] == 'fresh_explicit_handoff', 'fresh explicit replacement handoff should record the explicit-handoff reason'
|
|
415
|
+
assert routing['currentMissionAnchor'] == updated_mission, 'explicit-handoff routing should keep the current mission anchor until the user approves replacement'
|
|
416
|
+
assert routing['proposedMissionAnchor'] == replacement_mission, 'explicit-handoff routing should expose the proposed replacement mission'
|
|
417
|
+
assert routing['proposalSource'] == 'handoff_capsule', 'explicit-handoff routing should preserve the handoff source'
|
|
418
|
+
assert chooser['title'].startswith('Existing completion workflow found'), 'bare chooser snapshot should describe the existing-workflow prompt'
|
|
419
|
+
assert chooser['choices'][0].startswith('Continue current workflow'), 'bare chooser should keep the continue option'
|
|
420
|
+
assert chooser['choices'][1].startswith('Start new workflow from explicit primary-agent handoff'), 'bare chooser should offer the explicit-handoff replacement option'
|
|
421
|
+
assert 'Start/Cancel confirmation' in chooser['choices'][1], 'bare chooser should mention the approval-only replacement confirmation'
|
|
422
|
+
assert chooser['choices'][2].startswith('Cancel'), 'bare chooser should keep the cancel option'
|
|
423
|
+
assert 'Discuss changes in the main chat and rerun /cook.' in output, 'bare chooser cancel should redirect users back to the main chat and rerun /cook'
|
|
217
424
|
PY
|
|
218
425
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
CHOOSER_TWO="$TMPDIR/chooser-two.json"
|
|
225
|
-
PROPOSAL_TWO="$TMPDIR/proposal-two.json"
|
|
226
|
-
BASELINE_TWO="$TMPDIR/baseline-two.json"
|
|
227
|
-
bootstrap_active_workflow "$ROOT_TWO" "$SESSION_TWO_BOOTSTRAP" "$BOOTSTRAP_DISCUSSION" "$BOOTSTRAP_HANDOFF"
|
|
228
|
-
cd "$ROOT_TWO"
|
|
229
|
-
snapshot_tracked "$BASELINE_TWO"
|
|
230
|
-
write_session "$SESSION_TWO_REFOCUS" "$ROOT_TWO" "$REPLACEMENT_DISCUSSION"
|
|
231
|
-
|
|
232
|
-
PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$REPLACEMENT_HANDOFF" \
|
|
426
|
+
SESSION_BARE_FINAL_CANCEL="$TMPDIR/session-bare-final-cancel.jsonl"
|
|
427
|
+
BARE_ROUTING_FINAL_CANCEL="$TMPDIR/bare-routing-final-cancel.json"
|
|
428
|
+
BARE_PROPOSAL_CANCEL="$TMPDIR/bare-replacement-proposal-cancel.json"
|
|
429
|
+
write_session_messages "$SESSION_BARE_FINAL_CANCEL" "$TMPDIR" "$BARE_REFOCUS_MESSAGES"
|
|
430
|
+
|
|
233
431
|
PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
|
|
234
432
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=cancel \
|
|
433
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$BARE_PROPOSAL_CANCEL" \
|
|
434
|
+
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$BARE_ROUTING_FINAL_CANCEL" \
|
|
235
435
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$PROPOSAL_TWO" \
|
|
239
|
-
pi --session "$SESSION_TWO_REFOCUS" -e "$PKG_ROOT" -p "/cook" \
|
|
240
|
-
>"$TMPDIR/refocus-two.out" 2>"$TMPDIR/refocus-two.err"
|
|
436
|
+
pi --session "$SESSION_BARE_FINAL_CANCEL" -e "$PKG_ROOT" -p "/cook" \
|
|
437
|
+
>"$TMPDIR/pi-completion-bare-final-cancel.out" 2>"$TMPDIR/pi-completion-bare-final-cancel.err"
|
|
241
438
|
|
|
242
|
-
python3 - "$
|
|
439
|
+
python3 - "$BARE_PROPOSAL_CANCEL" "$BARE_ROUTING_FINAL_CANCEL" "$TMPDIR/pi-completion-bare-final-cancel.out" "$TMPDIR/pi-completion-bare-final-cancel.err" "$UPDATED_MISSION" "$BARE_REFOCUS_MISSION" <<'PY'
|
|
243
440
|
import json
|
|
244
441
|
import sys
|
|
245
442
|
from pathlib import Path
|
|
246
443
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
Path('.agent/state.json'),
|
|
256
|
-
Path('.agent/startup-plan.json'),
|
|
257
|
-
Path('.agent/startup-plan.md'),
|
|
258
|
-
Path('.agent/plan.json'),
|
|
259
|
-
Path('.agent/active-slice.json'),
|
|
260
|
-
Path('.agent/verification-evidence.json'),
|
|
261
|
-
]
|
|
262
|
-
after = {path.name: path.read_text() for path in tracked}
|
|
444
|
+
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
445
|
+
routing = json.loads(Path(sys.argv[2]).read_text())
|
|
446
|
+
output = Path(sys.argv[3]).read_text() + Path(sys.argv[4]).read_text()
|
|
447
|
+
updated_mission = sys.argv[5]
|
|
448
|
+
replacement_mission = sys.argv[6]
|
|
449
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
450
|
+
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
451
|
+
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
263
452
|
|
|
264
|
-
assert
|
|
265
|
-
assert
|
|
266
|
-
assert
|
|
267
|
-
assert
|
|
268
|
-
assert
|
|
269
|
-
assert
|
|
270
|
-
assert '
|
|
453
|
+
assert state['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep the current mission anchor'
|
|
454
|
+
assert plan['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep plan.json unchanged'
|
|
455
|
+
assert active['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep active-slice.json unchanged'
|
|
456
|
+
assert routing['action'] == 'refocus', 'final Start/Cancel cancel should still come from an explicit-handoff refocus classification'
|
|
457
|
+
assert routing['reason'] == 'fresh_explicit_handoff', 'final Start/Cancel cancel should preserve the explicit-handoff reason'
|
|
458
|
+
assert routing['currentMissionAnchor'] == updated_mission, 'final Start/Cancel cancel should keep the current mission anchor until the user approves replacement'
|
|
459
|
+
assert proposal['mission'] == replacement_mission, 'final Start/Cancel cancel should still prepare the replacement proposal before rewriting state'
|
|
460
|
+
assert proposal['source'] == 'handoff_capsule', 'final Start/Cancel cancel should preserve the explicit-handoff proposal source'
|
|
461
|
+
assert 'Discuss changes in the main chat and rerun /cook.' in output, 'final Start/Cancel cancel should redirect users back to the main chat and rerun /cook'
|
|
271
462
|
PY
|
|
272
463
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
CHOOSER_THREE="$TMPDIR/chooser-three.json"
|
|
279
|
-
PROPOSAL_THREE="$TMPDIR/proposal-three.json"
|
|
280
|
-
bootstrap_active_workflow "$ROOT_THREE" "$SESSION_THREE_BOOTSTRAP" "$BOOTSTRAP_DISCUSSION" "$BOOTSTRAP_HANDOFF"
|
|
281
|
-
cd "$ROOT_THREE"
|
|
282
|
-
write_session "$SESSION_THREE_REFOCUS" "$ROOT_THREE" "$REPLACEMENT_DISCUSSION"
|
|
283
|
-
|
|
284
|
-
PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$REPLACEMENT_HANDOFF" \
|
|
464
|
+
SESSION_BARE_ACCEPT="$TMPDIR/session-bare-accept.jsonl"
|
|
465
|
+
BARE_ROUTING_ACCEPT="$TMPDIR/bare-routing-accept.json"
|
|
466
|
+
BARE_PROPOSAL_ACCEPT="$TMPDIR/bare-replacement-proposal-accept.json"
|
|
467
|
+
write_session_messages "$SESSION_BARE_ACCEPT" "$TMPDIR" "$BARE_REFOCUS_MESSAGES"
|
|
468
|
+
|
|
285
469
|
PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
|
|
286
470
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
471
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$BARE_PROPOSAL_ACCEPT" \
|
|
472
|
+
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$BARE_ROUTING_ACCEPT" \
|
|
287
473
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$PROPOSAL_THREE" \
|
|
291
|
-
pi --session "$SESSION_THREE_REFOCUS" -e "$PKG_ROOT" -p "/cook" \
|
|
292
|
-
>"$TMPDIR/refocus-three.out" 2>"$TMPDIR/refocus-three.err"
|
|
474
|
+
pi --session "$SESSION_BARE_ACCEPT" -e "$PKG_ROOT" -p "/cook" \
|
|
475
|
+
>"$TMPDIR/pi-completion-bare-accept.out" 2>"$TMPDIR/pi-completion-bare-accept.err"
|
|
293
476
|
|
|
294
|
-
python3 - "$
|
|
477
|
+
python3 - "$BARE_PROPOSAL_ACCEPT" "$BARE_ROUTING_ACCEPT" <<'PY'
|
|
295
478
|
import json
|
|
296
479
|
import sys
|
|
297
480
|
from pathlib import Path
|
|
298
481
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
482
|
+
new_anchor = 'Exercise explicit active-workflow replacement coverage.'
|
|
483
|
+
expected_task_type = 'completion-workflow'
|
|
484
|
+
expected_eval_profile = 'completion-rubric-v1'
|
|
485
|
+
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
486
|
+
routing = json.loads(Path(sys.argv[2]).read_text())
|
|
487
|
+
mission_text = Path('.agent/mission.md').read_text()
|
|
488
|
+
profile = json.loads(Path('.agent/profile.json').read_text())
|
|
489
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
490
|
+
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
491
|
+
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
492
|
+
|
|
493
|
+
assert proposal['mission'] == new_anchor, 'accepted bare refocus should preserve the replacement proposal mission'
|
|
494
|
+
assert routing['mode'] == 'bare', 'accepted bare refocus should keep bare routing mode'
|
|
495
|
+
assert 'explicitGoal' not in routing, 'accepted bare refocus should not expose removed explicit-goal shim fields'
|
|
496
|
+
assert 'explicitGoalProvided' not in routing, 'accepted bare refocus should not expose removed explicit-goal shim fields'
|
|
497
|
+
assert routing['action'] == 'refocus', 'accepted bare refocus should keep the explicit-handoff refocus classification'
|
|
498
|
+
assert routing['reason'] == 'fresh_explicit_handoff', 'accepted bare refocus should keep the explicit-handoff reason'
|
|
499
|
+
assert routing['currentMissionAnchor'] == 'Remove completion status line, keep widget.', 'accepted bare refocus should expose the original mission until Start is accepted'
|
|
500
|
+
assert routing['proposalSource'] == 'handoff_capsule', 'accepted bare refocus should preserve the explicit-handoff source'
|
|
501
|
+
assert new_anchor in mission_text, '.agent/mission.md did not update to the bare refocus mission anchor'
|
|
502
|
+
assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after bare refocus'
|
|
503
|
+
assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after bare refocus'
|
|
504
|
+
assert state['mission_anchor'] == new_anchor, 'state.json mission_anchor mismatch after bare refocus'
|
|
505
|
+
assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after bare refocus'
|
|
506
|
+
assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after bare refocus'
|
|
507
|
+
assert state['advisory_startup_brief']['mission'] == new_anchor, 'bare refocus should preserve the confirmed startup brief as advisory intake'
|
|
508
|
+
assert plan['mission_anchor'] == new_anchor, 'plan.json mission_anchor mismatch after bare refocus'
|
|
509
|
+
assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after bare refocus'
|
|
510
|
+
assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after bare refocus'
|
|
511
|
+
assert active['mission_anchor'] == new_anchor, 'active-slice.json mission_anchor mismatch after bare refocus'
|
|
512
|
+
assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after bare refocus'
|
|
513
|
+
assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after bare refocus'
|
|
514
|
+
assert state['current_phase'] == 'reground', 'state.json current_phase should reset to reground after bare refocus'
|
|
515
|
+
assert state['requires_reground'] is True, 'state.json requires_reground should be true after bare refocus'
|
|
516
|
+
assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should reset to completion-regrounder after bare refocus'
|
|
517
|
+
assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'continuation_reason should record the bare refocus'
|
|
518
|
+
assert plan['plan_basis'] == 'user_refocus', 'plan.json plan_basis should be user_refocus after bare refocus'
|
|
519
|
+
assert active['status'] == 'idle', 'active-slice.json status should reset to idle after bare refocus'
|
|
520
|
+
PY
|
|
521
|
+
|
|
522
|
+
SYNTH_REPLACEMENT_SESSION="$TMPDIR/session-synthesized-active-replacement.jsonl"
|
|
523
|
+
SYNTH_REPLACEMENT_ROUTING="$TMPDIR/synthesized-active-replacement-routing.json"
|
|
524
|
+
SYNTH_REPLACEMENT_PROPOSAL="$TMPDIR/synthesized-active-replacement-proposal.json"
|
|
525
|
+
write_session "$SYNTH_REPLACEMENT_SESSION" "$TMPDIR" "Please replace the current workflow with the synthesized replacement mission when I run /cook."
|
|
526
|
+
|
|
527
|
+
SYNTH_REPLACEMENT_HANDOFF="$(python3 - <<'PY'
|
|
528
|
+
import json
|
|
529
|
+
capsule = {
|
|
530
|
+
"kind": "cook_handoff",
|
|
531
|
+
"source": "primary_agent",
|
|
532
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
533
|
+
"source_turn_id": "generated-primary-agent-handoff",
|
|
534
|
+
"mission": "Exercise same-entry active-workflow replacement synthesis.",
|
|
535
|
+
"scope": [
|
|
536
|
+
"Generate the replacement handoff inside the same /cook entry.",
|
|
537
|
+
"Keep the chooser and final Start/Cancel confirmation truthful."
|
|
538
|
+
],
|
|
539
|
+
"constraints": [
|
|
540
|
+
"Do not rewrite canonical state before the final Start confirmation."
|
|
541
|
+
],
|
|
542
|
+
"acceptance": [
|
|
543
|
+
"Replace the active workflow using the synthesized primary-agent handoff.",
|
|
544
|
+
"Keep deterministic coverage for same-entry active replacement."
|
|
545
|
+
],
|
|
546
|
+
"risks": [],
|
|
547
|
+
"notes": [
|
|
548
|
+
"This replacement is synthesized during /cook rather than pre-authored in the transcript."
|
|
549
|
+
],
|
|
550
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
551
|
+
"first_slice_goal": "Exercise same-entry active-workflow replacement synthesis.",
|
|
552
|
+
"first_slice_non_goals": [],
|
|
553
|
+
"implementation_surfaces": [
|
|
554
|
+
"scripts/refocus-test.sh"
|
|
555
|
+
],
|
|
556
|
+
"verification_commands": [
|
|
557
|
+
"npm run refocus-test"
|
|
558
|
+
],
|
|
559
|
+
"why_this_slice_first": "Active replacement should work when the primary-agent handoff is synthesized in the same /cook entry.",
|
|
560
|
+
"task_type": "completion-workflow",
|
|
561
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
562
|
+
"why_cook_now": "The user explicitly chose workflow mode and the replacement handoff can be synthesized immediately."
|
|
563
|
+
}
|
|
564
|
+
print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
|
|
565
|
+
PY
|
|
566
|
+
)"
|
|
567
|
+
|
|
568
|
+
PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$SYNTH_REPLACEMENT_HANDOFF" \
|
|
569
|
+
PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
|
|
570
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
571
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$SYNTH_REPLACEMENT_PROPOSAL" \
|
|
572
|
+
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$SYNTH_REPLACEMENT_ROUTING" \
|
|
573
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
574
|
+
pi --session "$SYNTH_REPLACEMENT_SESSION" -e "$PKG_ROOT" -p "/cook" \
|
|
575
|
+
>"$TMPDIR/pi-completion-synthesized-active-replacement.out" 2>"$TMPDIR/pi-completion-synthesized-active-replacement.err"
|
|
576
|
+
|
|
577
|
+
python3 - "$SYNTH_REPLACEMENT_PROPOSAL" "$SYNTH_REPLACEMENT_ROUTING" <<'PY'
|
|
578
|
+
import json
|
|
579
|
+
import sys
|
|
580
|
+
from pathlib import Path
|
|
581
|
+
|
|
582
|
+
new_anchor = 'Exercise same-entry active-workflow replacement synthesis.'
|
|
583
|
+
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
584
|
+
routing = json.loads(Path(sys.argv[2]).read_text())
|
|
303
585
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
304
|
-
startup_plan = json.loads(Path('.agent/startup-plan.json').read_text())
|
|
305
|
-
startup_plan_md = Path('.agent/startup-plan.md').read_text()
|
|
306
586
|
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
307
587
|
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
308
588
|
|
|
309
|
-
mission
|
|
310
|
-
assert routing['action'] == 'refocus',
|
|
311
|
-
assert routing['reason'] == '
|
|
312
|
-
assert
|
|
313
|
-
assert
|
|
314
|
-
assert
|
|
315
|
-
assert
|
|
316
|
-
assert state['
|
|
317
|
-
assert startup_plan['mission_anchor'] == mission, startup_plan
|
|
318
|
-
assert startup_plan['source'] == 'deferred_primary_agent_handoff', startup_plan
|
|
319
|
-
assert '## Goal' in startup_plan_md and mission in startup_plan_md, startup_plan_md
|
|
320
|
-
assert plan['mission_anchor'] == mission, plan
|
|
321
|
-
assert active['mission_anchor'] == mission, active
|
|
322
|
-
assert 'Refocused completion mission from same-entry primary-agent startup plan' in output, output
|
|
589
|
+
assert proposal['mission'] == new_anchor, 'same-entry synthesized replacement should preserve the replacement proposal mission'
|
|
590
|
+
assert routing['action'] == 'refocus', 'same-entry synthesized replacement should classify active bare /cook as refocus'
|
|
591
|
+
assert routing['reason'] == 'fresh_explicit_handoff', 'same-entry synthesized replacement should reuse the explicit-handoff routing reason because /cook synthesized an explicit handoff'
|
|
592
|
+
assert routing['proposalSource'] == 'handoff_capsule', 'same-entry synthesized replacement should surface the synthesized handoff as a handoff capsule source'
|
|
593
|
+
assert state['mission_anchor'] == new_anchor, 'state.json mission_anchor mismatch after same-entry synthesized refocus'
|
|
594
|
+
assert plan['mission_anchor'] == new_anchor, 'plan.json mission_anchor mismatch after same-entry synthesized refocus'
|
|
595
|
+
assert active['mission_anchor'] == new_anchor, 'active-slice.json mission_anchor mismatch after same-entry synthesized refocus'
|
|
596
|
+
assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'same-entry synthesized replacement should record the /cook refocus continuation reason'
|
|
323
597
|
PY
|
|
324
598
|
|
|
325
|
-
echo "refocus test passed: $
|
|
599
|
+
echo "refocus test passed: $TMPDIR"
|