@linimin/pi-letscook 0.1.49 → 0.1.51

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
@@ -2,6 +2,23 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.1.51
6
+
7
+ ### Added
8
+
9
+ - shipped assist-mode natural-language handoff that can offer to route `開始做`, `開始實作`, or `go ahead` style execution handoffs into the canonical `/cook` flow before the primary agent starts implementation work, while keeping `/cook` as the explicit workflow boundary and approval gate
10
+ - added `bash ./scripts/cook-trigger-routing-test.sh` to `npm run release-check` so packaged release parity now covers the natural-language takeover path alongside the existing `/cook` startup/refocus/context regressions
11
+
12
+ ### Changed
13
+
14
+ - streamlined the README into a more user-facing guide with a 30-second quick start, common actions table, clearer natural-language handoff expectations, and shorter `/cook` usage explanations
15
+
16
+ ## 0.1.50
17
+
18
+ ### Changed
19
+
20
+ - simplified the README opening so people can tell at a glance whether this extension helps with their workflow, while preserving the existing `/cook` behavior and release-parity guidance
21
+
5
22
  ## 0.1.49
6
23
 
7
24
  ### Changed
package/README.md CHANGED
@@ -1,26 +1,21 @@
1
1
  # @linimin/pi-letscook
2
2
 
3
- A Pi extension that turns `/cook` into a discussion-driven repo-local workflow command for long-running coding work.
3
+ `/cook` turns main-chat discussion about concrete repo changes into a resumable repo workflow stored in repo-local `.agent/**` state.
4
4
 
5
- ## Why this exists
5
+ Assist-mode natural-language handoff can also offer to enter that same `/cook` flow before the primary agent starts implementation work, but `/cook` remains the canonical workflow boundary.
6
6
 
7
- Normal chat is good for one-off tasks. It is much worse for work that needs to:
7
+ ## Use it when
8
8
 
9
- - continue across sessions
10
- - stay anchored to one mission
11
- - resume from repo state instead of chat memory
12
- - keep review, audit, and verification tied to the repo
9
+ - work spans multiple sessions
10
+ - you want one mission tracked in repo state instead of chat memory
11
+ - you want clear continue / refocus / next-round behavior
12
+ - you want review, audit, and verification tied to the repo
13
13
 
14
- `@linimin/pi-letscook` solves that by storing canonical workflow state in `.agent/**` and using `/cook` as one discussion-first command to start, continue, refocus, or advance the workflow.
14
+ ## Skip it when
15
15
 
16
- ## What you get
17
-
18
- - one command: `/cook`
19
- - repo-local canonical state in `.agent/**`
20
- - resumable long-running workflows
21
- - discussion-first startup, continue, refocus, and next-round routing
22
- - fail-closed guidance that sends you back to the main chat when the mission still needs clarification
23
- - deterministic verification, review, audit, and stop checks
16
+ - you only need a one-off answer
17
+ - you are brainstorming
18
+ - you are writing planning docs but are not ready to start concrete repo changes
24
19
 
25
20
  ## Install
26
21
 
@@ -30,55 +25,94 @@ pi install npm:@linimin/pi-letscook
30
25
 
31
26
  Then run `/reload` in Pi.
32
27
 
33
- ## Quick start
28
+ ## 30-second quick start
34
29
 
35
- Primary entrypoint:
30
+ 1. Install the package:
31
+ `pi install npm:@linimin/pi-letscook`
32
+ 2. Run `/reload` in Pi.
33
+ 3. In the main chat, describe the concrete repo change you want.
34
+ 4. Run `/cook` or `/cook <hint>`.
35
+ 5. Review the proposal and choose **Start** or **Cancel**.
36
+ 6. Later, run `/cook` again to continue, refocus, or start the next round.
36
37
 
37
38
  ```text
38
39
  /cook
40
+ /cook login redirect
39
41
  ```
40
42
 
41
- Use `/cook` after you discuss the mission in the main chat. You can run it bare or pass a short hint such as `/cook login redirect`. The same command can:
43
+ ## Common actions
42
44
 
