@linimin/pi-letscook 0.1.32 → 0.1.35

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.
@@ -0,0 +1,348 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ PKG_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ TMPDIR="$(mktemp -d)"
6
+ CURRENT_EVIDENCE_BACKUP=""
7
+
8
+ cleanup() {
9
+ if [[ -n "$CURRENT_EVIDENCE_BACKUP" && -f "$CURRENT_EVIDENCE_BACKUP" ]]; then
10
+ cp "$CURRENT_EVIDENCE_BACKUP" "$PKG_ROOT/.agent/verification-evidence.json"
11
+ fi
12
+ rm -rf "$TMPDIR"
13
+ }
14
+ trap cleanup EXIT
15
+
16
+ cd "$PKG_ROOT"
17
+
18
+ node <<'NODE'
19
+ const fs = require('node:fs');
20
+
21
+ const read = (file) => fs.readFileSync(file, 'utf8');
22
+ const assertIncludes = (file, snippet) => {
23
+ const text = read(file);
24
+ if (!text.includes(snippet)) {
25
+ throw new Error(`${file} is missing required canonical-evidence text: ${snippet}`);
26
+ }
27
+ };
28
+ const escapeRegex = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
29
+ const readSection = (file, heading) => {
30
+ const text = read(file);
31
+ const match = text.match(new RegExp(`^${escapeRegex(heading)}\\s*$\\n([\\s\\S]*?)(?=^##\\s+|\\Z)`, 'm'));
32
+ if (!match) {
33
+ throw new Error(`${file} is missing required section: ${heading}`);
34
+ }
35
+ return match[1];
36
+ };
37
+ const assertSectionIncludes = (file, heading, snippet) => {
38
+ const section = readSection(file, heading);
39
+ if (!section.includes(snippet)) {
40
+ throw new Error(`${file} section ${heading} is missing required canonical-evidence text: ${snippet}`);
41
+ }
42
+ };
43
+
44
+ assertIncludes('README.md', '.agent/verification-evidence.json');
45
+ assertIncludes('README.md', 'Fresh scaffolds create an idle placeholder');
46
+ assertIncludes('README.md', 'bash scripts/canonical-evidence-artifact-test.sh');
47
+ assertIncludes('.agent/README.md', '.agent/verification-evidence.json');
48
+ assertIncludes('.agent/README.md', 'durable canonical record of deterministic verification');
49
+ assertSectionIncludes('skills/completion-protocol/SKILL.md', '## Canonical Files', '- `.agent/verification-evidence.json`');
50
+ assertSectionIncludes('skills/completion-protocol/SKILL.md', '## Canonical Inputs', '- `.agent/verification-evidence.json`');
51
+ assertSectionIncludes('skills/completion-protocol/SKILL.md', '## Compaction And Recovery', '- `.agent/verification-evidence.json`');
52
+ assertSectionIncludes('skills/completion-protocol/SKILL.md', '## Compaction And Recovery', '`completion-implementer` must also re-read canonical `.agent/state.json`, `.agent/plan.json`, `.agent/active-slice.json`, and `.agent/verification-evidence.json` before resuming work.');
53
+ assertSectionIncludes('skills/completion-protocol/references/completion.md', '## Ignored Canonical Execution State', '- `.agent/verification-evidence.json`');
54
+ assertSectionIncludes('skills/completion-protocol/references/completion.md', '## Canonical Inputs', '- `.agent/verification-evidence.json`');
55
+ assertSectionIncludes('skills/completion-protocol/references/completion.md', '## Compaction And Recovery', '- `.agent/verification-evidence.json`');
56
+ assertSectionIncludes('skills/completion-protocol/references/completion.md', '## Compaction And Recovery', '`completion-implementer` must also re-read canonical `.agent/state.json`, `.agent/plan.json`, `.agent/active-slice.json`, and `.agent/verification-evidence.json` before resuming work.');
57
+ assertIncludes('extensions/completion/index.ts', 'Verification evidence artifact: ${evidence.path}');
58
+ assertIncludes('extensions/completion/index.ts', 'Verification evidence summary: ${evidence.summary}');
59
+ assertIncludes('extensions/completion/index.ts', 'Canonical verification evidence artifact is currently: ${evidence.path} (${evidence.status})');
60
+ assertIncludes('extensions/completion/index.ts', '`- verification_evidence_path: ${evidence.path}`');
61
+ assertIncludes('extensions/completion/index.ts', '`- verification_evidence_summary: ${evidence.summary}`');
62
+ assertIncludes('extensions/completion/index.ts', 'Consume .agent/verification-evidence.json instead of temp-only verification summaries when it is populated.');
63
+ assertIncludes('scripts/release-check.sh', 'bash .agent/verify_completion_control_plane.sh');
64
+ assertIncludes('scripts/release-check.sh', 'bash ./scripts/canonical-evidence-artifact-test.sh');
65
+ assertIncludes('.agent/verify_completion_control_plane.sh', '.agent/verification-evidence.json');
66
+ assertIncludes('.agent/verify_completion_control_plane.sh', 'subject_type must be selected_slice when active slice exact handoff requires verification evidence');
67
+ assertIncludes('.agent/verify_completion_stop.sh', '.agent/verification-evidence.json parity');
68
+ NODE
69
+
70
+ bash .agent/verify_completion_control_plane.sh >/dev/null
71
+
72
+ CURRENT_EVIDENCE_BACKUP="$TMPDIR/current-verification-evidence.json"
73
+ cp .agent/verification-evidence.json "$CURRENT_EVIDENCE_BACKUP"
74
+
75
+ python3 - <<'PY'
76
+ import json
77
+ from pathlib import Path
78
+ path = Path('.agent/verification-evidence.json')
79
+ evidence = json.loads(path.read_text())
80
+ evidence['head_sha'] = 'stale-head'
81
+ path.write_text(json.dumps(evidence, indent=2) + '\n')
82
+ PY
83
+
84
+ if bash ./scripts/release-check.sh >/dev/null 2>&1; then
85
+ echo "expected release-check to fail when current repo verification-evidence.json is stale" >&2
86
+ exit 1
87
+ fi
88
+
89
+ if bash .agent/verify_completion_stop.sh >/dev/null 2>&1; then
90
+ echo "expected verify_completion_stop.sh to fail when current repo verification-evidence.json is stale" >&2
91
+ exit 1
92
+ fi
93
+
94
+ cp "$CURRENT_EVIDENCE_BACKUP" .agent/verification-evidence.json
95
+ bash .agent/verify_completion_control_plane.sh >/dev/null
96
+
97
+ ROOT="$TMPDIR/repo"
98
+ SYSTEM_REMINDER="$TMPDIR/system-reminder.txt"
99
+ mkdir -p "$ROOT"
100
+ cd "$ROOT"
101
+ git init -q
102
+
103
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
104
+ pi -e "$PKG_ROOT" -p "/cook canonical evidence fixture" \
105
+ >"$TMPDIR/pi-canonical-evidence-bootstrap.out" 2>"$TMPDIR/pi-canonical-evidence-bootstrap.err"
106
+
107
+ for file in .agent/profile.json .agent/state.json .agent/plan.json .agent/active-slice.json .agent/verification-evidence.json; do
108
+ [[ -f "$file" ]] || { echo "missing canonical bootstrap file: $file" >&2; exit 1; }
109
+ done
110
+
111
+ bash .agent/verify_completion_control_plane.sh >/dev/null
112
+ bash .agent/verify_completion_stop.sh >/dev/null
113
+
114
+ python3 - <<'PY'
115
+ import json
116
+ from pathlib import Path
117
+
118
+ evidence = json.loads(Path('.agent/verification-evidence.json').read_text())
119
+ assert evidence['artifact_type'] == 'completion-verification-evidence', evidence
120
+ assert evidence['subject_type'] == 'none', evidence
121
+ assert evidence['verification_commands'] == [], evidence
122
+ assert evidence['outcome'] == 'not_recorded', evidence
123
+ assert evidence['recorded_at'] is None, evidence
124
+ assert evidence['head_sha'] is None, evidence
125
+ PY
126
+
127
+ git add .
128
+ git -c user.name='Test' -c user.email='test@example.com' commit -qm 'bootstrap fixture'
129
+ HEAD_SHA="$(git rev-parse HEAD)"
130
+
131
+ python3 - "$HEAD_SHA" <<'PY'
132
+ import json
133
+ import sys
134
+ from pathlib import Path
135
+
136
+ head_sha = sys.argv[1]
137
+ mission = 'Exercise canonical verification evidence parity.'
138
+ task_type = 'completion-workflow'
139
+ evaluation_profile = 'completion-rubric-v1'
140
+ verification_commands = [
141
+ 'bash .agent/verify_completion_control_plane.sh',
142
+ 'bash .agent/verify_completion_stop.sh',
143
+ ]
144
+ implementation_surfaces = [
145
+ '.agent/verification-evidence.json',
146
+ '.agent/verify_completion_control_plane.sh',
147
+ '.agent/verify_completion_stop.sh',
148
+ ]
149
+ acceptance = [
150
+ 'Canonical verification evidence is recorded for the selected slice.',
151
+ 'Fail-closed verification rejects missing or stale evidence.',
152
+ ]
153
+ state = {
154
+ 'schema_version': 1,
155
+ 'mission_anchor': mission,
156
+ 'current_phase': 'implement',
157
+ 'continuation_policy': 'continue',
158
+ 'continuation_reason': 'Fixture for canonical evidence artifact regression coverage.',
159
+ 'project_done': False,
160
+ 'task_type': task_type,
161
+ 'evaluation_profile': evaluation_profile,
162
+ 'requires_reground': False,
163
+ 'slices_since_last_reground': 0,
164
+ 'remaining_release_blockers': 0,
165
+ 'remaining_high_value_gaps': 1,
166
+ 'unsatisfied_contract_ids': ['CANONICAL-EVIDENCE-ARTIFACTS'],
167
+ 'release_blocker_ids': [],
168
+ 'next_mandatory_action': 'Implement selected slice evidence-fixture.',
169
+ 'next_mandatory_role': 'completion-implementer',
170
+ 'remaining_stop_judges': 3,
171
+ 'last_reground_at': '2026-05-03T00:00:00Z',
172
+ 'last_auditor_verdict': None,
173
+ 'contract_status': 'selected_slice_pending_implementation',
174
+ 'latest_completed_slice': head_sha,
175
+ 'latest_verified_slice': head_sha,
176
+ }
177
+ plan = {
178
+ 'schema_version': 1,
179
+ 'mission_anchor': mission,
180
+ 'task_type': task_type,
181
+ 'evaluation_profile': evaluation_profile,
182
+ 'last_reground_at': '2026-05-03T00:00:00Z',
183
+ 'plan_basis': 'canonical_evidence_fixture',
184
+ 'candidate_slices': [
185
+ {
186
+ 'slice_id': 'evidence-fixture',
187
+ 'goal': 'Persist canonical verification evidence for the selected slice.',
188
+ 'acceptance_criteria': acceptance,
189
+ 'contract_ids': ['CANONICAL-EVIDENCE-ARTIFACTS'],
190
+ 'priority': 70,
191
+ 'status': 'selected',
192
+ 'why_now': 'Exercise fail-closed evidence parity.',
193
+ 'blocked_on': [],
194
+ 'evidence': [],
195
+ 'locked_notes': ['Keep the fixture scoped to canonical verification evidence parity.'],
196
+ 'must_fix_findings': [],
197
+ 'implementation_surfaces': implementation_surfaces,
198
+ 'verification_commands': verification_commands,
199
+ 'basis_commit': head_sha,
200
+ 'remaining_contract_ids_before': ['CANONICAL-EVIDENCE-ARTIFACTS'],
201
+ 'release_blocker_count_before': 0,
202
+ 'high_value_gap_count_before': 1,
203
+ }
204
+ ],
205
+ }
206
+ active = {
207
+ 'schema_version': 1,
208
+ 'mission_anchor': mission,
209
+ 'task_type': task_type,
210
+ 'evaluation_profile': evaluation_profile,
211
+ 'status': 'selected',
212
+ 'slice_id': 'evidence-fixture',
213
+ 'goal': 'Persist canonical verification evidence for the selected slice.',
214
+ 'contract_ids': ['CANONICAL-EVIDENCE-ARTIFACTS'],
215
+ 'acceptance_criteria': acceptance,
216
+ 'blocked_on': [],
217
+ 'locked_notes': ['Keep the fixture scoped to canonical verification evidence parity.'],
218
+ 'must_fix_findings': [],
219
+ 'implementation_surfaces': implementation_surfaces,
220
+ 'verification_commands': verification_commands,
221
+ 'basis_commit': head_sha,
222
+ 'remaining_contract_ids_before': ['CANONICAL-EVIDENCE-ARTIFACTS'],
223
+ 'release_blocker_count_before': 0,
224
+ 'high_value_gap_count_before': 1,
225
+ 'priority': 70,
226
+ 'why_now': 'Exercise fail-closed evidence parity.',
227
+ }
228
+
229
+ Path('.agent/state.json').write_text(json.dumps(state, indent=2) + '\n')
230
+ Path('.agent/plan.json').write_text(json.dumps(plan, indent=2) + '\n')
231
+ Path('.agent/active-slice.json').write_text(json.dumps(active, indent=2) + '\n')
232
+ PY
233
+
234
+ if bash .agent/verify_completion_control_plane.sh >/dev/null 2>&1; then
235
+ echo "expected control-plane verification to fail while selected-slice evidence remains at idle placeholder state" >&2
236
+ exit 1
237
+ fi
238
+
239
+ rm .agent/verification-evidence.json
240
+ if bash .agent/verify_completion_control_plane.sh >/dev/null 2>&1; then
241
+ echo "expected control-plane verification to fail when verification-evidence.json is missing" >&2
242
+ exit 1
243
+ fi
244
+
245
+ python3 - "$HEAD_SHA" <<'PY'
246
+ import json
247
+ import sys
248
+ from pathlib import Path
249
+
250
+ head_sha = sys.argv[1]
251
+ verification_commands = [
252
+ 'bash .agent/verify_completion_control_plane.sh',
253
+ 'bash .agent/verify_completion_stop.sh',
254
+ ]
255
+ invalid = {
256
+ 'schema_version': 1,
257
+ 'artifact_type': 'completion-verification-evidence',
258
+ 'subject_type': 'selected_slice',
259
+ 'slice_id': 'evidence-fixture',
260
+ 'goal': 'Persist canonical verification evidence for the selected slice.',
261
+ 'contract_ids': ['CANONICAL-EVIDENCE-ARTIFACTS'],
262
+ 'basis_commit': head_sha,
263
+ 'head_sha': 'stale-head',
264
+ 'verification_commands': verification_commands,
265
+ 'outcome': 'passed',
266
+ 'recorded_at': '2026-05-03T00:00:00Z',
267
+ 'summary': 'Stale selected-slice evidence.',
268
+ }
269
+ Path('.agent/verification-evidence.json').write_text(json.dumps(invalid, indent=2) + '\n')
270
+ PY
271
+
272
+ HEAD_OUTPUT="$(bash .agent/verify_completion_control_plane.sh 2>&1 || true)"
273
+ [[ "$HEAD_OUTPUT" == *"head_sha"* ]] || { echo "expected stale-head verification failure to mention head_sha, got: $HEAD_OUTPUT" >&2; exit 1; }
274
+
275
+ python3 - "$HEAD_SHA" <<'PY'
276
+ import json
277
+ import sys
278
+ from pathlib import Path
279
+
280
+ head_sha = sys.argv[1]
281
+ invalid = {
282
+ 'schema_version': 1,
283
+ 'artifact_type': 'completion-verification-evidence',
284
+ 'subject_type': 'selected_slice',
285
+ 'slice_id': 'evidence-fixture',
286
+ 'goal': 'Persist canonical verification evidence for the selected slice.',
287
+ 'contract_ids': ['CANONICAL-EVIDENCE-ARTIFACTS'],
288
+ 'basis_commit': head_sha,
289
+ 'head_sha': head_sha,
290
+ 'verification_commands': ['bash .agent/verify_completion_control_plane.sh'],
291
+ 'outcome': 'passed',
292
+ 'recorded_at': '2026-05-03T00:00:00Z',
293
+ 'summary': 'Out-of-parity command set.',
294
+ }
295
+ Path('.agent/verification-evidence.json').write_text(json.dumps(invalid, indent=2) + '\n')
296
+ PY
297
+
298
+ COMMAND_OUTPUT="$(bash .agent/verify_completion_control_plane.sh 2>&1 || true)"
299
+ [[ "$COMMAND_OUTPUT" == *"verification_commands"* ]] || {
300
+ echo "expected verification-command parity failure to mention verification_commands, got: $COMMAND_OUTPUT" >&2
301
+ exit 1
302
+ }
303
+
304
+ python3 - "$HEAD_SHA" <<'PY'
305
+ import json
306
+ import sys
307
+ from pathlib import Path
308
+
309
+ head_sha = sys.argv[1]
310
+ valid = {
311
+ 'schema_version': 1,
312
+ 'artifact_type': 'completion-verification-evidence',
313
+ 'subject_type': 'selected_slice',
314
+ 'slice_id': 'evidence-fixture',
315
+ 'goal': 'Persist canonical verification evidence for the selected slice.',
316
+ 'contract_ids': ['CANONICAL-EVIDENCE-ARTIFACTS'],
317
+ 'basis_commit': head_sha,
318
+ 'head_sha': head_sha,
319
+ 'verification_commands': [
320
+ 'bash .agent/verify_completion_control_plane.sh',
321
+ 'bash .agent/verify_completion_stop.sh',
322
+ ],
323
+ 'outcome': 'passed',
324
+ 'recorded_at': '2026-05-03T00:00:00Z',
325
+ 'summary': 'Selected-slice verification evidence matches the active slice and current HEAD.',
326
+ }
327
+ Path('.agent/verification-evidence.json').write_text(json.dumps(valid, indent=2) + '\n')
328
+ PY
329
+
330
+ bash .agent/verify_completion_control_plane.sh >/dev/null
331
+ bash .agent/verify_completion_stop.sh >/dev/null
332
+
333
+ PI_COMPLETION_TEST_SYSTEM_REMINDER_PATH="$SYSTEM_REMINDER" \
334
+ pi -e "$PKG_ROOT" -p "Summarize the completion reminder briefly." \
335
+ >"$TMPDIR/pi-canonical-evidence-reminder.out" 2>"$TMPDIR/pi-canonical-evidence-reminder.err"
336
+
337
+ python3 - "$SYSTEM_REMINDER" <<'PY'
338
+ import sys
339
+ from pathlib import Path
340
+
341
+ text = Path(sys.argv[1]).read_text()
342
+ assert 'Canonical truth lives in .agent/state.json, .agent/plan.json, .agent/active-slice.json, .agent/slice-history.jsonl, .agent/stop-check-history.jsonl, and .agent/verification-evidence.json.' in text, text
343
+ assert 'Verification evidence artifact: .agent/verification-evidence.json (present)' in text, text
344
+ assert 'Verification evidence summary:' in text, text
345
+ assert 'selected_slice' in text, text
346
+ PY
347
+
348
+ echo "canonical evidence artifact test passed: $TMPDIR"
@@ -275,6 +275,35 @@ assert plan['plan_basis'] == 'user_refocus', 'plan_basis should be user_refocus
275
275
  assert active['status'] == 'idle', 'active slice should reset to idle after explicit-goal replacement'
