@linimin/pi-letscook 0.1.50 → 0.1.52

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,25 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.1.52
6
+
7
+ ### Changed
8
+
9
+ - updated README/help/release parity copy to describe the shipped `off` / `assist` / `router` natural-language routing behavior truthfully while keeping `/cook` as the canonical confirm-first workflow boundary and manual fallback
10
+ - documented the explicit router-mode **Send as normal chat** recovery path as a user choice, not as a silent downgrade, and kept public copy scoped to currently shipped router behavior rather than future auto-mode plans
11
+ - made `npm run release-check` fail closed on the shipped workflow-aware router docs/help contract while continuing to rerun `bash ./scripts/cook-trigger-routing-test.sh` alongside the existing `/cook` smoke/refocus/context regressions
12
+
13
+ ## 0.1.51
14
+
15
+ ### Added
16
+
17
+ - 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
18
+ - 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
19
+
20
+ ### Changed
21
+
22
+ - 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
23
+
5
24
  ## 0.1.50
6
25
 
7
26
  ### 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
+ Natural-language routing is optional and shipped in three modes: `off` disables it, `assist` offers short confirm-first handoffs after clear workflow discussion, and `router` reviews each non-bypass user turn before implementation starts while leaving ordinary questions in the main chat. In every mode, `/cook` remains the canonical workflow boundary and manual fallback.
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,101 @@ 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
+ | Change natural-language routing behavior | Set `PI_COMPLETION_TRIGGER_MODE=off`, `assist`, or `router` before starting Pi |
50
+ | Hand off from discussion into the same `/cook` flow | In `assist`, say `開始做`, `開始實作`, or `go ahead`, then accept the confirmation |
51
+ | Continue the current workflow | Run `/cook` |
52
+ | Use the canonical fallback when natural-language routing does not fire or you want to bypass it | Run `/cook` explicitly |
47
53
 
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`
54
+ ## What `/cook` expects
53
55
 
54
- What it expects:
55
56
  - recent main-chat discussion about concrete repo changes
56
57
  - README/CHANGELOG updates still count as concrete repo changes
57
58
  - assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts do not
58
59
 
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.
60
+ `/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
61
 
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`.
62
+ 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
63
 
63
- ## How `/cook` works
64
+ ## Natural-language routing modes
64
65
 
65
- `/cook` supports both bare discussion-driven startup and optional inline intent hints.
66
+ Set `PI_COMPLETION_TRIGGER_MODE` before starting Pi if you want to change how natural-language routing behaves:
66
67
 
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. |
68
+ - `off` natural-language routing is disabled. Only explicit `/cook` or `/cook <hint>` can enter the workflow.
69
+ - `assist` *(default)* — after clear discussion of a concrete repo change, short execution handoff phrases such as `開始做`, `開始實作`, or `go ahead` can offer to enter the same `/cook` flow before the primary agent starts implementation work.
70
+ - `router` the workflow-aware router reviews each non-bypass normal user turn before implementation starts. Ordinary questions stay in the main chat, while direct start/resume/refocus/next-round prompts can offer the shared `/cook` flow with confirmation or clarification.
72
71
 
73
- ## Approval-only confirmation and fail-closed behavior
72
+ Important behavior:
73
+ - natural-language routing is only a shortcut into `/cook`; `/cook` is still the canonical workflow boundary and manual fallback
74
+ - startup, refocus, and next-round routing stay confirm-first; nothing silently starts a workflow
75
+ - unclear router offers and classifier recovery stay fail-closed
76
+ - in router mode, the original message only reaches the normal chat path if you explicitly choose **Send as normal chat**
77
+ - explicit slash commands and ordinary main-chat questions continue normally unless you choose the workflow boundary
74
78
 
75
- All startup, next-round, and replacement proposals are **approval-only**:
79
+ ## Typical examples
76
80
 
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`
81
+ Start a new workflow from recent discussion:
82
+
83
+ ```text
84
+ I want to add login redirect handling and tests.
85
+ /cook
86
+ ```
87
+
88
+ Bias proposal derivation toward a specific intent:
89
+
90
+ ```text
91
+ /cook login redirect
92
+ ```
93
+
94
+ Hand off from discussion into the same `/cook` flow:
95
+
96
+ ```text
97
+ We should implement the natural-language routing path next.
98
+ 開始做
99
+ ```
80
100
 
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.
101
+ ## What happens when you run `/cook`
82
102
 
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:
103
+ `/cook` supports both bare discussion-driven startup and optional inline intent hints. Explicit `/cook` is always the canonical fallback, even when natural-language routing is enabled in `assist` or `router` mode.
84
104
 
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**
105
+ | Repo state | What you'll see |
106
+ |---|---|
107
+ | No workflow yet | A startup proposal built from recent main-chat discussion. You choose **Start** or **Cancel**. Weak or planning-only discussion fails closed. |
108
+ | 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. |
109
+ | 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. |
110
+
111
+ ## Confirmation and fail-closed behavior
112
+
113
+ `/cook` never silently starts or rewrites canonical `.agent/**` state on unclear input.
89
114
 
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.
115
+ - startup, next-round, and refocus proposals are approval-only
116
+ - actions are **Start** and **Cancel**
117
+ - **Cancel** is side-effect free: discuss changes in the main chat and rerun `/cook`
118
+ - weak, ambiguous, assistant-produced, or planning-only discussion does not start a workflow
119
+ - router-mode false positives and classifier failures stay fail-closed unless you explicitly choose **Send as normal chat**
120
+ - when recent discussion suggests a different workflow, `/cook` shows a chooser before any canonical state rewrite
91
121
 
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.
122
+ When you accept startup or refocus, `/cook` persists the chosen workflow state in canonical `.agent/**` files before the re-ground round begins.
93
123
 
