@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 +19 -0
- package/README.md +87 -52
- package/extensions/completion/driver.ts +199 -134
- package/extensions/completion/index.ts +20 -7
- package/extensions/completion/input-routing.ts +818 -0
- package/extensions/completion/prompt-surfaces.ts +375 -1
- package/extensions/completion/proposal.ts +1 -1
- package/extensions/completion/role-runner.ts +310 -3
- package/extensions/completion/types.ts +114 -0
- package/package.json +1 -1
- package/scripts/cook-trigger-routing-test.sh +1122 -0
- package/scripts/release-check.sh +29 -21
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
|
-
|
|
3
|
+
`/cook` turns main-chat discussion about concrete repo changes into a resumable repo workflow stored in repo-local `.agent/**` state.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
##
|
|
7
|
+
## Use it when
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
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
|
-
|
|
16
|
-
- one-off chat tasks
|
|
17
|
-
- brainstorming
|
|
18
|
-
- planning docs without immediate implementation
|
|
14
|
+
## Skip it when
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
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
|
-
##
|
|
28
|
+
## 30-second quick start
|
|
38
29
|
|
|
39
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
##
|
|
64
|
+
## Natural-language routing modes
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
Set `PI_COMPLETION_TRIGGER_MODE` before starting Pi if you want to change how natural-language routing behaves:
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
+
## Typical examples
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
101
|
+
## What happens when you run `/cook`
|
|
82
102
|
|
|
83
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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
|
-
|
|
581
|
-
|
|
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
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
}
|