276
276
  PY
277
277
 
278
+ # Active workflow: cancelling the replacement proposal should keep the current workflow and redirect
279
+ # the user back to the main chat before rerunning /cook.
280
+ SESSION_THREE_CANCEL="$TMPDIR/session-three-cancel.jsonl"
281
+ DISCUSSION_THREE_CANCEL=$'Scope:\n- Keep the current workflow unchanged when replacement confirmation is cancelled.\nConstraints:\n- Do not rewrite canonical state after cancel.\nAcceptance:\n- Print rerun guidance.'
282
+ write_session "$SESSION_THREE_CANCEL" "$ROOT" "$DISCUSSION_THREE_CANCEL"
283
+
284
+ PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
285
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=cancel \
286
+ PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
287
+ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
288
+ pi --session "$SESSION_THREE_CANCEL" -e "$PKG_ROOT" -p "/cook Cancelled replacement mission for the active workflow" >"$TMPDIR/pi-completion-context-proposal-replacement-cancel.out" 2>"$TMPDIR/pi-completion-context-proposal-replacement-cancel.err"
289
+
290
+ python3 - "$TMPDIR/pi-completion-context-proposal-replacement-cancel.out" "$TMPDIR/pi-completion-context-proposal-replacement-cancel.err" <<'PY'
291
+ import json
292
+ import sys
293
+ from pathlib import Path
294
+
295
+ mission = 'Explicit replacement mission for the active workflow.'
296
+ state = json.loads(Path('.agent/state.json').read_text())
297
+ plan = json.loads(Path('.agent/plan.json').read_text())
298
+ active = json.loads(Path('.agent/active-slice.json').read_text())
299
+ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
300
+
301
+ assert state['mission_anchor'] == mission, 'replacement proposal cancel should keep the existing mission anchor'
302
+ assert plan['mission_anchor'] == mission, 'replacement proposal cancel should keep plan.json unchanged'
303
+ assert active['mission_anchor'] == mission, 'replacement proposal cancel should keep active-slice.json unchanged'
304
+ assert 'Discuss changes in the main chat and rerun /cook.' in output, 'replacement proposal cancel should redirect back to the main chat and rerun /cook'
305
+ PY
306
+
278
307
  # Completed workflow again: /cook <goal> should start the next round directly from the explicit goal
