@linimin/pi-letscook 0.1.53 → 0.1.55

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.
@@ -85,117 +85,3 @@ export type CompletionStatusSurface = {
85
85
  liveStateDeltas?: string[];
86
86
  liveDetailsLines?: string[];
87
87
  };
88
-
89
- export type NaturalLanguageCookTriggerMode = "off" | "router" | "auto";
90
- export type CookTriggerClassifierDecision = "offer_workflow" | "normal_prompt" | "unclear";
91
- export type CookTriggerWorkflowBias = "startup" | "resume" | "refocus" | "next_round" | "unknown";
92
-
93
- export type CookTriggerClassification = {
94
- decision: CookTriggerClassifierDecision;
95
- confidence: number;
96
- workflowBias: CookTriggerWorkflowBias;
97
- reason: string;
98
- focusHint?: string;
99
- evidence: string[];
100
- riskFlags: string[];
101
- };
102
-
103
- export type CookTriggerConfirmationAction = "start_workflow" | "send_as_normal_chat" | "cancel";
104
- export type CookTriggerClarificationAction =
105
- | "route_startup"
106
- | "route_resume"
107
- | "route_refocus"
108
- | "route_next_round"
109
- | "send_as_normal_chat"
110
- | "cancel";
111
- export type CookTriggerRecoveryAction = "retry_routing" | "send_as_normal_chat" | "cancel";
112
- export type CookTriggerAdoptedArtifactKind = "recent_plan" | "repo_markdown";
113
-
114
- export type CookTriggerConfirmationActionItem = {
115
- id: CookTriggerConfirmationAction;
116
- label: string;
117
- description: string;
118
- };
119
-
120
- export type CookTriggerClarificationActionItem = {
121
- id: CookTriggerClarificationAction;
122
- label: string;
123
- description: string;
124
- };
125
-
126
- export type CookTriggerConfirmationLayout = {
127
- title: string;
128
- intro: string;
129
- evidenceHeading?: string;
130
- evidenceBody?: string;
131
- riskHeading?: string;
132
- riskBody?: string;
133
- focusHintHeading?: string;
134
- focusHintBody?: string;
135
- actionsHeading: string;
136
- actions: CookTriggerConfirmationActionItem[];
137
- footer: string;
138
- };
139
-
140
- export type CookTriggerClarificationCapsule = {
141
- goal?: string;
142
- scope?: string[];
143
- nonGoal?: string[];
144
- doneWhen?: string[];
145
- selectedWorkflowBias: CookTriggerWorkflowBias;
146
- reason: string;
147
- };
148
-
149
- export type CookTriggerAdoptedArtifact = {
150
- kind: CookTriggerAdoptedArtifactKind;
151
- basis: "explicit_user_adoption";
152
- title: string;
153
- path?: string;
154
- preview?: string;
155
- };
156
-
157
- export type CookNaturalLanguageHandoff = {
158
- preferredRoutingBias?: CookTriggerWorkflowBias;
159
- triggerText?: string;
160
- hintText?: string;
161
- clarificationCapsule?: CookTriggerClarificationCapsule;
162
- adoptedArtifact?: CookTriggerAdoptedArtifact;
163
- };
164
-
165
- export type CookTriggerClarificationLayout = {
166
- title: string;
167
- intro: string;
168
- currentMissionHeading?: string;
169
- currentMissionBody?: string;
170
- candidateMissionHeading?: string;
171
- candidateMissionBody?: string;
172
- adoptedArtifactHeading?: string;
173
- adoptedArtifactBody?: string;
174
- actionsHeading: string;
175
- actions: CookTriggerClarificationActionItem[];
176
- footer: string;
177
- };
178
-
179
- export type CookTriggerRecoveryActionItem = {
180
- id: CookTriggerRecoveryAction;
181
- label: string;
182
- description: string;
183
- };
184
-
185
- export type CookTriggerRecoveryLayout = {
186
- title: string;
187
- intro: string;
188
- failureHeading?: string;
189
- failureBody?: string;
190
- actionsHeading: string;
191
- actions: CookTriggerRecoveryActionItem[];
192
- footer: string;
193
- };
194
-
195
- export type CookTriggerDecision = {
196
- mode: NaturalLanguageCookTriggerMode;
197
- action: "continue" | "handled" | "routed_to_cook";
198
- reason: string;
199
- classification?: CookTriggerClassification;
200
- bypassReason?: string;
201
- };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linimin/pi-letscook",
3
- "version": "0.1.53",
3
+ "version": "0.1.55",
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,
@@ -13,7 +13,14 @@
13
13
  "bugs": {
14
14
  "url": "https://github.com/linimin/pi-letscook/issues"
15
15
  },
16
- "keywords": ["pi-package", "pi", "workflow", "completion", "agent", "subagent"],
16
+ "keywords": [
17
+ "pi-package",
18
+ "pi",
19
+ "workflow",
20
+ "completion",
21
+ "agent",
22
+ "subagent"
23
+ ],
17
24
  "files": [
18
25
  "extensions",
19
26
  "skills",
@@ -43,7 +50,11 @@
43
50
  "typebox": "*"
44
51
  },
45
52
  "pi": {
46
- "extensions": ["./extensions/completion"],
47
- "skills": ["./skills"]
53
+ "extensions": [
54
+ "./extensions/completion"
55
+ ],
56
+ "skills": [
57
+ "./skills"
58
+ ]
48
59
  }
