@linimin/pi-letscook 0.1.60 → 0.1.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,18 +1,21 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.1.62
4
4
 
5
- ## 0.1.60
5
+ ### Changed
6
+
7
+ - made ordinary chat implementation-first again so the primary agent may directly edit repo files without requiring `/cook` when workflow state is unnecessary
8
+ - repositioned `/cook` as optional workflow mode for confirm-first startup, resumability, review/audit flow, and canonical `.agent/**` state rather than as a mandatory implementation boundary
9
+ - updated ordinary-chat boundary docs, reminders, and release-parity checks so they no longer tell the agent to block repo edits pending explicit `/cook`
6
10
 
7
- ## 0.1.59
11
+ ## 0.1.61
8
12
 
9
13
  ### Changed
10
14
 
11
- - relaxed the pre-`/cook` ordinary-chat boundary so the primary agent can keep discussing and refining requirements before explicit `/cook` instead of switching into a hard handoff-only refusal mode as soon as workflow-worthiness is detected
12
- - kept `/cook` as the only explicit workflow boundary, while moving default startup and done-workflow next-round synthesis to bare `/cook` from recent ordinary-chat discussion behind the existing **Start** / **Cancel** approval gate
13
- - 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
14
- - kept active workflows resuming from canonical `.agent/**` state unless a fresh explicit handoff proposes a replacement, so discussion-only context does not silently rewrite an in-progress workflow
15
- - updated public parity and packaged release verification so README/help/changelog/release-check all describe and gate the shipped mixed model truthfully, while still packaging the tracked `.agent` contract files
15
+ - removed proactive primary-agent `/cook` prompting and default ordinary-chat `cook_handoff` emission so main chat stays advisory until the user explicitly runs `/cook`
16
+ - 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
17
+ - kept active-workflow bare `/cook` resumable from canonical `.agent/**` state by default while allowing `/cook` to confirm a concrete replacement mission derived from explicit entry context
18
+ - 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
16
19
 
17
20
  ## 0.1.58
18
21
 
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @linimin/pi-letscook
2
2
 
3
- `/cook` turns main-chat discussion about concrete repo changes into a resumable repo workflow stored in repo-local `.agent/**` state.
3
+ `/cook` is optional workflow mode for turning main-chat discussion about concrete repo changes into a resumable repo workflow stored in repo-local `.agent/**` state.
4
4
 
5
- `/cook` is the explicit workflow boundary for starting, continuing, refocusing, or beginning the next round of long-running repo work.
5
+ You can still implement directly in ordinary chat when you do not need workflow state. Use `/cook` when you want confirm-first startup, resumability, review/audit flow, or canonical `.agent/**` control.
6
6
 
7
7
  ## Use it when
8
8
 
@@ -10,10 +10,12 @@
10
10
  - you want one mission tracked in repo state instead of chat memory
11
11
  - you want clear continue / refocus / next-round behavior
12
12
  - you want review, audit, and verification tied to the repo
13
+ - you want a confirm-first workflow boundary before canonical state is written
13
14
 
14
15
  ## Skip it when
15
16
 
16
17
  - you only need a one-off answer
18
+ - you want the agent to implement directly in ordinary chat
17
19
  - you are brainstorming
18
20
  - you are writing planning docs but are not ready to start concrete repo changes
19
21
 
@@ -30,10 +32,10 @@ Then run `/reload` in Pi.
30
32
  1. Install the package:
31
33
  `pi install npm:@linimin/pi-letscook`
32
34
  2. Run `/reload` in Pi.
33
- 3. In the main chat, describe the concrete repo change you want and let the primary agent help refine it until the first slice is ready for `/cook`.
34
- 4. Run `/cook`.
35
- 5. Review the startup brief and choose **Start** or **Cancel**.
36
- 6. Later, run `/cook` again to resume from canonical state or confirm an explicit replacement or next-round handoff.
35
+ 3. In the main chat, either implement directly with the agent or refine the concrete repo change you want.
36
+ 4. When you want workflow mode, run `/cook`.
37
+ 5. Review the synthesized startup brief and choose **Start** or **Cancel**.
38
+ 6. Later, run `/cook` again to resume from canonical state or confirm a synthesized replacement or next-round startup brief.
37
39
 