43
- - start a brand-new workflow from recent discussion
44
- - continue the current workflow when recent discussion still matches it, or when discussion is too weak or ambiguous to justify a refocus
45
- - surface a conservative refocus chooser when recent discussion clearly points to a different workflow
46
- - start the next workflow round after the previous one is `done`
45
+ | If you want to... | Do this |
46
+ |---|---|
47
+ | Start a long-running task | Discuss the concrete repo change in the main chat, then run `/cook` |
48
+ | Bias mission detection toward one intent | Run `/cook <hint>` |
49
+ | Hand off from discussion into the same `/cook` flow | Say `開始做`, `開始實作`, or `go ahead`, then accept the confirmation |
50
+ | Continue the current workflow | Run `/cook` |
51
+ | Use the canonical fallback when the natural-language trigger does not fire | Run `/cook` explicitly |
47
52
 
48
- `/cook` expects recent main-chat discussion to describe concrete repo changes. README/CHANGELOG updates still count as concrete repo changes, but assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts do not. `/cook <hint>` acts as a high-priority intent hint that helps proposal derivation interpret the recent discussion, but it still goes through the same fail-closed routing and approval-only Start/Cancel confirmation flow.
53
+ ## What `/cook` expects
49
54
 
50
- On startup and next-round flows, if recent discussion is missing, weak, ambiguous, assistant-produced, or only describes planning artifacts instead of concrete repo changes, `/cook` fails closed, leaves canonical `.agent/**` state unchanged, and tells you to clarify the mission in the main chat before rerunning `/cook`.
55
+ - recent main-chat discussion about concrete repo changes
56
+ - README/CHANGELOG updates still count as concrete repo changes
57
+ - assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts do not
51
58
 
52
- ## How `/cook` works
59
+ `/cook <hint>` acts as a high-priority intent hint for interpreting recent discussion, but it does not bypass fail-closed behavior or the approval-only Start/Cancel confirmation flow.
53
60
 
54
- `/cook` supports both bare discussion-driven startup and optional inline intent hints.
61
+ If recent discussion is missing, weak, ambiguous, assistant-produced, or only describes planning artifacts instead of concrete repo changes, `/cook` fails closed, leaves canonical `.agent/**` state unchanged, and tells you to clarify the mission in the main chat before rerunning `/cook`.
55
62
 
56
- | Repo state | `/cook` behavior |
57
- |---|---|
58
- | No workflow yet | Summarizes recent main-chat discussion into a startup proposal, weighting the latest clear implementation intent ahead of older background discussion. Optional `/cook <hint>` text is treated as a high-priority cue for how to interpret that discussion, not as an unconditional mission override. The result still asks for approval with **Start** or **Cancel**. If the discussion is weak, ambiguous, assistant-produced, or only a plan/spec/design-doc/proposal artifact instead of concrete repo changes, `/cook` fails closed without writing `.agent/**` state and tells you to clarify the mission in the main chat before rerunning `/cook`. |
59
- | Active workflow exists | Reads the current mission plus recent non-command main-chat discussion. Matching or unclear discussion resumes from canonical `.agent/**` state. Clear replacement discussion about different concrete repo changes opens a chooser first, then only rewrites canonical state after the follow-on **Start** confirmation. If recent discussion implies more than one plausible replacement mission, `/cook` keeps the current workflow parked behind a multi-candidate chooser instead of silently resuming or guessing. Optional `/cook <hint>` text biases that routing and candidate ranking toward the hinted implementation intent without bypassing the chooser or final confirmation. Assistant/summary artifacts or plan/spec/design-doc/proposal-only context do not refocus the workflow. |
60
- | Previous workflow is `done` | Starts the next round from recent main-chat discussion, then asks for approval with **Start** or **Cancel**. Optional `/cook <hint>` text biases next-round proposal derivation toward the hinted intent while still preserving fail-closed behavior. Weak, ambiguous, assistant-produced, or planning-artifact-only discussion fails closed without rewriting canonical state and tells you to clarify the mission in the main chat before rerunning `/cook`. Recent discussion that only restates already-completed or already-verified work also fails closed instead of reopening the finished mission. |
63
+ ## Natural-language handoff (assist mode)
61
64
 