49
60
  }
@@ -82,9 +82,13 @@ assertIncludes('extensions/completion/prompt-surfaces.ts', 'Active slice contrac
82
82
  assertIncludes('extensions/completion/index.ts', 'Canonical active-slice contract drift is currently: ${activeContractDrift}');
83
83
  assertIncludes('extensions/completion/prompt-surfaces.ts', '`active_slice_contract_drift_fields: ${args.activeSliceContractDrift}`');
84
84
  assertIncludes('extensions/completion/index.ts', 'treat .agent/active-slice.json as the canonical implementation contract');
85
+ assertIncludes('.agent/verify_completion_control_plane.sh', 'const REQUIRED_TRACKED_CONTRACT_FILES = [');
86
+ assertIncludes('.agent/verify_completion_control_plane.sh', 'Required tracked completion contract file is missing from git index:');
85
87
  assertIncludes('.agent/verify_completion_control_plane.sh', "const planMirrorFields = ['locked_notes', 'must_fix_findings', 'implementation_surfaces', 'verification_commands', 'basis_commit', 'remaining_contract_ids_before', 'release_blocker_count_before', 'high_value_gap_count_before'];");
86
88
  assertIncludes('.agent/verify_completion_control_plane.sh', 'slice_id must match a slice in .agent/plan.json when status carries an exact handoff');
87
89
  assertIncludes('.agent/verify_completion_control_plane.sh', '.agent/active-slice.json must match the selected .agent/plan.json slice across: ');
90
+ assertIncludes('.agent/verify_completion_control_plane.sh', '.agent/active-slice.json implementation_surfaces must cover every tracked file changed from basis_commit to current HEAD; missing: ');
91
+ assertIncludes('scripts/release-check.sh', 'git ls-files --error-unmatch .agent/README.md .agent/mission.md .agent/profile.json .agent/verify_completion_stop.sh .agent/verify_completion_control_plane.sh >/dev/null');
88
92
  NODE
89
93
 
90
94
  ROOT="$TMPDIR/repo"
@@ -102,11 +106,24 @@ PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
102
106
  pi --session "$BOOTSTRAP_SESSION" -e "$PKG_ROOT" -p "/cook" \
103
107
  >"$TMPDIR/pi-active-slice-bootstrap.out" 2>"$TMPDIR/pi-active-slice-bootstrap.err"
104
108
 
105
- python3 - <<'PY'
109
+ git config user.name 'Completion Fixture'
110
+ git config user.email 'completion-fixture@example.com'
111
+ git add .
112
+ git commit -q -m 'fixture basis'
113
+ BASIS_SHA="$(git rev-parse HEAD)"
114
+ printf '\nSlice surface fixture change.\n' >> README.md
115
+ git add README.md
116
+ git commit -q -m 'fixture slice change'
117
+ HEAD_SHA="$(git rev-parse HEAD)"
118
+
119
+ BASIS_SHA="$BASIS_SHA" HEAD_SHA="$HEAD_SHA" python3 - <<'PY'
106
120
  import json
121
+ import os
107
122
  from pathlib import Path
108
123
 
109
124
  mission = 'Exercise active-slice contract parity.'
125
+ basis_sha = os.environ['BASIS_SHA']
126
+ head_sha = os.environ['HEAD_SHA']
110
127
  task_type = 'completion-workflow'
111
128
  evaluation_profile = 'completion-rubric-v1'
112
129
  verification_commands = [
@@ -157,8 +174,8 @@ state = {
157
174
  'last_reground_at': '2026-05-03T00:00:00Z',
158
175
  'last_auditor_verdict': None,
159
176
  'contract_status': 'selected_slice_pending_implementation',
160
- 'latest_completed_slice': 'fixturebasis',
161
- 'latest_verified_slice': 'fixturebasis',
177
+ 'latest_completed_slice': head_sha,
178
+ 'latest_verified_slice': head_sha,
162
179
  }
163
180
  plan = {
164
181
  'schema_version': 1,
@@ -182,7 +199,7 @@ plan = {
182
199
  'must_fix_findings': must_fix_findings,
183
200
  'implementation_surfaces': implementation_surfaces,
184
201
  'verification_commands': verification_commands,
185
- 'basis_commit': 'fixturebasis',
202
+ 'basis_commit': basis_sha,
186
203
  'remaining_contract_ids_before': remaining_contracts,
187
204
  'release_blocker_count_before': 0,
188
205
  'high_value_gap_count_before': 2,
@@ -204,7 +221,7 @@ active = {
204
221
  'must_fix_findings': must_fix_findings,
205
222
  'implementation_surfaces': implementation_surfaces,
206
223
  'verification_commands': verification_commands,
207
- 'basis_commit': 'fixturebasis',
224
+ 'basis_commit': basis_sha,
208
225
  'remaining_contract_ids_before': remaining_contracts,
209
226
  'release_blocker_count_before': 0,
210
227
  'high_value_gap_count_before': 2,
@@ -223,7 +240,7 @@ Path('.agent/verification-evidence.json').write_text(json.dumps({
223
240
  'goal': active['goal'],
224
241
  'contract_ids': active['contract_ids'],
225
242
  'basis_commit': active['basis_commit'],
226
- 'head_sha': active['basis_commit'],
243
+ 'head_sha': head_sha,
227
244
  'verification_commands': verification_commands,
228
245
  'outcome': 'passed',
229
246
  'recorded_at': '2026-05-03T00:00:00Z',
@@ -247,6 +264,19 @@ PY
247
264
 
248
265
  bash .agent/verify_completion_control_plane.sh >/dev/null
249
266
 
267
+ git rm --cached -q .agent/profile.json
268
+ if bash .agent/verify_completion_control_plane.sh >"$TMPDIR/untracked-contract.out" 2>"$TMPDIR/untracked-contract.err"; then
269
+ echo 'expected verifier to fail when a required tracked .agent contract file becomes untracked' >&2
270
+ exit 1
271
+ fi
272
+ if ! grep -q 'Required tracked completion contract file is missing from git index: .agent/profile.json' "$TMPDIR/untracked-contract.err"; then
273
+ echo 'expected verifier failure output to mention the untracked .agent/profile.json contract file' >&2
274
+ cat "$TMPDIR/untracked-contract.err" >&2
275
+ exit 1
276
+ fi
277
+ git add .agent/profile.json
278
+ bash .agent/verify_completion_control_plane.sh >/dev/null
279
+
250
280
  python3 - <<'PY'
251
281
  import copy
252
282
  import json
@@ -254,7 +284,9 @@ import subprocess
254
284
  from pathlib import Path
255
285
 
256
286
  plan_path = Path('.agent/plan.json')
287
+ active_path = Path('.agent/active-slice.json')
257
288
  base_plan = json.loads(plan_path.read_text())
289
+ base_active = json.loads(active_path.read_text())
258
290
 
259
291
  cases = [
260
292
  ('slice_id', lambda slice: slice.__setitem__('slice_id', 'different-slice')),
@@ -285,6 +317,29 @@ for label, mutate in cases:
285
317
  assert label in combined, f'expected verifier output to mention {label}, got: {combined}'
286
318
 
287
319
  plan_path.write_text(json.dumps(base_plan, indent=2) + '\n')
320
+ active_path.write_text(json.dumps(base_active, indent=2) + '\n')
321
+
322
+ surface_drift_plan = copy.deepcopy(base_plan)
323
+ surface_drift_active = copy.deepcopy(base_active)
324
+ surface_drift_plan['candidate_slices'][0]['implementation_surfaces'] = [
325
+ surface for surface in surface_drift_plan['candidate_slices'][0]['implementation_surfaces']
326
+ if surface != 'README.md'
327
+ ]
328
+ surface_drift_active['implementation_surfaces'] = [
329
+ surface for surface in surface_drift_active['implementation_surfaces']
330
+ if surface != 'README.md'
331
+ ]
332
+ plan_path.write_text(json.dumps(surface_drift_plan, indent=2) + '\n')
333
+ active_path.write_text(json.dumps(surface_drift_active, indent=2) + '\n')
334
+ result = subprocess.run(['bash', '.agent/verify_completion_control_plane.sh'], capture_output=True, text=True)
335
+ combined = (result.stdout or '') + (result.stderr or '')
336
+ assert result.returncode != 0, 'expected verifier failure when implementation_surfaces omit a tracked basis-to-HEAD diff file'
337
+ assert 'implementation_surfaces must cover every tracked file changed from basis_commit to current HEAD; missing: README.md' in combined, combined
338
+
339
+ plan_path.write_text(json.dumps(base_plan, indent=2) + '\n')
340
+ active_path.write_text(json.dumps(base_active, indent=2) + '\n')
288
341
  PY
289
342
 
343
+ bash .agent/verify_completion_control_plane.sh >/dev/null
344
+
290
345
  echo "active-slice contract test passed: $TMPDIR"
@@ -654,6 +654,16 @@ assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluatio
654
654
  assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after analyst-derived bootstrap'
655
655
  assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after analyst-derived bootstrap'
656
656
  assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after analyst-derived bootstrap'
657
+ brief = state['advisory_startup_brief']
658
+ assert brief['kind'] == 'startup_brief', 'state.json should preserve the confirmed startup brief as advisory intake'
659
+ assert brief['mission'] == mission, 'advisory startup brief mission should match the accepted mission anchor'
660
+ assert brief['scope'] == ['Keep the non-running completion widget.', 'Suppress the widget while a completion role is active.'], 'advisory startup brief should preserve scope items separately from canonical planning state'
661
+ assert brief['constraints'] == ['Do not reintroduce any other completion status surface.'], 'advisory startup brief should preserve constraints separately from canonical planning state'
662
+ assert brief['acceptance'] == ['Update README to match the shipped behavior.', 'Keep observability regression coverage truthful.'], 'advisory startup brief should preserve acceptance separately from canonical planning state'
663
+ assert brief['risks'] == ['Stale widget-removal discussion could broaden the startup plan if it gets treated as mission text.'], 'advisory startup brief should preserve derived risks'
664
+ assert brief['notes'] == ['Keep critique separate from the mission anchor so startup analysis does not rewrite the workflow goal.', 'Possible noise: older widget restyle ideas'], 'advisory startup brief should preserve operator notes and possible-noise cautions'
665
+ assert plan['candidate_slices'] == [], 'startup brief should remain advisory intake only until regrounder owns plan selection'
666
+ assert active['status'] == 'idle', 'startup brief should not become the active-slice source before regrounder runs'
657
667
  assert proposal['mission'] == mission, 'discussion-only proposal snapshot should keep the inferred mission anchor'
658
668
  assert proposal['analysis']['taskType'] == expected_task_type, 'discussion-only proposal snapshot should expose task_type hints separately'
659
669
  assert proposal['analysis']['evaluationProfile'] == expected_eval_profile, 'discussion-only proposal snapshot should expose evaluation_profile hints separately'
@@ -1080,6 +1090,7 @@ assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json eva
1080
1090
  assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after starting the next workflow round'
1081
1091
  assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after starting the next workflow round'
1082
1092
  assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after starting the next workflow round'
1093
+ assert state['advisory_startup_brief']['mission'] == mission, 'next-round startup should preserve the confirmed startup brief as advisory intake'
1083
1094
  assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after starting the next workflow round'
1084
1095
  assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after starting the next workflow round'
1085
1096
  assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after starting the next workflow round'
@@ -1101,8 +1112,7 @@ assert plan['plan_basis'] == 'user_refocus', 'plan_basis should reset to user_re
1101
1112
  assert active['status'] == 'idle', 'active-slice should reset to idle for the next workflow round'
1102
1113
  PY
1103
1114
 
1104
- # Active workflow: /cook <hint> should bias active-workflow proposal derivation,
1105
- # still route through the chooser, and leave canonical state unchanged when the user cancels.
1115
+ # Active workflow: inline `/cook` arguments should fail closed immediately and leave canonical state unchanged.
1106
1116
  ACTIVE_INLINE_REJECTION_ROUTING="$TMPDIR/context-proposal-active-inline-arg-routing.json"
1107
1117
  ACTIVE_INLINE_REJECTION_PROPOSAL="$TMPDIR/context-proposal-active-inline-arg-proposal.json"
1108
1118
  ACTIVE_INLINE_REJECTION_CHOOSER="$TMPDIR/context-proposal-active-inline-arg-chooser.json"
@@ -1123,9 +1133,6 @@ tracked = [
1123
1133
  Path(sys.argv[1]).write_text(json.dumps({path.name: path.read_text() for path in tracked}, indent=2) + '\n')
1124
1134
  PY
1125
1135
 
1126
- PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
1127
- PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=cancel \
1128
- PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT='{"mission":"Replacement mission for the active workflow.","scope":["Review the replacement through the existing workflow chooser first."],"constraints":["Do not rewrite canonical state before the final Start confirmation."],"acceptance":["Show the final replacement proposal only after the chooser selects refocus."],"task_type":"completion-workflow","evaluation_profile":"completion-rubric-v1","confidence":0.9}' \
1129
1136
  PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$ACTIVE_INLINE_REJECTION_PROPOSAL" \
1130
1137
  PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$ACTIVE_INLINE_REJECTION_ROUTING" \
1131
1138
  PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$ACTIVE_INLINE_REJECTION_CHOOSER" \
@@ -1138,9 +1145,9 @@ import sys
1138
1145
  from pathlib import Path
1139
1146
 
1140
1147
  output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
1141
- routing = json.loads(Path(sys.argv[3]).read_text())
1148
+ routing = Path(sys.argv[3])
1142
1149
  proposal = Path(sys.argv[4])
1143
- chooser = json.loads(Path(sys.argv[5]).read_text())
1150
+ chooser = Path(sys.argv[5])
1144
1151
  before = json.loads(Path(sys.argv[6]).read_text())
1145
1152
  tracked = [
1146
1153
  Path('.agent/mission.md'),
@@ -1151,17 +1158,15 @@ tracked = [
1151
1158
  Path('.agent/verification-evidence.json'),
1152
1159
  ]
1153
1160
 
1154
- assert routing['action'] == 'refocus', 'active /cook <hint> should run active-workflow routing'
1155
- assert routing['proposedMissionAnchor'] == 'Replacement mission for the active workflow.', 'active /cook <hint> should bias toward the hinted replacement mission'
1156
- assert json.loads(proposal.read_text())['mission'] == 'Replacement mission for the active workflow.', 'active /cook <hint> should carry the hinted mission into the final replacement proposal'
1157
- assert chooser['choices'][1].startswith('Start new workflow from recent discussion'), 'active /cook <hint> should open the existing-workflow chooser'
1158
- assert 'Cancelled replacement workflow proposal.' in output or 'Cancelled existing workflow confirmation.' in output, 'active /cook <hint> cancel should report cancellation'
1161
+ assert not routing.exists(), 'active /cook inline-args rejection should not run active-workflow routing'
1162
+ assert not proposal.exists(), 'active /cook inline-args rejection should not emit a replacement startup-brief proposal'
1163
+ assert not chooser.exists(), 'active /cook inline-args rejection should not open the existing-workflow chooser'
1164
+ assert '/cook no longer accepts inline arguments.' in output, 'active /cook inline-args rejection should explain the bare-only entry contract'
1159
1165
  after = {path.name: path.read_text() for path in tracked}
1160
- assert before == after, 'active /cook <hint> cancel should leave canonical files unchanged'
1166
+ assert before == after, 'active /cook inline-args rejection should leave canonical files unchanged'
1161
1167
  PY
1162
1168
 
1163
- # Completed workflow: /cook <hint> should bias next-round proposal derivation and still leave canonical state
1164
- # unchanged when the user cancels the approval-only proposal.
1169
+ # Completed workflow: inline `/cook` arguments should also fail closed before any next-round proposal derivation.
1165
1170
  mark_done
1166
1171
 
1167
1172
  DONE_INLINE_REJECTION_ROUTING="$TMPDIR/context-proposal-done-inline-arg-routing.json"
@@ -1184,8 +1189,6 @@ tracked = [
1184
1189
  Path(sys.argv[1]).write_text(json.dumps({path.name: path.read_text() for path in tracked}, indent=2) + '\n')
1185
1190
  PY
1186
1191
 
1187
- PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=cancel \
1188
- PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT='{"mission":"Update README guidance for the next workflow round.","scope":["Refresh README guidance for /cook hint-driven startup behavior."],"constraints":["Do not rewrite canonical state before Start is accepted."],"acceptance":["Keep the next-round proposal scoped to README updates only."],"task_type":"completion-workflow","evaluation_profile":"completion-rubric-v1","confidence":0.9}' \
1189
1192
  PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DONE_INLINE_REJECTION_PROPOSAL" \
1190
1193
  PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$DONE_INLINE_REJECTION_ROUTING" \
1191
1194
  PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$DONE_INLINE_REJECTION_CHOOSER" \
@@ -1199,7 +1202,7 @@ from pathlib import Path
1199
1202
 
1200
1203
  output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
1201
1204
  routing = Path(sys.argv[3])
1202
- proposal = json.loads(Path(sys.argv[4]).read_text())
1205
+ proposal = Path(sys.argv[4])
1203
1206
  chooser = Path(sys.argv[5])
1204
1207
  before = json.loads(Path(sys.argv[6]).read_text())
1205
1208
  tracked = [
@@ -1211,14 +1214,14 @@ tracked = [
1211
1214
  Path('.agent/verification-evidence.json'),
1212
1215
  ]
1213
1216
  state_before = json.loads(before['state.json'])
1214
- assert state_before['current_phase'] == 'done', 'done /cook <hint> should start from a completed workflow'
1215
- assert state_before['project_done'] is True, 'done /cook <hint> should start from project_done=true'
1216
- assert not routing.exists(), 'done /cook <hint> should not run active-workflow routing while starting the next round'
1217
- assert proposal['mission'] == 'Update README guidance for the next workflow round.', 'done /cook <hint> should bias next-round mission derivation toward the hint'
1218
- assert not chooser.exists(), 'done /cook <hint> should not open the existing-workflow chooser when starting the next round'
1219
- assert 'Cancelled next workflow round proposal.' in output, 'done /cook <hint> cancel should report next-round proposal cancellation'
1217
+ assert state_before['current_phase'] == 'done', 'done /cook inline-args rejection should start from a completed workflow'
1218
+ assert state_before['project_done'] is True, 'done /cook inline-args rejection should start from project_done=true'
1219
+ assert not routing.exists(), 'done /cook inline-args rejection should not run active-workflow routing while starting the next round'
1220
+ assert not proposal.exists(), 'done /cook inline-args rejection should not emit a next-round startup-brief proposal'
1221
+ assert not chooser.exists(), 'done /cook inline-args rejection should not open the existing-workflow chooser when starting the next round'
1222
+ assert '/cook no longer accepts inline arguments.' in output, 'done /cook inline-args rejection should explain the bare-only entry contract'
1220
1223
  after = {path.name: path.read_text() for path in tracked}
1221
- assert before == after, 'done /cook <hint> cancel should leave canonical files unchanged'
1224
+ assert before == after, 'done /cook inline-args rejection should leave canonical files unchanged'
1222
1225
  PY
1223
1226
 
1224
1227
  # Completed workflow again: /cook with no goal should be able to use model-assisted
@@ -1296,15 +1299,15 @@ from pathlib import Path
1296
1299
  snapshot = json.loads(Path(sys.argv[1]).read_text())
1297
1300
  state = json.loads(Path('.agent/state.json').read_text())
1298
1301
 
1299
- assert snapshot['proposalHeading'] == 'Proposed workflow', 'custom confirmation snapshot should expose a dedicated proposal section'
1300
- assert snapshot['critiqueHeading'] == 'Critique and risks', 'custom confirmation snapshot should expose critique separately from the proposal body'
1302
+ assert snapshot['proposalHeading'] == 'Startup brief', 'custom confirmation snapshot should expose a dedicated startup-brief section'
1303
+ assert snapshot['critiqueHeading'] == 'Notes and risks', 'custom confirmation snapshot should expose notes separately from the startup-brief body'
1301
1304
  assert snapshot['routingHeading'] == 'Routing recommendations', 'custom confirmation snapshot should expose routing recommendations separately from the proposal body'
1302
1305
  assert 'approval-only' in snapshot['intro'], 'custom confirmation intro should explain the approval-only gate'
1303
1306
  assert state['task_type'] == 'completion-workflow', 'start action should preserve canonical task_type'
1304
1307
  assert state['evaluation_profile'] == 'completion-rubric-v1', 'start action should preserve canonical evaluation_profile'
1305
1308
  assert 'Mission\nReplace the crowded selector with a clearer action layout.' in snapshot['proposalBody'], 'proposal body should be captured separately from the action list'
1306
- 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'
1307
- assert 'Critique\n- Keep critique details separate from the approval-only proposal summary.' in snapshot['critiqueBody'], 'critique section should render accepted critique notes separately'
1309
+ assert 'Keep critique details separate from the approval-only proposal summary.' not in snapshot['proposalBody'], 'critique notes should not be embedded in the startup-brief body'
1310
+ assert 'Critique\n- Keep critique details separate from the approval-only proposal summary.' in snapshot['critiqueBody'], 'notes section should render accepted critique notes separately'
1308
1311
  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'
1309
1312
  assert 'Possible noise\n- old selector wording' in snapshot['critiqueBody'], 'critique section should render possible-noise notes separately'
1310
1313
  assert '- task_type: completion-workflow' in snapshot['routingBody'], 'routing section should render the recommended task_type'
@@ -1316,6 +1319,7 @@ for action in snapshot['actions']:
1316
1319
  assert 'Replace the crowded selector with a clearer action layout.' not in action['label'], 'proposal mission should not be embedded in action labels'
1317
1320
  assert 'Separate proposal text from actions.' not in action['description'], 'proposal scope should not be embedded in action descriptions'
1318
1321
  assert state['mission_anchor'] == 'Replace the crowded selector with a clearer action layout.', 'start action should still accept the proposed mission'
1322
+ assert state['advisory_startup_brief']['mission'] == 'Replace the crowded selector with a clearer action layout.', 'start action should preserve the confirmed startup brief canonically'
1319
1323
  assert state['continuation_reason'].startswith('User started workflow via /cook:'), 'start action should persist the startup routing outcome in continuation_reason'
1320
1324
  assert 'Keep critique details separate from the approval-only proposal summary.' in state['continuation_reason'], 'start action should persist the accepted critique outcome canonically'
1321
1325
  PY
@@ -54,6 +54,13 @@ assertIncludes('extensions/completion/role-runner.ts', 'async function runContex
54
54
  assertIncludes('extensions/completion/prompt-surfaces.ts', 'export function buildSystemReminder(');
55
55
  assertIncludes('extensions/completion/prompt-surfaces.ts', 'export function buildResumeCapsule(');
56
56
 
57
+ if (fs.existsSync('extensions/completion/input-routing.ts')) {
58
+ throw new Error('extensions/completion/input-routing.ts should be removed after explicit-/cook cleanup');
59
+ }
60
+ if (fs.existsSync('scripts/cook-trigger-routing-test.sh')) {
61
+ throw new Error('scripts/cook-trigger-routing-test.sh should be removed after explicit-/cook cleanup');
62
+ }
63
+
57
64
  assertIncludes('extensions/completion/index.ts', 'scaffoldCompletionFiles as scaffoldCompletionFilesOnDisk');
58
65
  assertIncludes('extensions/completion/index.ts', 'return await scaffoldCompletionFilesOnDisk(root, missionAnchor, {');
59
66
  assertIncludes('extensions/completion/index.ts', 'applyLiveRoleEvent,');
@@ -102,6 +109,10 @@ assertNotIncludes('extensions/completion/index.ts', 'async function analyzeConte
102
109
  assertNotIncludes('extensions/completion/index.ts', 'function deriveMissionAnchor(');
103
110
  assertNotIncludes('extensions/completion/index.ts', 'function buildSystemReminder(');
104
111
  assertNotIncludes('extensions/completion/index.ts', 'function buildResumeCapsule(');
112
+ assertNotIncludes('extensions/completion/role-runner.ts', 'classifyCookTriggerIntentWithAgent(');
113
+ assertNotIncludes('extensions/completion/role-runner.ts', 'COOK_TRIGGER_CLASSIFIER_SYSTEM_PROMPT');
114
+ assertNotIncludes('extensions/completion/prompt-surfaces.ts', 'buildCookTriggerConfirmationLayout(');
115
+ assertNotIncludes('extensions/completion/prompt-surfaces.ts', 'buildNaturalLanguageHandoffMetadataLines(');
105
116
  NODE
106
117
 
107
118
  echo "legacy cleanup test passed: $ROOT"
@@ -102,8 +102,6 @@ tracked = [
102
102
  Path(sys.argv[1]).write_text(json.dumps({path.name: path.read_text() for path in tracked}, indent=2) + '\n')
103
103
  PY
104
104
 
105
- PI_COMPLETION_EXISTING_WORKFLOW_ACTION=cancel \
106
- PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT='{"mission":"Replacement mission that should stay in the main chat.","scope":["Replace the current workflow only after explicit approval."],"constraints":["Do not rewrite canonical state before the final Start confirmation."],"acceptance":["Surface the chooser before any replacement workflow rewrite."],"task_type":"completion-workflow","evaluation_profile":"completion-rubric-v1","confidence":0.9}' \
107
105
  PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$INLINE_REJECTION_ROUTING" \
108
106
  PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$INLINE_REJECTION_PROPOSAL" \
109
107
  PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$INLINE_REJECTION_CHOOSER" \
@@ -117,9 +115,9 @@ import sys
117
115
  from pathlib import Path
118
116
 
119
117
  output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
120
- routing = json.loads(Path(sys.argv[3]).read_text())
118
+ routing = Path(sys.argv[3])
121
119
  proposal = Path(sys.argv[4])
122
- chooser = json.loads(Path(sys.argv[5]).read_text())
120
+ chooser = Path(sys.argv[5])
123
121
  initial_mission = sys.argv[6]
124
122
  before = json.loads(Path(sys.argv[7]).read_text())
125
123
  tracked = [
@@ -131,14 +129,13 @@ tracked = [
131
129
  Path('.agent/verification-evidence.json'),
132
130
  ]
133
131
  current_state = json.loads(before['state.json'])
134
- assert current_state['mission_anchor'] == initial_mission, 'active /cook <hint> should start from the current mission anchor'
135
- assert routing['action'] == 'refocus', 'active /cook <hint> should route through active-workflow replacement assessment'
136
- assert routing['proposedMissionAnchor'] == 'Replacement mission that should stay in the main chat.', 'active /cook <hint> should preserve the hinted replacement mission'
137
- assert not proposal.exists(), 'active /cook <hint> chooser cancel should not open final proposal confirmation'
138
- assert chooser['choices'][1].startswith('Start new workflow from recent discussion'), 'active /cook <hint> should open the existing-workflow chooser'
139
- assert 'Cancelled existing workflow confirmation.' in output, 'active /cook <hint> chooser cancel should report cancellation'
132
+ assert current_state['mission_anchor'] == initial_mission, 'active /cook inline-args rejection should start from the current mission anchor'
133
+ assert not routing.exists(), 'active /cook inline-args rejection should not run active-workflow routing'
134
+ assert not proposal.exists(), 'active /cook inline-args rejection should not open final startup-brief confirmation'
135
+ assert not chooser.exists(), 'active /cook inline-args rejection should not open the existing-workflow chooser'
136
+ assert '/cook no longer accepts inline arguments.' in output, 'active /cook inline-args rejection should explain the bare-only entry contract'
140
137
  after = {path.name: path.read_text() for path in tracked}
141
- assert before == after, 'active /cook <hint> chooser cancel should leave canonical files unchanged'
138
+ assert before == after, 'active /cook inline-args rejection should leave canonical files unchanged'
142
139
  PY
143
140
 
144
141
  SESSION_INITIAL_REFOCUS="$TMPDIR/session-initial-bare-refocus.jsonl"
@@ -175,6 +172,7 @@ assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json eva
175
172
  assert state['mission_anchor'] == new_anchor, 'state.json mission_anchor mismatch after refocus'
176
173
  assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after refocus'
177
174
  assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after refocus'
175
+ assert state['advisory_startup_brief']['mission'] == new_anchor, 'refocus should preserve the confirmed startup brief as advisory intake'
178
176
  assert plan['mission_anchor'] == new_anchor, 'plan.json mission_anchor mismatch after refocus'
179
177
  assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after refocus'
180
178
  assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after refocus'
@@ -338,6 +336,7 @@ assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json eva
338
336
  assert state['mission_anchor'] == new_anchor, 'state.json mission_anchor mismatch after bare refocus'
339
337
  assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after bare refocus'
340
338
  assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after bare refocus'
339
+ assert state['advisory_startup_brief']['mission'] == new_anchor, 'bare refocus should preserve the confirmed startup brief as advisory intake'
341
340
  assert plan['mission_anchor'] == new_anchor, 'plan.json mission_anchor mismatch after bare refocus'
342
341
  assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after bare refocus'
343
342
  assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after bare refocus'
@@ -4,54 +4,47 @@ set -euo pipefail
4
4
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
5
  cd "$ROOT"
6
6
 
7
- echo "[release-check] running control-plane validation, /cook public parity, workflow-aware router coverage, role-runner extraction, startup/refocus/context regressions, canonical evidence artifact, active-slice contract, observability, legacy cleanup, evaluator calibration, and rubric contract coverage"
7
+ echo "[release-check] running control-plane validation, tracked .agent contract coverage, slice-surface parity, explicit-/cook parity, startup/refocus/context regressions, canonical evidence artifact, active-slice contract, observability, legacy cleanup, evaluator calibration, and rubric contract coverage"
8
8
  bash .agent/verify_completion_control_plane.sh
9
+ git ls-files --error-unmatch .agent/README.md .agent/mission.md .agent/profile.json .agent/verify_completion_stop.sh .agent/verify_completion_control_plane.sh >/dev/null
9
10
 
10
- echo "[release-check] verifying public /cook parity and workflow-aware router docs/help"
11
+ echo "[release-check] verifying public /cook parity and explicit-entry docs/help"
11
12
  python3 - <<'PY'
12
13
  import re
13
14
  from pathlib import Path
14
15
 
15
16
  checks = {
16
17
  "README.md": [
17
- "Natural-language routing is optional and shipped in two modes: `off` disables it, and `router` reviews each non-bypass user turn before implementation starts while leaving ordinary questions in the main chat.",
18
- "Set `PI_COMPLETION_TRIGGER_MODE` before starting Pi if you want to change how natural-language routing behaves:",
19
- "- `off` — natural-language routing is disabled. Only explicit `/cook` or `/cook <hint>` can enter the workflow.",
20
- "- `router` *(default)* — the workflow-aware router reviews each non-bypass normal user turn before implementation starts.",
21
- "the original message only reaches the normal chat path if you explicitly choose **Send as normal chat**",
22
- "Explicit `/cook` is always the canonical fallback, even when natural-language routing is enabled in `router` mode.",
23
- "router-mode false positives and classifier failures stay fail-closed unless you explicitly choose **Send as normal chat**",
24
- "bash ./scripts/cook-trigger-routing-test.sh",
18
+ "`/cook` is the explicit workflow boundary for starting, continuing, refocusing, or beginning the next round of long-running repo work.",
19
+ "Only explicit `/cook` enters the workflow. Ordinary prompts stay in the main chat and go straight to the primary agent.",
20
+ "`/cook` is the canonical workflow boundary and manual entry point",
21
+ "Discuss the concrete repo change in the main chat, then run `/cook`",
22
+ "The confirmed startup brief is also preserved there as advisory intake for later re-grounding.",
25
23
  ],
26
24
  "CHANGELOG.md": [
27
- "removed assist mode from public routing behavior so natural-language entry is now either off or router, and made router the default trigger mode while keeping `/cook` as the canonical workflow boundary",
28
- "documented the explicit router-mode **Send as normal chat** recovery path as a user choice, not as a silent downgrade, and kept public copy scoped to currently shipped router behavior rather than future auto-mode plans",
29
- "made `npm run release-check` fail closed on the shipped workflow-aware router docs/help contract while continuing to rerun `bash ./scripts/cook-trigger-routing-test.sh` alongside the existing `/cook` smoke/refocus/context regressions",
25
+ "made `/cook` derive a confirmable startup brief from recent discussion before any canonical workflow rewrite, then preserve the confirmed brief in canonical state as advisory intake for later re-grounding",
26
+ "removed inline `/cook` arguments from the shipped entry path again so explicit bare `/cook` is the only public command, and fail closed when recent discussion is insufficient or unreliable",
30
27
  ],
31
28
  "extensions/completion/index.ts": [
32
- 'description: "/cook workflow: start, continue, refocus, or start the next round; /cook stays canonical while natural-language routing can be off or router"',
33
- 'const COOK_BARE_ONLY_GUIDANCE =',
34
- '"/cook remains the canonical workflow boundary. Natural-language routing can stay off or run in router mode to review each non-bypass user turn before implementation starts, but the shared /cook flow still owns mission selection and confirmation."',
35
- '"/cook failed closed because recent discussion did not produce a clear execution-ready Mission/Scope/Constraints/Acceptance proposal for concrete repo changes. Router mode only offers the same /cook flow, and router recovery only replays to normal chat when you explicitly choose Send as normal chat, so clarify the concrete repo changes in the main chat and rerun /cook."',
29
+ 'description: "/cook workflow: derive a startup brief from recent discussion, then start, continue, refocus, or start the next round from the explicit /cook command"',
30
+ '"/cook failed closed because recent discussion did not produce a clear execution-ready startup brief with Mission/Scope/Constraints/Acceptance for concrete repo changes. Clarify the concrete repo changes in the main chat and rerun /cook."',
36
31
  ],
37
32
  }
38
33
 
39
34
  forbidden = {
40
35
  "README.md": [
41
- "Assist-mode natural-language handoff can also offer to enter that same `/cook` flow before the primary agent starts implementation work, but `/cook` remains the canonical workflow boundary.",
42
- "## Natural-language handoff (assist mode)",
43
- "`assist`",
36
+ "`/cook <hint>`",
37
+ "Natural-language routing is optional and shipped in two modes",
38
+ "PI_COMPLETION_TRIGGER_MODE",
39
+ "workflow-aware router",
40
+ "Send as normal chat",
41
+ "bash ./scripts/cook-trigger-routing-test.sh",
44
42
  ],
45
43
  "CHANGELOG.md": ["compatibility" + " shim"],
46
44
  "extensions/completion/index.ts": [
47
- 'description: "/cook workflow: start, continue, refocus, or start the next round; assist-mode natural-language handoff can offer the same /cook boundary"',
48
- '"/cook remains the canonical workflow boundary. Assist-mode natural-language handoff can offer to enter the same /cook flow before implementation starts, while mission selection still comes from recent discussion, repo truth, and the approval-only confirmation flow."',
49
- '"/cook failed closed because recent discussion did not produce a clear execution-ready Mission/Scope/Constraints/Acceptance proposal for concrete repo changes. Natural-language handoff only offers to enter the same /cook flow, so clarify the concrete repo changes in the main chat and rerun /cook."',
50
- 'assist, or router',
51
- 'Assist and router modes only offer the same /cook flow',
52
- 'run in assist mode',
53
- "temporary" + " compatibility" + " shim, pass /cook",
54
- "optional inline /cook hint",
45
+ 'description: "/cook workflow: start, continue, refocus, or start the next round from an explicit /cook command"',
46
+ '"/cook failed closed because recent discussion did not produce a clear execution-ready Mission/Scope/Constraints/Acceptance proposal for concrete repo changes. Clarify the concrete repo changes in the main chat and rerun /cook."',
47
+ 'handleCookNaturalLanguageTrigger',
55
48
  ],
56
49
  }
57
50
 
@@ -68,7 +61,6 @@ for path, needles in forbidden.items():
68
61
  raise SystemExit(f"[release-check] found stale compatibility wording in {path}: {needle}")
69
62
  PY
70
63
 
71
- bash ./scripts/cook-trigger-routing-test.sh
72
64
  npm run smoke-test
73
65
  npm run refocus-test
74
66
  npm run context-proposal-test