38
40
  ```text
39
41
  /cook
@@ -43,64 +45,62 @@ Then run `/reload` in Pi.
43
45
 
44
46
  | If you want to... | Do this |
45
47
  |---|---|
46
- | Start a long-running task | Discuss the concrete repo change in the main chat, then run `/cook` once the recent discussion is specific enough for a startup brief. If you explicitly want a pre-`/cook` preview or capsule first, ask for one. |
48
+ | Implement directly without workflow | Ask in ordinary chat and let the agent modify the repo directly |
49
+ | Start a tracked workflow | Discuss the concrete repo change in the main chat, then run `/cook` when you want workflow to begin |
47
50
  | Continue the current workflow | Run `/cook` |
48
- | Refocus or start the next round | Discuss the new concrete repo change in the main chat, then run `/cook` to synthesize the next startup brief. Active-workflow replacement still stays explicit and confirm-first. |
51
+ | Refocus or start the next round | Discuss the new concrete repo change in the main chat, then run `/cook` when you want a new startup brief synthesized |
49
52
 
50
53
  ## What `/cook` expects
51
54
 
52
- - recent ordinary-chat discussion concrete enough for bare `/cook` to synthesize a startup brief for a new workflow or the next round after a completed workflow
53
- - enough repo-change detail for that startup brief to stay implementation-oriented once you review it behind **Start** or **Cancel**
55
+ - enough recent main-chat discussion for `/cook` to synthesize a concrete startup brief when you explicitly invoke it
56
+ - a mission that is concrete enough to anchor bounded repo work rather than planning-only discussion
57
+ - acceptance and verification intent that can support a truthful first workflow round
54
58
  - README/CHANGELOG updates still count as concrete repo changes
55
- - assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts still do not become canonical workflow state by themselves
56
- - any pre-`/cook` preview or `cook_handoff` capsule only when you explicitly ask for it; that preview stays advisory startup intake, not canonical `.agent/**` state
57
- - active-workflow replacement still stays conservative: `/cook` resumes from canonical state unless a fresh explicit handoff proposes a different concrete repo change and you confirm that replacement
59
+ - assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts still do not count unless they can be turned into a concrete startup brief
60
+ - optional explicit `cook_handoff` capsules may still be consumed as a compatibility intake path, but they are no longer required for new-workflow or next-round entry
58
61
 
59
- If recent discussion is too weak, ambiguous, stale, or planning-only for new-workflow or next-round entry, `/cook` fails closed, leaves canonical `.agent/**` state unchanged, and tells you to clarify the concrete repo change in the main chat before rerunning `/cook`.
62
+ If `/cook` cannot derive a concrete startup brief, it fails closed, leaves canonical `.agent/**` state unchanged, and tells you to refine the mission, first slice, or verification intent in the main chat before rerunning `/cook`.
60
63
 
61
- If you explicitly asked for a preview capsule and it is still workflow-worthy rather than implementation-startable, `/cook` also fails closed instead of silently treating that preview as planning support or canonical workflow state.
64
+ If a fresh explicit handoff exists but is still workflow-worthy rather than implementation-startable, `/cook` also fails closed instead of silently treating that capsule as planning support or canonical workflow state.
62
65
 
63
66
  If you pass inline arguments to `/cook`, it also fails closed and tells you to move that intent into the main chat before rerunning bare `/cook`.
64
67
 
65
68
  ## Workflow entry
66
69
 
67
- Only explicit `/cook` enters the workflow. Ordinary prompts stay in the main chat and go straight to the primary agent.
70
+ Only explicit `/cook` enters workflow mode. Ordinary prompts stay in the main chat and go straight to the primary agent.
68
71
 
69
- If a task has clearly matured into completion-workflow scope, the primary agent should recommend `/cook` instead of starting long-running implementation directly in ordinary chat.
72
+ Ordinary chat can still directly implement repo changes. `/cook` is for the cases where you want workflow control rather than just implementation help.
70
73
 
71
- 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.
74
+ When you explicitly run `/cook`, it synthesizes a startup brief from recent discussion using primary-agent-style context, then asks you to **Start** or **Cancel** before rewriting canonical workflow state.
72
75
 
73
- 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**.
74
-
75
- 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.
76
+ Optional explicit `/cook` capsules may still be used as compatibility startup intake, but they are not required for new-workflow or next-round entry.
76
77
 
77
78
  Important behavior:
78
- - `/cook` is the canonical workflow boundary and manual entry point
79
- - startup and next-round entry stay confirm-first: bare `/cook` synthesizes the startup brief from recent discussion, then waits for **Start** or **Cancel**
80
- - active workflows resume from canonical `.agent/**` state unless a fresh valid explicit handoff proposes a replacement
81
- - any pre-`/cook` preview or capsule is explicit-request-only and non-canonical
79
+ - `/cook` is an optional workflow boundary and manual entry point
80
+ - startup and next-round entry stay confirm-first, deriving startup from explicit user `/cook` entry plus recent discussion when needed
81
+ - active workflows resume from canonical `.agent/**` state unless `/cook` synthesizes or receives a concrete replacement mission
82
82
  - explicit slash commands other than `/cook` continue normally in the main chat
83
- - ordinary main-chat discussion may clarify or propose, but mature long-running implementation should be handed off to `/cook`
83
+ - ordinary main-chat discussion may clarify, propose, or directly implement repo changes without entering workflow mode
84
84
 
85
85
  ## Typical examples
86
86
 
87
- Start a new workflow from recent discussion:
87
+ Start a new workflow from recent main-chat discussion:
88
88
 
89
89
  ```text
90
90
  I want to add login redirect handling and tests.
91
- # discuss scope until the startup brief is clear enough
91
+ # discuss/refine in main chat
92
92
  /cook
93
93
  ```
94
94
 
95
95
  ## What happens when you run `/cook`
96
96
 
97
- When no workflow is active, bare `/cook` synthesizes a startup brief from recent ordinary-chat discussion and then waits for **Start** or **Cancel**. If recent discussion is too weak, ambiguous, stale, or planning-only, `/cook` fails closed instead of guessing. If you explicitly asked for a preview capsule first and that preview is fresh but still non-startable, `/cook` also fails closed instead of silently treating it as canonical state. When a workflow is already active and no fresh valid explicit replacement handoff is present, `/cook` resumes from canonical `.agent/**` state instead of deriving replacement startup from recent discussion.
97
+ `/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. New-workflow entry and done-workflow next-round entry still fail closed when that synthesis is too weak or planning-only. When a workflow is already active and no concrete replacement mission is available, `/cook` resumes from canonical `.agent/**` state. None of this prevents ordinary-chat implementation when you choose not to enter workflow mode.
98
98
 
99
99
  | Repo state | What you'll see |
100
100
  |---|---|
101
- | No workflow yet | `/cook` synthesizes a startup brief from recent discussion and shows **Start** / **Cancel**. If recent discussion is too weak, ambiguous, stale, or planning-only, `/cook` fails closed and leaves canonical state unchanged. An explicit-request preview capsule can inform that startup brief, but it is still non-canonical until you choose **Start**. |
102
- | Active workflow exists | Usually a resume of the current workflow from canonical `.agent/**` state. If a fresh explicit handoff capsule points to a different concrete repo change, `/cook` shows a chooser first and only rewrites canonical state after you confirm the replacement. Ambiguous intake stays conservative. |
103
- | Previous workflow is `done` | `/cook` synthesizes the next implementation round from recent discussion behind **Start** / **Cancel**. If that recent discussion is too weak or ambiguous, `/cook` fails closed and leaves the finished workflow state unchanged. |
101
+ | No workflow yet | `/cook` synthesizes a startup brief from recent discussion and asks you to choose **Start** or **Cancel**. A fresh explicit handoff capsule may still be used if present. Weak, unreliable, stale, planning-only, or non-startable intake still fails closed. |
102
+ | Active workflow exists | Usually a resume of the current workflow from canonical `.agent/**` state. If `/cook` finds a different concrete replacement mission from a compatibility capsule or deferred synthesis, it shows a chooser first and only rewrites canonical state after you confirm the replacement. Ambiguous intake stays conservative. |
103
+ | Previous workflow is `done` | `/cook` can synthesize the next implementation round from recent discussion behind **Start** or **Cancel**. Weak or planning-only next-round intake still fails closed. |
104
104
 
105
105
  ## Confirmation and fail-closed behavior
106
106
 
@@ -108,10 +108,9 @@ When no workflow is active, bare `/cook` synthesizes a startup brief from recent
108
108
 
109
109
  - startup, next-round, and refocus proposals are approval-only
110
110
  - actions are **Start** and **Cancel**
111
- - **Cancel** is side-effect free: canonical workflow state stays unchanged, so you can discuss changes in the main chat and rerun `/cook`
111
+ - **Cancel** is side-effect free: discuss changes in the main chat and rerun `/cook`
112
112
  - weak, ambiguous, stale, invalid, assistant-produced, or planning-only intake does not start a workflow
113
- - any pre-`/cook` preview or capsule is advisory only and never writes canonical workflow state by itself
114
- - when a fresh explicit handoff suggests replacing an active workflow, `/cook` shows a chooser before any canonical state rewrite
113
+ - when a concrete replacement mission suggests replacing an active workflow, `/cook` shows a chooser before any canonical state rewrite
115
114
 
116
115
  When you accept startup or refocus, `/cook` persists the chosen workflow state in canonical `.agent/**` files before the re-ground round begins.
117
116
 
@@ -264,7 +263,7 @@ npm run rubric-contract-test
264
263
  npm run release-check
265
264
  ```
266
265
 
267
- `npm run release-check` is the broad packaged-release verifier. It begins with `bash .agent/verify_completion_control_plane.sh`, so missing or stale `.agent/verification-evidence.json` parity fails closed before the broader suite runs, then asserts the shipped mixed-model `/cook` public parity surfaces in `README.md`, `CHANGELOG.md`, and the `/cook` help/fail-closed copy in `extensions/completion/index.ts`, reruns the startup/refocus/context checks — including the critique-aware `/cook` confirmation regression and the smoke auto-resume prompt path — includes deterministic canonical evidence artifact coverage and includes deterministic active-slice contract coverage plus observability coverage, evaluator calibration, and the rubric-contract regression, and finishes with `npm pack --dry-run`.
266
+ `npm run release-check` is the broad packaged-release verifier. It begins with `bash .agent/verify_completion_control_plane.sh`, so missing or stale `.agent/verification-evidence.json` parity fails closed before the broader suite runs, then asserts the shipped `/cook` public parity surfaces in `README.md`, `CHANGELOG.md`, and the `/cook` help/fail-closed copy in `extensions/completion/index.ts`, reruns the startup/refocus/context checks — including the critique-aware `/cook` confirmation regression and the smoke auto-resume prompt path — includes deterministic canonical evidence artifact coverage and includes deterministic active-slice contract coverage plus observability coverage, evaluator calibration, and the rubric-contract regression, and finishes with `npm pack --dry-run`.
268
267
 
269
268
  The direct package-root verifier commands above intentionally self-isolate the repo-local extension when they shell back into `pi`, so you should not need to wrap them with `pi --no-extensions` even if `@linimin/pi-letscook` is also installed globally on the same machine.
270
269
 
@@ -38,7 +38,7 @@ type ContextProposalAlternate = {
38
38
  analysis: ContextProposalAnalysis;
39
39
  goalText: string;
40
40
  basisPreview: string;
41
- source: "session" | "analyst" | "handoff_capsule";
41
+ source: "session" | "analyst" | "handoff_capsule" | "deferred_primary_agent_handoff";
42
42
  };
43
43
 
44
44
  type ContextProposal = ContextProposalAlternate & {
@@ -60,9 +60,8 @@ type CookContextProposalResult = {
60
60
  blockedFailureMessage?: string;
61
61
  };
62
62
 
63
- function buildCookStartupBriefRequiredMessage(deps: CompletionDriverDeps, prefix?: string): string {
64
- const requirement =
65
- "/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.";
63
+ function buildCookStartupDerivationFailureMessage(deps: CompletionDriverDeps, prefix?: string): string {
64
+ const requirement = deps.structuredDiscussionFailureDetail;
66
65
  return prefix ? `${prefix} ${requirement}` : requirement;
67
66
  }
68
67
 
@@ -73,9 +72,10 @@ type ActiveWorkflowProposalAssessment = {
73
72
  blockedFailureMessage?: string;
74
73
  reason:
75
74
  | "matching_mission"
76
- | "missing_explicit_handoff"
77
- | "fresh_explicit_handoff"
78
- | "fresh_explicit_handoff_not_startable";
75
+ | "no_replacement_proposal"
76
+ | "explicit_handoff_replacement"
77
+ | "deferred_replacement"
78
+ | "replacement_not_startable";
79
79
  };
80
80
 
81
81
  type ExistingWorkflowChooserOptions = {
@@ -321,23 +321,23 @@ async function assessActiveWorkflowProposalRouting(
321
321
  ): Promise<ActiveWorkflowProposalAssessment> {
322
322
  const currentMission = currentMissionAnchor(snapshot);
323
323
  const projectName = path.basename(snapshot.files.root);
324
- const explicitHandoff = await deps.deriveCookStartupProposal(ctx, projectName);
325
- if (explicitHandoff.blockedFailureMessage) {
324
+ const derived = await deps.deriveCookContextProposal(ctx, projectName);
325
+ if (derived.blockedFailureMessage) {
326
326
  const assessment: ActiveWorkflowProposalAssessment = {
327
327
  action: "blocked",
328
328
  currentMissionAnchor: currentMission,
329
- blockedFailureMessage: explicitHandoff.blockedFailureMessage,
330
- reason: "fresh_explicit_handoff_not_startable",
329
+ blockedFailureMessage: derived.blockedFailureMessage,
330
+ reason: "replacement_not_startable",
331
331
  };
332
332
  deps.maybeWriteActiveWorkflowRoutingSnapshot(assessment);
333
333
  return assessment;
334
334
  }
335
- const proposal = explicitHandoff.proposal;
335
+ const proposal = derived.proposal;
336
336
  if (!proposal) {
337
337
  const assessment: ActiveWorkflowProposalAssessment = {
338
338
  action: "continue",
339
339
  currentMissionAnchor: currentMission,
340
- reason: "missing_explicit_handoff",
340
+ reason: "no_replacement_proposal",
341
341
  };
342
342
  deps.maybeWriteActiveWorkflowRoutingSnapshot(assessment);
343
343
  return assessment;
@@ -356,7 +356,7 @@ async function assessActiveWorkflowProposalRouting(
356
356
  action: "refocus",
357
357
  currentMissionAnchor: currentMission,
358
358
  proposal,
359
- reason: "fresh_explicit_handoff",
359
+ reason: proposal.source === "handoff_capsule" ? "explicit_handoff_replacement" : "deferred_replacement",
360
360
  };
361
361
  deps.maybeWriteActiveWorkflowRoutingSnapshot(assessment);
362
362
  return assessment;
@@ -536,14 +536,14 @@ export async function runCookEntry(
536
536
  if (!snapshot) {
537
537
  const root = findRepoRoot(cwd) ?? cwd;
538
538
  const projectName = path.basename(root);
539
- const derived = await deps.deriveCookContextProposal(ctx, projectName);
539
+ const derived = await deps.deriveCookStartupProposal(ctx, projectName);
540
540
  if (derived.blockedFailureMessage) {
541
541
  deps.emitCommandText(ctx, derived.blockedFailureMessage, "info");
542
542
  return;
543
543
  }
544
544
  const proposal = derived.proposal;
545
545
  if (!proposal) {
546
- deps.emitCommandText(ctx, buildCookStartupBriefRequiredMessage(deps), "info");
546
+ deps.emitCommandText(ctx, buildCookStartupDerivationFailureMessage(deps), "info");
547
547
  return;
548
548
  }
549
549
  const decision = await deps.confirmContextProposal(ctx, proposal, {
@@ -581,14 +581,14 @@ export async function runCookEntry(
581
581
  if (!goal) {
582
582
  if (workflowDone) {
583
583
  const projectName = path.basename(snapshot.files.root);
584
- const derived = await deps.deriveCookContextProposal(ctx, projectName);
584
+ const derived = await deps.deriveCookStartupProposal(ctx, projectName);
585
585
  if (derived.blockedFailureMessage) {
586
586
  deps.emitCommandText(ctx, derived.blockedFailureMessage, "info");
587
587
  return;
588
588
  }
589
589
  const proposal = derived.proposal;
590
590
  if (!proposal) {
591
- deps.emitCommandText(ctx, buildCookStartupBriefRequiredMessage(deps, "The previous completion workflow is already done."), "info");
591
+ deps.emitCommandText(ctx, buildCookStartupDerivationFailureMessage(deps, "The previous completion workflow is already done."), "info");
592
592
  return;
593
593
  }
594
594
  const decision = await deps.confirmContextProposal(ctx, proposal, {
@@ -614,7 +614,7 @@ export async function runCookEntry(
614
614
  ctx,
615
615
  proposal.source === "handoff_capsule"
616
616
  ? `Started a new completion workflow round from explicit primary-agent handoff: ${decision.missionAnchor}`
617
- : `Started a new completion workflow round from recent discussion: ${decision.missionAnchor}`,
617
+ : `Started a new completion workflow round from deferred primary-agent handoff: ${decision.missionAnchor}`,
618
618
  "info",
619
619
  );
620
620
  } else {
@@ -627,20 +627,29 @@ export async function runCookEntry(
627
627
  await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps);
628
628
  return;
629
629
  }
630
- const explicitReplacement = assessment.reason === "fresh_explicit_handoff";
630
+ const explicitReplacement = assessment.reason === "explicit_handoff_replacement";
631
+ const deferredReplacement = assessment.reason === "deferred_replacement";
631
632
  const decision = await confirmExistingWorkflowProposal(ctx, snapshot, assessment.proposal, deps, {
632
633
  intro: explicitReplacement
633
634
  ? "A fresh explicit primary-agent handoff proposes replacing the current workflow. Choose how /cook should proceed:"
634
- : "A replacement workflow is ready. Choose how /cook should proceed:",
635
+ : deferredReplacement
636
+ ? "A deferred primary-agent handoff synthesized from your recent discussion proposes replacing the current workflow. Choose how /cook should proceed:"
637
+ : "A replacement workflow is ready. Choose how /cook should proceed:",
635
638
  proposedMissionLabel: explicitReplacement
636
639
  ? "Proposed mission from explicit primary-agent handoff"
637
- : "Proposed mission",
640
+ : deferredReplacement
641
+ ? "Proposed mission from deferred primary-agent handoff"
642
+ : "Proposed mission",
638
643
  refocusChoiceLabel: explicitReplacement
639
644
  ? "Start new workflow from explicit primary-agent handoff\n\nReview the proposed replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state."
640
- : "Start new workflow\n\nReview the proposed replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state.",
645
+ : deferredReplacement
646
+ ? "Start new workflow from deferred primary-agent handoff\n\nReview the proposed replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state."
647
+ : "Start new workflow\n\nReview the proposed replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state.",
641
648
  alternateChoiceLabel: explicitReplacement
642
649
  ? "Start alternate workflow from explicit primary-agent handoff\n\nReview this alternate replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state."
643
- : undefined,
650
+ : deferredReplacement
651
+ ? "Start alternate workflow from deferred primary-agent handoff\n\nReview this alternate replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state."
652
+ : undefined,
644
653
  comparison: "strict",
645
654
  });
646
655
  if (!decision) {
@@ -653,9 +662,11 @@ export async function runCookEntry(
653
662
  }
654
663
  const selectedProposal = decision.proposal;
655
664
  const proposalDecision = await deps.confirmContextProposal(ctx, selectedProposal, {
656
- title: assessment.reason === "fresh_explicit_handoff"
665
+ title: explicitReplacement
657
666
  ? "Start the replacement workflow from this explicit startup brief?"
658
- : "Start the replacement workflow from this startup brief?",
667
+ : deferredReplacement
668
+ ? "Start the replacement workflow from this deferred startup brief?"
669
+ : "Start the replacement workflow from this startup brief?",
659
670
  });
660
671
  if (!proposalDecision) {
661
672
  deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled replacement workflow proposal", deps), "info");
@@ -675,9 +686,11 @@ export async function runCookEntry(
675
686
  snapshot = (await loadCompletionSnapshot(snapshot.files.root)) ?? snapshot;
676
687
  deps.emitCommandText(
677
688
  ctx,
678
- assessment.reason === "fresh_explicit_handoff"
689
+ explicitReplacement
679
690
  ? `Refocused completion mission from explicit primary-agent handoff to: ${proposalDecision.missionAnchor}`
680
- : `Refocused completion mission to: ${proposalDecision.missionAnchor}`,
691
+ : deferredReplacement
692
+ ? `Refocused completion mission from deferred primary-agent handoff to: ${proposalDecision.missionAnchor}`
693
+ : `Refocused completion mission to: ${proposalDecision.missionAnchor}`,
681
694
  "info",
682
695
  );
683
696
  }
@@ -25,6 +25,7 @@ import {
25
25
  missionAnchorsStrictlyEquivalent,
26
26
  normalizeMissionAnchorText,
27
27
  resolveContextProposalConfirmationAction,
28
+ retagContextProposalSource,
28
29
  stripCodeBlocks,
29
30
  } from "./proposal";
30
31
  import type {
@@ -107,6 +108,7 @@ type RubricEvaluationRole = (typeof RUBRIC_EVALUATION_ROLES)[number];
107
108
  const liveRoleActivityByRoot = new Map<string, LiveRoleActivity>();
108
109
  const activatedCompletionRoutingRoots = new Set<string>();
109
110
  const LIVE_ROLE_HEARTBEAT_MS = 5_000;
111
+ const COOK_HANDOFF_BLOCK_REGEX = /```cook_handoff\s*[\s\S]*?```/giu;
110
112
 
111
113
  function asBoolean(value: unknown): boolean | undefined {
112
114
  return typeof value === "boolean" ? value : undefined;
@@ -133,9 +135,10 @@ type ActiveWorkflowProposalAssessment = {
133
135
  blockedFailureMessage?: string;
134
136
  reason:
135
137
  | "matching_mission"
136
- | "missing_explicit_handoff"
137
- | "fresh_explicit_handoff"
138
- | "fresh_explicit_handoff_not_startable";
138
+ | "no_replacement_proposal"
139
+ | "explicit_handoff_replacement"
140
+ | "deferred_replacement"
141
+ | "replacement_not_startable";
139
142
  };
140
143
 
141
144
  function completionTestWorkflowActionOverride(): "continue" | "refocus" | "cancel" | undefined {
@@ -209,7 +212,7 @@ function maybeWriteTestSnapshot(targetPath: string | undefined, content: string)
209
212
 
210
213
  const COOK_MAIN_CHAT_RERUN_GUIDANCE = "Discuss changes in the main chat and rerun /cook.";
211
214
  const COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL =
212
- "/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.";
215
+ "/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.";
213
216
 
214
217
  function isWorkflowDone(snapshot: CompletionStateSnapshot | undefined): boolean {
215
218
  return asString(snapshot?.state?.continuation_policy) === "done";
@@ -372,6 +375,72 @@ async function promptContextProposalConfirmationAction(
372
375
  });
373
376
  }
374
377
 
378
+ function stripCookHandoffBlocks(text: string): string {
379
+ return text.replace(COOK_HANDOFF_BLOCK_REGEX, " ").replace(/\s+/g, " ").trim();
380
+ }
381
+
382
+ async function deriveCookRecentDiscussionProposal(
383
+ ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
384
+ projectName: string,
385
+ ): Promise<ContextProposal | undefined> {
386
+ const recentMessages = collectRecentSessionMessages(ctx, { isRecord, asString, asNumber, isStaleContextError });
387
+ const recentEntries = recentMessages
388
+ .filter((entry) => (entry.role === "user" || entry.role === "custom") && !entry.isCommand)
389
+ .filter((entry) => !/```cook_handoff\b/i.test(entry.text))
390
+ .slice(0, 8)
391
+ .map((entry) => ({ role: entry.role, text: stripCookHandoffBlocks(entry.text) }))
392
+ .filter((entry) => entry.text.length > 0);
393
+ const snapshot = await loadCompletionSnapshot(getCtxCwd(ctx));
394
+ const workflowContextLines = snapshot
395
+ ? [
396
+ `current mission anchor: ${asString(snapshot.state?.mission_anchor) ?? asString(snapshot.plan?.mission_anchor) ?? asString(snapshot.active?.mission_anchor) ?? "(none)"}`,
397
+ `continuation policy: ${asString(snapshot.state?.continuation_policy) ?? "(none)"}`,
398
+ `latest completed slice: ${asString(snapshot.state?.latest_completed_slice) ?? "(none)"}`,
399
+ `latest verified slice: ${asString(snapshot.state?.latest_verified_slice) ?? "(none)"}`,
400
+ `active slice goal: ${asString(snapshot.active?.goal) ?? "(none)"}`,
401
+ `active slice why_now: ${asString(snapshot.active?.why_now) ?? "(none)"}`,
402
+ `verification goal: ${asString(snapshot.verificationEvidence?.goal) ?? "(none)"}`,
403
+ `verification summary: ${asString(snapshot.verificationEvidence?.summary) ?? "(none)"}`,
404
+ ]
405
+ : [];
406
+ const proposal = await deriveCookContextProposalFromRecentDiscussion(projectName, recentEntries, {
407
+ asString,
408
+ asStringArray,
409
+ workflowContext: snapshot
410
+ ? {
411
+ currentMissionAnchor:
412
+ asString(snapshot.state?.mission_anchor) ?? asString(snapshot.plan?.mission_anchor) ?? asString(snapshot.active?.mission_anchor),
413
+ latestCompletedSlice: asString(snapshot.state?.latest_completed_slice),
414
+ latestVerifiedSlice: asString(snapshot.state?.latest_verified_slice),
415
+ activeSliceGoal: asString(snapshot.active?.goal),
416
+ activeSliceWhyNow: asString(snapshot.active?.why_now),
417
+ verificationGoal: asString(snapshot.verificationEvidence?.goal),
418
+ verificationSummary: asString(snapshot.verificationEvidence?.summary),
419
+ continuationPolicy: asString(snapshot.state?.continuation_policy),
420
+ }
421
+ : undefined,
422
+ analyzeContextProposal: async (entries) =>
423
+ await analyzeContextProposalWithAgent({
424
+ ctx,
425
+ projectName,
426
+ recentEntries: entries,
427
+ workflowContextLines,
428
+ liveRoleActivityByRoot,
429
+ completionStatusKey: COMPLETION_STATUS_KEY,
430
+ safeUiCall,
431
+ getCtxCwd,
432
+ getCtxHasUI,
433
+ getCtxUi,
434
+ }),
435
+ assessMissionAnchor,
436
+ isWeakMissionAnchor,
437
+ missionAnchorsStrictlyEquivalent,
438
+ normalizeMissionAnchorText,
439
+ stripCodeBlocks,
440
+ });
441
+ return retagContextProposalSource(proposal, "deferred_primary_agent_handoff");
442
+ }
443
+
375
444
  async function deriveCookStartupProposal(
376
445
  ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
377
446
  projectName: string,
@@ -390,70 +459,14 @@ async function deriveCookStartupProposal(
390
459
  if (explicitHandoff.status === "fresh_but_not_startable") {
391
460
  return { blockedFailureMessage: explicitHandoff.message };
392
461
  }
393
- return {};
462
+ return { proposal: await deriveCookRecentDiscussionProposal(ctx, projectName) };
394
463
  }
395
464
 
396
465
  async function deriveCookContextProposal(
397
466
  ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
398
467
  projectName: string,
399
468
  ): Promise<CookContextProposalResult> {
400
- const recentMessages = collectRecentSessionMessages(ctx, { isRecord, asString, asNumber, isStaleContextError });
401
- const recentEntries = recentMessages
402
- .filter((entry) => (entry.role === "user" || entry.role === "custom") && !entry.isCommand)
403
- .slice(0, 8)
404
- .map((entry) => ({ role: entry.role, text: entry.text }));
405
- const snapshot = await loadCompletionSnapshot(getCtxCwd(ctx));
406
- const workflowContextLines = snapshot
407
- ? [
408
- `current mission anchor: ${asString(snapshot.state?.mission_anchor) ?? asString(snapshot.plan?.mission_anchor) ?? asString(snapshot.active?.mission_anchor) ?? "(none)"}`,
409
- `continuation policy: ${asString(snapshot.state?.continuation_policy) ?? "(none)"}`,
410
- `latest completed slice: ${asString(snapshot.state?.latest_completed_slice) ?? "(none)"}`,
411
- `latest verified slice: ${asString(snapshot.state?.latest_verified_slice) ?? "(none)"}`,
412
- `active slice goal: ${asString(snapshot.active?.goal) ?? "(none)"}`,
413
- `active slice why_now: ${asString(snapshot.active?.why_now) ?? "(none)"}`,
414
- `verification goal: ${asString(snapshot.verificationEvidence?.goal) ?? "(none)"}`,
415
- `verification summary: ${asString(snapshot.verificationEvidence?.summary) ?? "(none)"}`,
416
- ]
417
- : [];
418
- const explicitHandoff = await deriveCookStartupProposal(ctx, projectName);
419
- if (explicitHandoff.proposal || explicitHandoff.blockedFailureMessage) return explicitHandoff;
420
- return {
421
- proposal: await deriveCookContextProposalFromRecentDiscussion(projectName, recentEntries, {
422
- asString,
423
- asStringArray,
424
- workflowContext: snapshot
425
- ? {
426
- currentMissionAnchor:
427
- asString(snapshot.state?.mission_anchor) ?? asString(snapshot.plan?.mission_anchor) ?? asString(snapshot.active?.mission_anchor),
428
- latestCompletedSlice: asString(snapshot.state?.latest_completed_slice),
429
- latestVerifiedSlice: asString(snapshot.state?.latest_verified_slice),
430
- activeSliceGoal: asString(snapshot.active?.goal),
431
- activeSliceWhyNow: asString(snapshot.active?.why_now),
432
- verificationGoal: asString(snapshot.verificationEvidence?.goal),
433
- verificationSummary: asString(snapshot.verificationEvidence?.summary),
434
- continuationPolicy: asString(snapshot.state?.continuation_policy),
435
- }
436
- : undefined,
437
- analyzeContextProposal: async (entries) =>
438
- await analyzeContextProposalWithAgent({
439
- ctx,
440
- projectName,
441
- recentEntries: entries,
442
- workflowContextLines,
443
- liveRoleActivityByRoot,
444
- completionStatusKey: COMPLETION_STATUS_KEY,
445
- safeUiCall,
446
- getCtxCwd,
447
- getCtxHasUI,
448
- getCtxUi,
449
- }),
450
- assessMissionAnchor,
451
- isWeakMissionAnchor,
452
- missionAnchorsStrictlyEquivalent,
453
- normalizeMissionAnchorText,
454
- stripCodeBlocks,
455
- }),
456
- };
469
+ return await deriveCookStartupProposal(ctx, projectName);
457
470
  }
458
471
 
459
472
  async function confirmContextProposal(
@@ -929,7 +942,7 @@ export default function completionExtension(pi: ExtensionAPI) {
929
942
  structuredDiscussionFailureDetail: COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL,
930
943
  mainChatRerunGuidance: COOK_MAIN_CHAT_RERUN_GUIDANCE,
931
944
  cookCommandSpec: {
932
- 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",
945
+ description: "/cook workflow: optionally enter tracked workflow mode, synthesize a startup brief from explicit /cook entry, resume the current workflow from canonical state, or confirm a replacement mission",
933
946
  },
934
947
  buildContextProposalContinuationReason,
935
948
  completionKickoff,
@@ -10,7 +10,7 @@ import type {
10
10
 
11
11
  export type AdvisoryStartupBrief = {
12
12
  kind: "startup_brief";
13
- source: "recent_discussion" | "primary_agent_handoff";
13
+ source: "recent_discussion" | "primary_agent_handoff" | "deferred_primary_agent_handoff";
14
14
  confirmed: true;
15
15
  captured_at: string;
16
16
  goal_text: string;
@@ -26,20 +26,16 @@ export type AdvisoryStartupBrief = {
26
26
 
27
27
  export function buildCookHandoffBoundaryReminder(): string {
28
28
  return [
29
- "You are still in ordinary main chat before any explicit /cook workflow entry.",
30
- "Use ordinary chat to clarify requirements, discuss tradeoffs, propose implementation approaches, and refine scope with the user.",
31
- "/cook is the only explicit entrypoint into long-running completion workflow.",
32
- "When you judge that the task has matured into completion-workflow scope for example the user has clearly shifted from exploration into implementation intent, you have just produced a concrete plan or proposal whose next step would naturally be implementation, or the task spans multiple files, steps, or verification surfaces — do not begin long-running product implementation in ordinary chat and do not edit tracked product files for that workflow-level task.",
33
- "Instead, recommend /cook as the workflow boundary while keeping the conversation in ordinary chat until the user explicitly runs /cook.",
34
- "If the user keeps asking follow-up questions or refining requirements before /cook, continue that ordinary-chat discussion normally instead of switching into a handoff-only refusal mode, but do not act as though /cook had already been invoked.",
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
- "If the task is workflow-worthy but the user still wants to refine scope before /cook, keep refining in ordinary chat without acting as though workflow already started.",
37
- "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.",
38
- "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 with kind/source/handoff_kind plus mission, scope, constraints or non_goals, acceptance, risks, notes, captured_at, source_turn_id, first_slice_goal, first_slice_non_goals, implementation_surfaces, verification_commands, why_this_slice_first, and optional task_type/evaluation_profile/why_cook_now.",
39
- "Use handoff_kind implementation_workflow_handoff for that opt-in preview capsule.",
40
- "If later ordinary-chat discussion materially changes the startup brief before /cook runs, update or replace the preview capsule in a later assistant reply instead of pretending the workflow already started.",
29
+ "You are in ordinary main chat unless the user explicitly runs /cook.",
30
+ "Ordinary chat may clarify requirements, discuss tradeoffs, refine scope, and directly implement requested repo changes, including multi-file work, when that is the most helpful response.",
31
+ "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.",
32
+ "/cook is optional workflow mode for resumability, review, audit, canonical .agent state, or deliberate multi-session control; it is not required just to edit repo files in ordinary chat.",
33
+ "If the user wants direct implementation now, stay in ordinary chat and help directly instead of blocking on /cook.",
34
+ "If the user asks follow-up questions or wants to keep refining scope, continue helping naturally in ordinary chat.",
35
+ "If the user explicitly runs /cook, /cook will synthesize a startup brief from recent discussion using primary-agent-style context, then show Start/Cancel confirmation before canonical workflow state is rewritten.",
36
+ "Only provide a preview startup brief or ```cook_handoff``` capsule in ordinary chat when the user explicitly asks for that preview behavior.",
41
37
  "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.",
42
- "If the task is still ordinary Q&A, lightweight brainstorming, or a tiny one-off fix, continue normally without forcing /cook or emitting an unsolicited preview capsule.",
38
+ "When you continue in ordinary chat, do not pretend /cook already started and do not silently rewrite discussion into canonical workflow state.",
43
39
  ].join(" ");
44
40
  }
45
41
 
@@ -97,7 +93,12 @@ export function buildAdvisoryStartupBrief(args: {
97
93
  }): AdvisoryStartupBrief {
98
94
  return {
99
95
  kind: "startup_brief",
100
- source: args.proposal.source === "handoff_capsule" ? "primary_agent_handoff" : "recent_discussion",
96
+ source:
97
+ args.proposal.source === "handoff_capsule"
98
+ ? "primary_agent_handoff"
99
+ : args.proposal.source === "deferred_primary_agent_handoff"
100
+ ? "deferred_primary_agent_handoff"
101
+ : "recent_discussion",
101
102
  confirmed: true,
102
103
  captured_at: args.capturedAt ?? new Date().toISOString(),
103
104
  goal_text: args.proposal.goalText,