@linimin/pi-letscook 0.1.67 → 0.1.68

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
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.68
4
+
5
+ ### Changed
6
+
7
+ - simplified `/cook` startup sourcing so workflow proposals now come only from same-entry primary-agent startup-plan synthesis
8
+ - stopped `/cook` from directly adopting old preview capsules or falling back to transcript-derived startup proposals
9
+ - kept preview capsules advisory-only for humans while active-workflow replacement and next-round startup now depend on same-entry primary-agent synthesis from current task context
10
+
3
11
  ## 0.1.67
4
12
 
5
13
  ### Changed
package/README.md CHANGED
@@ -57,11 +57,11 @@ Then run `/reload` in Pi.
57
57
  - a mission, scope, acceptance, and verification intent concrete enough for `completion-regrounder` to derive truthful slices after startup
58
58
  - README/CHANGELOG updates still count as concrete repo changes
59
59
  - assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts still do not count unless the primary-agent startup-plan step turns them into concrete startup intake for `/cook`
60
- - `/cook` first prefers a fresh explicit `cook_handoff` preview when one already exists, but otherwise calls the primary-agent startup-plan synthesis step in the same `/cook` entry
60
+ - `/cook` always runs a same-entry primary-agent startup-plan synthesis step from the current task context instead of directly adopting an old preview or transcript-derived proposal
61
61
 
62
62
  If the startup-plan step still cannot prepare a concrete startup plan, `/cook` fails closed, leaves canonical `.agent/**` state unchanged, and tells you to refine the mission, scope, acceptance, or verification intent in the main chat before rerunning `/cook`.
63
63
 
64
- If a fresh explicit preview exists but is still workflow-worthy rather than concrete enough to seed workflow planning, `/cook` also fails closed instead of silently treating that capsule as canonical workflow state.
64
+ If the same-entry synthesized startup plan is still too vague or planning-only to seed workflow planning, `/cook` also fails closed instead of silently treating that output as canonical workflow state.
65
65
 