279
308
  # even when analyst output is unavailable, without merging session-derived scope, constraints, or acceptance.
280
309
  mark_done
@@ -393,15 +422,15 @@ assert 'critique outcome=accepted critique=none' in continuation_reason, 'analys
393
422
  assert 'Keep explicit goals anchored.' in continuation_reason, 'analyst-derived scope should be preserved'
394
423
  PY
395
424
 
396
- # Custom confirmation UI: start should render proposal content separately from explicit Start/Edit/Cancel actions.
425
+ # Custom confirmation UI: start should render proposal content separately from approval-only Start/Cancel actions.
397
426
  UI_ROOT_START="$TMPDIR/ui-root-start"
398
427
  mkdir -p "$UI_ROOT_START"
399
428
  cd "$UI_ROOT_START"
400
429
  git init -q
401
430
 
402
431
  UI_SESSION_START="$TMPDIR/ui-session-start.jsonl"
403
- 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.'
404
- 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."],"critique":["Keep critique details separate from the editable proposal body."],"risks":["Bundling critique into the action list would make the confirmation harder to scan."],"task_type":"completion-workflow","evaluation_profile":"completion-rubric-v1","possible_noise":["old selector wording"],"confidence":0.95}'
432
+ UI_DISCUSSION_START=$'Mission: Replace the crowded selector with a clearer action layout.\nScope:\n- Separate proposal text from actions.\nConstraints:\n- Preserve approval-only Start/Cancel behavior.\nAcceptance:\n- Add regression coverage.'
433
+ UI_ANALYST_OUTPUT_START='{"mission":"Replace the crowded selector with a clearer action layout.","scope":["Separate proposal text from actions."],"constraints":["Preserve approval-only Start/Cancel behavior."],"acceptance":["Add regression coverage."],"critique":["Keep critique details separate from the approval-only proposal summary."],"risks":["Bundling critique into the action list would make the confirmation harder to scan."],"task_type":"completion-workflow","evaluation_profile":"completion-rubric-v1","possible_noise":["old selector wording"],"confidence":0.95}'
405
434
  UI_SNAPSHOT_START="$TMPDIR/context-proposal-ui-start.json"