62
- ## Approval-only confirmation and fail-closed behavior
65
+ After you have discussed a concrete repo change in the main chat, short execution handoff phrases such as `開始做`, `開始實作`, or `go ahead` can offer to enter the same `/cook` flow before the primary agent starts implementation work.
63
66
 
64
- All startup, next-round, and replacement proposals are **approval-only**:
67
+ Important behavior:
68
+ - the handoff is only a shortcut into `/cook`; `/cook` is still the canonical workflow boundary
69
+ - it asks for confirmation before `/cook` takes over
70
+ - if the trigger is unclear or unavailable, nothing is auto-started and you can run `/cook` explicitly
71
+ - ordinary questions and explicit slash commands continue normally
65
72
 
66
- - the proposal body is shown separately from actions
67
- - actions are only **Start** and **Cancel**
68
- - **Cancel** is side-effect free: discuss changes in the main chat and rerun `/cook`
73
+ ## Typical examples
74
+
75
+ Start a new workflow from recent discussion:
69
76
 
70
- When `/cook` cannot derive a clear startup, next-round, or replacement proposal for concrete repo changes from recent main-chat discussion, it fails closed instead of guessing. That means no canonical `.agent/**` state is created or rewritten until the discussion is clarified in the main chat and you rerun `/cook`. Tracked docs-only work such as README/CHANGELOG updates is still execution-ready, but assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts are not enough to start or refocus a workflow on their own. Optional `/cook <hint>` text can bias proposal ranking, but it still fails closed when repo truth or recent discussion does not support a clear executable mission.
77
+ ```text
78
+ I want to add login redirect handling and tests.
79
+ /cook
80
+ ```
71
81
 
72
- When an active workflow already exists and recent discussion suggests a different workflow, `/cook` shows a separate chooser first. The chooser can stay conservative or list multiple candidate replacements when the latest discussion contains more than one plausible implementation goal:
82
+ Bias proposal derivation toward a specific intent:
73
83
 
74
- - **Continue current workflow**
75
- - **Start new workflow from recent discussion**
76
- - **Start alternate workflow from recent discussion** (when a second plausible mission exists)
77
- - **Cancel**
84
+ ```text
85
+ /cook login redirect
86
+ ```
78
87
 
79
- Chooser options summarize each candidate mission with its latest scope/constraint/acceptance highlights before the follow-on approval-only Start/Cancel gate. Canonical `.agent/**` state changes still happen only after **Start** is accepted.
88
+ Hand off from discussion into the same `/cook` flow:
80
89
 
81
- When you accept startup or refocus from that flow, `/cook` persists the chosen `task_type` and `evaluation_profile` across `.agent/profile.json`, `.agent/state.json`, `.agent/plan.json`, and `.agent/active-slice.json`, and records the accepted critique outcome plus any alternate-mission / suppression notes in canonical continuation state before the re-ground round begins.
90
+ ```text
91
+ We should implement the natural-language routing path next.
92
+ 開始做
93
+ ```
94
+
95
+ ## What happens when you run `/cook`
96
+
97
+ `/cook` supports both bare discussion-driven startup and optional inline intent hints. Assist-mode natural-language handoff is optional; explicit `/cook` is always the canonical fallback.
98
+
99
+ | Repo state | What you'll see |
100
+ |---|---|
101
+ | No workflow yet | A startup proposal built from recent main-chat discussion. You choose **Start** or **Cancel**. Weak or planning-only discussion fails closed. |
102
+ | Active workflow exists | Usually a resume of the current workflow. If recent discussion clearly points to a different concrete repo change, `/cook` shows a chooser first and only rewrites canonical state after confirmation. Ambiguous discussion stays conservative. |
103
+ | Previous workflow is `done` | A next-round proposal from recent main-chat discussion, again behind **Start** or **Cancel**. Discussion that only restates already-finished work fails closed. |
104
+
105
+ ## Confirmation and fail-closed behavior
106
+
107
+ `/cook` never silently starts or rewrites canonical `.agent/**` state on unclear input.
108
+
109
+ - startup, next-round, and refocus proposals are approval-only
110
+ - actions are **Start** and **Cancel**
111
+ - **Cancel** is side-effect free: discuss changes in the main chat and rerun `/cook`
112
+ - weak, ambiguous, assistant-produced, or planning-only discussion does not start a workflow
113
+ - when recent discussion suggests a different workflow, `/cook` shows a chooser before any canonical state rewrite
114
+
115
+ When you accept startup or refocus, `/cook` persists the chosen workflow state in canonical `.agent/**` files before the re-ground round begins.
82
116
 