94
124
  ## Observability
95
125
 
@@ -109,6 +139,10 @@ While a `completion_role` subprocess is running:
109
139
  - running-role output distinguishes tool work from `PROGRESS`, `RATIONALE`, `NEXT`, `VERIFYING`, and `STATE-DELTA`
110
140
  - waiting and stalled states are surfaced deterministically from timestamps
111
141
 
142
+ ## Maintainer and protocol details
143
+
144
+ The sections below are mainly useful if you maintain the extension, inspect canonical `.agent/**` state, or work on the packaged completion protocol itself.
145
+
112
146
  ## Structured evaluation rubrics
113
147
 
114
148
  The packaged completion workflow now defines a shared structured evaluation-rubric contract for the read-only evaluation roles:
@@ -225,6 +259,7 @@ Run validation from the package root:
225
259
  npm run smoke-test
226
260
  npm run refocus-test
227
261
  npm run context-proposal-test
262
+ bash ./scripts/cook-trigger-routing-test.sh
228
263
  bash scripts/canonical-evidence-artifact-test.sh
229
264
  npm run observability-status-test
230
265
  npm run evaluator-calibration-test
@@ -232,7 +267,7 @@ npm run rubric-contract-test
232
267
  npm run release-check
233
268
  ```
234
269
 
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`.
270
+ `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 `bash ./scripts/cook-trigger-routing-test.sh` for workflow-aware router coverage including explicit **Send as normal chat** recovery, 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
271
 
237
272
  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
273
 
@@ -12,7 +12,7 @@ import {
12
12
  loadCompletionSnapshot,
13
13
  writeJsonFile,
14
14
  } from "./state-store";
15
- import type { CompletionStateSnapshot } from "./types";
15
+ import type { CompletionStateSnapshot, CookNaturalLanguageHandoff, CookTriggerWorkflowBias } from "./types";
16
16
 
17
17
  type ContextProposalAnalysis = {
18
18
  taskType?: string;
@@ -104,8 +104,13 @@ export type CompletionDriverDeps = {
104
104
  evaluationProfile: string,
105
105
  intent?: "auto" | "continue" | "refocus",
106
106
  missionAnchor?: string,
107
+ naturalLanguageHandoff?: CookNaturalLanguageHandoff,
108
+ ) => string;
109
+ completionResumePrompt: (
110
+ taskType: string,
111
+ evaluationProfile: string,
112
+ naturalLanguageHandoff?: CookNaturalLanguageHandoff,
107
113
  ) => string;
108
- completionResumePrompt: (taskType: string, evaluationProfile: string) => string;
109
114
  deriveCookContextProposal: (ctx: DriverContext, projectName: string, hintText?: string) => Promise<ContextProposal | undefined>;
110
115
  confirmContextProposal: (
111
116
  ctx: { hasUI: boolean; ui: any },
@@ -374,10 +379,15 @@ async function resumeActiveWorkflowFromCanonicalState(
374
379
  ctx: { cwd: string; hasUI: boolean; ui: any },
375
380
  snapshot: CompletionStateSnapshot,
376
381
  deps: CompletionDriverDeps,
382
+ naturalLanguageHandoff?: CookNaturalLanguageHandoff,
377
383
  ): Promise<void> {
378
384
  const mission = currentMissionAnchor(snapshot);
379
385
  pi.setSessionName(`completion: ${mission.slice(0, 60)}`);
380
- const resumePrompt = deps.completionResumePrompt(currentTaskType(snapshot) ?? "(missing)", currentEvaluationProfile(snapshot) ?? "(missing)");
386
+ const resumePrompt = deps.completionResumePrompt(
387
+ currentTaskType(snapshot) ?? "(missing)",
388
+ currentEvaluationProfile(snapshot) ?? "(missing)",
389
+ naturalLanguageHandoff,
390
+ );
381
391
  const rootKey = deps.completionRootKey(snapshot, deps.getCtxCwd(ctx));
382
392
  const fingerprint = completionContinuationFingerprint(snapshot) ?? JSON.stringify({
383
393
  kind: "resume",
@@ -530,142 +540,197 @@ function buildMission(projectName: string, missionAnchor: string): string {
530
540
  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
541
  }
532
542
 
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);
543
+ export type CookInvocationOrigin = "command" | "natural-language-trigger";
544
+
545
+ export type RunCookEntryOptions = {
546
+ origin: CookInvocationOrigin;
547
+ hintText?: string;
548
+ originalInput?: string;
549
+ triggerText?: string;
550
+ preferredRoutingBias?: CookTriggerWorkflowBias;
551
+ clarificationCapsule?: CookNaturalLanguageHandoff["clarificationCapsule"];
552
+ adoptedArtifact?: CookNaturalLanguageHandoff["adoptedArtifact"];
553
+ };
554
+
555
+ function buildNaturalLanguageDerivationHint(handoff: CookNaturalLanguageHandoff | undefined): string | undefined {
556
+ if (!handoff) return undefined;
557
+ const lines: string[] = [];
558
+ if (handoff.hintText) lines.push(`Focus hint: ${handoff.hintText}`);
559
+ if (handoff.clarificationCapsule?.goal) lines.push(`Clarified goal: ${handoff.clarificationCapsule.goal}`);
560
+ if (handoff.clarificationCapsule?.scope?.length) lines.push(`Clarified scope: ${handoff.clarificationCapsule.scope.join(" | ")}`);
561
+ if (handoff.clarificationCapsule?.nonGoal?.length) lines.push(`Clarified non-goal: ${handoff.clarificationCapsule.nonGoal.join(" | ")}`);
562
+ if (handoff.clarificationCapsule?.doneWhen?.length) lines.push(`Clarified done-when: ${handoff.clarificationCapsule.doneWhen.join(" | ")}`);
563
+ if (handoff.clarificationCapsule?.selectedWorkflowBias) {
564
+ lines.push(`Clarified routing bias: ${handoff.clarificationCapsule.selectedWorkflowBias}`);
565
+ }
566
+ if (handoff.adoptedArtifact) {
567
+ lines.push(`User explicitly adopted artifact: ${handoff.adoptedArtifact.title}`);
568
+ if (handoff.adoptedArtifact.path) lines.push(`Adopted artifact path: ${handoff.adoptedArtifact.path}`);
569
+ if (handoff.adoptedArtifact.preview) lines.push(`Adopted artifact preview: ${handoff.adoptedArtifact.preview}`);
570
+ }
571
+ return lines.length > 0 ? lines.join("\n") : undefined;
572
+ }
573
+
574
+ export async function runCookEntry(
575
+ pi: ExtensionAPI,
576
+ ctx: DriverContext,
577
+ deps: CompletionDriverDeps,
578
+ options: RunCookEntryOptions,
579
+ ): Promise<void> {
580
+ const naturalLanguageHandoff =
581
+ options.origin === "natural-language-trigger"
582
+ ? {
583
+ preferredRoutingBias: options.preferredRoutingBias,
584
+ triggerText: options.triggerText?.trim() ? options.triggerText.trim() : options.originalInput?.trim() ? options.originalInput.trim() : undefined,
585
+ hintText: options.hintText?.trim() ? options.hintText.trim() : undefined,
586
+ clarificationCapsule: options.clarificationCapsule,
587
+ adoptedArtifact: options.adoptedArtifact,
588
+ }
589
+ : undefined;
590
+ const derivationHint = buildNaturalLanguageDerivationHint(naturalLanguageHandoff);
591
+ const explicitHint = [options.hintText?.trim(), derivationHint].filter((value): value is string => Boolean(value)).join("\n\n") || undefined;
592
+ let goal: string | undefined;
593
+ const cwd = deps.getCtxCwd(ctx);
594
+ let snapshot = await loadCompletionSnapshot(cwd);
595
+ const workflowDone = isWorkflowDone(snapshot);
596
+ let kickoffIntent: "auto" | "continue" | "refocus" = "auto";
597
+ let kickoffMissionAnchor = snapshot ? currentMissionAnchor(snapshot) : undefined;
598
+ let kickoffAnalysis: ContextProposalAnalysis | undefined;
599
+
600
+ if (!snapshot) {
601
+ const root = findRepoRoot(cwd) ?? cwd;
602
+ const projectName = path.basename(root);
603
+ const proposal = await deps.deriveCookContextProposal(ctx, projectName, explicitHint);
604
+ if (!proposal) {
605
+ deps.emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(deps), "info");
606
+ return;
607
+ }
608
+ const decision = await deps.confirmContextProposal(ctx, proposal, {
609
+ title: "Start a completion workflow from the recent discussion?",
610
+ });
611
+ if (!decision) {
612
+ deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled recent-discussion workflow proposal", deps), "info");
613
+ return;
614
+ }
615
+ goal = decision.goalText;
616
+ kickoffMissionAnchor = decision.missionAnchor;
617
+ kickoffAnalysis = decision.analysis;
618
+ const startupRouting = deps.finalizeContextProposalAnalysis(kickoffAnalysis, [goal ?? kickoffMissionAnchor ?? projectName]);
619
+ const created = await deps.scaffoldCompletionFiles(root, kickoffMissionAnchor ?? projectName, {
620
+ analysis: startupRouting,
621
+ continuationReason: deps.buildContextProposalContinuationReason(
622
+ "User started workflow via /cook:",
623
+ goal ?? kickoffMissionAnchor ?? projectName,
624
+ startupRouting,
625
+ ),
626
+ });
627
+ deps.emitCommandText(
628
+ ctx,
629
+ `Initialized completion control plane in ${created.root}${created.created.length > 0 ? ` (${created.created.length} files created)` : ""}`,
630
+ "info",
631
+ );
632
+ snapshot = await loadCompletionSnapshot(root);
633
+ }
634
+ if (!snapshot) {
635
+ deps.emitCommandText(ctx, "Failed to load completion workflow state", "error");
636
+ return;
637
+ }
638
+ deps.activateCompletionRoutingForRoot(snapshot.files.root);
639
+ if (!goal) {
640
+ if (workflowDone) {
641
+ const projectName = path.basename(snapshot.files.root);
642
+ const proposal = await deps.deriveCookContextProposal(ctx, projectName, explicitHint);
643
+ if (!proposal) {
644
+ deps.emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(deps, "The previous completion workflow is already done."), "info");
645
+ return;
579
646
  }
580
- if (!snapshot) {
581
- deps.emitCommandText(ctx, "Failed to load completion workflow state", "error");
647
+ const decision = await deps.confirmContextProposal(ctx, proposal, {
648
+ title: "The previous completion workflow is done. Start the next workflow round from the recent discussion?",
649
+ });
650
+ if (!decision) {
651
+ deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled next workflow round proposal", deps), "info");
582
652
  return;
583
653
  }
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
- }
654
+ goal = decision.goalText;
655
+ kickoffIntent = "refocus";
656
+ kickoffMissionAnchor = decision.missionAnchor;
657
+ await refocusCompletionMission(snapshot, decision.missionAnchor, decision.goalText, decision.analysis, deps);
658
+ snapshot = (await loadCompletionSnapshot(snapshot.files.root)) ?? snapshot;
659
+ deps.emitCommandText(ctx, `Started a new completion workflow round from recent discussion: ${decision.missionAnchor}`, "info");
660
+ } else {
661
+ const assessment = await assessActiveWorkflowProposalRouting(ctx, snapshot, deps, explicitHint);
662
+ if (!assessment.proposal || assessment.action === "continue") {
663
+ await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps, naturalLanguageHandoff);
664
+ return;
665
+ }
666
+ const decision = await confirmExistingWorkflowProposal(ctx, snapshot, assessment.proposal, deps, {
667
+ intro:
668
+ assessment.action === "refocus"
669
+ ? "Recent non-command discussion suggests a different workflow. Choose how /cook should proceed:"
670
+ : "Recent discussion may point to a different implementation goal. Review the current mission and the latest inferred mission before deciding how /cook should proceed:",
671
+ proposedMissionLabel: "Proposed mission from recent discussion",
672
+ refocusChoiceLabel:
673
+ "Start new workflow from recent discussion\n\nReview the proposed replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state.",
674
+ comparison: assessment.action === "refocus" ? "semantic" : "strict",
675
+ });
676
+ if (!decision) {
677
+ deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled existing workflow confirmation", deps), "info");
678
+ return;
648
679
  }
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)",
680
+ if (decision.action === "continue") {
681
+ await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps, naturalLanguageHandoff);
682
+ return;
683
+ }
684
+ const selectedProposal = decision.proposal;
685
+ const proposalDecision = await deps.confirmContextProposal(ctx, selectedProposal, {
686
+ title:
687
+ assessment.action === "refocus"
688
+ ? "Start the replacement workflow from recent discussion?"
689
+ : "Start the latest inferred workflow from recent discussion?",
690
+ });
691
+ if (!proposalDecision) {
692
+ deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled replacement workflow proposal", deps), "info");
693
+ return;
694
+ }
695
+ goal = proposalDecision.goalText;
696
+ kickoffIntent = "refocus";
697
+ kickoffMissionAnchor = proposalDecision.missionAnchor;
698
+ await refocusCompletionMission(snapshot, proposalDecision.missionAnchor, proposalDecision.goalText, proposalDecision.analysis, deps);
699
+ snapshot = (await loadCompletionSnapshot(snapshot.files.root)) ?? snapshot;
700
+ deps.emitCommandText(ctx, `Refocused completion mission from recent discussion to: ${proposalDecision.missionAnchor}`, "info");
701
+ }
702
+ }
703
+ kickoffMissionAnchor = kickoffMissionAnchor ?? currentMissionAnchor(snapshot);
704
+ const kickoffGoal = goal ?? kickoffMissionAnchor;
705
+ pi.setSessionName(`completion: ${kickoffMissionAnchor.slice(0, 60)}`);
706
+ const kickoffPrompt = deps.completionKickoff(
707
+ kickoffGoal,
708
+ currentTaskType(snapshot) ?? "(missing)",
709
+ currentEvaluationProfile(snapshot) ?? "(missing)",
710
+ kickoffIntent,
711
+ kickoffMissionAnchor,
712
+ naturalLanguageHandoff,
713
+ );
714
+ const rootKey = deps.completionRootKey(snapshot, deps.getCtxCwd(ctx));
715
+ const fingerprint = completionContinuationFingerprint(snapshot) ?? JSON.stringify({
716
+ kind: "kickoff",
717
+ mission_anchor: kickoffMissionAnchor,
718
+ goal: kickoffGoal,
719
+ intent: kickoffIntent,
720
+ task_type: currentTaskType(snapshot) ?? "(missing)",
721
+ evaluation_profile: currentEvaluationProfile(snapshot) ?? "(missing)",
722
+ });
723
+ await queueCompletionDriverPrompt(pi, ctx, rootKey, fingerprint, kickoffPrompt, "kickoff", deps);
724
+ }
725
+
726
+ export function registerCookCommand(pi: ExtensionAPI, deps: CompletionDriverDeps): void {
727
+ pi.registerCommand("cook", {
728
+ description: deps.cookCommandSpec.description,
729
+ handler: async (args, ctx) => {
730
+ await runCookEntry(pi, ctx, deps, {
731
+ origin: "command",
732
+ hintText: args,
667
733
  });
668
- await queueCompletionDriverPrompt(pi, ctx, rootKey, fingerprint, kickoffPrompt, "kickoff", deps);
669
734
  },
670
735
  });
671
736
  }