406
435
  write_session "$UI_SESSION_START" "$UI_ROOT_START" "$UI_DISCUSSION_START"
407
436
 
@@ -422,70 +451,37 @@ state = json.loads(Path('.agent/state.json').read_text())
422
451
  assert snapshot['proposalHeading'] == 'Proposed workflow', 'custom confirmation snapshot should expose a dedicated proposal section'
423
452
  assert snapshot['critiqueHeading'] == 'Critique and risks', 'custom confirmation snapshot should expose critique separately from the proposal body'
424
453
  assert snapshot['routingHeading'] == 'Routing recommendations', 'custom confirmation snapshot should expose routing recommendations separately from the proposal body'
454
+ assert 'approval-only' in snapshot['intro'], 'custom confirmation intro should explain the approval-only gate'
425
455
  assert state['task_type'] == 'completion-workflow', 'start action should preserve canonical task_type'
426
456
  assert state['evaluation_profile'] == 'completion-rubric-v1', 'start action should preserve canonical evaluation_profile'
427
457
  assert 'Mission\nReplace the crowded selector with a clearer action layout.' in snapshot['proposalBody'], 'proposal body should be captured separately from the action list'
428
- assert 'Keep critique details separate from the editable proposal body.' not in snapshot['proposalBody'], 'critique notes should not be embedded in the proposal body'
429
- assert 'Critique\n- Keep critique details separate from the editable proposal body.' in snapshot['critiqueBody'], 'critique section should render accepted critique notes separately'
458
+ assert 'Keep critique details separate from the approval-only proposal summary.' not in snapshot['proposalBody'], 'critique notes should not be embedded in the proposal body'
459
+ assert 'Critique\n- Keep critique details separate from the approval-only proposal summary.' in snapshot['critiqueBody'], 'critique section should render accepted critique notes separately'
430
460
  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'
