@linimin/pi-letscook 0.1.54 → 0.1.55
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 +2 -2
- package/README.md +12 -17
- package/extensions/completion/driver.ts +44 -110
- package/extensions/completion/index.ts +10 -70
- package/extensions/completion/prompt-surfaces.ts +53 -380
- package/extensions/completion/proposal.ts +5 -65
- package/extensions/completion/role-runner.ts +4 -311
- package/extensions/completion/state-store.ts +212 -5
- package/extensions/completion/transcription.ts +0 -8
- package/extensions/completion/types.ts +0 -114
- package/package.json +15 -4
- package/scripts/active-slice-contract-test.sh +61 -6
- package/scripts/context-proposal-test.sh +33 -29
- package/scripts/legacy-cleanup-test.sh +11 -0
- package/scripts/refocus-test.sh +10 -11
- package/scripts/release-check.sh +11 -11
- package/scripts/role-runner-contract-test.sh +1 -2
- package/scripts/rubric-contract-test.sh +0 -1
- package/scripts/smoke-test.sh +14 -10
- package/extensions/completion/input-routing.ts +0 -819
- package/scripts/cook-trigger-routing-test.sh +0 -1122
package/CHANGELOG.md
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
### Changed
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
7
|
+
- made `/cook` derive a confirmable startup brief from recent discussion before any canonical workflow rewrite, then preserve the confirmed brief in canonical state as advisory intake for later re-grounding
|
|
8
|
+
- removed inline `/cook` arguments from the shipped entry path again so explicit bare `/cook` is the only public command, and fail closed when recent discussion is insufficient or unreliable
|
|
9
9
|
|
|
10
10
|
## 0.1.54
|
|
11
11
|
|
package/README.md
CHANGED
|
@@ -31,13 +31,12 @@ Then run `/reload` in Pi.
|
|
|
31
31
|
`pi install npm:@linimin/pi-letscook`
|
|
32
32
|
2. Run `/reload` in Pi.
|
|
33
33
|
3. In the main chat, describe the concrete repo change you want.
|
|
34
|
-
4. Run `/cook
|
|
35
|
-
5. Review the
|
|
34
|
+
4. Run `/cook`.
|
|
35
|
+
5. Review the startup brief and choose **Start** or **Cancel**.
|
|
36
36
|
6. Later, run `/cook` again to continue, refocus, or start the next round.
|
|
37
37
|
|
|
38
38
|
```text
|
|
39
39
|
/cook
|
|
40
|
-
/cook login redirect
|
|
41
40
|
```
|
|
42
41
|
|
|
43
42
|
## Common actions
|
|
@@ -45,23 +44,23 @@ Then run `/reload` in Pi.
|
|
|
45
44
|
| If you want to... | Do this |
|
|
46
45
|
|---|---|
|
|
47
46
|
| 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
47
|
| Continue the current workflow | Run `/cook` |
|
|
50
48
|
| Refocus or start the next round | Discuss the new concrete repo change in the main chat, then run `/cook` |
|
|
51
49
|
|
|
52
50
|
## What `/cook` expects
|
|
53
51
|
|
|
54
52
|
- recent main-chat discussion about concrete repo changes
|
|
53
|
+
- enough detail to derive a startup brief with mission, scope, constraints or non-goals, acceptance, and notes or risks
|
|
55
54
|
- README/CHANGELOG updates still count as concrete repo changes
|
|
56
55
|
- assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts do not
|
|
57
56
|
|
|
58
|
-
`/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.
|
|
59
|
-
|
|
60
57
|
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
58
|
|
|
59
|
+
If you pass inline arguments to `/cook`, it also fails closed and tells you to move that intent into the main chat before rerunning bare `/cook`.
|
|
60
|
+
|
|
62
61
|
## Workflow entry
|
|
63
62
|
|
|
64
|
-
Only explicit `/cook`
|
|
63
|
+
Only explicit `/cook` enters the workflow. Ordinary prompts stay in the main chat and go straight to the primary agent.
|
|
65
64
|
|
|
66
65
|
Important behavior:
|
|
67
66
|
- `/cook` is the canonical workflow boundary and manual entry point
|
|
@@ -77,21 +76,15 @@ I want to add login redirect handling and tests.
|
|
|
77
76
|
/cook
|
|
78
77
|
```
|
|
79
78
|
|
|
80
|
-
Bias proposal derivation toward a specific intent:
|
|
81
|
-
|
|
82
|
-
```text
|
|
83
|
-
/cook login redirect
|
|
84
|
-
```
|
|
85
|
-
|
|
86
79
|
## What happens when you run `/cook`
|
|
87
80
|
|
|
88
|
-
`/cook`
|
|
81
|
+
`/cook` first derives a startup brief from recent discussion, then shows the existing approval-only Start/Cancel gate.
|
|
89
82
|
|
|
90
83
|
| Repo state | What you'll see |
|
|
91
84
|
|---|---|
|
|
92
|
-
| No workflow yet | A startup
|
|
93
|
-
| 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
|
|
94
|
-
| Previous workflow is `done` | A next-round
|
|
85
|
+
| No workflow yet | A startup brief built from recent main-chat discussion. You choose **Start** or **Cancel**. Weak, unreliable, or planning-only discussion fails closed. |
|
|
86
|
+
| 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 you confirm the new startup brief. Ambiguous discussion stays conservative. |
|
|
87
|
+
| Previous workflow is `done` | A next-round startup brief from recent main-chat discussion, again behind **Start** or **Cancel**. Discussion that only restates already-finished work fails closed. |
|
|
95
88
|
|
|
96
89
|
## Confirmation and fail-closed behavior
|
|
97
90
|
|
|
@@ -105,6 +98,8 @@ Bias proposal derivation toward a specific intent:
|
|
|
105
98
|
|
|
106
99
|
When you accept startup or refocus, `/cook` persists the chosen workflow state in canonical `.agent/**` files before the re-ground round begins.
|
|
107
100
|
|
|
101
|
+
The confirmed startup brief is also preserved there as advisory intake for later re-grounding. It does not replace `.agent/plan.json` or `.agent/active-slice.json`, which remain under regrounder authority.
|
|
102
|
+
|
|
108
103
|
## Observability
|
|
109
104
|
|
|
110
105
|
When canonical `.agent/**` state exists and no role is actively running, the extension shows a completion widget sourced from that state. The widget summarizes:
|
|
@@ -2,7 +2,11 @@ import { promises as fsp } from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
4
4
|
import {
|
|
5
|
+
buildMission,
|
|
5
6
|
buildProfileRecord,
|
|
7
|
+
currentEvaluationProfile,
|
|
8
|
+
currentMissionAnchor,
|
|
9
|
+
currentTaskType,
|
|
6
10
|
defaultActiveSlice,
|
|
7
11
|
defaultPlan,
|
|
8
12
|
defaultState,
|
|
@@ -12,7 +16,8 @@ import {
|
|
|
12
16
|
loadCompletionSnapshot,
|
|
13
17
|
writeJsonFile,
|
|
14
18
|
} from "./state-store";
|
|
15
|
-
import
|
|
19
|
+
import { buildAdvisoryStartupBrief } from "./prompt-surfaces";
|
|
20
|
+
import type { CompletionStateSnapshot } from "./types";
|
|
16
21
|
|
|
17
22
|
type ContextProposalAnalysis = {
|
|
18
23
|
taskType?: string;
|
|
@@ -82,7 +87,6 @@ type DriverContinuationTracker = {
|
|
|
82
87
|
};
|
|
83
88
|
|
|
84
89
|
export type CompletionDriverDeps = {
|
|
85
|
-
bareOnlyGuidance: string;
|
|
86
90
|
structuredDiscussionFailureDetail: string;
|
|
87
91
|
mainChatRerunGuidance: string;
|
|
88
92
|
cookCommandSpec: {
|
|
@@ -104,14 +108,9 @@ export type CompletionDriverDeps = {
|
|
|
104
108
|
evaluationProfile: string,
|
|
105
109
|
intent?: "auto" | "continue" | "refocus",
|
|
106
110
|
missionAnchor?: string,
|
|
107
|
-
naturalLanguageHandoff?: CookNaturalLanguageHandoff,
|
|
108
111
|
) => string;
|
|
109
|
-
completionResumePrompt: (
|
|
110
|
-
|
|
111
|
-
evaluationProfile: string,
|
|
112
|
-
naturalLanguageHandoff?: CookNaturalLanguageHandoff,
|
|
113
|
-
) => string;
|
|
114
|
-
deriveCookContextProposal: (ctx: DriverContext, projectName: string, hintText?: string) => Promise<ContextProposal | undefined>;
|
|
112
|
+
completionResumePrompt: (taskType: string, evaluationProfile: string) => string;
|
|
113
|
+
deriveCookContextProposal: (ctx: DriverContext, projectName: string) => Promise<ContextProposal | undefined>;
|
|
115
114
|
confirmContextProposal: (
|
|
116
115
|
ctx: { hasUI: boolean; ui: any },
|
|
117
116
|
proposal: ContextProposal,
|
|
@@ -122,7 +121,7 @@ export type CompletionDriverDeps = {
|
|
|
122
121
|
scaffoldCompletionFiles: (
|
|
123
122
|
root: string,
|
|
124
123
|
missionAnchor: string,
|
|
125
|
-
options?: { analysis?: ContextProposalAnalysis; continuationReason?: string },
|
|
124
|
+
options?: { analysis?: ContextProposalAnalysis; continuationReason?: string; advisoryStartupBrief?: Record<string, unknown> },
|
|
126
125
|
) => Promise<{ root: string; created: string[] }>;
|
|
127
126
|
maybeWriteActiveWorkflowRoutingSnapshot: (assessment: ActiveWorkflowProposalAssessment) => void;
|
|
128
127
|
missionAnchorsLikelyEquivalent: (left: string, right: string) => boolean;
|
|
@@ -168,33 +167,6 @@ function buildCookStructuredDiscussionFailureMessage(deps: CompletionDriverDeps,
|
|
|
168
167
|
return prefix ? `${prefix} ${deps.structuredDiscussionFailureDetail}` : deps.structuredDiscussionFailureDetail;
|
|
169
168
|
}
|
|
170
169
|
|
|
171
|
-
function currentMissionAnchor(snapshot: CompletionStateSnapshot): string {
|
|
172
|
-
return (
|
|
173
|
-
asString(snapshot.state?.mission_anchor) ??
|
|
174
|
-
asString(snapshot.plan?.mission_anchor) ??
|
|
175
|
-
asString(snapshot.active?.mission_anchor) ??
|
|
176
|
-
path.basename(snapshot.files.root)
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function currentTaskType(snapshot: CompletionStateSnapshot): string | undefined {
|
|
181
|
-
return (
|
|
182
|
-
asString(snapshot.active?.task_type) ??
|
|
183
|
-
asString(snapshot.state?.task_type) ??
|
|
184
|
-
asString(snapshot.plan?.task_type) ??
|
|
185
|
-
asString(snapshot.profile?.task_type)
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function currentEvaluationProfile(snapshot: CompletionStateSnapshot): string | undefined {
|
|
190
|
-
return (
|
|
191
|
-
asString(snapshot.active?.evaluation_profile) ??
|
|
192
|
-
asString(snapshot.state?.evaluation_profile) ??
|
|
193
|
-
asString(snapshot.plan?.evaluation_profile) ??
|
|
194
|
-
asString(snapshot.profile?.evaluation_profile)
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
170
|
export function completionContinuationFingerprint(snapshot: CompletionStateSnapshot): string | undefined {
|
|
199
171
|
if (asString(snapshot.state?.continuation_policy) !== "continue") return undefined;
|
|
200
172
|
const nextMandatoryRole = asString(snapshot.state?.next_mandatory_role);
|
|
@@ -330,11 +302,10 @@ async function assessActiveWorkflowProposalRouting(
|
|
|
330
302
|
ctx: DriverContext,
|
|
331
303
|
snapshot: CompletionStateSnapshot,
|
|
332
304
|
deps: CompletionDriverDeps,
|
|
333
|
-
hintText?: string,
|
|
334
305
|
): Promise<ActiveWorkflowProposalAssessment> {
|
|
335
306
|
const currentMission = currentMissionAnchor(snapshot);
|
|
336
307
|
const projectName = path.basename(snapshot.files.root);
|
|
337
|
-
const proposal = await deps.deriveCookContextProposal(ctx, projectName
|
|
308
|
+
const proposal = await deps.deriveCookContextProposal(ctx, projectName);
|
|
338
309
|
if (!proposal) {
|
|
339
310
|
const assessment: ActiveWorkflowProposalAssessment = {
|
|
340
311
|
action: "unclear",
|
|
@@ -379,15 +350,10 @@ async function resumeActiveWorkflowFromCanonicalState(
|
|
|
379
350
|
ctx: { cwd: string; hasUI: boolean; ui: any },
|
|
380
351
|
snapshot: CompletionStateSnapshot,
|
|
381
352
|
deps: CompletionDriverDeps,
|
|
382
|
-
naturalLanguageHandoff?: CookNaturalLanguageHandoff,
|
|
383
353
|
): Promise<void> {
|
|
384
354
|
const mission = currentMissionAnchor(snapshot);
|
|
385
355
|
pi.setSessionName(`completion: ${mission.slice(0, 60)}`);
|
|
386
|
-
const resumePrompt = deps.completionResumePrompt(
|
|
387
|
-
currentTaskType(snapshot) ?? "(missing)",
|
|
388
|
-
currentEvaluationProfile(snapshot) ?? "(missing)",
|
|
389
|
-
naturalLanguageHandoff,
|
|
390
|
-
);
|
|
356
|
+
const resumePrompt = deps.completionResumePrompt(currentTaskType(snapshot) ?? "(missing)", currentEvaluationProfile(snapshot) ?? "(missing)");
|
|
391
357
|
const rootKey = deps.completionRootKey(snapshot, deps.getCtxCwd(ctx));
|
|
392
358
|
const fingerprint = completionContinuationFingerprint(snapshot) ?? JSON.stringify({
|
|
393
359
|
kind: "resume",
|
|
@@ -495,6 +461,7 @@ async function refocusCompletionMission(
|
|
|
495
461
|
rawGoal: string,
|
|
496
462
|
analysis: ContextProposalAnalysis | undefined,
|
|
497
463
|
deps: CompletionDriverDeps,
|
|
464
|
+
advisoryStartupBrief?: Record<string, unknown>,
|
|
498
465
|
): Promise<void> {
|
|
499
466
|
const requiredStopJudges = asNumber(snapshot.profile?.required_stop_judges) ?? 3;
|
|
500
467
|
const root = snapshot.files.root;
|
|
@@ -513,7 +480,7 @@ async function refocusCompletionMission(
|
|
|
513
480
|
taskType: routing.taskType,
|
|
514
481
|
evaluationProfile: routing.evaluationProfile,
|
|
515
482
|
continuationReason: deps.buildContextProposalContinuationReason("User refocused workflow via /cook:", rawGoal, routing),
|
|
516
|
-
}),
|
|
483
|
+
}, advisoryStartupBrief),
|
|
517
484
|
remaining_stop_judges: requiredStopJudges,
|
|
518
485
|
next_mandatory_action: "Reconcile canonical state from current repo truth for the refocused mission",
|
|
519
486
|
};
|
|
@@ -536,59 +503,11 @@ function isWorkflowDone(snapshot: CompletionStateSnapshot | undefined): boolean
|
|
|
536
503
|
return asString(snapshot?.state?.continuation_policy) === "done";
|
|
537
504
|
}
|
|
538
505
|
|
|
539
|
-
function buildMission(projectName: string, missionAnchor: string): string {
|
|
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`;
|
|
541
|
-
}
|
|
542
|
-
|
|
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
506
|
export async function runCookEntry(
|
|
575
507
|
pi: ExtensionAPI,
|
|
576
508
|
ctx: DriverContext,
|
|
577
509
|
deps: CompletionDriverDeps,
|
|
578
|
-
options: RunCookEntryOptions,
|
|
579
510
|
): 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
511
|
let goal: string | undefined;
|
|
593
512
|
const cwd = deps.getCtxCwd(ctx);
|
|
594
513
|
let snapshot = await loadCompletionSnapshot(cwd);
|
|
@@ -600,13 +519,13 @@ export async function runCookEntry(
|
|
|
600
519
|
if (!snapshot) {
|
|
601
520
|
const root = findRepoRoot(cwd) ?? cwd;
|
|
602
521
|
const projectName = path.basename(root);
|
|
603
|
-
const proposal = await deps.deriveCookContextProposal(ctx, projectName
|
|
522
|
+
const proposal = await deps.deriveCookContextProposal(ctx, projectName);
|
|
604
523
|
if (!proposal) {
|
|
605
524
|
deps.emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(deps), "info");
|
|
606
525
|
return;
|
|
607
526
|
}
|
|
608
527
|
const decision = await deps.confirmContextProposal(ctx, proposal, {
|
|
609
|
-
title: "Start a completion workflow from
|
|
528
|
+
title: "Start a completion workflow from this startup brief?",
|
|
610
529
|
});
|
|
611
530
|
if (!decision) {
|
|
612
531
|
deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled recent-discussion workflow proposal", deps), "info");
|
|
@@ -623,6 +542,7 @@ export async function runCookEntry(
|
|
|
623
542
|
goal ?? kickoffMissionAnchor ?? projectName,
|
|
624
543
|
startupRouting,
|
|
625
544
|
),
|
|
545
|
+
advisoryStartupBrief: buildAdvisoryStartupBrief({ proposal, analysis: decision.analysis }),
|
|
626
546
|
});
|
|
627
547
|
deps.emitCommandText(
|
|
628
548
|
ctx,
|
|
@@ -639,13 +559,13 @@ export async function runCookEntry(
|
|
|
639
559
|
if (!goal) {
|
|
640
560
|
if (workflowDone) {
|
|
641
561
|
const projectName = path.basename(snapshot.files.root);
|
|
642
|
-
const proposal = await deps.deriveCookContextProposal(ctx, projectName
|
|
562
|
+
const proposal = await deps.deriveCookContextProposal(ctx, projectName);
|
|
643
563
|
if (!proposal) {
|
|
644
564
|
deps.emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(deps, "The previous completion workflow is already done."), "info");
|
|
645
565
|
return;
|
|
646
566
|
}
|
|
647
567
|
const decision = await deps.confirmContextProposal(ctx, proposal, {
|
|
648
|
-
title: "The previous completion workflow is done. Start the next workflow round from
|
|
568
|
+
title: "The previous completion workflow is done. Start the next workflow round from this startup brief?",
|
|
649
569
|
});
|
|
650
570
|
if (!decision) {
|
|
651
571
|
deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled next workflow round proposal", deps), "info");
|
|
@@ -654,13 +574,20 @@ export async function runCookEntry(
|
|
|
654
574
|
goal = decision.goalText;
|
|
655
575
|
kickoffIntent = "refocus";
|
|
656
576
|
kickoffMissionAnchor = decision.missionAnchor;
|
|
657
|
-
await refocusCompletionMission(
|
|
577
|
+
await refocusCompletionMission(
|
|
578
|
+
snapshot,
|
|
579
|
+
decision.missionAnchor,
|
|
580
|
+
decision.goalText,
|
|
581
|
+
decision.analysis,
|
|
582
|
+
deps,
|
|
583
|
+
buildAdvisoryStartupBrief({ proposal, analysis: decision.analysis }),
|
|
584
|
+
);
|
|
658
585
|
snapshot = (await loadCompletionSnapshot(snapshot.files.root)) ?? snapshot;
|
|
659
586
|
deps.emitCommandText(ctx, `Started a new completion workflow round from recent discussion: ${decision.missionAnchor}`, "info");
|
|
660
587
|
} else {
|
|
661
|
-
const assessment = await assessActiveWorkflowProposalRouting(ctx, snapshot, deps
|
|
588
|
+
const assessment = await assessActiveWorkflowProposalRouting(ctx, snapshot, deps);
|
|
662
589
|
if (!assessment.proposal || assessment.action === "continue") {
|
|
663
|
-
await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps
|
|
590
|
+
await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps);
|
|
664
591
|
return;
|
|
665
592
|
}
|
|
666
593
|
const decision = await confirmExistingWorkflowProposal(ctx, snapshot, assessment.proposal, deps, {
|
|
@@ -678,15 +605,15 @@ export async function runCookEntry(
|
|
|
678
605
|
return;
|
|
679
606
|
}
|
|
680
607
|
if (decision.action === "continue") {
|
|
681
|
-
await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps
|
|
608
|
+
await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps);
|
|
682
609
|
return;
|
|
683
610
|
}
|
|
684
611
|
const selectedProposal = decision.proposal;
|
|
685
612
|
const proposalDecision = await deps.confirmContextProposal(ctx, selectedProposal, {
|
|
686
613
|
title:
|
|
687
614
|
assessment.action === "refocus"
|
|
688
|
-
? "Start the replacement workflow from
|
|
689
|
-
: "Start the latest inferred workflow from
|
|
615
|
+
? "Start the replacement workflow from this startup brief?"
|
|
616
|
+
: "Start the latest inferred workflow from this startup brief?",
|
|
690
617
|
});
|
|
691
618
|
if (!proposalDecision) {
|
|
692
619
|
deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled replacement workflow proposal", deps), "info");
|
|
@@ -695,7 +622,14 @@ export async function runCookEntry(
|
|
|
695
622
|
goal = proposalDecision.goalText;
|
|
696
623
|
kickoffIntent = "refocus";
|
|
697
624
|
kickoffMissionAnchor = proposalDecision.missionAnchor;
|
|
698
|
-
await refocusCompletionMission(
|
|
625
|
+
await refocusCompletionMission(
|
|
626
|
+
snapshot,
|
|
627
|
+
proposalDecision.missionAnchor,
|
|
628
|
+
proposalDecision.goalText,
|
|
629
|
+
proposalDecision.analysis,
|
|
630
|
+
deps,
|
|
631
|
+
buildAdvisoryStartupBrief({ proposal: selectedProposal, analysis: proposalDecision.analysis }),
|
|
632
|
+
);
|
|
699
633
|
snapshot = (await loadCompletionSnapshot(snapshot.files.root)) ?? snapshot;
|
|
700
634
|
deps.emitCommandText(ctx, `Refocused completion mission from recent discussion to: ${proposalDecision.missionAnchor}`, "info");
|
|
701
635
|
}
|
|
@@ -709,7 +643,6 @@ export async function runCookEntry(
|
|
|
709
643
|
currentEvaluationProfile(snapshot) ?? "(missing)",
|
|
710
644
|
kickoffIntent,
|
|
711
645
|
kickoffMissionAnchor,
|
|
712
|
-
naturalLanguageHandoff,
|
|
713
646
|
);
|
|
714
647
|
const rootKey = deps.completionRootKey(snapshot, deps.getCtxCwd(ctx));
|
|
715
648
|
const fingerprint = completionContinuationFingerprint(snapshot) ?? JSON.stringify({
|
|
@@ -727,10 +660,11 @@ export function registerCookCommand(pi: ExtensionAPI, deps: CompletionDriverDeps
|
|
|
727
660
|
pi.registerCommand("cook", {
|
|
728
661
|
description: deps.cookCommandSpec.description,
|
|
729
662
|
handler: async (args, ctx) => {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
}
|
|
663
|
+
if (args.trim().length > 0) {
|
|
664
|
+
deps.emitCommandText(ctx, "/cook no longer accepts inline arguments. Discuss the concrete repo change in the main chat and rerun /cook.", "info");
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
await runCookEntry(pi, ctx, deps);
|
|
734
668
|
},
|
|
735
669
|
});
|
|
736
670
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import * as fs from "node:fs";
|
|
3
2
|
import { promises as fsp } from "node:fs";
|
|
4
3
|
import * as os from "node:os";
|
|
@@ -41,7 +40,6 @@ import {
|
|
|
41
40
|
buildContextProposalContinuationReason as buildExtractedContextProposalContinuationReason,
|
|
42
41
|
buildEvaluationRoleContextLines as buildExtractedEvaluationRoleContextLines,
|
|
43
42
|
buildEvaluationRoleReminderText as buildExtractedEvaluationRoleReminderText,
|
|
44
|
-
buildNaturalLanguageHandoffMetadataLines,
|
|
45
43
|
buildResumeCapsule as buildExtractedResumeCapsule,
|
|
46
44
|
buildSystemReminder as buildExtractedSystemReminder,
|
|
47
45
|
maybeWriteContextProposalConfirmationSnapshot,
|
|
@@ -76,11 +74,9 @@ import {
|
|
|
76
74
|
readText,
|
|
77
75
|
scaffoldCompletionFiles as scaffoldCompletionFilesOnDisk,
|
|
78
76
|
} from "./state-store";
|
|
79
|
-
import { parseFirstNumber, parseYesNo } from "./transcription";
|
|
80
77
|
import type { TranscriptionResult } from "./transcription";
|
|
81
|
-
import type { CompletionStateSnapshot, CompletionRole,
|
|
78
|
+
import type { CompletionStateSnapshot, CompletionRole, JsonRecord, LiveRoleActivity } from "./types";
|
|
82
79
|
|
|
83
|
-
const PROTOCOL_ID = "completion";
|
|
84
80
|
const ROLE_NAMES = [
|
|
85
81
|
"completion-bootstrapper",
|
|
86
82
|
"completion-regrounder",
|
|
@@ -123,10 +119,6 @@ function candidateSlices(plan: JsonRecord | undefined): JsonRecord[] {
|
|
|
123
119
|
return Array.isArray(slices) ? slices.filter(isRecord) : [];
|
|
124
120
|
}
|
|
125
121
|
|
|
126
|
-
type ExistingWorkflowDecision =
|
|
127
|
-
| { action: "continue"; currentMissionAnchor: string }
|
|
128
|
-
| { action: "refocus"; currentMissionAnchor: string; missionAnchor: string };
|
|
129
|
-
|
|
130
122
|
type ActiveWorkflowProposalAssessment = {
|
|
131
123
|
action: "continue" | "refocus" | "unclear";
|
|
132
124
|
currentMissionAnchor: string;
|
|
@@ -134,13 +126,6 @@ type ActiveWorkflowProposalAssessment = {
|
|
|
134
126
|
reason: "matching_mission" | "clear_refocus" | "missing_proposal" | "ambiguous_discussion";
|
|
135
127
|
};
|
|
136
128
|
|
|
137
|
-
type ExistingWorkflowChooserOptions = {
|
|
138
|
-
intro?: string;
|
|
139
|
-
proposedMissionLabel?: string;
|
|
140
|
-
refocusChoiceLabel?: string;
|
|
141
|
-
comparison?: "semantic" | "strict";
|
|
142
|
-
};
|
|
143
|
-
|
|
144
129
|
function completionTestWorkflowActionOverride(): "continue" | "refocus" | "cancel" | undefined {
|
|
145
130
|
const raw = process.env.PI_COMPLETION_EXISTING_WORKFLOW_ACTION?.trim().toLowerCase();
|
|
146
131
|
return raw === "continue" || raw === "refocus" || raw === "cancel" ? raw : undefined;
|
|
@@ -207,26 +192,8 @@ function maybeWriteTestSnapshot(targetPath: string | undefined, content: string)
|
|
|
207
192
|
}
|
|
208
193
|
|
|
209
194
|
const COOK_MAIN_CHAT_RERUN_GUIDANCE = "Discuss changes in the main chat and rerun /cook.";
|
|
210
|
-
const COOK_BARE_ONLY_GUIDANCE =
|
|
211
|
-
"/cook is the canonical workflow boundary. Discuss the concrete repo changes in the main chat, then run /cook when you want to start, continue, refocus, or begin the next workflow round.";
|
|
212
195
|
const COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL =
|
|
213
|
-
"/cook failed closed because recent discussion did not produce a clear execution-ready Mission/Scope/Constraints/Acceptance
|
|
214
|
-
|
|
215
|
-
function buildCookCancellationMessage(prefix: string): string {
|
|
216
|
-
return `${prefix}. ${COOK_MAIN_CHAT_RERUN_GUIDANCE}`;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function buildCookStructuredDiscussionFailureMessage(prefix?: string): string {
|
|
220
|
-
return prefix ? `${prefix} ${COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL}` : COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function shouldDisableContextProposalAnalyst(): boolean {
|
|
224
|
-
return process.env.PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST === "1";
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function completionTestContextProposalAnalystOutput(): string | undefined {
|
|
228
|
-
return asString(process.env.PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT);
|
|
229
|
-
}
|
|
196
|
+
"/cook failed closed because recent discussion did not produce a clear execution-ready startup brief with Mission/Scope/Constraints/Acceptance for concrete repo changes. Clarify the concrete repo changes in the main chat and rerun /cook.";
|
|
230
197
|
|
|
231
198
|
function isWorkflowDone(snapshot: CompletionStateSnapshot | undefined): boolean {
|
|
232
199
|
return asString(snapshot?.state?.continuation_policy) === "done";
|
|
@@ -378,7 +345,6 @@ async function promptContextProposalConfirmationAction(
|
|
|
378
345
|
async function deriveCookContextProposal(
|
|
379
346
|
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
380
347
|
projectName: string,
|
|
381
|
-
hintText?: string,
|
|
382
348
|
): Promise<ContextProposal | undefined> {
|
|
383
349
|
const recentEntries = collectRecentDiscussionEntries(ctx, { isRecord, asString, isStaleContextError });
|
|
384
350
|
const snapshot = await loadCompletionSnapshot(getCtxCwd(ctx));
|
|
@@ -394,11 +360,9 @@ async function deriveCookContextProposal(
|
|
|
394
360
|
`verification summary: ${asString(snapshot.verificationEvidence?.summary) ?? "(none)"}`,
|
|
395
361
|
]
|
|
396
362
|
: [];
|
|
397
|
-
if (hintText) workflowContextLines.push(`cook hint: ${hintText}`);
|
|
398
363
|
return await deriveCookContextProposalFromRecentDiscussion(projectName, recentEntries, {
|
|
399
364
|
asString,
|
|
400
365
|
asStringArray,
|
|
401
|
-
hintText,
|
|
402
366
|
workflowContext: snapshot
|
|
403
367
|
? {
|
|
404
368
|
currentMissionAnchor:
|
|
@@ -412,15 +376,12 @@ async function deriveCookContextProposal(
|
|
|
412
376
|
continuationPolicy: asString(snapshot.state?.continuation_policy),
|
|
413
377
|
}
|
|
414
378
|
: undefined,
|
|
415
|
-
analyzeContextProposal: async (entries
|
|
379
|
+
analyzeContextProposal: async (entries) =>
|
|
416
380
|
await analyzeContextProposalWithAgent({
|
|
417
381
|
ctx,
|
|
418
382
|
projectName,
|
|
419
383
|
recentEntries: entries,
|
|
420
|
-
workflowContextLines
|
|
421
|
-
derivedHintText && !workflowContextLines.includes(`cook hint: ${derivedHintText}`)
|
|
422
|
-
? [...workflowContextLines, `cook hint: ${derivedHintText}`]
|
|
423
|
-
: workflowContextLines,
|
|
384
|
+
workflowContextLines,
|
|
424
385
|
liveRoleActivityByRoot,
|
|
425
386
|
completionStatusKey: COMPLETION_STATUS_KEY,
|
|
426
387
|
safeUiCall,
|
|
@@ -470,12 +431,13 @@ async function confirmContextProposal(
|
|
|
470
431
|
async function scaffoldCompletionFiles(
|
|
471
432
|
root: string,
|
|
472
433
|
missionAnchor: string,
|
|
473
|
-
options?: { analysis?: ContextProposalAnalysis; continuationReason?: string },
|
|
434
|
+
options?: { analysis?: ContextProposalAnalysis; continuationReason?: string; advisoryStartupBrief?: JsonRecord },
|
|
474
435
|
) {
|
|
475
436
|
const routing = finalizeContextProposalAnalysis(options?.analysis, [missionAnchor]);
|
|
476
437
|
return await scaffoldCompletionFilesOnDisk(root, missionAnchor, {
|
|
477
438
|
analysis: { taskType: routing.taskType, evaluationProfile: routing.evaluationProfile },
|
|
478
439
|
continuationReason: options?.continuationReason,
|
|
440
|
+
advisoryStartupBrief: options?.advisoryStartupBrief,
|
|
479
441
|
});
|
|
480
442
|
}
|
|
481
443
|
|
|
@@ -875,45 +837,24 @@ function composeResumeCapsule(snapshot: CompletionStateSnapshot, sliceHistory: J
|
|
|
875
837
|
});
|
|
876
838
|
}
|
|
877
839
|
|
|
878
|
-
async function gitHeadSha(cwd: string): Promise<string | undefined> {
|
|
879
|
-
return await new Promise((resolve) => {
|
|
880
|
-
const proc = spawn("git", ["rev-parse", "HEAD"], { cwd, stdio: ["ignore", "pipe", "ignore"] });
|
|
881
|
-
let stdout = "";
|
|
882
|
-
proc.stdout.on("data", (chunk) => {
|
|
883
|
-
stdout += chunk.toString();
|
|
884
|
-
});
|
|
885
|
-
proc.on("close", (code) => {
|
|
886
|
-
resolve(code === 0 ? asString(stdout) : undefined);
|
|
887
|
-
});
|
|
888
|
-
proc.on("error", () => resolve(undefined));
|
|
889
|
-
});
|
|
890
|
-
}
|
|
891
|
-
|
|
892
840
|
function completionKickoff(
|
|
893
841
|
goal: string,
|
|
894
842
|
taskType: string,
|
|
895
843
|
evaluationProfile: string,
|
|
896
844
|
intent: "auto" | "continue" | "refocus" = "auto",
|
|
897
845
|
missionAnchor?: string,
|
|
898
|
-
naturalLanguageHandoff?: CookNaturalLanguageHandoff,
|
|
899
846
|
): string {
|
|
900
|
-
const naturalLanguageHandoffBlock = buildNaturalLanguageHandoffMetadataLines(naturalLanguageHandoff).join("\n");
|
|
901
847
|
const intentBlock =
|
|
902
848
|
intent === "continue" && missionAnchor
|
|
903
849
|
? `Existing canonical mission anchor:\n${missionAnchor}\n\nWorkflow intent:\n- Continue the existing workflow.\n- Treat the new user text as supplemental direction unless canonical reconciliation proves the mission itself must change.\n\n`
|
|
904
850
|
: intent === "refocus" && missionAnchor
|
|
905
851
|
? `Updated canonical mission anchor:\n${missionAnchor}\n\nWorkflow intent:\n- The user explicitly refocused the workflow before this kickoff.\n- Re-read canonical .agent/** state and continue from the refocused mission.\n\n`
|
|
906
852
|
: "";
|
|
907
|
-
return `/skill:completion-protocol Start or continue the completion workflow for this repo.\n\nBefore acting, read:\n- ${SKILL_PATH}\n- ${REFERENCE_PATH}\n\nCanonical routing profile:\n- task_type: ${taskType}\n- evaluation_profile: ${evaluationProfile}\n\nUser goal:\n${goal}\n\n${
|
|
853
|
+
return `/skill:completion-protocol Start or continue the completion workflow for this repo.\n\nBefore acting, read:\n- ${SKILL_PATH}\n- ${REFERENCE_PATH}\n\nCanonical routing profile:\n- task_type: ${taskType}\n- evaluation_profile: ${evaluationProfile}\n\nUser goal:\n${goal}\n\n${intentBlock}Driver instructions:\n- Canonical truth is in .agent/**. Re-read .agent/state.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json before acting when they exist.\n- If tracked completion contract files are missing or onboarding is required, invoke completion_role with role completion-bootstrapper.\n- Otherwise follow the mandatory dispatch rules from completion-protocol.\n- For selected, in-progress, committed, or done slices, treat .agent/active-slice.json as the canonical implementation contract and route to completion-regrounder if it drifts from the selected plan slice or the exact handoff is unclear.\n- Consume .agent/verification-evidence.json instead of temp-only verification summaries when it is populated.\n- Use completion_role for all completion-* role work. Do not directly implement tracked product changes yourself.\n- Continue dispatching mandatory roles while continuation_policy == continue.\n- Only stop for the user when continuation_policy is await_user_input, blocked, paused, or done.`;
|
|
908
854
|
}
|
|
909
855
|
|
|
910
|
-
function completionResumePrompt(
|
|
911
|
-
taskType:
|
|
912
|
-
evaluationProfile: string,
|
|
913
|
-
naturalLanguageHandoff?: CookNaturalLanguageHandoff,
|
|
914
|
-
): string {
|
|
915
|
-
const naturalLanguageHandoffBlock = buildNaturalLanguageHandoffMetadataLines(naturalLanguageHandoff).join("\n");
|
|
916
|
-
return `/skill:completion-protocol Resume the completion workflow from canonical state.\n\nBefore acting, read:\n- ${SKILL_PATH}\n- ${REFERENCE_PATH}\n\nCanonical routing profile:\n- task_type: ${taskType}\n- evaluation_profile: ${evaluationProfile}\n\n${naturalLanguageHandoffBlock}Resume instructions:\n- Re-read .agent/state.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json before acting.\n- If canonical state is missing, invalid, contradictory, stale, or ambiguous, route to completion-regrounder first.\n- For selected, in-progress, committed, or done slices, treat .agent/active-slice.json as the canonical implementation contract and route to completion-regrounder if it drifts from the selected plan slice or the exact handoff is unclear.\n- Consume .agent/verification-evidence.json instead of temp-only verification summaries when it is populated.\n- Continue from next_mandatory_role and next_mandatory_action.\n- Use completion_role for all completion-* role work.\n- Continue dispatching mandatory roles while continuation_policy == continue.\n- Only stop for the user when continuation_policy is await_user_input, blocked, paused, or done.`;
|
|
856
|
+
function completionResumePrompt(taskType: string, evaluationProfile: string): string {
|
|
857
|
+
return `/skill:completion-protocol Resume the completion workflow from canonical state.\n\nBefore acting, read:\n- ${SKILL_PATH}\n- ${REFERENCE_PATH}\n\nCanonical routing profile:\n- task_type: ${taskType}\n- evaluation_profile: ${evaluationProfile}\n\nResume instructions:\n- Re-read .agent/state.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json before acting.\n- If canonical state is missing, invalid, contradictory, stale, or ambiguous, route to completion-regrounder first.\n- For selected, in-progress, committed, or done slices, treat .agent/active-slice.json as the canonical implementation contract and route to completion-regrounder if it drifts from the selected plan slice or the exact handoff is unclear.\n- Consume .agent/verification-evidence.json instead of temp-only verification summaries when it is populated.\n- Continue from next_mandatory_role and next_mandatory_action.\n- Use completion_role for all completion-* role work.\n- Continue dispatching mandatory roles while continuation_policy == continue.\n- Only stop for the user when continuation_policy is await_user_input, blocked, paused, or done.`;
|
|
917
858
|
}
|
|
918
859
|
|
|
919
860
|
export default function completionExtension(pi: ExtensionAPI) {
|
|
@@ -926,11 +867,10 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
926
867
|
getCtxUi,
|
|
927
868
|
};
|
|
928
869
|
const driverDeps = {
|
|
929
|
-
bareOnlyGuidance: COOK_BARE_ONLY_GUIDANCE,
|
|
930
870
|
structuredDiscussionFailureDetail: COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL,
|
|
931
871
|
mainChatRerunGuidance: COOK_MAIN_CHAT_RERUN_GUIDANCE,
|
|
932
872
|
cookCommandSpec: {
|
|
933
|
-
description: "/cook workflow: start, continue, refocus, or start the next round from
|
|
873
|
+
description: "/cook workflow: derive a startup brief from recent discussion, then start, continue, refocus, or start the next round from the explicit /cook command",
|
|
934
874
|
},
|
|
935
875
|
buildContextProposalContinuationReason,
|
|
936
876
|
completionKickoff,
|