@linimin/pi-letscook 0.1.27 → 0.1.29
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/CHANGELOG.md +14 -0
- package/README.md +114 -118
- package/extensions/completion/index.ts +588 -89
- package/package.json +1 -1
- package/scripts/context-proposal-test.sh +180 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linimin/pi-letscook",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29",
|
|
4
4
|
"description": "Pi package for long-running completion workflows with canonical .agent state, role-based subagents, continuity, and verification helpers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
@@ -101,12 +101,30 @@ mkdir -p "$ROOT"
|
|
|
101
101
|
cd "$ROOT"
|
|
102
102
|
git init -q
|
|
103
103
|
|
|
104
|
-
# No workflow yet: /cook with no goal should
|
|
104
|
+
# No workflow yet: /cook with no goal should not bootstrap from discussion alone when analyst output is unavailable.
|
|
105
|
+
SESSION_ZERO="$TMPDIR/session-zero.jsonl"
|
|
106
|
+
DISCUSSION_ZERO=$'Mission: Remove the completion status line while keeping the completion widget.\nScope:\n- Keep the non-running completion widget.\n- Suppress the widget while a completion role is active.\nConstraints:\n- Do not reintroduce any other completion status surface.\nAcceptance:\n- Update README to match the shipped behavior.\n- Keep observability regression coverage truthful.'
|
|
107
|
+
write_session "$SESSION_ZERO" "$ROOT" "$DISCUSSION_ZERO"
|
|
108
|
+
|
|
109
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
110
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
111
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
112
|
+
pi --session "$SESSION_ZERO" -e "$PKG_ROOT" -p "/cook" >/tmp/pi-completion-context-proposal-no-analyst.out 2>/tmp/pi-completion-context-proposal-no-analyst.err
|
|
113
|
+
|
|
114
|
+
python3 - <<'PY'
|
|
115
|
+
from pathlib import Path
|
|
116
|
+
|
|
117
|
+
assert not Path('.agent').exists(), '/cook should not bootstrap canonical state from discussion alone without analyst output'
|
|
118
|
+
PY
|
|
119
|
+
|
|
120
|
+
# No workflow yet: /cook with no goal should infer from recent discussion through analyst output.
|
|
105
121
|
SESSION_ONE="$TMPDIR/session-one.jsonl"
|
|
106
|
-
DISCUSSION_ONE
|
|
122
|
+
DISCUSSION_ONE="$DISCUSSION_ZERO"
|
|
123
|
+
ANALYST_OUTPUT_ONE='{"mission":"Remove the completion status line while keeping the completion widget.","scope":["Keep the non-running completion widget.","Suppress the widget while a completion role is active."],"constraints":["Do not reintroduce any other completion status surface."],"acceptance":["Update README to match the shipped behavior.","Keep observability regression coverage truthful."],"confidence":0.94}'
|
|
107
124
|
write_session "$SESSION_ONE" "$ROOT" "$DISCUSSION_ONE"
|
|
108
125
|
|
|
109
126
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
127
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$ANALYST_OUTPUT_ONE" \
|
|
110
128
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
111
129
|
pi --session "$SESSION_ONE" -e "$PKG_ROOT" -p "/cook" >/tmp/pi-completion-context-proposal-bootstrap.out 2>/tmp/pi-completion-context-proposal-bootstrap.err
|
|
112
130
|
|
|
@@ -120,22 +138,24 @@ state = json.loads(Path('.agent/state.json').read_text())
|
|
|
120
138
|
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
121
139
|
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
122
140
|
|
|
123
|
-
assert mission in mission_text, '.agent/mission.md did not record the
|
|
124
|
-
assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after
|
|
125
|
-
assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after
|
|
126
|
-
assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after
|
|
127
|
-
assert state['current_phase'] == 'reground', 'state.json current_phase should start at reground after
|
|
128
|
-
assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should start at completion-regrounder after
|
|
141
|
+
assert mission in mission_text, '.agent/mission.md did not record the analyst-derived mission anchor'
|
|
142
|
+
assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after analyst-derived bootstrap'
|
|
143
|
+
assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after analyst-derived bootstrap'
|
|
144
|
+
assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after analyst-derived bootstrap'
|
|
145
|
+
assert state['current_phase'] == 'reground', 'state.json current_phase should start at reground after analyst-derived bootstrap'
|
|
146
|
+
assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should start at completion-regrounder after analyst-derived bootstrap'
|
|
129
147
|
PY
|
|
130
148
|
|
|
131
|
-
# Completed workflow: /cook with no goal should infer the next round from recent discussion.
|
|
149
|
+
# Completed workflow: /cook with no goal should infer the next round from recent discussion through analyst output.
|
|
132
150
|
mark_done
|
|
133
151
|
|
|
134
152
|
SESSION_TWO="$TMPDIR/session-two.jsonl"
|
|
135
153
|
DISCUSSION_TWO=$'Mission: Ship the next workflow round for richer context-derived /cook startup.\nScope:\n- Start a new workflow round from recent discussion after the previous one is done.\n- Keep using canonical .agent state after confirmation.\nConstraints:\n- Do not resume the completed workflow when the new round is clearly different.\nAcceptance:\n- Reset canonical state back to reground for the new mission.\n- Preserve the tracked completion control-plane files.'
|
|
154
|
+
ANALYST_OUTPUT_TWO='{"mission":"Ship the next workflow round for richer context-derived /cook startup.","scope":["Start a new workflow round from recent discussion after the previous one is done.","Keep using canonical .agent state after confirmation."],"constraints":["Do not resume the completed workflow when the new round is clearly different."],"acceptance":["Reset canonical state back to reground for the new mission.","Preserve the tracked completion control-plane files."],"confidence":0.93}'
|
|
136
155
|
write_session "$SESSION_TWO" "$ROOT" "$DISCUSSION_TWO"
|
|
137
156
|
|
|
138
157
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
158
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$ANALYST_OUTPUT_TWO" \
|
|
139
159
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
140
160
|
pi --session "$SESSION_TWO" -e "$PKG_ROOT" -p "/cook" >/tmp/pi-completion-context-proposal-next-round.out 2>/tmp/pi-completion-context-proposal-next-round.err
|
|
141
161
|
|
|
@@ -163,14 +183,15 @@ assert plan['plan_basis'] == 'user_refocus', 'plan_basis should reset to user_re
|
|
|
163
183
|
assert active['status'] == 'idle', 'active-slice should reset to idle for the next workflow round'
|
|
164
184
|
PY
|
|
165
185
|
|
|
166
|
-
# Active workflow: /cook <goal> plus refocus should use the explicit goal as the mission anchor
|
|
167
|
-
#
|
|
186
|
+
# Active workflow: /cook <goal> plus refocus should use the explicit goal as the mission anchor
|
|
187
|
+
# even when analyst output is unavailable, without falling back to session-derived proposal parsing.
|
|
168
188
|
SESSION_THREE="$TMPDIR/session-three.jsonl"
|
|
169
189
|
DISCUSSION_THREE=$'Scope:\n- Preserve the richer proposal structure from discussion.\nConstraints:\n- Keep explicit goals as the mission anchor when they conflict with earlier text.\nAcceptance:\n- Refresh canonical state from the replacement mission.'
|
|
170
190
|
write_session "$SESSION_THREE" "$ROOT" "$DISCUSSION_THREE"
|
|
171
191
|
|
|
172
192
|
PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
|
|
173
193
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
194
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
174
195
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
175
196
|
pi --session "$SESSION_THREE" -e "$PKG_ROOT" -p "/cook Explicit replacement mission for the active workflow" >/tmp/pi-completion-context-proposal-active-goal.out 2>/tmp/pi-completion-context-proposal-active-goal.err
|
|
176
197
|
|
|
@@ -192,31 +213,37 @@ assert state['current_phase'] == 'reground', 'current_phase should reset to regr
|
|
|
192
213
|
assert state['continuation_policy'] == 'continue', 'continuation_policy should stay continue after explicit-goal replacement'
|
|
193
214
|
assert state['next_mandatory_role'] == 'completion-regrounder', 'next role should reset to completion-regrounder after explicit-goal replacement'
|
|
194
215
|
assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'continuation_reason should record the explicit-goal replacement'
|
|
216
|
+
assert 'Preserve the richer proposal structure from discussion.' not in state['continuation_reason'], 'session scope should not be merged when analyst output is unavailable'
|
|
217
|
+
assert 'Keep explicit goals as the mission anchor when they conflict with earlier text.' not in state['continuation_reason'], 'session constraints should not be merged when analyst output is unavailable'
|
|
218
|
+
assert 'Refresh canonical state from the replacement mission.' not in state['continuation_reason'], 'session acceptance should not be merged when analyst output is unavailable'
|
|
195
219
|
assert plan['plan_basis'] == 'user_refocus', 'plan_basis should be user_refocus after explicit-goal replacement'
|
|
196
220
|
assert active['status'] == 'idle', 'active slice should reset to idle after explicit-goal replacement'
|
|
197
221
|
PY
|
|
198
222
|
|
|
199
223
|
# Completed workflow again: /cook <goal> should start the next round directly from the explicit goal
|
|
200
|
-
# without
|
|
224
|
+
# even when analyst output is unavailable, without merging session-derived scope, constraints, or acceptance.
|
|
201
225
|
mark_done
|
|
202
226
|
|
|
203
227
|
SESSION_FOUR="$TMPDIR/session-four.jsonl"
|
|
204
|
-
DISCUSSION_FOUR=$'
|
|
228
|
+
DISCUSSION_FOUR=$'Scope:\n- Add session-only scope.\n- Restyle widget.\nConstraints:\n- Keep rules.\nAcceptance:\n- Add test.'
|
|
229
|
+
EXPLICIT_GOAL_FOUR=$'Mission: Filter scope by mission.\nScope:\n- Keep explicit scope.'
|
|
205
230
|
write_session "$SESSION_FOUR" "$ROOT" "$DISCUSSION_FOUR"
|
|
206
231
|
|
|
207
232
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
233
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
208
234
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
209
|
-
pi --session "$SESSION_FOUR" -e "$PKG_ROOT" -p "/cook
|
|
235
|
+
pi --session "$SESSION_FOUR" -e "$PKG_ROOT" -p "/cook $EXPLICIT_GOAL_FOUR" >/tmp/pi-completion-context-proposal-done-goal.out 2>/tmp/pi-completion-context-proposal-done-goal.err
|
|
210
236
|
|
|
211
237
|
python3 - <<'PY'
|
|
212
238
|
import json
|
|
213
239
|
from pathlib import Path
|
|
214
240
|
|
|
215
|
-
mission = '
|
|
241
|
+
mission = 'Filter scope by mission.'
|
|
216
242
|
mission_text = Path('.agent/mission.md').read_text()
|
|
217
243
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
218
244
|
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
219
245
|
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
246
|
+
continuation_reason = state['continuation_reason']
|
|
220
247
|
|
|
221
248
|
assert mission in mission_text, '.agent/mission.md did not update to the explicit next-round mission anchor'
|
|
222
249
|
assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after explicit-goal next-round start'
|
|
@@ -227,9 +254,146 @@ assert state['continuation_policy'] == 'continue', 'continuation_policy should r
|
|
|
227
254
|
assert state['project_done'] is False, 'project_done should reset to false after explicit-goal next-round start'
|
|
228
255
|
assert state['requires_reground'] is True, 'requires_reground should reset to true after explicit-goal next-round start'
|
|
229
256
|
assert state['next_mandatory_role'] == 'completion-regrounder', 'next role should reset to completion-regrounder after explicit-goal next-round start'
|
|
230
|
-
assert
|
|
257
|
+
assert continuation_reason.startswith('User refocused workflow via /cook:'), 'continuation_reason should record the explicit-goal next-round start'
|
|
258
|
+
assert 'Keep explicit scope.' in continuation_reason, 'explicit scope should remain in the explicit-goal proposal'
|
|
259
|
+
assert 'Add session-only scope.' not in continuation_reason, 'session-derived scope should not be merged when analyst output is unavailable'
|
|
260
|
+
assert 'Restyle widget.' not in continuation_reason, 'unrelated session-derived scope should not be merged when analyst output is unavailable'
|
|
261
|
+
assert 'Keep rules.' not in continuation_reason, 'session-derived constraints should not merge when analyst output is unavailable'
|
|
262
|
+
assert 'Add test.' not in continuation_reason, 'session-derived acceptance should not merge when analyst output is unavailable'
|
|
231
263
|
assert plan['plan_basis'] == 'user_refocus', 'plan_basis should be user_refocus after explicit-goal next-round start'
|
|
232
264
|
assert active['status'] == 'idle', 'active slice should reset to idle after explicit-goal next-round start'
|
|
233
265
|
PY
|
|
234
266
|
|
|
267
|
+
# Completed workflow again: /cook with no goal should be able to use model-assisted
|
|
268
|
+
# analysis of natural discussion when discussion-only startup depends on analyst output.
|
|
269
|
+
mark_done
|
|
270
|
+
|
|
271
|
+
SESSION_FIVE="$TMPDIR/session-five.jsonl"
|
|
272
|
+
DISCUSSION_FIVE=$'I do not want to rewrite the parser. The safer path is to let /cook analyze the discussion first, keep the user\'s explicit mission if they provided one, and ignore stale scope that drifted in from earlier turns. We should still prove it with a regression test before writing canonical state.'
|
|
273
|
+
ANALYST_OUTPUT_FIVE='{"mission":"Use a proposal analyst to summarize natural discussion before /cook writes canonical state.","scope":["Keep explicit goals anchored.","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"]}'
|
|
274
|
+
write_session "$SESSION_FIVE" "$ROOT" "$DISCUSSION_FIVE"
|
|
275
|
+
|
|
276
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
277
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$ANALYST_OUTPUT_FIVE" \
|
|
278
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
279
|
+
pi --session "$SESSION_FIVE" -e "$PKG_ROOT" -p "/cook" >/tmp/pi-completion-context-proposal-analyst.out 2>/tmp/pi-completion-context-proposal-analyst.err
|
|
280
|
+
|
|
281
|
+
python3 - <<'PY'
|
|
282
|
+
import json
|
|
283
|
+
from pathlib import Path
|
|
284
|
+
|
|
285
|
+
mission = 'Use a proposal analyst to summarize natural discussion before /cook writes canonical state.'
|
|
286
|
+
mission_text = Path('.agent/mission.md').read_text()
|
|
287
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
288
|
+
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
289
|
+
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
290
|
+
continuation_reason = state['continuation_reason']
|
|
291
|
+
|
|
292
|
+
assert mission in mission_text, '.agent/mission.md did not record the analyst-derived mission anchor'
|
|
293
|
+
assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after analyst-derived bootstrap'
|
|
294
|
+
assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after analyst-derived bootstrap'
|
|
295
|
+
assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after analyst-derived bootstrap'
|
|
296
|
+
assert state['current_phase'] == 'reground', 'current_phase should reset to reground after analyst-derived bootstrap'
|
|
297
|
+
assert state['next_mandatory_role'] == 'completion-regrounder', 'next role should reset to completion-regrounder after analyst-derived bootstrap'
|
|
298
|
+
assert continuation_reason.startswith('User refocused workflow via /cook:'), 'continuation_reason should record the analyst-derived restart'
|
|
299
|
+
assert 'Keep explicit goals anchored.' in continuation_reason, 'analyst-derived scope should be preserved'
|
|
300
|
+
PY
|
|
301
|
+
|
|
302
|
+
# Custom confirmation UI: start should render proposal content separately from explicit Start/Edit/Cancel actions.
|
|
303
|
+
UI_ROOT_START="$TMPDIR/ui-root-start"
|
|
304
|
+
mkdir -p "$UI_ROOT_START"
|
|
305
|
+
cd "$UI_ROOT_START"
|
|
306
|
+
git init -q
|
|
307
|
+
|
|
308
|
+
UI_SESSION_START="$TMPDIR/ui-session-start.jsonl"
|
|
309
|
+
UI_DISCUSSION_START=$'Mission: Replace the crowded selector with a clearer action layout.\nScope:\n- Separate proposal text from actions.\nConstraints:\n- Preserve Start/Edit/Cancel behavior.\nAcceptance:\n- Add regression coverage.'
|
|
310
|
+
UI_ANALYST_OUTPUT_START='{"mission":"Replace the crowded selector with a clearer action layout.","scope":["Separate proposal text from actions."],"constraints":["Preserve Start/Edit/Cancel behavior."],"acceptance":["Add regression coverage."],"confidence":0.95}'
|
|
311
|
+
UI_SNAPSHOT_START="$TMPDIR/context-proposal-ui-start.json"
|
|
312
|
+
write_session "$UI_SESSION_START" "$UI_ROOT_START" "$UI_DISCUSSION_START"
|
|
313
|
+
|
|
314
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_ACTION=start \
|
|
315
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_PATH="$UI_SNAPSHOT_START" \
|
|
316
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$UI_ANALYST_OUTPUT_START" \
|
|
317
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
318
|
+
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"
|
|
319
|
+
|
|
320
|
+
python3 - "$UI_SNAPSHOT_START" <<'PY'
|
|
321
|
+
import json
|
|
322
|
+
import sys
|
|
323
|
+
from pathlib import Path
|
|
324
|
+
|
|
325
|
+
snapshot = json.loads(Path(sys.argv[1]).read_text())
|
|
326
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
327
|
+
|
|
328
|
+
assert snapshot['proposalHeading'] == 'Proposed workflow', 'custom confirmation snapshot should expose a dedicated proposal section'
|
|
329
|
+
assert 'Mission\nReplace the crowded selector with a clearer action layout.' in snapshot['proposalBody'], 'proposal body should be captured separately from the action list'
|
|
330
|
+
assert [action['id'] for action in snapshot['actions']] == ['start', 'edit', 'cancel'], 'custom confirmation actions should stay Start/Edit/Cancel'
|
|
331
|
+
assert [action['label'] for action in snapshot['actions']] == ['Start', 'Edit', 'Cancel'], 'custom confirmation action labels should be concise'
|
|
332
|
+
for action in snapshot['actions']:
|
|
333
|
+
assert 'Replace the crowded selector with a clearer action layout.' not in action['label'], 'proposal mission should not be embedded in action labels'
|
|
334
|
+
assert 'Separate proposal text from actions.' not in action['description'], 'proposal scope should not be embedded in action descriptions'
|
|
335
|
+
assert state['mission_anchor'] == 'Replace the crowded selector with a clearer action layout.', 'start action should still accept the proposed mission'
|
|
336
|
+
PY
|
|
337
|
+
|
|
338
|
+
# Custom confirmation UI: edit should keep the existing editor/parsing flow when the action comes from the new layout.
|
|
339
|
+
UI_ROOT_EDIT="$TMPDIR/ui-root-edit"
|
|
340
|
+
mkdir -p "$UI_ROOT_EDIT"
|
|
341
|
+
cd "$UI_ROOT_EDIT"
|
|
342
|
+
git init -q
|
|
343
|
+
|
|
344
|
+
UI_SESSION_EDIT="$TMPDIR/ui-session-edit.jsonl"
|
|
345
|
+
UI_DISCUSSION_EDIT=$'Mission: Keep editing support in the custom confirmation UI.\nScope:\n- Preserve the proposal editor.\nConstraints:\n- Keep parsing structured proposal text.\nAcceptance:\n- Update the mission anchor after edit.'
|
|
346
|
+
UI_ANALYST_OUTPUT_EDIT='{"mission":"Keep editing support in the custom confirmation UI.","scope":["Preserve the proposal editor."],"constraints":["Keep parsing structured proposal text."],"acceptance":["Update the mission anchor after edit."],"confidence":0.94}'
|
|
347
|
+
UI_EDIT_TEXT=$'Mission: Edited mission from the custom confirmation UI.\nScope:\n- Preserve parsing after edit.\nConstraints:\n- Keep the shared confirmation flow.\nAcceptance:\n- Start the workflow from the edited proposal.'
|
|
348
|
+
write_session "$UI_SESSION_EDIT" "$UI_ROOT_EDIT" "$UI_DISCUSSION_EDIT"
|
|
349
|
+
|
|
350
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_ACTION=edit \
|
|
351
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_EDIT_TEXT="$UI_EDIT_TEXT" \
|
|
352
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$UI_ANALYST_OUTPUT_EDIT" \
|
|
353
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
354
|
+
pi --session "$UI_SESSION_EDIT" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-ui-edit.out" 2>"$TMPDIR/pi-completion-context-proposal-ui-edit.err"
|
|
355
|
+
|
|
356
|
+
python3 - <<'PY'
|
|
357
|
+
import json
|
|
358
|
+
from pathlib import Path
|
|
359
|
+
|
|
360
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
361
|
+
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
362
|
+
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
363
|
+
mission = 'Edited mission from the custom confirmation UI.'
|
|
364
|
+
|
|
365
|
+
assert state['mission_anchor'] == mission, 'edit action should still route through the proposal parser and update state.json'
|
|
366
|
+
assert plan['mission_anchor'] == mission, 'edit action should still route through the proposal parser and update plan.json'
|
|
367
|
+
assert active['mission_anchor'] == mission, 'edit action should still route through the proposal parser and update active-slice.json'
|
|
368
|
+
assert state['current_phase'] == 'reground', 'edit action should still bootstrap/reground the workflow'
|
|
369
|
+
PY
|
|
370
|
+
|
|
371
|
+
# Custom confirmation UI: cancel should exit without writing canonical state.
|
|
372
|
+
UI_ROOT_CANCEL="$TMPDIR/ui-root-cancel"
|
|
373
|
+
mkdir -p "$UI_ROOT_CANCEL"
|
|
374
|
+
cd "$UI_ROOT_CANCEL"
|
|
375
|
+
git init -q
|
|
376
|
+
|
|
377
|
+
UI_SESSION_CANCEL="$TMPDIR/ui-session-cancel.jsonl"
|
|
378
|
+
UI_DISCUSSION_CANCEL=$'Mission: Cancel from the custom confirmation UI without writing state.\nScope:\n- Show the proposal separately from the actions.\nConstraints:\n- Keep cancellation side-effect free.\nAcceptance:\n- Leave .agent absent after cancel.'
|
|
379
|
+
UI_ANALYST_OUTPUT_CANCEL='{"mission":"Cancel from the custom confirmation UI without writing state.","scope":["Show the proposal separately from the actions."],"constraints":["Keep cancellation side-effect free."],"acceptance":["Leave .agent absent after cancel."],"confidence":0.92}'
|
|
380
|
+
UI_SNAPSHOT_CANCEL="$TMPDIR/context-proposal-ui-cancel.json"
|
|
381
|
+
write_session "$UI_SESSION_CANCEL" "$UI_ROOT_CANCEL" "$UI_DISCUSSION_CANCEL"
|
|
382
|
+
|
|
383
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_ACTION=cancel \
|
|
384
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_PATH="$UI_SNAPSHOT_CANCEL" \
|
|
385
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$UI_ANALYST_OUTPUT_CANCEL" \
|
|
386
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
387
|
+
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"
|
|
388
|
+
|
|
389
|
+
python3 - "$UI_SNAPSHOT_CANCEL" <<'PY'
|
|
390
|
+
import json
|
|
391
|
+
import sys
|
|
392
|
+
from pathlib import Path
|
|
393
|
+
|
|
394
|
+
snapshot = json.loads(Path(sys.argv[1]).read_text())
|
|
395
|
+
assert [action['id'] for action in snapshot['actions']] == ['start', 'edit', 'cancel'], 'cancel snapshot should still expose Start/Edit/Cancel actions'
|
|
396
|
+
assert not Path('.agent').exists(), 'cancel action should not write canonical workflow state'
|
|
397
|
+
PY
|
|
398
|
+
|
|
235
399
|
echo "context proposal test passed: $ROOT"
|