431
461
  assert 'Possible noise\n- old selector wording' in snapshot['critiqueBody'], 'critique section should render possible-noise notes separately'
432
462
  assert '- task_type: completion-workflow' in snapshot['routingBody'], 'routing section should render the recommended task_type'
433
463
  assert '- evaluation_profile: completion-rubric-v1' in snapshot['routingBody'], 'routing section should render the recommended evaluation_profile'
434
- assert [action['id'] for action in snapshot['actions']] == ['start', 'edit', 'cancel'], 'custom confirmation actions should stay Start/Edit/Cancel'
435
- assert [action['label'] for action in snapshot['actions']] == ['Start', 'Edit', 'Cancel'], 'custom confirmation action labels should be concise'
464
+ assert [action['id'] for action in snapshot['actions']] == ['start', 'cancel'], 'custom confirmation actions should stay Start/Cancel only'
465
+ assert [action['label'] for action in snapshot['actions']] == ['Start', 'Cancel'], 'custom confirmation action labels should be concise'
466
+ 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'
436
467
  for action in snapshot['actions']:
437
468
  assert 'Replace the crowded selector with a clearer action layout.' not in action['label'], 'proposal mission should not be embedded in action labels'
438
469
  assert 'Separate proposal text from actions.' not in action['description'], 'proposal scope should not be embedded in action descriptions'
