@linimin/pi-letscook 0.1.60 → 0.1.61

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.
@@ -27,7 +27,7 @@ export type ContextProposalAlternate = {
27
27
  analysis: ContextProposalAnalysis;
28
28
  goalText: string;
29
29
  basisPreview: string;
30
- source: "session" | "analyst" | "handoff_capsule";
30
+ source: "session" | "analyst" | "handoff_capsule" | "deferred_primary_agent_handoff";
31
31
  };
32
32
 
33
33
  export type ContextProposal = ContextProposalAlternate & {
@@ -1246,6 +1246,7 @@ export function extractContextProposalFromStructuredSession(
1246
1246
 
1247
1247
  const COOK_HANDOFF_BLOCK_REGEX = /```cook_handoff\s*([\s\S]*?)```/giu;
1248
1248
  const COOK_HANDOFF_MAX_AGE_MS = 45 * 60 * 1000;
1249
+ const COOK_HANDOFF_MAX_LATER_NON_COMMAND_MESSAGES = 2;
1249
1250
  const COOK_HANDOFF_NEGATIVE_MISSION_REGEX =
1250
1251
  /(?:\b(?:do not|don't|dont|not|never|avoid|skip|refuse|recognize that|suppress|ignore|block|prevent)\b|(?:不要|別|别|勿|禁止|避免|忽略|阻止))/iu;
1251
1252
  const COOK_HANDOFF_WORKFLOW_ONLY_ACCEPTANCE_REGEX =
@@ -1388,6 +1389,19 @@ function isStartableCookHandoffCapsule(
1388
1389
  return cookHandoffStartabilityFailures(capsule, deps).length === 0;
1389
1390
  }
1390
1391
 
1392
+ function laterMessagesInvalidateCookHandoff(
1393
+ laterMessages: RecentSessionMessage[],
1394
+ deps: Pick<ProposalParseDeps, "stripCodeBlocks">,
1395
+ ): boolean {
1396
+ const laterNonCommandMessages = laterMessages.filter((entry) => !entry.isCommand);
1397
+ if (laterNonCommandMessages.length > COOK_HANDOFF_MAX_LATER_NON_COMMAND_MESSAGES) return true;
1398
+ return laterNonCommandMessages.some((entry) => {
1399
+ if (entry.role === "summary") return false;
1400
+ if (!hasRecentDiscussionImplementationIntent(entry.text, deps.stripCodeBlocks)) return false;
1401
+ return true;
1402
+ });
1403
+ }
1404
+
1391
1405
  function cookHandoffIsFreshEnough(capsule: CookHandoffCapsule, laterMessages: RecentSessionMessage[]): boolean {
1392
1406
  const capturedAtMs = Date.parse(capsule.captured_at);
1393
1407
  if (!Number.isFinite(capturedAtMs)) return false;
@@ -1470,6 +1484,7 @@ export function assessLatestCookHandoffProposal(
1470
1484
  const capsule = capsules[capsuleIndex];
1471
1485
  const laterMessages = recentMessages.slice(0, index);
1472
1486
  if (!cookHandoffIsFreshEnough(capsule, laterMessages)) continue;
1487
+ if (laterMessagesInvalidateCookHandoff(laterMessages, deps)) continue;
1473
1488
  const failures = cookHandoffStartabilityFailures(capsule, deps);
1474
1489
  if (failures.length > 0) {
1475
1490
  return {
@@ -1515,6 +1530,18 @@ export async function deriveCookContextProposalFromRecentDiscussion(
1515
1530
  return undefined;
1516
1531
  }
1517
1532
 
1533
+ export function retagContextProposalSource(
1534
+ proposal: ContextProposal | undefined,
1535
+ source: ContextProposalAlternate["source"],
1536
+ ): ContextProposal | undefined {
1537
+ if (!proposal) return undefined;
1538
+ return {
1539
+ ...proposal,
1540
+ source,
1541
+ alternateProposals: proposal.alternateProposals.map((alternate) => ({ ...alternate, source })),
1542
+ };
1543
+ }
1544
+
1518
1545
  export function resolveContextProposalConfirmationAction(
1519
1546
  proposal: ContextProposal,
1520
1547
  action: ContextProposalConfirmAction,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linimin/pi-letscook",
3
- "version": "0.1.60",
3
+ "version": "0.1.61",
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,
@@ -147,15 +147,14 @@ mkdir -p "$ROOT"
147
147
  cd "$ROOT"
148
148
  git init -q
149
149
 
150
- # No workflow yet: bare /cook should synthesize a startup brief from recent discussion,
151
- # and Cancel should leave canonical workflow state untouched.
150
+ # No workflow yet: bare /cook should synthesize a deferred startup brief from recent discussion,
151
+ # even when no explicit ordinary-chat handoff capsule exists.
152
152
  SESSION_ZERO="$TMPDIR/session-zero.jsonl"
153
153
  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.'
154
154
  DISCUSSION_SNAPSHOT_ZERO="$TMPDIR/context-proposal-structured-fallback.json"
155
155
  write_session "$SESSION_ZERO" "$ROOT" "$DISCUSSION_ZERO"
156
156
 
157
- PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=cancel \
158
- PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
157
+ PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
159
158
  PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO" \
160
159
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
161
160
  pi --session "$SESSION_ZERO" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-structured-fallback.out" 2>"$TMPDIR/pi-completion-context-proposal-structured-fallback.err"
@@ -167,14 +166,17 @@ from pathlib import Path
167
166
 
168
167
  output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
169
168
  snapshot = Path(sys.argv[3])
169
+ assert Path('.agent').exists(), 'bare /cook should scaffold canonical state from structured recent discussion'
170
+ assert snapshot.exists(), 'bare /cook should emit a startup proposal snapshot when recent discussion is concrete enough'
170
171
  proposal = json.loads(snapshot.read_text())
171
- assert not Path('.agent').exists(), 'recent-discussion Cancel should leave canonical state untouched before workflow bootstrap'
172
- assert snapshot.exists(), 'recent-discussion startup synthesis should emit a proposal snapshot before the Start/Cancel gate'
173
- assert proposal['mission'] == 'Remove the completion status line while keeping the completion widget.', 'recent-discussion startup synthesis should preserve the structured mission anchor'
174
- assert proposal['source'] == 'session', 'recent-discussion startup synthesis should snapshot the structured-session proposal source'
175
- assert 'Cancelled recent-discussion workflow proposal' in output, 'recent-discussion Cancel should report that canonical state was left unchanged'
172
+ brief = json.loads(Path('.agent/state.json').read_text())['advisory_startup_brief']
173
+ assert proposal['source'] == 'deferred_primary_agent_handoff', 'structured startup should snapshot the deferred primary-agent handoff source'
174
+ assert brief['source'] == 'deferred_primary_agent_handoff', 'structured startup should record the deferred primary-agent handoff source in advisory intake'
175
+ assert 'Initialized completion control plane' in output, 'structured startup should initialize canonical workflow state'
176
176
  PY
177
177
 
178
+ rm -rf .agent
179
+
178
180
  # No workflow yet: user-authored faux handoffs must not bootstrap canonical workflow state.
179
181
  SESSION_ZERO_USER_AUTHORED="$TMPDIR/session-zero-user-authored.jsonl"
180
182
  USER_AUTHORED_SNAPSHOT_ZERO="$TMPDIR/context-proposal-user-authored-handoff.json"
@@ -206,7 +208,6 @@ PY
206
208
  )"
207
209
  write_session_messages "$SESSION_ZERO_USER_AUTHORED" "$ROOT" "$USER_AUTHORED_MESSAGES_ZERO"
208
210
 
209
- PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
210
211
  PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$USER_AUTHORED_SNAPSHOT_ZERO" \
211
212
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
212
213
  pi --session "$SESSION_ZERO_USER_AUTHORED" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-user-authored.out" 2>"$TMPDIR/pi-completion-context-proposal-user-authored.err"
@@ -217,9 +218,9 @@ from pathlib import Path
217
218
 
218
219
  output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
219
220
  snapshot = Path(sys.argv[3])
220
- assert not Path('.agent').exists(), 'user-authored faux handoff should fail closed without writing canonical state'
221
+ assert not Path('.agent').exists(), 'user-authored faux handoff without supporting discussion should still fail closed without writing canonical state'
221
222
  assert not snapshot.exists(), 'user-authored faux handoff should not emit a startup proposal snapshot'
222
- assert 'recent discussion did not produce a clear execution-ready startup brief' in output, 'user-authored faux handoff should still fail closed unless ordinary discussion is concretely startable'
223
+ assert 'could not derive a concrete startup brief from recent discussion' in output, 'user-authored faux handoff should fall back to the deferred-synthesis fail-closed message'
223
224
  PY
224
225
 
225
226
  # No workflow yet: malformed or invalid assistant handoff capsules must also fail closed.
@@ -228,7 +229,6 @@ INVALID_SNAPSHOT_ZERO="$TMPDIR/context-proposal-invalid-handoff.json"
228
229
  INVALID_MESSAGES_ZERO='[{"role":"assistant","content":"This is not a valid startup capsule.\n\n```cook_handoff\n{\"kind\":\"cook_handoff\",\"source\":\"primary_agent\",\"mission\":\"Broken JSON handoff\"\n```"}]'
229
230
  write_session_messages "$SESSION_ZERO_INVALID" "$ROOT" "$INVALID_MESSAGES_ZERO"
230
231
 
231
- PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
232
232
  PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$INVALID_SNAPSHOT_ZERO" \
233
233
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
234
234
  pi --session "$SESSION_ZERO_INVALID" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-invalid-handoff.out" 2>"$TMPDIR/pi-completion-context-proposal-invalid-handoff.err"
@@ -239,9 +239,9 @@ from pathlib import Path
239
239
 
240
240
  output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
241
241
  snapshot = Path(sys.argv[3])
242
- assert not Path('.agent').exists(), 'invalid assistant handoff should fail closed without writing canonical state'
242
+ assert not Path('.agent').exists(), 'invalid assistant handoff without supporting discussion should fail closed without writing canonical state'
243
243
  assert not snapshot.exists(), 'invalid assistant handoff should not emit a startup proposal snapshot'
244
- assert 'recent discussion did not produce a clear execution-ready startup brief' in output, 'invalid assistant handoff should still fail closed when no clear recent-discussion startup brief exists'
244
+ assert 'could not derive a concrete startup brief from recent discussion' in output, 'invalid assistant handoff should explain the deferred-synthesis fail-closed contract'
245
245
  PY
246
246
 
247
247
  # No workflow yet: a fresh explicit primary-agent handoff should still bootstrap canonical startup state.
@@ -389,7 +389,7 @@ assert routing['mode'] == 'bare', 'active bare /cook resume regression should sn
389
389
  assert 'explicitGoal' not in routing, 'active bare /cook resume routing should not expose removed explicit-goal shim fields'
390
390
  assert 'explicitGoalProvided' not in routing, 'active bare /cook resume routing should not expose removed explicit-goal shim fields'
391
391
  assert routing['action'] == 'continue', 'active bare /cook should resume when no fresh explicit handoff exists'
392
- assert routing['reason'] == 'missing_explicit_handoff', 'active bare /cook should explain that resume happened because no fresh explicit handoff existed'
392
+ assert routing['reason'] == 'no_replacement_proposal', 'active bare /cook should explain that resume happened because no replacement mission was derived'
393
393
  assert routing['currentMissionAnchor'] == mission, 'resume routing should preserve the current mission anchor'
394
394
  assert routing['proposedMissionAnchor'] is None, 'resume routing should not derive a replacement mission from recent discussion'
395
395
  assert 'Resume the completion workflow from canonical state.' in resume, 'active bare /cook resume should still use the canonical resume prompt'
@@ -434,7 +434,7 @@ active = json.loads(Path('.agent/active-slice.json').read_text())
434
434
 
435
435
  assert routing['mode'] == 'bare', 'discussion-driven refocus removal should snapshot bare routing mode'
436
436
  assert routing['action'] == 'continue', 'bare /cook should resume instead of deriving a replacement workflow from recent discussion'
437
- assert routing['reason'] == 'missing_explicit_handoff', 'discussion-driven refocus removal should explain that no fresh explicit handoff existed'
437
+ assert routing['reason'] == 'no_replacement_proposal', 'discussion-driven refocus removal should explain that no replacement mission was derived'
438
438
  assert routing['currentMissionAnchor'] == mission, 'discussion-driven refocus removal should preserve the current mission anchor'
439
439
  assert routing['proposedMissionAnchor'] is None, 'discussion-driven refocus removal should not preserve a replacement mission from recent discussion'
440
440
  assert 'Resume the completion workflow from canonical state.' in resume, 'discussion-driven refocus removal should still queue the canonical resume prompt'
@@ -508,7 +508,7 @@ active = json.loads(Path('.agent/active-slice.json').read_text())
508
508
 
509
509
  assert routing['mode'] == 'bare', 'summary-only active bare /cook regression should snapshot bare routing mode'
510
510
  assert routing['action'] == 'continue', 'summary-only active bare /cook should resume rather than derive replacement startup'
511
- assert routing['reason'] == 'missing_explicit_handoff', 'summary-only active bare /cook should explain that no fresh explicit handoff existed'
511
+ assert routing['reason'] == 'no_replacement_proposal', 'summary-only active bare /cook should explain that no replacement mission was derived'
512
512
  assert routing['currentMissionAnchor'] == mission, 'summary-only active bare /cook should preserve the current mission anchor'
513
513
  assert routing['proposedMissionAnchor'] is None, 'summary-only active bare /cook should not derive a replacement mission from summary artifacts alone'
514
514
  assert 'Resume the completion workflow from canonical state.' in resume, 'summary-only active bare /cook should still resume the canonical workflow'
@@ -613,7 +613,7 @@ after = {
613
613
 
614
614
  assert routing['mode'] == 'bare', 'fresh non-startable explicit handoff should snapshot bare routing mode'
615
615
  assert routing['action'] == 'blocked', 'fresh non-startable explicit handoff should fail closed for active bare /cook'
616
- assert routing['reason'] == 'fresh_explicit_handoff_not_startable', 'fresh non-startable explicit handoff should keep the dedicated explicit-handoff fail-closed reason'
616
+ assert routing['reason'] == 'replacement_not_startable', 'fresh non-startable explicit handoff should keep the dedicated replacement fail-closed reason'
617
617
  assert 'fresh explicit primary-agent handoff exists' in routing['blockedFailureMessage'], 'fresh non-startable explicit handoff should surface the dedicated fail-closed message'
618
618
  assert 'acceptance is not anchored to concrete repo changes or verification' in routing['blockedFailureMessage'], 'fresh non-startable explicit handoff should explain why the capsule is not startable'
619
619
  assert not resume_path.exists(), 'fresh non-startable explicit handoff should not queue a resume prompt'
@@ -708,57 +708,34 @@ assert not snapshot.exists(), 'verification-evidence overlap suppression should
708
708
  assert '/cook failed closed' in output, 'verification-evidence overlap suppression should fail closed when the latest discussion only repeats verified work'
709
709
  PY
710
710
 
711
- # Completed workflow: bare /cook should synthesize the next-round startup brief from recent discussion.
711
+ # Completed workflow: bare /cook should synthesize the next round from discussion-only startup too,
712
+ # even when no explicit ordinary-chat handoff capsule exists.
712
713
  SESSION_TWO_NORMALIZED="$TMPDIR/session-two-normalized.jsonl"
713
- DISCUSSION_TWO_NORMALIZED=$'Mission: Ship the next workflow round from recent discussion.\nScope:\n- Reset canonical state back to reground for the new mission.\n- Preserve the tracked completion control-plane files.\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.'
714
+ DISCUSSION_TWO_NORMALIZED=$'Mission: 開始實作這個方案\nScope:\n- Normalize bare /cook planning phrasing for the next workflow round.\n- Reset canonical state for the new implementation mission.\nConstraints:\n- Do not resume the completed workflow when the new round is clearly different.\nAcceptance:\n- Start a new round with the normalized mission anchor.'
714
715
  DISCUSSION_SNAPSHOT_TWO_NORMALIZED="$TMPDIR/context-proposal-next-round-normalized.json"
715
716
  write_session "$SESSION_TWO_NORMALIZED" "$ROOT" "$DISCUSSION_TWO_NORMALIZED"
716
717
 
717
718
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
718
- PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
719
719
  PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_TWO_NORMALIZED" \
720
720
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
721
721
  pi --session "$SESSION_TWO_NORMALIZED" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-next-round-normalized.out" 2>"$TMPDIR/pi-completion-context-proposal-next-round-normalized.err"
722
722
 
723
- python3 - "$DISCUSSION_SNAPSHOT_TWO_NORMALIZED" <<'PY'
723
+ python3 - "$TMPDIR/pi-completion-context-proposal-next-round-normalized.out" "$TMPDIR/pi-completion-context-proposal-next-round-normalized.err" "$DISCUSSION_SNAPSHOT_TWO_NORMALIZED" "$CURRENT_DONE_MISSION" <<'PY'
724
724
  import json
725
725
  import sys
726
726
  from pathlib import Path
727
727
 
728
- mission = 'Ship the next workflow round from recent discussion.'
729
- expected_task_type = 'completion-workflow'
730
- expected_eval_profile = 'completion-rubric-v1'
731
- mission_text = Path('.agent/mission.md').read_text()
732
- profile = json.loads(Path('.agent/profile.json').read_text())
728
+ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
729
+ snapshot = Path(sys.argv[3])
730
+ previous = sys.argv[4]
733
731
  state = json.loads(Path('.agent/state.json').read_text())
734
- plan = json.loads(Path('.agent/plan.json').read_text())
735
- active = json.loads(Path('.agent/active-slice.json').read_text())
736
- proposal = json.loads(Path(sys.argv[1]).read_text())
737
732
 
738
- assert mission in mission_text, '.agent/mission.md did not update to the recent-discussion next-round mission anchor'
739
- assert profile['task_type'] == expected_task_type, 'profile.json task_type mismatch after recent-discussion next-round startup'
740
- assert profile['evaluation_profile'] == expected_eval_profile, 'profile.json evaluation_profile mismatch after recent-discussion next-round startup'
741
- assert state['mission_anchor'] == mission, 'state.json mission_anchor mismatch after starting the next workflow round from recent discussion'
742
- assert state['task_type'] == expected_task_type, 'state.json task_type mismatch after starting the next workflow round from recent discussion'
743
- assert state['evaluation_profile'] == expected_eval_profile, 'state.json evaluation_profile mismatch after starting the next workflow round from recent discussion'
744
- assert state['advisory_startup_brief']['mission'] == mission, 'recent-discussion next round should preserve the confirmed startup brief as advisory intake'
745
- assert state['advisory_startup_brief']['source'] == 'recent_discussion', 'recent-discussion next round should preserve the advisory source'
746
- assert plan['mission_anchor'] == mission, 'plan.json mission_anchor mismatch after starting the next workflow round from recent discussion'
747
- assert plan['task_type'] == expected_task_type, 'plan.json task_type mismatch after starting the next workflow round from recent discussion'
748
- assert plan['evaluation_profile'] == expected_eval_profile, 'plan.json evaluation_profile mismatch after starting the next workflow round from recent discussion'
749
- assert active['mission_anchor'] == mission, 'active-slice.json mission_anchor mismatch after starting the next workflow round from recent discussion'
750
- assert active['task_type'] == expected_task_type, 'active-slice.json task_type mismatch after starting the next workflow round from recent discussion'
751
- assert active['evaluation_profile'] == expected_eval_profile, 'active-slice.json evaluation_profile mismatch after starting the next workflow round from recent discussion'
752
- assert proposal['mission'] == mission, 'recent-discussion next-round proposal snapshot should preserve the synthesized mission anchor'
753
- assert proposal['source'] == 'session', 'recent-discussion next-round proposal snapshot should record the structured-session source'
754
- assert state['current_phase'] == 'reground', 'state.json current_phase should reset to reground for the recent-discussion next workflow round'
755
- assert state['continuation_policy'] == 'continue', 'continuation_policy should reset to continue for the recent-discussion next workflow round'
756
- assert state['requires_reground'] is True, 'requires_reground should reset to true for the recent-discussion next workflow round'
757
- assert state['project_done'] is False, 'project_done should reset to false for the recent-discussion next workflow round'
758
- assert state['next_mandatory_role'] == 'completion-regrounder', 'next_mandatory_role should reset to completion-regrounder for the recent-discussion next workflow round'
759
- assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'continuation_reason should record the recent-discussion next-round refocus'
760
- assert plan['plan_basis'] == 'user_refocus', 'plan_basis should reset to user_refocus for the recent-discussion next workflow round'
761
- assert active['status'] == 'idle', 'active-slice should reset to idle for the recent-discussion next workflow round'
733
+ assert snapshot.exists(), 'done-workflow discussion-only startup should emit a proposal snapshot from deferred synthesis'
734
+ proposal = json.loads(snapshot.read_text())
735
+ assert state['mission_anchor'] != previous, 'done-workflow discussion-only startup should advance to the new mission anchor'
736
+ assert state['continuation_policy'] == 'continue', 'done-workflow discussion-only startup should reopen workflow state'
737
+ assert proposal['source'] == 'deferred_primary_agent_handoff', 'done-workflow discussion-only startup should snapshot the deferred handoff source'
738
+ assert 'Started a new completion workflow round from deferred primary-agent handoff' in output, 'done-workflow discussion-only startup should report deferred next-round startup'
762
739
  PY
763
740
 
764
741
  # Completed workflow: a fresh explicit primary-agent handoff should still start the next round.
@@ -977,7 +954,8 @@ after = {path.name: path.read_text() for path in tracked}
977
954
  assert before == after, 'done /cook inline-args rejection should leave canonical files unchanged'
978
955
  PY
979
956
 
980
- # Completed workflow again: model-assisted discussion analysis should also synthesize the next-round startup brief.
957
+ # Completed workflow again: model-assisted discussion analysis alone should be able to
958
+ # synthesize the next round from explicit /cook entry.
981
959
  mark_done
982
960
 
983
961
  SESSION_FIVE="$TMPDIR/session-five.jsonl"
@@ -992,20 +970,18 @@ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_FIVE" \
992
970
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
993
971
  pi --session "$SESSION_FIVE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-analyst.out" 2>"$TMPDIR/pi-completion-context-proposal-analyst.err"
994
972
 
995
- python3 - "$DISCUSSION_SNAPSHOT_FIVE" <<'PY'
973
+ python3 - "$TMPDIR/pi-completion-context-proposal-analyst.out" "$TMPDIR/pi-completion-context-proposal-analyst.err" "$DISCUSSION_SNAPSHOT_FIVE" <<'PY'
996
974
  import json
997
975
  import sys
998
976
  from pathlib import Path
999
977
 
1000
- mission = 'Use a proposal analyst to summarize natural discussion before /cook writes canonical state.'
978
+ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
979
+ snapshot = Path(sys.argv[3])
1001
980
  state = json.loads(Path('.agent/state.json').read_text())
1002
- proposal = json.loads(Path(sys.argv[1]).read_text())
1003
981
 
1004
- assert proposal['mission'] == mission, 'analyst-driven restart should emit the synthesized mission in the proposal snapshot'
1005
- assert proposal['source'] == 'analyst', 'analyst-driven restart should preserve the analyst proposal source'
1006
- assert state['mission_anchor'] == mission, 'analyst-driven restart should promote the synthesized mission into canonical state after Start'
1007
- assert state['continuation_policy'] == 'continue', 'analyst-driven restart should reopen the workflow after Start'
1008
- assert state['advisory_startup_brief']['source'] == 'recent_discussion', 'analyst-driven restart should still record recent-discussion advisory intake'
982
+ assert snapshot.exists(), 'done-workflow analyst-only restart should emit a startup proposal snapshot'
983
+ assert state['continuation_policy'] == 'continue', 'done-workflow analyst-only restart should reopen the workflow'
984
+ assert 'deferred primary-agent handoff' in output, 'done-workflow analyst-only restart should report deferred startup'
1009
985
  PY
1010
986
 
1011
987
  # Custom confirmation UI: start should render proposal content separately from approval-only Start/Cancel actions.
@@ -1231,85 +1207,6 @@ assert 'Why this slice first: The redirect callback bug is already bounded enoug
1231
1207
  assert 'Primary-agent /cook handoff rationale: The implementation plan is concrete and ready for repo changes.' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve why_cook_now as notes'
1232
1208
  PY
1233
1209
 
1234
- # Fresh explicit handoff: later ordinary-chat follow-up before /cook should not invalidate startup.
1235
- HANDOFF_ROOT_FOLLOWUP="$TMPDIR/handoff-root-followup"
1236
- mkdir -p "$HANDOFF_ROOT_FOLLOWUP"
1237
- cd "$HANDOFF_ROOT_FOLLOWUP"
1238
- git init -q
1239
-
1240
- HANDOFF_SESSION_FOLLOWUP="$TMPDIR/handoff-session-followup.jsonl"
1241
- HANDOFF_SNAPSHOT_FOLLOWUP="$TMPDIR/handoff-proposal-followup.json"
1242
- HANDOFF_MESSAGES_FOLLOWUP="$(python3 - <<'PY'
1243
- import json
1244
- capsule = {
1245
- "kind": "cook_handoff",
1246
- "source": "primary_agent",
1247
- "captured_at": "2026-01-01T00:00:02.000Z",
1248
- "source_turn_id": "m0002",
1249
- "mission": "Fix login redirect callback behavior.",
1250
- "scope": [
1251
- "Update the callback redirect decision logic.",
1252
- "Preserve the broader auth flow."
1253
- ],
1254
- "constraints": [
1255
- "Do not refactor the broader auth flow."
1256
- ],
1257
- "acceptance": [
1258
- "Add a regression test for returning to the requested page."
1259
- ],
1260
- "risks": [
1261
- "Late ordinary-chat clarifications should not force the user into a fresh handoff-only retry loop."
1262
- ],
1263
- "notes": [
1264
- "Keep the startup brief anchored to the explicit primary-agent handoff until a later assistant reply replaces it."
1265
- ],
1266
- "handoff_kind": "implementation_workflow_handoff",
1267
- "first_slice_goal": "Land the redirect callback fix and its regression coverage.",
1268
- "first_slice_non_goals": [
1269
- "Do not refactor the broader auth flow."
1270
- ],
1271
- "implementation_surfaces": [
1272
- "src/auth/redirect.ts",
1273
- "tests/auth/redirect.spec.ts"
1274
- ],
1275
- "verification_commands": [
1276
- "npm test -- redirect.spec.ts"
1277
- ],
1278
- "why_this_slice_first": "The redirect callback bug is already bounded enough to start implementation safely.",
1279
- "task_type": "completion-workflow",
1280
- "evaluation_profile": "completion-rubric-v1",
1281
- "why_cook_now": "The implementation plan is concrete and ready for repo changes."
1282
- }
1283
- messages = [
1284
- {"role": "user", "content": "Please think through the login redirect fix and tell me when it is ready for /cook."},
1285
- {"role": "assistant", "content": "This task is now ready for /cook whenever you want to start implementation.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
1286
- {"role": "user", "content": "Before I run /cook, one more clarification: keep the broader auth flow unchanged and keep the later verification focused on the redirect regression."},
1287
- ]
1288
- print(json.dumps(messages, ensure_ascii=False))
1289
- PY
1290
- )"
1291
- write_session_messages "$HANDOFF_SESSION_FOLLOWUP" "$HANDOFF_ROOT_FOLLOWUP" "$HANDOFF_MESSAGES_FOLLOWUP"
1292
-
1293
- PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
1294
- PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_FOLLOWUP" \
1295
- PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
1296
- pi --session "$HANDOFF_SESSION_FOLLOWUP" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-followup.out" 2>"$TMPDIR/pi-completion-handoff-followup.err"
1297
-
1298
- python3 - "$HANDOFF_SNAPSHOT_FOLLOWUP" <<'PY'
1299
- import json
1300
- import sys
1301
- from pathlib import Path
1302
-
1303
- snapshot = json.loads(Path(sys.argv[1]).read_text())
1304
- state = json.loads(Path('.agent/state.json').read_text())
1305
-
1306
- assert snapshot['source'] == 'handoff_capsule', 'later ordinary-chat follow-up should still use the explicit handoff capsule as startup source'
1307
- assert snapshot['mission'] == 'Fix login redirect callback behavior.', 'later ordinary-chat follow-up should preserve the handoff mission'
1308
- assert state['mission_anchor'] == 'Fix login redirect callback behavior.', 'later ordinary-chat follow-up should still start from the explicit handoff mission'
1309
- assert state['advisory_startup_brief']['source'] == 'primary_agent_handoff', 'later ordinary-chat follow-up should keep the advisory intake source as the explicit handoff'
1310
- assert 'Verification commands: npm test -- redirect.spec.ts' in state['advisory_startup_brief']['notes'], 'later ordinary-chat follow-up should preserve the handoff verification commands'
1311
- PY
1312
-
1313
1210
  # Fresh but non-startable explicit handoff: /cook should fail closed instead of falling back
1314
1211
  # to a broad recent-discussion startup brief when the explicit capsule is still too vague.
1315
1212
  HANDOFF_ROOT_VAGUE="$TMPDIR/handoff-root-vague"
@@ -1576,7 +1473,7 @@ assert 'First slice goal: Patch the callback edge case and cover it with a focus
1576
1473
  assert 'Verification commands: npm test -- redirect-edge.spec.ts' in state['advisory_startup_brief']['notes'], 'done-workflow handoff should preserve verification_commands in advisory notes'
1577
1474
  PY
1578
1475
 
1579
- # Stale handoff: an aged-out capsule should no longer block /cook from synthesizing a fresh startup brief from later discussion.
1476
+ # Stale handoff: later discussion should invalidate the older handoff capsule and fail closed instead of falling back to newer discussion.
1580
1477
  HANDOFF_ROOT_STALE="$TMPDIR/handoff-root-stale"
1581
1478
  mkdir -p "$HANDOFF_ROOT_STALE"
1582
1479
  cd "$HANDOFF_ROOT_STALE"
@@ -1589,7 +1486,7 @@ import json
1589
1486
  capsule = {
1590
1487
  "kind": "cook_handoff",
1591
1488
  "source": "primary_agent",
1592
- "captured_at": "2025-12-31T22:00:02.000Z",
1489
+ "captured_at": "2026-01-01T00:00:02.000Z",
1593
1490
  "source_turn_id": "m0002",
1594
1491
  "mission": "Fix the original login redirect callback behavior.",
1595
1492
  "scope": ["Update the original callback redirect logic."],
@@ -1602,7 +1499,7 @@ capsule = {
1602
1499
  "first_slice_non_goals": ["Do not refactor the auth stack."],
1603
1500
  "implementation_surfaces": ["src/auth/login-redirect.ts"],
1604
1501
  "verification_commands": ["npm test -- login-redirect.spec.ts"],
1605
- "why_this_slice_first": "The original callback follow-up was the first bounded implementation slice before it aged out."
1502
+ "why_this_slice_first": "The original callback follow-up was the first bounded implementation slice before later discussion replaced it."
1606
1503
  }
1607
1504
  newer_discussion = "Mission: Ship logout redirect consistency instead.\nScope:\n- Update the logout redirect path.\nConstraints:\n- Leave the login callback flow unchanged.\nAcceptance:\n- Add a logout redirect regression test."
1608
1505
  messages = [
@@ -1621,18 +1518,16 @@ PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_STALE" \
1621
1518
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
1622
1519
  pi --session "$HANDOFF_SESSION_STALE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-stale.out" 2>"$TMPDIR/pi-completion-handoff-stale.err"
1623
1520
 
1624
- python3 - "$HANDOFF_SNAPSHOT_STALE" <<'PY'
1625
- import json
1521
+ python3 - "$HANDOFF_SNAPSHOT_STALE" "$TMPDIR/pi-completion-handoff-stale.out" "$TMPDIR/pi-completion-handoff-stale.err" <<'PY'
1626
1522
  import sys
1627
1523
  from pathlib import Path
1628
1524
 
1629
- snapshot = json.loads(Path(sys.argv[1]).read_text())
1630
- state = json.loads(Path('.agent/state.json').read_text())
1525
+ snapshot = Path(sys.argv[1])
1526
+ output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
1631
1527
 
1632
- assert snapshot['mission'] == 'Ship logout redirect consistency instead.', 'aged-out handoff should fall back to the newer recent-discussion mission'
1633
- assert snapshot['source'] == 'session', 'aged-out handoff fallback should preserve the structured-session proposal source'
1634
- assert state['mission_anchor'] == 'Ship logout redirect consistency instead.', 'aged-out handoff fallback should promote the newer recent-discussion mission after Start'
1635
- assert state['advisory_startup_brief']['source'] == 'recent_discussion', 'aged-out handoff fallback should keep the advisory startup source non-canonical'
1528
+ assert not snapshot.exists(), 'stale handoff should not emit a startup proposal snapshot'
1529
+ assert not Path('.agent').exists(), 'stale handoff should fail closed without writing canonical state'
1530
+ assert 'could not derive a concrete startup brief from recent discussion' in output, 'stale handoff should explain that deferred startup synthesis failed closed'
1636
1531
  PY
1637
1532
 
1638
1533
  # Negative handoff rationale: a non-startable capsule must not become the startup mission.
@@ -313,7 +313,7 @@ assert routing['mode'] == 'bare', 'supported refocus should use bare active-work
313
313
  assert 'explicitGoal' not in routing, 'supported bare refocus should not expose removed explicit-goal shim fields'
314
314
  assert 'explicitGoalProvided' not in routing, 'supported bare refocus should not expose removed explicit-goal shim fields'
315
315
  assert routing['action'] == 'refocus', 'supported bare /cook should classify as refocus when a fresh explicit handoff proposes a different mission'
316
- assert routing['reason'] == 'fresh_explicit_handoff', 'supported bare /cook should record the explicit-handoff replacement reason'
316
+ assert routing['reason'] == 'explicit_handoff_replacement', 'supported bare /cook should record the explicit-handoff replacement reason'
317
317
  assert routing['proposedMissionAnchor'] == new_anchor, 'explicit handoff routing snapshot should expose the replacement mission anchor'
318
318
  assert routing['proposalSource'] == 'handoff_capsule', 'explicit handoff routing snapshot should preserve the handoff source'
319
319
  PY
@@ -411,7 +411,7 @@ assert routing['mode'] == 'bare', 'bare /cook should snapshot bare active-workfl
411
411
  assert 'explicitGoal' not in routing, 'bare chooser routing should not expose removed explicit-goal shim fields'
412
412
  assert 'explicitGoalProvided' not in routing, 'bare chooser routing should not expose removed explicit-goal shim fields'
413
413
  assert routing['action'] == 'refocus', 'fresh explicit replacement handoff should classify active bare /cook as refocus'
414
- assert routing['reason'] == 'fresh_explicit_handoff', 'fresh explicit replacement handoff should record the explicit-handoff reason'
414
+ assert routing['reason'] == 'explicit_handoff_replacement', 'fresh explicit replacement handoff should record the explicit-handoff reason'
415
415
  assert routing['currentMissionAnchor'] == updated_mission, 'explicit-handoff routing should keep the current mission anchor until the user approves replacement'
416
416
  assert routing['proposedMissionAnchor'] == replacement_mission, 'explicit-handoff routing should expose the proposed replacement mission'
417
417
  assert routing['proposalSource'] == 'handoff_capsule', 'explicit-handoff routing should preserve the handoff source'
@@ -454,7 +454,7 @@ assert state['mission_anchor'] == updated_mission, 'final Start/Cancel cancel sh
454
454
  assert plan['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep plan.json unchanged'
455
455
  assert active['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep active-slice.json unchanged'
456
456
  assert routing['action'] == 'refocus', 'final Start/Cancel cancel should still come from an explicit-handoff refocus classification'
457
- assert routing['reason'] == 'fresh_explicit_handoff', 'final Start/Cancel cancel should preserve the explicit-handoff reason'
457
+ assert routing['reason'] == 'explicit_handoff_replacement', 'final Start/Cancel cancel should preserve the explicit-handoff reason'
458
458
  assert routing['currentMissionAnchor'] == updated_mission, 'final Start/Cancel cancel should keep the current mission anchor until the user approves replacement'
459
459
  assert proposal['mission'] == replacement_mission, 'final Start/Cancel cancel should still prepare the replacement proposal before rewriting state'
460
460
  assert proposal['source'] == 'handoff_capsule', 'final Start/Cancel cancel should preserve the explicit-handoff proposal source'
@@ -495,7 +495,7 @@ assert routing['mode'] == 'bare', 'accepted bare refocus should keep bare routin
495
495
  assert 'explicitGoal' not in routing, 'accepted bare refocus should not expose removed explicit-goal shim fields'
496
496
  assert 'explicitGoalProvided' not in routing, 'accepted bare refocus should not expose removed explicit-goal shim fields'
497
497
  assert routing['action'] == 'refocus', 'accepted bare refocus should keep the explicit-handoff refocus classification'
498
- assert routing['reason'] == 'fresh_explicit_handoff', 'accepted bare refocus should keep the explicit-handoff reason'
498
+ assert routing['reason'] == 'explicit_handoff_replacement', 'accepted bare refocus should keep the explicit-handoff reason'
499
499
  assert routing['currentMissionAnchor'] == 'Remove completion status line, keep widget.', 'accepted bare refocus should expose the original mission until Start is accepted'
500
500
  assert routing['proposalSource'] == 'handoff_capsule', 'accepted bare refocus should preserve the explicit-handoff source'
501
501
  assert new_anchor in mission_text, '.agent/mission.md did not update to the bare refocus mission anchor'
@@ -5,58 +5,51 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
5
  cd "$ROOT"
6
6
  export PI_COMPLETION_RUNNING_RELEASE_CHECK=1
7
7
 
8
- echo "[release-check] running control-plane validation, tracked .agent contract coverage, slice-surface parity, mixed-model /cook parity, startup/refocus/context regressions, canonical evidence artifact, active-slice contract, observability, legacy cleanup, evaluator calibration, and rubric contract coverage"
8
+ 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"
9
9
  bash .agent/verify_completion_control_plane.sh
10
10
  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
11
11
 
12
- echo "[release-check] verifying public /cook mixed-model parity across docs/help"
12
+ echo "[release-check] verifying public /cook parity and explicit-entry docs/help"
13
13
  python3 - <<'PY'
14
14
  from pathlib import Path
15
15
 
16
16
  checks = {
17
17
  "README.md": [
18
18
  "`/cook` is the explicit workflow boundary for starting, continuing, refocusing, or beginning the next round of long-running repo work.",
19
- "Before you explicitly run `/cook`, the conversation can still stay in ordinary chat: the primary agent may keep answering follow-up questions and refining requirements rather than switching into a hard handoff-only refusal mode.",
20
- "If you explicitly ask for a pre-`/cook` preview or capsule, the primary agent may provide one, but that preview is opt-in only and stays non-canonical until you later run `/cook` and choose **Start**.",
21
- "Bare `/cook` is still the canonical workflow boundary: it synthesizes the startup brief from recent ordinary-chat discussion at `/cook` time, then waits for **Start** or **Cancel** before any canonical `.agent/**` write.",
22
- "- startup and next-round entry stay confirm-first: bare `/cook` synthesizes the startup brief from recent discussion, then waits for **Start** or **Cancel**",
23
- "- any pre-`/cook` preview or capsule is explicit-request-only and non-canonical",
24
- "When no workflow is active, bare `/cook` synthesizes a startup brief from recent ordinary-chat discussion and then waits for **Start** or **Cancel**.",
25
- "| No workflow yet | `/cook` synthesizes a startup brief from recent discussion and shows **Start** / **Cancel**.",
26
- "| Previous workflow is `done` | `/cook` synthesizes the next implementation round from recent discussion behind **Start** / **Cancel**.",
27
- "any pre-`/cook` preview or capsule is advisory only and never writes canonical workflow state by itself",
19
+ "Only explicit `/cook` enters the workflow. Ordinary prompts stay in the main chat and go straight to the primary agent.",
20
+ "Ordinary chat remains advisory until you explicitly run `/cook`. At that point `/cook` synthesizes a startup brief from recent discussion using primary-agent-style context, then asks you to **Start** or **Cancel** before rewriting canonical workflow state.",
21
+ "- startup and next-round entry stay confirm-first, but they now derive startup from explicit user `/cook` entry plus recent discussion when needed",
22
+ "- active workflows resume from canonical `.agent/**` state unless `/cook` synthesizes or receives a concrete replacement mission",
23
+ "`/cook` first checks for a fresh explicit primary-agent handoff capsule as a compatibility intake path. If none is present, `/cook` synthesizes a startup brief from recent discussion using primary-agent-style context.",
24
+ "when a concrete replacement mission suggests replacing an active workflow, `/cook` shows a chooser before any canonical state rewrite",
28
25
  ],
29
26
  "CHANGELOG.md": [
30
- "moving default startup and done-workflow next-round synthesis to bare `/cook` from recent ordinary-chat discussion behind the existing **Start** / **Cancel** approval gate",
31
- "kept pre-`/cook` previews or `cook_handoff` capsules opt-in only, non-canonical, and advisory until the user explicitly runs `/cook`; bare `/cook` no longer depends on a default prebuilt capsule for new-workflow startup",
32
- "updated public parity and packaged release verification so README/help/changelog/release-check all describe and gate the shipped mixed model truthfully",
27
+ "removed proactive primary-agent `/cook` prompting and default ordinary-chat `cook_handoff` emission so main chat stays advisory until the user explicitly runs `/cook`",
28
+ "changed bare `/cook` startup and done-workflow next-round entry to synthesize a deferred primary-agent startup brief from recent discussion instead of requiring a pre-authored explicit handoff capsule",
29
+ "updated public parity and shipped package contents so the tracked `.agent` contract files are included in package tarballs and packaged smoke/release verification can scaffold canonical state truthfully",
33
30
  ],
34
31
  "extensions/completion/prompt-surfaces.ts": [
35
- '"Distinguish a workflow-worthy handoff from an opt-in preview request: by default, do not emit a structured preview or cook_handoff capsule in ordinary chat once the task is concrete enough; recommend bare /cook instead."',
36
- '"When handing off, explain that bare /cook will synthesize a startup brief from recent ordinary-chat discussion for a new workflow or next round, while already-active workflows resume from canonical .agent state unless the user explicitly chooses a replacement path backed by a fresh valid explicit handoff."',
37
- '"If the user explicitly asks for a /cook preview or capsule before running /cook, you may append one exact fenced block in the same assistant reply using ```cook_handoff ... ``` JSON',
38
- '"Any preview capsule is startup intake for /cook only: do not present it as canonical .agent state, an active slice, or a persistent repo contract."',
32
+ '"/cook is the only explicit entrypoint into long-running completion workflow."',
33
+ '"Do not proactively tell the user to run /cook just because a task looks workflow-worthy, and do not emit a ```cook_handoff``` capsule by default in ordinary chat."',
34
+ '"Only provide a preview startup brief or ```cook_handoff``` capsule in ordinary chat when the user explicitly asks for that preview behavior."',
39
35
  ],
40
36
  "extensions/completion/index.ts": [
41
- '"/cook failed closed because recent discussion did not produce a clear execution-ready startup brief for bare /cook with Mission/Scope/Constraints/Acceptance for concrete repo changes. Clarify the concrete repo changes in the main chat and rerun /cook; canonical workflow state is still only written after Start."',
42
- 'description: "/cook workflow: synthesize an approval-gated startup brief from recent discussion for new-workflow or next-round entry, resume the current workflow from canonical state, or confirm an explicit active-workflow replacement"',
37
+ '"/cook failed closed because it could not derive a concrete startup brief from recent discussion. Clarify the mission, first slice, or verification intent in the main chat, then rerun /cook."',
38
+ 'description: "/cook workflow: synthesize a startup brief when the user explicitly enters /cook, resume the current workflow from canonical state, or confirm a replacement mission from explicit /cook entry"',
43
39
  ],
44
40
  }
45
41
 
46
42
  forbidden = {
47
43
  "README.md": [
48
- "wait for a fresh primary-agent handoff, then run `/cook`",
49
- "That handoff should include an explicit structured `/cook` capsule in the assistant reply once the first slice is implementation-ready, so `/cook` can confirm the already-formed mission instead of re-deriving it from broad ambient context.",
50
- "- startup and next-round entry stay confirm-first and require a fresh valid explicit primary-agent handoff",
51
- "`/cook` first looks for a fresh explicit primary-agent handoff capsule from recent ordinary-chat discussion.",
52
- "Without one, `/cook` fails closed instead of deriving the next round from recent discussion.",
44
+ "wait for a fresh primary-agent handoff",
45
+ "require a fresh valid explicit primary-agent handoff",
53
46
  ],
54
- "CHANGELOG.md": [
55
- "kept bare `/cook` startup and done-workflow next-round entry fail-closed on missing or non-startable explicit handoffs, while active workflows still resume from canonical `.agent/**` state unless a fresh explicit handoff proposes replacement",
47
+ "extensions/completion/prompt-surfaces.ts": [
48
+ '"When handing off, explain that /cook can start a new workflow or next round only from a fresh valid explicit primary-agent handoff capsule; otherwise it fails closed, while already-active workflows resume from canonical .agent state unless a fresh valid explicit handoff proposes replacement."',
56
49
  ],
57
50
  "extensions/completion/index.ts": [
58
- 'description: "/cook workflow: derive a startup brief from recent discussion for new-workflow or next-round entry, resume the current workflow from canonical state, or confirm an explicit active-workflow replacement"',
59
- '"/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."',
51
+ 'description: "/cook workflow: start a new workflow or next round only from a fresh explicit primary-agent handoff, resume the current workflow from canonical state, or confirm an explicit replacement from the explicit /cook command"',
52
+ '"/cook failed closed because new-workflow startup now requires a fresh valid explicit primary-agent handoff from the immediately preceding ordinary-chat turn; recent discussion alone no longer starts a workflow. Ask the primary agent to hand off explicitly in the main chat, then rerun /cook."',
60
53
  ],
61
54
  }
62
55
 
@@ -64,13 +57,13 @@ for path, needles in checks.items():
64
57
  text = Path(path).read_text()
65
58
  for needle in needles:
66
59
  if needle not in text:
67
- raise SystemExit(f"[release-check] missing expected /cook mixed-model parity text in {path}: {needle}")
60
+ raise SystemExit(f"[release-check] missing expected /cook parity text in {path}: {needle}")
68
61
 
69
62
  for path, needles in forbidden.items():
70
63
  text = Path(path).read_text()
71
64
  for needle in needles:
72
65
  if needle in text:
73
- raise SystemExit(f"[release-check] found stale /cook explicit-handoff-only text in {path}: {needle}")
66
+ raise SystemExit(f"[release-check] found stale /cook parity text in {path}: {needle}")
74
67
  PY
75
68
 
76
69
  npm run smoke-test