83
117
  ## Observability
84
118
 
@@ -98,6 +132,10 @@ While a `completion_role` subprocess is running:
98
132
  - running-role output distinguishes tool work from `PROGRESS`, `RATIONALE`, `NEXT`, `VERIFYING`, and `STATE-DELTA`
99
133
  - waiting and stalled states are surfaced deterministically from timestamps
100
134
 
135
+ ## Maintainer and protocol details
136
+
137
+ The sections below are mainly useful if you maintain the extension, inspect canonical `.agent/**` state, or work on the packaged completion protocol itself.
138
+
101
139
  ## Structured evaluation rubrics
102
140
 
103
141
  The packaged completion workflow now defines a shared structured evaluation-rubric contract for the read-only evaluation roles:
@@ -214,6 +252,7 @@ Run validation from the package root:
214
252
  npm run smoke-test
215
253
  npm run refocus-test
216
254
  npm run context-proposal-test
255
+ bash ./scripts/cook-trigger-routing-test.sh
217
256
  bash scripts/canonical-evidence-artifact-test.sh
218
257
  npm run observability-status-test
219
258
  npm run evaluator-calibration-test
@@ -221,7 +260,7 @@ npm run rubric-contract-test
221
260
  npm run release-check
222
261
  ```
223
262
 
224
- `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 single-command `/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`.
263
+ `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 single-command `/cook` public parity surfaces in `README.md`, `CHANGELOG.md`, and the `/cook` help/fail-closed copy in `extensions/completion/index.ts`, reruns `bash ./scripts/cook-trigger-routing-test.sh` for the assist-mode natural-language handoff path, 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`.
225
264
 
226
265
  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.
227
266
 
@@ -530,142 +530,162 @@ function buildMission(projectName: string, missionAnchor: string): string {
530
530
  return `# Mission\n\nProject: ${projectName}\n\nMission anchor:\n${missionAnchor}\n\nThis file is a tracked human-readable statement of the repo's completion mission. Re-grounders may refine this file when repo truth becomes clearer, but it must stay truthful to shipped behavior and the active completion objective.\n`;
531
531
  }
532
532
 