439
470
  assert state['mission_anchor'] == 'Replace the crowded selector with a clearer action layout.', 'start action should still accept the proposed mission'
440
471
  assert state['continuation_reason'].startswith('User started workflow via /cook:'), 'start action should persist the startup routing outcome in continuation_reason'
441
- assert 'Keep critique details separate from the editable proposal body.' in state['continuation_reason'], 'start action should persist the accepted critique outcome canonically'
442
- PY
443
-
444
- # Custom confirmation UI: edit should keep the existing editor/parsing flow when the action comes from the new layout.
445
- UI_ROOT_EDIT="$TMPDIR/ui-root-edit"
446
- mkdir -p "$UI_ROOT_EDIT"
447
- cd "$UI_ROOT_EDIT"
448
- git init -q
449
-
450
- UI_SESSION_EDIT="$TMPDIR/ui-session-edit.jsonl"
451
- 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.'
452
- 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."],"critique":["Keep critique persistence even when the operator edits the proposal body."],"task_type":"completion-workflow","evaluation_profile":"completion-rubric-v1","confidence":0.94}'
453
- 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.'
454
- write_session "$UI_SESSION_EDIT" "$UI_ROOT_EDIT" "$UI_DISCUSSION_EDIT"
455
-
456
- PI_COMPLETION_TEST_CONTEXT_PROPOSAL_UI_ACTION=edit \
457
- PI_COMPLETION_CONTEXT_PROPOSAL_EDIT_TEXT="$UI_EDIT_TEXT" \
458
- PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$UI_ANALYST_OUTPUT_EDIT" \
459
- PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
460
- 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"
461
-
462
- python3 - <<'PY'
463
- import json
464
- from pathlib import Path
465
-
466
- state = json.loads(Path('.agent/state.json').read_text())
467
- plan = json.loads(Path('.agent/plan.json').read_text())
468
- active = json.loads(Path('.agent/active-slice.json').read_text())
469
- mission = 'Edited mission from the custom confirmation UI.'
470
-
471
- assert state['mission_anchor'] == mission, 'edit action should still route through the proposal parser and update state.json'
472
- assert state['task_type'] == 'completion-workflow', 'edit action should preserve canonical task_type'
473
- assert state['evaluation_profile'] == 'completion-rubric-v1', 'edit action should preserve canonical evaluation_profile'
474
- assert plan['mission_anchor'] == mission, 'edit action should still route through the proposal parser and update plan.json'
475
- assert active['mission_anchor'] == mission, 'edit action should still route through the proposal parser and update active-slice.json'
476
- assert state['current_phase'] == 'reground', 'edit action should still bootstrap/reground the workflow'
477
- assert 'Keep critique persistence even when the operator edits the proposal body.' in state['continuation_reason'], 'edit action should preserve the accepted critique outcome canonically'
472
+ assert 'Keep critique details separate from the approval-only proposal summary.' in state['continuation_reason'], 'start action should persist the accepted critique outcome canonically'
478
473
  PY
