@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 +8 -0
- package/README.md +11 -11
- package/extensions/completion/driver.ts +25 -26
- package/extensions/completion/index.ts +25 -87
- package/extensions/completion/prompt-surfaces.ts +3 -3
- package/extensions/completion/proposal.ts +42 -7
- package/extensions/completion/role-runner.ts +6 -2
- package/package.json +1 -1
- package/scripts/active-slice-contract-test.sh +7 -12
- package/scripts/canonical-evidence-artifact-test.sh +9 -14
- package/scripts/context-proposal-test.sh +307 -1426
- package/scripts/refocus-test.sh +185 -459
- package/scripts/release-check.sh +7 -5
- package/scripts/role-runner-contract-test.sh +2 -2
- package/scripts/smoke-test.sh +11 -16
- package/skills/cook-handoff-boundary/SKILL.md +7 -6
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`
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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`
|
|
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`
|
|
103
|
-
| Active workflow exists | Usually a resume of the current workflow from canonical `.agent/**` state. If
|
|
104
|
-
| Previous workflow is `done` | `/cook` can start the next implementation round from a
|
|
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
|
|
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
|
-
| "
|
|
75
|
-
| "
|
|
76
|
-
| "
|
|
77
|
-
| "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|
|
428
|
-
: `${options.alternateChoiceLabel ?? "Start alternate workflow from
|
|
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
|
|
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
|
|
665
|
+
const generatedReplacement = assessment.reason === "generated_replacement_startup_plan";
|
|
667
666
|
const decision = await confirmExistingWorkflowProposal(ctx, snapshot, assessment.proposal, deps, {
|
|
668
|
-
intro:
|
|
669
|
-
? "A
|
|
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:
|
|
672
|
-
? "Proposed mission from
|
|
670
|
+
proposedMissionLabel: generatedReplacement
|
|
671
|
+
? "Proposed mission from same-entry primary-agent startup plan"
|
|
673
672
|
: "Proposed mission",
|
|
674
|
-
refocusChoiceLabel:
|
|
675
|
-
? "Start new workflow from
|
|
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:
|
|
678
|
-
? "Start alternate workflow from
|
|
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 === "
|
|
693
|
-
? "Start the replacement workflow from this
|
|
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 === "
|
|
722
|
-
? `Refocused completion mission from
|
|
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 {
|
|
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
|
-
| "
|
|
139
|
-
| "
|
|
140
|
-
| "
|
|
141
|
-
| "
|
|
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 === "
|
|
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
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
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
|
-
"
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
187
|
+
write_session "$BOOTSTRAP_SESSION" "$ROOT" "$BOOTSTRAP_DISCUSSION"
|
|
193
188
|
|
|
194
189
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
195
|
-
|
|
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"
|