@linimin/pi-letscook 0.1.50 → 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,17 @@
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
+
5
16
  ## 0.1.50
6
17
 
7
18
  ### Changed
package/README.md CHANGED
@@ -1,30 +1,21 @@
1
1
  # @linimin/pi-letscook
2
2
 
3
- A Pi extension for long-running repo 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
- It gives you `/cook`: a discussion-driven workflow command that keeps mission, progress, and verification in repo-local `.agent/**` state instead of chat memory.
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
- ## Is this for you?
7
+ ## Use it when
8
8
 
9
- **Useful if your work needs to:**
10
- - continue across sessions
11
- - stay anchored to one mission
12
- - resume from repo state instead of chat memory
13
- - 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
14
13
 
15
- **Probably overkill if you mostly do:**
16
- - one-off chat tasks
17
- - brainstorming
18
- - planning docs without immediate implementation
14
+ ## Skip it when
19
15
 
20
- ## What you get
21
-
22
- - one command: `/cook`
23
- - repo-local canonical state in `.agent/**`
24
- - resumable long-running workflows
25
- - discussion-first startup, continue, refocus, and next-round routing
26
- - fail-closed guidance that sends you back to the main chat when the mission still needs clarification
27
- - 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
28
19
 
29
20
  ## Install
30
21
 
@@ -34,62 +25,94 @@ pi install npm:@linimin/pi-letscook
34
25
 
35
26
  Then run `/reload` in Pi.
36
27
 
37
- ## Quick start
28
+ ## 30-second quick start
38
29
 
39
- `/cook` supports both bare discussion-driven startup and optional inline intent hints.
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.
40
37
 
41
38
  ```text
42
39
  /cook
43
40
  /cook login redirect
44
41
  ```
45
42
 
46
- Use `/cook` after you discuss the mission in the main chat.
43
+ ## Common actions
44
+
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
- What it can do:
49
- - start a brand-new workflow from recent discussion
50
- - continue the current workflow when recent discussion still matches it, or when discussion is too weak or ambiguous to justify a refocus
51
- - surface a conservative refocus chooser when recent discussion clearly points to a different workflow
52
- - start the next workflow round after the previous one is `done`
53
+ ## What `/cook` expects
53
54
 
54
- What it expects:
55
55
  - recent main-chat discussion about concrete repo changes
56
56
  - README/CHANGELOG updates still count as concrete repo changes
57
57
  - assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts do not
58
58
 
59
- `/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.
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.
60
60
 
61
- 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`.
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`.
62
62
 
63
- ## How `/cook` works
63
+ ## Natural-language handoff (assist mode)
64
64
 
65
- `/cook` supports both bare discussion-driven startup and optional inline intent hints.
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.
66
66
 
67
- | Repo state | `/cook` behavior |
68
- |---|---|
69
- | 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`. |
70
- | 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. |
71
- | 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. |
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
72
72
 
73
- ## Approval-only confirmation and fail-closed behavior
73
+ ## Typical examples
74
74
 
75
- All startup, next-round, and replacement proposals are **approval-only**:
75
+ Start a new workflow from recent discussion:
76
76
 
77
- - the proposal body is shown separately from actions
78
- - actions are only **Start** and **Cancel**
79
- - **Cancel** is side-effect free: discuss changes in the main chat and rerun `/cook`
77
+ ```text
78
+ I want to add login redirect handling and tests.
79
+ /cook
80
+ ```
80
81
 
81
- 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.
82
+ Bias proposal derivation toward a specific intent:
82
83
 
83
- 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:
84
+ ```text
85
+ /cook login redirect
86
+ ```
84
87
 
85
- - **Continue current workflow**
86
- - **Start new workflow from recent discussion**
87
- - **Start alternate workflow from recent discussion** (when a second plausible mission exists)
88
- - **Cancel**
88
+ Hand off from discussion into the same `/cook` flow:
89
89
 
90
- 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.
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
91
106
 
92
- 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.
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.
93
116
 
94
117
  ## Observability
95
118
 
@@ -109,6 +132,10 @@ While a `completion_role` subprocess is running:
109
132
  - running-role output distinguishes tool work from `PROGRESS`, `RATIONALE`, `NEXT`, `VERIFYING`, and `STATE-DELTA`
110
133
  - waiting and stalled states are surfaced deterministically from timestamps
111
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
+
112
139
  ## Structured evaluation rubrics
113
140
 
114
141
  The packaged completion workflow now defines a shared structured evaluation-rubric contract for the read-only evaluation roles:
@@ -225,6 +252,7 @@ Run validation from the package root:
225
252
  npm run smoke-test
226
253
  npm run refocus-test
227
254
  npm run context-proposal-test
255
+ bash ./scripts/cook-trigger-routing-test.sh
228
256
  bash scripts/canonical-evidence-artifact-test.sh
229
257
  npm run observability-status-test
230
258
  npm run evaluator-calibration-test
@@ -232,7 +260,7 @@ npm run rubric-contract-test
232
260
  npm run release-check
233
261
  ```
234
262
 
235
- `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`.
236
264
 
237
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.
238
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()) {