479
474
 
480
- # Custom confirmation UI: cancel should exit without writing canonical state.
475
+ # Custom confirmation UI: cancel should exit without writing canonical state and should tell the user
476
+ # to discuss changes in the main chat before rerunning /cook.
481
477
  UI_ROOT_CANCEL="$TMPDIR/ui-root-cancel"
482
478
  mkdir -p "$UI_ROOT_CANCEL"
483
479
  cd "$UI_ROOT_CANCEL"
484
480
  git init -q
485
481
 
486
482
  UI_SESSION_CANCEL="$TMPDIR/ui-session-cancel.jsonl"
487
- 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.'
488
- 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}'
483
+ UI_DISCUSSION_CANCEL=$'Mission: Cancel from the custom confirmation UI without writing state.\nScope:\n- Show the proposal separately from the approval-only actions.\nConstraints:\n- Keep cancellation side-effect free.\nAcceptance:\n- Leave .agent absent after cancel.'
484
+ UI_ANALYST_OUTPUT_CANCEL='{"mission":"Cancel from the custom confirmation UI without writing state.","scope":["Show the proposal separately from the approval-only actions."],"constraints":["Keep cancellation side-effect free."],"acceptance":["Leave .agent absent after cancel."],"confidence":0.92}'
489
485
  UI_SNAPSHOT_CANCEL="$TMPDIR/context-proposal-ui-cancel.json"