66
66
  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`.
67
67
 
@@ -71,15 +71,15 @@ Only explicit `/cook` enters workflow mode. Ordinary prompts stay in the main ch
71
71
 
72
72
  Ordinary chat can still directly implement repo changes. `/cook` is for the cases where you want workflow control rather than just implementation help, and the primary agent should prepare the startup plan before workflow begins.
73
73
 
74
- When you explicitly run `/cook`, it first checks for a fresh explicit primary-agent startup-plan preview. If one is missing, it calls a same-entry primary-agent startup-plan synthesis step from the current task context, then asks you to **Start** or **Cancel** before rewriting canonical workflow state.
74
+ When you explicitly run `/cook`, it always calls a same-entry primary-agent startup-plan synthesis step from the current task context, then asks you to **Start** or **Cancel** before rewriting canonical workflow state.
75
75
 
76
- Explicit `/cook` capsules are still valid startup intake, but they are no longer the only path because `/cook` can synthesize the primary-agent startup plan in the same entry when needed.
76
+ Optional preview capsules in ordinary chat are advisory only. `/cook` does not directly consume them as approval-ready workflow state; it synthesizes a fresh startup plan in the `/cook` entry.
77
77
 
78
78
  Important behavior:
79
79
  - `/cook` is an optional workflow boundary and manual entry point
80
- - startup and next-round entry stay confirm-first, using explicit primary-agent startup-plan data when present and otherwise running the primary-agent startup-plan synthesis step in the same `/cook` entry
80
+ - startup and next-round entry stay confirm-first, always using same-entry primary-agent startup-plan synthesis from the current task context
81
81
  - after **Start**, `/cook` records the approved startup plan under `.agent/startup-plan.json` / `.agent/startup-plan.md`, then `completion-regrounder` derives canonical slices from repo truth
82
- - active workflows resume from canonical `.agent/**` state unless a concrete replacement startup plan is available or synthesized in the same `/cook` entry
82
+ - active workflows resume from canonical `.agent/**` state unless same-entry primary-agent startup-plan synthesis produces a concrete replacement mission
83
83
  - explicit slash commands other than `/cook` continue normally in the main chat
84
84
  - ordinary main-chat discussion may clarify, propose, or directly implement repo changes without entering workflow mode
85
85
 
@@ -95,13 +95,13 @@ I want to add login redirect handling and tests.
95
95
 
96
96
  ## What happens when you run `/cook`
97
97
 
98
- `/cook` first checks for a fresh explicit primary-agent startup-plan preview. New-workflow entry and done-workflow next-round entry use that plan when it already exists; otherwise `/cook` calls a same-entry primary-agent startup-plan synthesis step, then immediately continues to Start / Cancel if the generated plan is concrete enough. After **Start**, the approved startup plan is written into `.agent/startup-plan.json` / `.agent/startup-plan.md`, and `completion-regrounder` uses it to derive canonical slices from current repo truth. Active workflows still resume canonical state by default unless a concrete replacement startup plan is available or synthesized in the same `/cook` entry. None of this prevents ordinary-chat implementation when you choose not to enter workflow mode.
98
+ `/cook` always runs a same-entry primary-agent startup-plan synthesis step from the current task context. If that synthesized plan is concrete enough, `/cook` continues to Start / Cancel confirmation. After **Start**, the approved startup plan is written into `.agent/startup-plan.json` / `.agent/startup-plan.md`, and `completion-regrounder` uses it to derive canonical slices from current repo truth. Active workflows still resume canonical state by default unless same-entry synthesis produces a concrete replacement mission. None of this prevents ordinary-chat implementation when you choose not to enter workflow mode.
99
99
 
100
100
  | Repo state | What you'll see |
101
101
  |---|---|
102
- | No workflow yet | `/cook` consumes a fresh explicit primary-agent startup-plan preview when one already exists, or synthesizes one from the primary-agent view in the same entry, then asks you to choose **Start** or **Cancel**. After **Start**, the approved startup plan is persisted under `.agent/` and `completion-regrounder` derives canonical slices. Stale, planning-only, or non-startable startup plans still fail closed. |
103
- | Active workflow exists | Usually a resume of the current workflow from canonical `.agent/**` state. If a concrete replacement startup plan exists already or is synthesized in the same `/cook` entry and points to a different mission, `/cook` shows a chooser first and only rewrites canonical state after you confirm the replacement. Ambiguous or missing replacement startup plans stay conservative. |
104
- | Previous workflow is `done` | `/cook` can start the next implementation round from a fresh explicit primary-agent startup plan or from the same-entry primary-agent startup-plan synthesis step behind **Start** or **Cancel**. Weak or planning-only next-round startup plans still fail closed. |
102
+ | No workflow yet | `/cook` synthesizes a primary-agent startup plan in the same entry, then asks you to choose **Start** or **Cancel**. After **Start**, the approved startup plan is persisted under `.agent/` and `completion-regrounder` derives canonical slices. Weak, planning-only, or non-startable synthesized plans still fail closed. |
103
+ | Active workflow exists | Usually a resume of the current workflow from canonical `.agent/**` state. If same-entry primary-agent startup-plan synthesis produces a concrete replacement mission, `/cook` shows a chooser first and only rewrites canonical state after you confirm the replacement. Ambiguous, missing, or non-startable synthesized replacement plans stay conservative. |
104
+ | Previous workflow is `done` | `/cook` can start the next implementation round from a same-entry primary-agent startup-plan synthesis step behind **Start** or **Cancel**. Weak or planning-only synthesized next-round startup plans still fail closed. |
105
105
 
106
106
  ## Confirmation and fail-closed behavior
107
107
 
@@ -117,7 +117,7 @@ When you accept startup or refocus, `/cook` persists the chosen workflow state i
117
117
 
118
118
  The confirmed startup plan is preserved under `.agent/startup-plan.json` / `.agent/startup-plan.md` and summarized in `state.json` as advisory intake for later re-grounding. It does not replace `.agent/plan.json` or `.agent/active-slice.json`, which remain under regrounder authority.
119
119
 
120
- The pre-`/cook` preview capsule itself is not canonical workflow state. It is only startup intake for `/cook`.
120
+ The pre-`/cook` preview capsule itself is not canonical workflow state. It is only an advisory preview for the human; `/cook` still synthesizes a fresh startup plan in the entry where workflow actually begins.
121
121
 
122
122
  ## Observability
123
123
 
@@ -38,7 +38,7 @@ type ContextProposalAlternate = {
38
38
  analysis: ContextProposalAnalysis;
39
39
  goalText: string;
40
40
  basisPreview: string;
41
- source: "session" | "analyst" | "handoff_capsule";
41
+ source: "session" | "analyst" | "handoff_capsule" | "deferred_primary_agent_handoff";
42
42
  };
43
43
 
44
44
  type ContextProposal = ContextProposalAlternate & {
@@ -71,10 +71,10 @@ type ActiveWorkflowProposalAssessment = {
71
71
  proposal?: ContextProposal;
72
72
  blockedFailureMessage?: string;
73
73
  reason:
74
- | "matching_mission"
75
- | "missing_explicit_handoff"
76
- | "fresh_explicit_handoff"
77
- | "fresh_explicit_handoff_not_startable";
74
+ | "matching_generated_startup_plan"
75
+ | "no_generated_startup_plan"
76
+ | "generated_replacement_startup_plan"
77
+ | "generated_startup_plan_not_startable";
78
78
  };
79
79
 
80
80
  type ExistingWorkflowChooserOptions = {
@@ -126,7 +126,6 @@ export type CompletionDriverDeps = {
126
126
  ) => string;
127
127
  completionResumePrompt: (taskType: string, evaluationProfile: string) => string;
128
128
  deriveCookContextProposal: (ctx: DriverContext, projectName: string) => Promise<CookContextProposalResult>;
129
- deriveCookStartupProposal: (ctx: DriverContext, projectName: string) => Promise<CookContextProposalResult>;
130
129
  confirmContextProposal: (
131
130
  ctx: { hasUI: boolean; ui: any },
132
131
  proposal: ContextProposal,
@@ -332,7 +331,7 @@ async function assessActiveWorkflowProposalRouting(
332
331
  action: "blocked",
333
332
  currentMissionAnchor: currentMission,
334
333
  blockedFailureMessage: proposalResult.blockedFailureMessage,
335
- reason: "fresh_explicit_handoff_not_startable",
334
+ reason: "generated_startup_plan_not_startable",
336
335
  };
337
336
  deps.maybeWriteActiveWorkflowRoutingSnapshot(assessment);
338
337
  return assessment;
@@ -342,7 +341,7 @@ async function assessActiveWorkflowProposalRouting(
342
341
  const assessment: ActiveWorkflowProposalAssessment = {
343
342
  action: "continue",
344
343
  currentMissionAnchor: currentMission,
345
- reason: "missing_explicit_handoff",
344
+ reason: "no_generated_startup_plan",
346
345
  };
347
346
  deps.maybeWriteActiveWorkflowRoutingSnapshot(assessment);
348
347
  return assessment;
@@ -352,7 +351,7 @@ async function assessActiveWorkflowProposalRouting(
352
351
  action: "continue",
353
352
  currentMissionAnchor: currentMission,
354
353
  proposal,
355
- reason: "matching_mission",
354
+ reason: "matching_generated_startup_plan",
356
355
  };
357
356
  deps.maybeWriteActiveWorkflowRoutingSnapshot(assessment);
358
357
  return assessment;
@@ -361,7 +360,7 @@ async function assessActiveWorkflowProposalRouting(
361
360
  action: "refocus",
362
361
  currentMissionAnchor: currentMission,
363
362
  proposal,
364
- reason: "fresh_explicit_handoff",
363
+ reason: "generated_replacement_startup_plan",
365
364
  };
366
365
  deps.maybeWriteActiveWorkflowRoutingSnapshot(assessment);
367
366
  return assessment;
@@ -424,8 +423,8 @@ async function confirmExistingWorkflowProposal(
424
423
  const continueChoice = "Continue current workflow\n\nKeep the current mission and treat the new goal as extra direction only.";
425
424
  const buildRefocusChoice = (candidate: ContextProposalAlternate, variant: "primary" | "alternate") =>
426
425
  variant === "primary"
427
- ? `${options.refocusChoiceLabel ?? "Start new workflow from recent discussion\n\nReview the proposed replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state."}\n\n${summarizeProposalForChoice(candidate)}`
428
- : `${options.alternateChoiceLabel ?? "Start alternate workflow from recent discussion\n\nReview this alternate replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state."}\n\n${summarizeProposalForChoice(candidate)}`;
426
+ ? `${options.refocusChoiceLabel ?? "Start new workflow from synthesized startup plan\n\nReview the proposed replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state."}\n\n${summarizeProposalForChoice(candidate)}`
427
+ : `${options.alternateChoiceLabel ?? "Start alternate workflow from synthesized startup plan\n\nReview this alternate replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state."}\n\n${summarizeProposalForChoice(candidate)}`;
429
428
  const refocusChoices = candidateProposals.map((candidate, index) => buildRefocusChoice(candidate, index === 0 ? "primary" : "alternate"));
430
429
  const cancelChoice = `Cancel\n\nKeep the current workflow unchanged. ${deps.mainChatRerunGuidance}`;
431
430
  deps.maybeWriteTestSnapshot(
@@ -578,7 +577,7 @@ export async function runCookEntry(
578
577
  title: "Start a completion workflow from this startup plan?",
579
578
  });
580
579
  if (!decision) {
581
- deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled recent-discussion workflow proposal", deps), "info");
580
+ deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled synthesized startup plan", deps), "info");
582
581
  return;
583
582
  }
584
583
  goal = decision.goalText;
@@ -663,19 +662,19 @@ export async function runCookEntry(
663
662
  await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps);
664
663
  return;
665
664
  }
666
- const explicitReplacement = assessment.reason === "fresh_explicit_handoff";
665
+ const generatedReplacement = assessment.reason === "generated_replacement_startup_plan";
667
666
  const decision = await confirmExistingWorkflowProposal(ctx, snapshot, assessment.proposal, deps, {
668
- intro: explicitReplacement
669
- ? "A fresh explicit primary-agent startup plan proposes replacing the current workflow. Choose how /cook should proceed:"
667
+ intro: generatedReplacement
668
+ ? "A same-entry primary-agent startup plan proposes replacing the current workflow. Choose how /cook should proceed:"
670
669
  : "A replacement workflow is ready. Choose how /cook should proceed:",
671
- proposedMissionLabel: explicitReplacement
672
- ? "Proposed mission from explicit primary-agent startup plan"
670
+ proposedMissionLabel: generatedReplacement
671
+ ? "Proposed mission from same-entry primary-agent startup plan"
673
672
  : "Proposed mission",
674
- refocusChoiceLabel: explicitReplacement
675
- ? "Start new workflow from explicit primary-agent startup plan\n\nReview the proposed replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state."
673
+ refocusChoiceLabel: generatedReplacement
674
+ ? "Start new workflow from same-entry primary-agent startup plan\n\nReview the proposed replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state."
676
675
  : "Start new workflow\n\nReview the proposed replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state.",
677
- alternateChoiceLabel: explicitReplacement
678
- ? "Start alternate workflow from explicit primary-agent startup plan\n\nReview this alternate replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state."
676
+ alternateChoiceLabel: generatedReplacement
677
+ ? "Start alternate workflow from same-entry primary-agent startup plan\n\nReview this alternate replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state."
679
678
  : undefined,
680
679
  comparison: "strict",
681
680
  });
@@ -689,8 +688,8 @@ export async function runCookEntry(
689
688
  }
690
689
  const selectedProposal = decision.proposal;
691
690
  const proposalDecision = await deps.confirmContextProposal(ctx, selectedProposal, {
692
- title: assessment.reason === "fresh_explicit_handoff"
693
- ? "Start the replacement workflow from this explicit startup plan?"
691
+ title: assessment.reason === "generated_replacement_startup_plan"
692
+ ? "Start the replacement workflow from this synthesized startup plan?"
694
693
  : "Start the replacement workflow from this startup plan?",
695
694
  });
696
695
  if (!proposalDecision) {
@@ -718,8 +717,8 @@ export async function runCookEntry(
718
717
  snapshot = (await loadCompletionSnapshot(snapshot.files.root)) ?? snapshot;
719
718
  deps.emitCommandText(
720
719
  ctx,
721
- assessment.reason === "fresh_explicit_handoff"
722
- ? `Refocused completion mission from explicit primary-agent startup plan and rewrote the approved startup plan to: ${proposalDecision.missionAnchor}`
720
+ assessment.reason === "generated_replacement_startup_plan"
721
+ ? `Refocused completion mission from same-entry primary-agent startup plan and rewrote the approved startup plan to: ${proposalDecision.missionAnchor}`
723
722
  : `Refocused completion mission and rewrote the approved startup plan to: ${proposalDecision.missionAnchor}`,
724
723
  "info",
725
724
  );
@@ -14,18 +14,17 @@ import {
14
14
  registerCookCommand,
15
15
  } from "./driver";
16
16
  import {
17
+ assessCookHandoffText,
17
18
  assessMissionAnchor,
18
19
  collectRecentDiscussionEntries,
19
20
  collectRecentSessionMessages,
20
- assessLatestCookHandoffProposal,
21
- deriveCookContextProposalFromRecentDiscussion,
22
21
  finalizeContextProposalAnalysis,
23
- hasStructuredContextProposalSignal,
24
22
  isWeakMissionAnchor,
25
23
  missionAnchorsLikelyEquivalent,
26
24
  missionAnchorsStrictlyEquivalent,
27
25
  normalizeMissionAnchorText,
28
26
  resolveContextProposalConfirmationAction,
27
+ retagContextProposalSource,
29
28
  stripCodeBlocks,
30
29
  } from "./proposal";
31
30
  import type {
@@ -49,7 +48,7 @@ import {
49
48
  maybeWriteContextProposalSnapshot,
50
49
  } from "./prompt-surfaces";
51
50
  import { toolCallBlockReason } from "./policy-guards";
52
- import { analyzeContextProposalWithAgent, runCompletionRole } from "./role-runner";
51
+ import { runCompletionRole } from "./role-runner";
53
52
  import { generateCookHandoffWithAgent } from "./role-runner";
54
53
  import {
55
54
  applyLiveRoleEvent,
@@ -135,10 +134,10 @@ type ActiveWorkflowProposalAssessment = {
135
134
  proposal?: ContextProposal;
136
135
  blockedFailureMessage?: string;
137
136
  reason:
138
- | "matching_mission"
139
- | "missing_explicit_handoff"
140
- | "fresh_explicit_handoff"
141
- | "fresh_explicit_handoff_not_startable";
137
+ | "matching_generated_startup_plan"
138
+ | "no_generated_startup_plan"
139
+ | "generated_replacement_startup_plan"
140
+ | "generated_startup_plan_not_startable";
142
141
  };
143
142
 
144
143
  function completionTestWorkflowActionOverride(): "continue" | "refocus" | "cancel" | undefined {
@@ -379,36 +378,13 @@ function stripCookHandoffBlocks(text: string): string {
379
378
  return text.replace(COOK_HANDOFF_BLOCK_REGEX, " ").replace(/\s+/g, " ").trim();
380
379
  }
381
380
 
382
- async function deriveCookStartupProposal(
383
- ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
384
- projectName: string,
385
- ): Promise<CookContextProposalResult> {
386
- const recentMessages = collectRecentSessionMessages(ctx, { isRecord, asString, asNumber, isStaleContextError });
387
- const explicitHandoff = assessLatestCookHandoffProposal(recentMessages, projectName, {
388
- asString,
389
- asStringArray,
390
- assessMissionAnchor,
391
- normalizeMissionAnchorText,
392
- isWeakMissionAnchor,
393
- missionAnchorsStrictlyEquivalent,
394
- stripCodeBlocks,
395
- });
396
- if (explicitHandoff.status === "startable") return { proposal: explicitHandoff.proposal };
397
- if (explicitHandoff.status === "fresh_but_not_startable") {
398
- return { blockedFailureMessage: explicitHandoff.message };
399
- }
400
- return {};
401
- }
402
-
403
381
  async function deriveCookContextProposal(
404
382
  ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
405
383
  projectName: string,
406
384
  ): Promise<CookContextProposalResult> {
407
- const explicit = await deriveCookStartupProposal(ctx, projectName);
408
- if (explicit.proposal || explicit.blockedFailureMessage) return explicit;
409
385
  const recentMessages = collectRecentSessionMessages(ctx, { isRecord, asString, asNumber, isStaleContextError });
410
386
  const recentEntries = recentMessages
411
- .filter((entry) => !entry.isCommand && (entry.role === "user" || entry.role === "assistant" || entry.role === "custom" || entry.role === "summary"))
387
+ .filter((entry) => !entry.isCommand && (entry.role === "user" || entry.role === "custom" || entry.role === "summary"))
412
388
  .slice(0, 12)
413
389
  .map((entry) => ({ role: entry.role, text: stripCookHandoffBlocks(entry.text) }))
414
390
  .filter((entry) => entry.text.length > 0);
@@ -426,18 +402,6 @@ async function deriveCookContextProposal(
426
402
  `verification summary: ${asString(snapshot.verificationEvidence?.summary) ?? "(none)"}`,
427
403
  ]
428
404
  : [];
429
- const workflowContext = snapshot
430
- ? {
431
- currentMissionAnchor: asString(snapshot.state?.mission_anchor) ?? asString(snapshot.plan?.mission_anchor) ?? asString(snapshot.active?.mission_anchor),
432
- latestCompletedSlice: asString(snapshot.state?.latest_completed_slice),
433
- latestVerifiedSlice: asString(snapshot.state?.latest_verified_slice),
434
- activeSliceGoal: asString(snapshot.active?.goal),
435
- activeSliceWhyNow: asString(snapshot.active?.why_now),
436
- verificationGoal: asString(snapshot.verificationEvidence?.goal),
437
- verificationSummary: asString(snapshot.verificationEvidence?.summary),
438
- continuationPolicy: asString(snapshot.state?.continuation_policy),
439
- }
440
- : undefined;
441
405
  const raw = await generateCookHandoffWithAgent({
442
406
  ctx,
443
407
  projectName,
@@ -450,49 +414,24 @@ async function deriveCookContextProposal(
450
414
  getCtxHasUI,
451
415
  getCtxUi,
452
416
  });
453
- if (raw) {
454
- const generated = assessLatestCookHandoffProposal([
455
- { role: "assistant", text: raw, messageId: "generated-primary-agent-handoff", timestampMs: Date.now(), isCommand: false },
456
- ], projectName, {
457
- asString,
458
- asStringArray,
459
- assessMissionAnchor,
460
- normalizeMissionAnchorText,
461
- isWeakMissionAnchor,
462
- missionAnchorsStrictlyEquivalent,
463
- stripCodeBlocks,
464
- });
465
- if (generated.status === "startable") return { proposal: generated.proposal };
466
- if (generated.status === "fresh_but_not_startable") return { blockedFailureMessage: generated.message };
467
- }
468
- const canFallbackToDiscussionProposal =
469
- recentEntries.length >= 2 || recentEntries.some((entry) => hasStructuredContextProposalSignal(entry.text, stripCodeBlocks));
470
- if (canFallbackToDiscussionProposal) {
471
- const fromDiscussion = await deriveCookContextProposalFromRecentDiscussion(projectName, recentEntries, {
472
- asString,
473
- asStringArray,
474
- assessMissionAnchor,
475
- normalizeMissionAnchorText,
476
- isWeakMissionAnchor,
477
- missionAnchorsStrictlyEquivalent,
478
- stripCodeBlocks,
479
- analyzeContextProposal: async (candidateEntries) =>
480
- await analyzeContextProposalWithAgent({
481
- ctx,
482
- projectName,
483
- recentEntries: candidateEntries,
484
- workflowContextLines,
485
- liveRoleActivityByRoot,
486
- completionStatusKey: COMPLETION_STATUS_KEY,
487
- safeUiCall,
488
- getCtxCwd,
489
- getCtxHasUI,
490
- getCtxUi,
491
- }),
492
- workflowContext,
493
- });
494
- if (fromDiscussion) return { proposal: fromDiscussion };
417
+ if (!raw) return {};
418
+ const generated = assessCookHandoffText(raw, projectName, {
419
+ asString,
420
+ asStringArray,
421
+ assessMissionAnchor,
422
+ normalizeMissionAnchorText,
423
+ isWeakMissionAnchor,
424
+ missionAnchorsStrictlyEquivalent,
425
+ stripCodeBlocks,
426
+ }, {
427
+ messageId: "generated-primary-agent-handoff",
428
+ timestampMs: Date.now(),
429
+ context: "same_entry_synthesis",
430
+ });
431
+ if (generated.status === "startable") {
432
+ return { proposal: retagContextProposalSource(generated.proposal, "deferred_primary_agent_handoff") };
495
433
  }
434
+ if (generated.status === "fresh_but_not_startable") return { blockedFailureMessage: generated.message };
496
435
  return {};
497
436
  }
498
437
 
@@ -1012,7 +951,6 @@ export default function completionExtension(pi: ExtensionAPI) {
1012
951
  completionTestWorkflowMissionOverride,
1013
952
  confirmContextProposal,
1014
953
  deriveCookContextProposal,
1015
- deriveCookStartupProposal,
1016
954
  emitCommandText,
1017
955
  finalizeContextProposalAnalysis,
1018
956
  getCtxCwd,
@@ -54,9 +54,9 @@ export function buildCookHandoffBoundaryReminder(): string {
54
54
  "If the user wants direct implementation now, stay in ordinary chat and help directly instead of blocking on /cook.",
55
55
  "If the user asks follow-up questions or wants to keep refining scope, continue helping naturally in ordinary chat.",
56
56
  "If the user explicitly runs /cook, the extension should call a primary-agent startup-plan synthesis step from the current task context, show Start/Cancel confirmation in the same /cook entry, and only write the approved plan into .agent after Start.",
57
- "Do not expect /cook to infer or guess startup intent from recent discussion alone; /cook should use primary-agent-authored startup-plan data, whether it already exists as preview intake or is synthesized in the same /cook entry.",
57
+ "Do not expect /cook to infer or guess startup intent from recent discussion alone, and do not expect /cook to directly reuse an old preview capsule; /cook should always synthesize the startup plan fresh in the same entry from current task context.",
58
58
  "Only provide a preview startup plan or ```cook_handoff``` capsule in ordinary chat when the user explicitly asks for that preview behavior.",
59
- "Any preview capsule is startup intake for /cook only: do not present it as canonical .agent state, an active slice, or a persistent repo contract.",
59
+ "Any preview capsule is advisory only until /cook reruns same-entry primary-agent startup-plan synthesis: do not present it as canonical .agent state, an active slice, or a persistent repo contract.",
60
60
  "When /cook starts, the approved startup plan should be written into .agent and then handed to completion-regrounder so canonical slices can be derived from repo truth.",
61
61
  "When you continue in ordinary chat, do not pretend /cook already started and do not silently rewrite discussion into canonical workflow state.",
62
62
  ].join(" ");
@@ -106,7 +106,7 @@ function buildAdvisoryStartupBriefNotes(analysis: ContextProposalAnalysis): stri
106
106
  ...analysis.critique,
107
107
  ...analysis.possibleNoise.map((item) => `Possible noise: ${item}`),
108
108
  ];
109
- return notes.length > 0 ? notes : ["No additional operator notes were derived from recent discussion."];
109
+ return notes.length > 0 ? notes : ["No additional operator notes were captured for the approved startup plan."];
110
110
  }
111
111
 
112
112
  function startupPlanSourceForProposal(source: ContextProposal["source"]): AdvisoryStartupBrief["source"] {
@@ -1374,12 +1374,21 @@ function cookHandoffStartabilityFailures(
1374
1374
  return failures;
1375
1375
  }
1376
1376
 
1377
- function buildNonStartableCookHandoffMessage(failures: string[]): string {
1378
- return [
1379
- "/cook failed closed because a fresh explicit primary-agent startup plan exists, but it is not concrete enough to seed workflow planning yet.",
1380
- "Tighten the startup plan in the main chat so it captures a concrete mission, repo-change-oriented acceptance, and truthful verification intent, then rerun /cook.",
1381
- `Blocking details: ${failures.join("; ")}.`,
1382
- ].join(" ");
1377
+ function buildNonStartableCookHandoffMessage(
1378
+ failures: string[],
1379
+ context: "explicit_preview" | "same_entry_synthesis" = "explicit_preview",
1380
+ ): string {
1381
+ return context === "same_entry_synthesis"
1382
+ ? [
1383
+ "/cook failed closed because the same-entry primary-agent startup-plan synthesis step returned a startup plan that is still not concrete enough to seed workflow planning yet.",
1384
+ "Clarify the mission, scope, acceptance, or verification intent in the main chat, then rerun /cook so the primary agent can synthesize a tighter startup plan.",
1385
+ `Blocking details: ${failures.join("; ")}.`,
1386
+ ].join(" ")
1387
+ : [
1388
+ "/cook failed closed because a fresh explicit primary-agent startup plan exists, but it is not concrete enough to seed workflow planning yet.",
1389
+ "Tighten the startup plan in the main chat so it captures a concrete mission, repo-change-oriented acceptance, and truthful verification intent, then rerun /cook.",
1390
+ `Blocking details: ${failures.join("; ")}.`,
1391
+ ].join(" ");
1383
1392
  }
1384
1393
 
1385
1394
  function isStartableCookHandoffCapsule(
@@ -1470,6 +1479,32 @@ function buildContextProposalFromCookHandoffCapsule(
1470
1479
  return finalizeContextProposal(proposal, projectName, deps);
1471
1480
  }
1472
1481
 
1482
+ export function assessCookHandoffText(
1483
+ text: string,
1484
+ projectName: string,
1485
+ deps: ProposalParseDeps,
1486
+ options?: {
1487
+ messageId?: string;
1488
+ timestampMs?: number;
1489
+ context?: "explicit_preview" | "same_entry_synthesis";
1490
+ },
1491
+ ): CookHandoffProposalAssessment {
1492
+ const capsules = parseCookHandoffCapsulesFromText(text, options?.messageId, options?.timestampMs, deps);
1493
+ for (let capsuleIndex = capsules.length - 1; capsuleIndex >= 0; capsuleIndex -= 1) {
1494
+ const capsule = capsules[capsuleIndex];
1495
+ const failures = cookHandoffStartabilityFailures(capsule, deps);
1496
+ if (failures.length > 0) {
1497
+ return {
1498
+ status: "fresh_but_not_startable",
1499
+ message: buildNonStartableCookHandoffMessage(failures, options?.context ?? "explicit_preview"),
1500
+ };
1501
+ }
1502
+ const proposal = buildContextProposalFromCookHandoffCapsule(capsule, projectName, deps);
1503
+ if (proposal) return { status: "startable", proposal };
1504
+ }
1505
+ return { status: "none" };
1506
+ }
1507
+
1473
1508
  export function assessLatestCookHandoffProposal(
1474
1509
  recentMessages: RecentSessionMessage[],
1475
1510
  projectName: string,
@@ -1489,7 +1524,7 @@ export function assessLatestCookHandoffProposal(
1489
1524
  if (failures.length > 0) {
1490
1525
  return {
1491
1526
  status: "fresh_but_not_startable",
1492
- message: buildNonStartableCookHandoffMessage(failures),
1527
+ message: buildNonStartableCookHandoffMessage(failures, "explicit_preview"),
1493
1528
  };
1494
1529
  }
1495
1530
  const proposal = buildContextProposalFromCookHandoffCapsule(capsule, projectName, deps);
@@ -103,7 +103,10 @@ const PRIMARY_AGENT_HANDOFF_SYSTEM_PROMPT = [
103
103
  "Author the approved workflow startup plan now from the primary-agent view of the task so /cook can persist it under .agent before completion-regrounder derives canonical slices.",
104
104
  "Capture the agreed mission, scope, constraints or non_goals, acceptance, risks, notes, and any concrete planning hints that will help completion-regrounder split slices later.",
105
105
  "If a bounded first slice, likely implementation surfaces, or likely verification commands are already obvious, include first_slice_goal, first_slice_non_goals, implementation_surfaces, verification_commands, and why_this_slice_first as optional hints only. They are not required when the overall startup plan is already concrete enough to begin workflow planning.",
106
- "Do not make /cook infer or rediscover the mission from recent discussion later; author the startup plan now from the primary-agent view of the task.",
106
+ "Prefer the latest user-authored task context plus canonical workflow context over older assistant-authored previews or stale planning text.",
107
+ "Do not directly reuse an old preview capsule as-is; either synthesize a fresh startup plan from the current task context or return a brief plain sentence saying no concrete startup plan should replace canonical state yet.",
108
+ "If canonical workflow context already exists and the latest discussion does not clearly ask to replace the mission or start the next round, return a brief plain sentence instead of inventing a replacement startup plan.",
109
+ "Do not make /cook infer or rediscover the mission later; author the startup plan now from the primary-agent view of the task.",
107
110
  "Do not emit markdown commentary before or after the capsule.",
108
111
  "If the task is not concrete enough for workflow startup, do not invent missing detail.",
109
112
  ].join(" ");
@@ -343,7 +346,8 @@ function buildPrimaryAgentHandoffPrompt(projectName: string, recentEntries: Rece
343
346
  lines.push(
344
347
  "",
345
348
  "Task:",
346
- "The user explicitly invoked /cook. Prepare the primary-agent startup plan that /cook should consume immediately for Start/Cancel confirmation, persistence under .agent, and later slice derivation by completion-regrounder.",
349
+ "The user explicitly invoked /cook. Prepare the primary-agent startup plan that /cook should synthesize immediately for Start/Cancel confirmation, persistence under .agent, and later slice derivation by completion-regrounder.",
350
+ "If the latest discussion does not justify a concrete new startup plan, return a brief plain sentence instead of speculative JSON.",
347
351
  );
348
352
  return lines.join("\n");
349
353
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linimin/pi-letscook",
3
- "version": "0.1.67",
3
+ "version": "0.1.68",
4
4
  "description": "Pi package for long-running completion workflows with canonical .agent state, role-based subagents, continuity, and verification helpers.",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -137,17 +137,16 @@ NODE
137
137
  ROOT="$TMPDIR/repo"
138
138
  PROMPT="$TMPDIR/resume-prompt.txt"
139
139
  BOOTSTRAP_SESSION="$TMPDIR/session-active-slice-bootstrap.jsonl"
140
- BOOTSTRAP_MESSAGES="$(python3 - <<'PY'
140
+ BOOTSTRAP_DISCUSSION=$'Prepare the active-slice contract bootstrap fixture and tell me when it is ready for /cook.'
141
+ GENERATED_HANDOFF="$(python3 - <<'PY'
141
142
  import json
142
143
  capsule = {
143
144
  "kind": "cook_handoff",
144
145
  "source": "primary_agent",
145
- "captured_at": "2026-01-01T00:00:02.000Z",
146
- "source_turn_id": "m0002",
147
146
  "mission": "Exercise active-slice contract parity.",
148
147
  "scope": [
149
148
  "Bootstrap canonical completion files for the active-slice contract fixture.",
150
- "Keep the fixture on the shipped explicit-handoff startup path."
149
+ "Keep the fixture on the shipped same-entry synthesis startup path."
151
150
  ],
152
151
  "constraints": [
153
152
  "Use supported bare /cook startup only."
@@ -157,7 +156,7 @@ capsule = {
157
156
  "Keep scripts/active-slice-contract-test.sh aligned with the packaged startup contract."
158
157
  ],
159
158
  "risks": [
160
- "Active-slice fixture bootstrap must stay anchored to the fresh explicit startup-plan preview."
159
+ "Active-slice fixture bootstrap must stay anchored to same-entry primary-agent startup-plan synthesis."
161
160
  ],
162
161
  "notes": [
163
162
  "This handoff exists only to scaffold canonical files before the fixture rewrites them for contract parity coverage."
@@ -179,20 +178,16 @@ capsule = {
179
178
  "evaluation_profile": "completion-rubric-v1",
180
179
  "why_cook_now": "The fixture bootstrap is concrete enough to scaffold canonical control-plane files."
181
180
  }
182
- messages = [
183
- {"role": "user", "content": "Prepare the active-slice contract bootstrap fixture and tell me when it is ready for /cook."},
184
- {"role": "assistant", "content": "The active-slice contract bootstrap fixture is ready for /cook. Run /cook to confirm it.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
185
- ]
186
- print(json.dumps(messages, ensure_ascii=False))
181
+ print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
187
182
  PY
188
183
  )"
189
184
  mkdir -p "$ROOT"
190
185
  cd "$ROOT"
191
186
  git init -q
192
- write_session_messages "$BOOTSTRAP_SESSION" "$ROOT" "$BOOTSTRAP_MESSAGES"
187
+ write_session "$BOOTSTRAP_SESSION" "$ROOT" "$BOOTSTRAP_DISCUSSION"
193
188
 
194
189
  PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
195
- PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
190
+ PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$GENERATED_HANDOFF" \
196
191
  PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
197
192
  pi --session "$BOOTSTRAP_SESSION" -e "$PKG_ROOT" -p "/cook" \
198
193
  >"$TMPDIR/pi-active-slice-bootstrap.out" 2>"$TMPDIR/pi-active-slice-bootstrap.err"