533
- export function registerCookCommand(pi: ExtensionAPI, deps: CompletionDriverDeps): void {
534
- pi.registerCommand("cook", {
535
- description: deps.cookCommandSpec.description,
536
- handler: async (args, ctx) => {
537
- const explicitHint = args.trim().length > 0 ? args.trim() : undefined;
538
- let goal: string | undefined;
539
- const cwd = deps.getCtxCwd(ctx);
540
- let snapshot = await loadCompletionSnapshot(cwd);
541
- const workflowDone = isWorkflowDone(snapshot);
542
- let kickoffIntent: "auto" | "continue" | "refocus" = "auto";
543
- let kickoffMissionAnchor = snapshot ? currentMissionAnchor(snapshot) : undefined;
544
- let kickoffAnalysis: ContextProposalAnalysis | undefined;
545
-
546
- if (!snapshot) {
547
- const root = findRepoRoot(cwd) ?? cwd;
548
- const projectName = path.basename(root);
549
- const proposal = await deps.deriveCookContextProposal(ctx, projectName, explicitHint);
550
- if (!proposal) {
551
- deps.emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(deps), "info");
552
- return;
553
- }
554
- const decision = await deps.confirmContextProposal(ctx, proposal, {
555
- title: "Start a completion workflow from the recent discussion?",
556
- });
557
- if (!decision) {
558
- deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled recent-discussion workflow proposal", deps), "info");
559
- return;
560
- }
561
- goal = decision.goalText;
562
- kickoffMissionAnchor = decision.missionAnchor;
563
- kickoffAnalysis = decision.analysis;
564
- const startupRouting = deps.finalizeContextProposalAnalysis(kickoffAnalysis, [goal ?? kickoffMissionAnchor ?? projectName]);
565
- const created = await deps.scaffoldCompletionFiles(root, kickoffMissionAnchor ?? projectName, {
566
- analysis: startupRouting,
567
- continuationReason: deps.buildContextProposalContinuationReason(
568
- "User started workflow via /cook:",
569
- goal ?? kickoffMissionAnchor ?? projectName,
570
- startupRouting,
571
- ),
572
- });
573
- deps.emitCommandText(
574
- ctx,
575
- `Initialized completion control plane in ${created.root}${created.created.length > 0 ? ` (${created.created.length} files created)` : ""}`,
576
- "info",
577
- );
578
- snapshot = await loadCompletionSnapshot(root);
533
+ export type CookInvocationOrigin = "command" | "natural-language-trigger";
534
+
535
+ export type RunCookEntryOptions = {
536
+ origin: CookInvocationOrigin;
537
+ hintText?: string;
538
+ originalInput?: string;
539
+ };
540
+
541
+ export async function runCookEntry(
542
+ pi: ExtensionAPI,
543
+ ctx: DriverContext,
544
+ deps: CompletionDriverDeps,
545
+ options: RunCookEntryOptions,
546
+ ): Promise<void> {
547
+ const explicitHint = options.hintText?.trim() ? options.hintText.trim() : undefined;
548
+ let goal: string | undefined;
549
+ const cwd = deps.getCtxCwd(ctx);
550
+ let snapshot = await loadCompletionSnapshot(cwd);
551
+ const workflowDone = isWorkflowDone(snapshot);
552
+ let kickoffIntent: "auto" | "continue" | "refocus" = "auto";
553
+ let kickoffMissionAnchor = snapshot ? currentMissionAnchor(snapshot) : undefined;
554
+ let kickoffAnalysis: ContextProposalAnalysis | undefined;
555
+
556
+ if (!snapshot) {
557
+ const root = findRepoRoot(cwd) ?? cwd;
558
+ const projectName = path.basename(root);
559
+ const proposal = await deps.deriveCookContextProposal(ctx, projectName, explicitHint);
560
+ if (!proposal) {
561
+ deps.emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(deps), "info");
562
+ return;
563
+ }
564
+ const decision = await deps.confirmContextProposal(ctx, proposal, {
565
+ title: "Start a completion workflow from the recent discussion?",
566
+ });
567
+ if (!decision) {
568
+ deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled recent-discussion workflow proposal", deps), "info");
569
+ return;
570
+ }
571
+ goal = decision.goalText;
572
+ kickoffMissionAnchor = decision.missionAnchor;
573
+ kickoffAnalysis = decision.analysis;
574
+ const startupRouting = deps.finalizeContextProposalAnalysis(kickoffAnalysis, [goal ?? kickoffMissionAnchor ?? projectName]);
575
+ const created = await deps.scaffoldCompletionFiles(root, kickoffMissionAnchor ?? projectName, {
576
+ analysis: startupRouting,
577
+ continuationReason: deps.buildContextProposalContinuationReason(
578
+ "User started workflow via /cook:",
579
+ goal ?? kickoffMissionAnchor ?? projectName,
580
+ startupRouting,
581
+ ),
582
+ });
583
+ deps.emitCommandText(
584
+ ctx,
585
+ `Initialized completion control plane in ${created.root}${created.created.length > 0 ? ` (${created.created.length} files created)` : ""}`,
586
+ "info",
587
+ );
588
+ snapshot = await loadCompletionSnapshot(root);
589
+ }
590
+ if (!snapshot) {
591
+ deps.emitCommandText(ctx, "Failed to load completion workflow state", "error");
592
+ return;
593
+ }
594
+ deps.activateCompletionRoutingForRoot(snapshot.files.root);
595
+ if (!goal) {
596
+ if (workflowDone) {
597
+ const projectName = path.basename(snapshot.files.root);
598
+ const proposal = await deps.deriveCookContextProposal(ctx, projectName, explicitHint);
599
+ if (!proposal) {
600
+ deps.emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(deps, "The previous completion workflow is already done."), "info");
601
+ return;
579
602
  }
580
- if (!snapshot) {
581
- deps.emitCommandText(ctx, "Failed to load completion workflow state", "error");
603
+ const decision = await deps.confirmContextProposal(ctx, proposal, {
604
+ title: "The previous completion workflow is done. Start the next workflow round from the recent discussion?",
605
+ });
606
+ if (!decision) {
607
+ deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled next workflow round proposal", deps), "info");
582
608
  return;
583
609
  }
584
- deps.activateCompletionRoutingForRoot(snapshot.files.root);
585
- if (!goal) {
586
- if (workflowDone) {
587
- const projectName = path.basename(snapshot.files.root);
588
- const proposal = await deps.deriveCookContextProposal(ctx, projectName, explicitHint);
589
- if (!proposal) {
590
- deps.emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(deps, "The previous completion workflow is already done."), "info");
591
- return;
592
- }
593
- const decision = await deps.confirmContextProposal(ctx, proposal, {
594
- title: "The previous completion workflow is done. Start the next workflow round from the recent discussion?",
595
- });
596
- if (!decision) {
597
- deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled next workflow round proposal", deps), "info");
598
- return;
599
- }
600
- goal = decision.goalText;
601
- kickoffIntent = "refocus";
602
- kickoffMissionAnchor = decision.missionAnchor;
603
- await refocusCompletionMission(snapshot, decision.missionAnchor, decision.goalText, decision.analysis, deps);
604
- snapshot = (await loadCompletionSnapshot(snapshot.files.root)) ?? snapshot;
605
- deps.emitCommandText(ctx, `Started a new completion workflow round from recent discussion: ${decision.missionAnchor}`, "info");
606
- } else {
607
- const assessment = await assessActiveWorkflowProposalRouting(ctx, snapshot, deps, explicitHint);
608
- if (!assessment.proposal || assessment.action === "continue") {
609
- await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps);
610
- return;
611
- }
612
- const decision = await confirmExistingWorkflowProposal(ctx, snapshot, assessment.proposal, deps, {
613
- intro:
614
- assessment.action === "refocus"
615
- ? "Recent non-command discussion suggests a different workflow. Choose how /cook should proceed:"
616
- : "Recent discussion may point to a different implementation goal. Review the current mission and the latest inferred mission before deciding how /cook should proceed:",
617
- proposedMissionLabel: "Proposed mission from recent discussion",
618
- refocusChoiceLabel:
619
- "Start new workflow from recent discussion\n\nReview the proposed replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state.",
620
- comparison: assessment.action === "refocus" ? "semantic" : "strict",
621
- });
622
- if (!decision) {
623
- deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled existing workflow confirmation", deps), "info");
624
- return;
625
- }
626
- if (decision.action === "continue") {
627
- await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps);
628
- return;
629
- }
630
- const selectedProposal = decision.proposal;
631
- const proposalDecision = await deps.confirmContextProposal(ctx, selectedProposal, {
632
- title:
633
- assessment.action === "refocus"
634
- ? "Start the replacement workflow from recent discussion?"
635
- : "Start the latest inferred workflow from recent discussion?",
636
- });
637
- if (!proposalDecision) {
638
- deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled replacement workflow proposal", deps), "info");
639
- return;
640
- }
641
- goal = proposalDecision.goalText;
642
- kickoffIntent = "refocus";
643
- kickoffMissionAnchor = proposalDecision.missionAnchor;
644
- await refocusCompletionMission(snapshot, proposalDecision.missionAnchor, proposalDecision.goalText, proposalDecision.analysis, deps);
645
- snapshot = (await loadCompletionSnapshot(snapshot.files.root)) ?? snapshot;
646
- deps.emitCommandText(ctx, `Refocused completion mission from recent discussion to: ${proposalDecision.missionAnchor}`, "info");
647
- }
610
+ goal = decision.goalText;
611
+ kickoffIntent = "refocus";
612
+ kickoffMissionAnchor = decision.missionAnchor;
613
+ await refocusCompletionMission(snapshot, decision.missionAnchor, decision.goalText, decision.analysis, deps);
614
+ snapshot = (await loadCompletionSnapshot(snapshot.files.root)) ?? snapshot;
615
+ deps.emitCommandText(ctx, `Started a new completion workflow round from recent discussion: ${decision.missionAnchor}`, "info");
616
+ } else {
617
+ const assessment = await assessActiveWorkflowProposalRouting(ctx, snapshot, deps, explicitHint);
618
+ if (!assessment.proposal || assessment.action === "continue") {
619
+ await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps);
620
+ return;
621
+ }
622
+ const decision = await confirmExistingWorkflowProposal(ctx, snapshot, assessment.proposal, deps, {
623
+ intro:
624
+ assessment.action === "refocus"
625
+ ? "Recent non-command discussion suggests a different workflow. Choose how /cook should proceed:"
626
+ : "Recent discussion may point to a different implementation goal. Review the current mission and the latest inferred mission before deciding how /cook should proceed:",
627
+ proposedMissionLabel: "Proposed mission from recent discussion",
628
+ refocusChoiceLabel:
629
+ "Start new workflow from recent discussion\n\nReview the proposed replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state.",
630
+ comparison: assessment.action === "refocus" ? "semantic" : "strict",
631
+ });
632
+ if (!decision) {
633
+ deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled existing workflow confirmation", deps), "info");
634
+ return;
635
+ }
636
+ if (decision.action === "continue") {
637
+ await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps);
638
+ return;
639
+ }
640
+ const selectedProposal = decision.proposal;
641
+ const proposalDecision = await deps.confirmContextProposal(ctx, selectedProposal, {
642
+ title:
643
+ assessment.action === "refocus"
644
+ ? "Start the replacement workflow from recent discussion?"
645
+ : "Start the latest inferred workflow from recent discussion?",
646
+ });
647
+ if (!proposalDecision) {
648
+ deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled replacement workflow proposal", deps), "info");
649
+ return;
648
650
  }
649
- kickoffMissionAnchor = kickoffMissionAnchor ?? currentMissionAnchor(snapshot);
650
- const kickoffGoal = goal ?? kickoffMissionAnchor;
651
- pi.setSessionName(`completion: ${kickoffMissionAnchor.slice(0, 60)}`);
652
- const kickoffPrompt = deps.completionKickoff(
653
- kickoffGoal,
654
- currentTaskType(snapshot) ?? "(missing)",
655
- currentEvaluationProfile(snapshot) ?? "(missing)",
656
- kickoffIntent,
657
- kickoffMissionAnchor,
658
- );
659
- const rootKey = deps.completionRootKey(snapshot, deps.getCtxCwd(ctx));
660
- const fingerprint = completionContinuationFingerprint(snapshot) ?? JSON.stringify({
661
- kind: "kickoff",
662
- mission_anchor: kickoffMissionAnchor,
663
- goal: kickoffGoal,
664
- intent: kickoffIntent,
665
- task_type: currentTaskType(snapshot) ?? "(missing)",
666
- evaluation_profile: currentEvaluationProfile(snapshot) ?? "(missing)",
651
+ goal = proposalDecision.goalText;
652
+ kickoffIntent = "refocus";
653
+ kickoffMissionAnchor = proposalDecision.missionAnchor;
654
+ await refocusCompletionMission(snapshot, proposalDecision.missionAnchor, proposalDecision.goalText, proposalDecision.analysis, deps);
655
+ snapshot = (await loadCompletionSnapshot(snapshot.files.root)) ?? snapshot;
656
+ deps.emitCommandText(ctx, `Refocused completion mission from recent discussion to: ${proposalDecision.missionAnchor}`, "info");
657
+ }
658
+ }
659
+ kickoffMissionAnchor = kickoffMissionAnchor ?? currentMissionAnchor(snapshot);
660
+ const kickoffGoal = goal ?? kickoffMissionAnchor;
661
+ pi.setSessionName(`completion: ${kickoffMissionAnchor.slice(0, 60)}`);
662
+ const kickoffPrompt = deps.completionKickoff(
663
+ kickoffGoal,
664
+ currentTaskType(snapshot) ?? "(missing)",
665
+ currentEvaluationProfile(snapshot) ?? "(missing)",
666
+ kickoffIntent,
667
+ kickoffMissionAnchor,
668
+ );
669
+ const rootKey = deps.completionRootKey(snapshot, deps.getCtxCwd(ctx));
670
+ const fingerprint = completionContinuationFingerprint(snapshot) ?? JSON.stringify({
671
+ kind: "kickoff",
672
+ mission_anchor: kickoffMissionAnchor,
673
+ goal: kickoffGoal,
674
+ intent: kickoffIntent,
675
+ task_type: currentTaskType(snapshot) ?? "(missing)",
676
+ evaluation_profile: currentEvaluationProfile(snapshot) ?? "(missing)",
677
+ });
678
+ await queueCompletionDriverPrompt(pi, ctx, rootKey, fingerprint, kickoffPrompt, "kickoff", deps);
679
+ }
680
+
681
+ export function registerCookCommand(pi: ExtensionAPI, deps: CompletionDriverDeps): void {
682
+ pi.registerCommand("cook", {
683
+ description: deps.cookCommandSpec.description,
684
+ handler: async (args, ctx) => {
685
+ await runCookEntry(pi, ctx, deps, {
686
+ origin: "command",
687
+ hintText: args,
667
688
  });
668
- await queueCompletionDriverPrompt(pi, ctx, rootKey, fingerprint, kickoffPrompt, "kickoff", deps);
669
689
  },
670
690
  });
671
691
  }
@@ -14,6 +14,7 @@ import {
14
14
  markQueuedDriverPromptInFlight,
15
15
  registerCookCommand,
16
16
  } from "./driver";
17
+ import { handleCookNaturalLanguageTrigger } from "./input-routing";
17
18
  import {
18
19
  assessMissionAnchor,
19
20
  collectRecentDiscussionEntries,
@@ -207,9 +208,9 @@ function maybeWriteTestSnapshot(targetPath: string | undefined, content: string)
207
208
 
208
209
  const COOK_MAIN_CHAT_RERUN_GUIDANCE = "Discuss changes in the main chat and rerun /cook.";
209
210
  const COOK_BARE_ONLY_GUIDANCE =
210
- "/cook supports optional inline hints as high-priority intent cues, but mission selection still comes from recent discussion, repo truth, and the approval-only confirmation flow.";
211
+ "/cook remains the canonical workflow boundary. Assist-mode natural-language handoff can offer to enter the same /cook flow before implementation starts, while mission selection still comes from recent discussion, repo truth, and the approval-only confirmation flow.";
211
212
  const COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL =
212
- "/cook failed closed because recent discussion did not produce a clear execution-ready Mission/Scope/Constraints/Acceptance proposal for concrete repo changes. Clarify the concrete repo changes in the main chat and rerun /cook.";
213
+ "/cook failed closed because recent discussion did not produce a clear execution-ready Mission/Scope/Constraints/Acceptance proposal for concrete repo changes. Natural-language handoff only offers to enter the same /cook flow, so clarify the concrete repo changes in the main chat and rerun /cook.";
213
214
 
214
215
  function buildCookCancellationMessage(prefix: string): string {
215
216
  return `${prefix}. ${COOK_MAIN_CHAT_RERUN_GUIDANCE}`;
@@ -922,7 +923,7 @@ export default function completionExtension(pi: ExtensionAPI) {
922
923
  structuredDiscussionFailureDetail: COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL,
923
924
  mainChatRerunGuidance: COOK_MAIN_CHAT_RERUN_GUIDANCE,
924
925
  cookCommandSpec: {
925
- description: "/cook workflow: start, continue, refocus, or start the next round (optional hint supported)",
926
+ description: "/cook workflow: start, continue, refocus, or start the next round; assist-mode natural-language handoff can offer the same /cook boundary",
926
927
  },
927
928
  buildContextProposalContinuationReason,
928
929
  completionKickoff,
@@ -952,6 +953,10 @@ export default function completionExtension(pi: ExtensionAPI) {
952
953
  shouldTreatBareActiveWorkflowProposalAsClearRefocus,
953
954
  };
954
955
 
956
+ pi.on("input", async (event, ctx) => {
957
+ return await handleCookNaturalLanguageTrigger(pi, event, ctx, driverDeps);
958
+ });
959
+
955
960
  pi.on("session_start", async (_event, ctx) => {
956
961
  await refreshCompletionStatus({ ctx, ...statusSurfaceArgs });
957
962
  if (shouldTestAutoContinueOnSessionStart()) {