490
486
  write_session "$UI_SESSION_CANCEL" "$UI_ROOT_CANCEL" "$UI_DISCUSSION_CANCEL"
491
487
 
@@ -495,13 +491,18 @@ PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$UI_ANALYST_OUTPUT_CANCEL" \
495
491
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
496
492
  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"
497
493
 
498
- python3 - "$UI_SNAPSHOT_CANCEL" <<'PY'
494
+ python3 - "$UI_SNAPSHOT_CANCEL" "$TMPDIR/pi-completion-context-proposal-ui-cancel.out" "$TMPDIR/pi-completion-context-proposal-ui-cancel.err" <<'PY'
499
495
  import json
500
496
  import sys
501
497
  from pathlib import Path
502
498
 
503
499
  snapshot = json.loads(Path(sys.argv[1]).read_text())
504
- assert [action['id'] for action in snapshot['actions']] == ['start', 'edit', 'cancel'], 'cancel snapshot should still expose Start/Edit/Cancel actions'
500
+ output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
501
+ assert 'approval-only' in snapshot['intro'], 'cancel snapshot should keep the approval-only intro'
502
+ assert [action['id'] for action in snapshot['actions']] == ['start', 'cancel'], 'cancel snapshot should expose Start/Cancel actions only'
503
+ assert [action['label'] for action in snapshot['actions']] == ['Start', 'Cancel'], 'cancel snapshot should keep concise Start/Cancel labels'
504
+ 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'
505
+ 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'
505
506
  assert not Path('.agent').exists(), 'cancel action should not write canonical workflow state'
506
507
  PY
507
508