@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 CHANGED
@@ -4,8 +4,8 @@
4
4
 
5
5
  ### Changed
6
6
 
7
- - removed workflow-aware prompt interception so only explicit `/cook` or `/cook <hint>` enters the workflow; ordinary prompts now always stay on the main chat path
8
- - updated docs and release checks to describe explicit `/cook` entry instead of router-managed natural-language takeover
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` or `/cook <hint>`.
35
- 5. Review the proposal and choose **Start** or **Cancel**.
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` or `/cook <hint>` enters the workflow. Ordinary prompts stay in the main chat and go straight to the primary agent.
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` supports both bare discussion-driven startup and optional inline intent hints.
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 proposal built from recent main-chat discussion. You choose **Start** or **Cancel**. Weak or planning-only discussion fails closed. |
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 confirmation. Ambiguous discussion stays conservative. |
94
- | 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. |
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 type { CompletionStateSnapshot, CookNaturalLanguageHandoff, CookTriggerWorkflowBias } from "./types";
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
- taskType: string,
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, hintText);
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, explicitHint);
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 the recent discussion?",
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, explicitHint);
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 the recent discussion?",
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(snapshot, decision.missionAnchor, decision.goalText, decision.analysis, deps);
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, explicitHint);
588
+ const assessment = await assessActiveWorkflowProposalRouting(ctx, snapshot, deps);
662
589
  if (!assessment.proposal || assessment.action === "continue") {
663
- await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps, naturalLanguageHandoff);
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, naturalLanguageHandoff);
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 recent discussion?"
689
- : "Start the latest inferred workflow from recent discussion?",
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(snapshot, proposalDecision.missionAnchor, proposalDecision.goalText, proposalDecision.analysis, deps);
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
- await runCookEntry(pi, ctx, deps, {
731
- origin: "command",
732
- hintText: args,
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, CookNaturalLanguageHandoff, JsonRecord, LiveRoleActivity } from "./types";
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 proposal for concrete repo changes. Clarify the concrete repo changes in the main chat and rerun /cook.";
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, derivedHintText) =>
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${naturalLanguageHandoffBlock}${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.`;
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: string,
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 an explicit /cook command",
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,