@linimin/pi-letscook 0.1.66 → 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/.agent/README.md +4 -0
- package/.agent/verify_completion_control_plane.sh +34 -0
- package/CHANGELOG.md +17 -0
- package/README.md +26 -21
- package/agents/completion-bootstrapper.md +2 -1
- package/agents/completion-regrounder.md +16 -10
- package/extensions/completion/driver.ts +85 -37
- package/extensions/completion/index.ts +54 -41
- package/extensions/completion/prompt-surfaces.ts +158 -17
- package/extensions/completion/proposal.ts +61 -26
- package/extensions/completion/role-runner.ts +15 -10
- package/extensions/completion/state-store.ts +87 -2
- package/extensions/completion/types.ts +3 -0
- package/package.json +1 -1
- package/scripts/active-slice-contract-test.sh +7 -12
- package/scripts/canonical-evidence-artifact-test.sh +55 -15
- package/scripts/context-proposal-test.sh +307 -1427
- package/scripts/refocus-test.sh +185 -459
- package/scripts/release-check.sh +13 -11
- package/scripts/role-runner-contract-test.sh +2 -2
- package/scripts/smoke-test.sh +76 -21
- package/skills/completion-protocol/SKILL.md +9 -3
- package/skills/completion-protocol/references/completion.md +37 -2
- package/skills/cook-handoff-boundary/SKILL.md +18 -17
|
@@ -14,16 +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
21
|
finalizeContextProposalAnalysis,
|
|
22
22
|
isWeakMissionAnchor,
|
|
23
23
|
missionAnchorsLikelyEquivalent,
|
|
24
24
|
missionAnchorsStrictlyEquivalent,
|
|
25
25
|
normalizeMissionAnchorText,
|
|
26
26
|
resolveContextProposalConfirmationAction,
|
|
27
|
+
retagContextProposalSource,
|
|
27
28
|
stripCodeBlocks,
|
|
28
29
|
} from "./proposal";
|
|
29
30
|
import type {
|
|
@@ -47,7 +48,8 @@ import {
|
|
|
47
48
|
maybeWriteContextProposalSnapshot,
|
|
48
49
|
} from "./prompt-surfaces";
|
|
49
50
|
import { toolCallBlockReason } from "./policy-guards";
|
|
50
|
-
import {
|
|
51
|
+
import { runCompletionRole } from "./role-runner";
|
|
52
|
+
import { generateCookHandoffWithAgent } from "./role-runner";
|
|
51
53
|
import {
|
|
52
54
|
applyLiveRoleEvent,
|
|
53
55
|
buildInlineRunningLines,
|
|
@@ -132,10 +134,10 @@ type ActiveWorkflowProposalAssessment = {
|
|
|
132
134
|
proposal?: ContextProposal;
|
|
133
135
|
blockedFailureMessage?: string;
|
|
134
136
|
reason:
|
|
135
|
-
| "
|
|
136
|
-
| "
|
|
137
|
-
| "
|
|
138
|
-
| "
|
|
137
|
+
| "matching_generated_startup_plan"
|
|
138
|
+
| "no_generated_startup_plan"
|
|
139
|
+
| "generated_replacement_startup_plan"
|
|
140
|
+
| "generated_startup_plan_not_startable";
|
|
139
141
|
};
|
|
140
142
|
|
|
141
143
|
function completionTestWorkflowActionOverride(): "continue" | "refocus" | "cancel" | undefined {
|
|
@@ -209,7 +211,7 @@ function maybeWriteTestSnapshot(targetPath: string | undefined, content: string)
|
|
|
209
211
|
|
|
210
212
|
const COOK_MAIN_CHAT_RERUN_GUIDANCE = "Discuss changes in the main chat and rerun /cook.";
|
|
211
213
|
const COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL =
|
|
212
|
-
"/cook failed closed because the
|
|
214
|
+
"/cook failed closed because the startup-plan step could not prepare a concrete workflow startup plan from the current task context. Clarify the mission, scope, acceptance, or verification intent in the main chat, then rerun /cook.";
|
|
213
215
|
|
|
214
216
|
function isWorkflowDone(snapshot: CompletionStateSnapshot | undefined): boolean {
|
|
215
217
|
return asString(snapshot?.state?.continuation_policy) === "done";
|
|
@@ -376,36 +378,13 @@ function stripCookHandoffBlocks(text: string): string {
|
|
|
376
378
|
return text.replace(COOK_HANDOFF_BLOCK_REGEX, " ").replace(/\s+/g, " ").trim();
|
|
377
379
|
}
|
|
378
380
|
|
|
379
|
-
async function deriveCookStartupProposal(
|
|
380
|
-
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
381
|
-
projectName: string,
|
|
382
|
-
): Promise<CookContextProposalResult> {
|
|
383
|
-
const recentMessages = collectRecentSessionMessages(ctx, { isRecord, asString, asNumber, isStaleContextError });
|
|
384
|
-
const explicitHandoff = assessLatestCookHandoffProposal(recentMessages, projectName, {
|
|
385
|
-
asString,
|
|
386
|
-
asStringArray,
|
|
387
|
-
assessMissionAnchor,
|
|
388
|
-
normalizeMissionAnchorText,
|
|
389
|
-
isWeakMissionAnchor,
|
|
390
|
-
missionAnchorsStrictlyEquivalent,
|
|
391
|
-
stripCodeBlocks,
|
|
392
|
-
});
|
|
393
|
-
if (explicitHandoff.status === "startable") return { proposal: explicitHandoff.proposal };
|
|
394
|
-
if (explicitHandoff.status === "fresh_but_not_startable") {
|
|
395
|
-
return { blockedFailureMessage: explicitHandoff.message };
|
|
396
|
-
}
|
|
397
|
-
return {};
|
|
398
|
-
}
|
|
399
|
-
|
|
400
381
|
async function deriveCookContextProposal(
|
|
401
382
|
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
402
383
|
projectName: string,
|
|
403
384
|
): Promise<CookContextProposalResult> {
|
|
404
|
-
const explicit = await deriveCookStartupProposal(ctx, projectName);
|
|
405
|
-
if (explicit.proposal || explicit.blockedFailureMessage) return explicit;
|
|
406
385
|
const recentMessages = collectRecentSessionMessages(ctx, { isRecord, asString, asNumber, isStaleContextError });
|
|
407
386
|
const recentEntries = recentMessages
|
|
408
|
-
.filter((entry) => !entry.isCommand && (entry.role === "user" || entry.role === "
|
|
387
|
+
.filter((entry) => !entry.isCommand && (entry.role === "user" || entry.role === "custom" || entry.role === "summary"))
|
|
409
388
|
.slice(0, 12)
|
|
410
389
|
.map((entry) => ({ role: entry.role, text: stripCookHandoffBlocks(entry.text) }))
|
|
411
390
|
.filter((entry) => entry.text.length > 0);
|
|
@@ -418,6 +397,7 @@ async function deriveCookContextProposal(
|
|
|
418
397
|
`latest verified slice: ${asString(snapshot.state?.latest_verified_slice) ?? "(none)"}`,
|
|
419
398
|
`active slice goal: ${asString(snapshot.active?.goal) ?? "(none)"}`,
|
|
420
399
|
`active slice why_now: ${asString(snapshot.active?.why_now) ?? "(none)"}`,
|
|
400
|
+
`approved startup plan summary: ${asString(snapshot.startupPlan?.goal_text) ?? "(none)"}`,
|
|
421
401
|
`verification goal: ${asString(snapshot.verificationEvidence?.goal) ?? "(none)"}`,
|
|
422
402
|
`verification summary: ${asString(snapshot.verificationEvidence?.summary) ?? "(none)"}`,
|
|
423
403
|
]
|
|
@@ -435,9 +415,7 @@ async function deriveCookContextProposal(
|
|
|
435
415
|
getCtxUi,
|
|
436
416
|
});
|
|
437
417
|
if (!raw) return {};
|
|
438
|
-
const generated =
|
|
439
|
-
{ role: "assistant", text: raw, messageId: "generated-primary-agent-handoff", timestampMs: Date.now(), isCommand: false },
|
|
440
|
-
], projectName, {
|
|
418
|
+
const generated = assessCookHandoffText(raw, projectName, {
|
|
441
419
|
asString,
|
|
442
420
|
asStringArray,
|
|
443
421
|
assessMissionAnchor,
|
|
@@ -445,8 +423,14 @@ async function deriveCookContextProposal(
|
|
|
445
423
|
isWeakMissionAnchor,
|
|
446
424
|
missionAnchorsStrictlyEquivalent,
|
|
447
425
|
stripCodeBlocks,
|
|
426
|
+
}, {
|
|
427
|
+
messageId: "generated-primary-agent-handoff",
|
|
428
|
+
timestampMs: Date.now(),
|
|
429
|
+
context: "same_entry_synthesis",
|
|
448
430
|
});
|
|
449
|
-
if (generated.status === "startable")
|
|
431
|
+
if (generated.status === "startable") {
|
|
432
|
+
return { proposal: retagContextProposalSource(generated.proposal, "deferred_primary_agent_handoff") };
|
|
433
|
+
}
|
|
450
434
|
if (generated.status === "fresh_but_not_startable") return { blockedFailureMessage: generated.message };
|
|
451
435
|
return {};
|
|
452
436
|
}
|
|
@@ -485,13 +469,19 @@ async function confirmContextProposal(
|
|
|
485
469
|
async function scaffoldCompletionFiles(
|
|
486
470
|
root: string,
|
|
487
471
|
missionAnchor: string,
|
|
488
|
-
options?: {
|
|
472
|
+
options?: {
|
|
473
|
+
analysis?: ContextProposalAnalysis;
|
|
474
|
+
continuationReason?: string;
|
|
475
|
+
advisoryStartupBrief?: JsonRecord;
|
|
476
|
+
approvedStartupPlan?: JsonRecord;
|
|
477
|
+
},
|
|
489
478
|
) {
|
|
490
479
|
const routing = finalizeContextProposalAnalysis(options?.analysis, [missionAnchor]);
|
|
491
480
|
return await scaffoldCompletionFilesOnDisk(root, missionAnchor, {
|
|
492
481
|
analysis: { taskType: routing.taskType, evaluationProfile: routing.evaluationProfile },
|
|
493
482
|
continuationReason: options?.continuationReason,
|
|
494
483
|
advisoryStartupBrief: options?.advisoryStartupBrief,
|
|
484
|
+
approvedStartupPlan: options?.approvedStartupPlan,
|
|
495
485
|
});
|
|
496
486
|
}
|
|
497
487
|
|
|
@@ -654,6 +644,20 @@ function verificationEvidenceContext(snapshot: CompletionStateSnapshot) {
|
|
|
654
644
|
};
|
|
655
645
|
}
|
|
656
646
|
|
|
647
|
+
function startupPlanContext(snapshot: CompletionStateSnapshot) {
|
|
648
|
+
const startupPlan = snapshot.startupPlan;
|
|
649
|
+
return {
|
|
650
|
+
path: path.relative(snapshot.files.root, snapshot.files.startupPlanPath) || ".agent/startup-plan.json",
|
|
651
|
+
status: startupPlan ? "present" : "missing",
|
|
652
|
+
source: asString(startupPlan?.source),
|
|
653
|
+
plannedSurfaces: asStringArray(startupPlan?.planned_surfaces),
|
|
654
|
+
verificationIntent: asStringArray(startupPlan?.verification_intent),
|
|
655
|
+
summary:
|
|
656
|
+
asString(startupPlan?.goal_text) ??
|
|
657
|
+
(startupPlan ? "Approved startup plan is present but its goal_text is missing." : "Approved startup plan is missing."),
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
|
|
657
661
|
function buildEvaluationRoleContextLines(snapshot: CompletionStateSnapshot, role: RubricEvaluationRole): string[] {
|
|
658
662
|
return buildExtractedEvaluationRoleContextLines(snapshot, role, {
|
|
659
663
|
asString,
|
|
@@ -684,6 +688,7 @@ function composeSystemReminder(snapshot: CompletionStateSnapshot, sliceHistory:
|
|
|
684
688
|
const exactActiveContract = activeCarriesExactHandoff(snapshot.active);
|
|
685
689
|
const activeContractDrift = activeSliceContractDriftSummary(snapshot);
|
|
686
690
|
const evidence = verificationEvidenceContext(snapshot);
|
|
691
|
+
const startupPlan = startupPlanContext(snapshot);
|
|
687
692
|
const activePriorityLine = activePriority !== undefined ? `Active slice priority: ${activePriority}` : undefined;
|
|
688
693
|
const activeWhyNowLine = activeWhyNow ? `Active slice why_now: ${activeWhyNow}` : undefined;
|
|
689
694
|
const implementationSurfacesLine =
|
|
@@ -713,6 +718,7 @@ function composeSystemReminder(snapshot: CompletionStateSnapshot, sliceHistory:
|
|
|
713
718
|
implementationSurfacesLine,
|
|
714
719
|
verificationCommandsLine,
|
|
715
720
|
evidence,
|
|
721
|
+
startupPlan,
|
|
716
722
|
evaluationRoleReminderText: isRubricEvaluationRole(nextRole) ? buildEvaluationRoleReminderText(snapshot, nextRole) : undefined,
|
|
717
723
|
});
|
|
718
724
|
}
|
|
@@ -732,17 +738,19 @@ function buildPostCompactionDriverInstructions(snapshot: CompletionStateSnapshot
|
|
|
732
738
|
const exactActiveContract = activeCarriesExactHandoff(snapshot.active);
|
|
733
739
|
const activeContractDrift = activeSliceContractDriftSummary(snapshot);
|
|
734
740
|
const evidence = verificationEvidenceContext(snapshot);
|
|
741
|
+
const startupPlan = startupPlanContext(snapshot);
|
|
735
742
|
const lines = [
|
|
736
743
|
"POST-COMPACTION RECOVERY MODE is active.",
|
|
737
744
|
`Compaction marker time: ${markerAt}`,
|
|
738
745
|
"Treat the previous conversation as lossy continuity support only.",
|
|
739
|
-
"Before taking any substantive action, re-read .agent/state.json, .agent/plan.json, .agent/active-slice.json, .agent/slice-history.jsonl, .agent/stop-check-history.jsonl, and .agent/verification-evidence.json from disk.",
|
|
746
|
+
"Before taking any substantive action, re-read .agent/state.json, .agent/startup-plan.json, .agent/plan.json, .agent/active-slice.json, .agent/slice-history.jsonl, .agent/stop-check-history.jsonl, and .agent/verification-evidence.json from disk.",
|
|
740
747
|
`Canonical task_type is currently: ${taskType}`,
|
|
741
748
|
`Canonical evaluation_profile is currently: ${evaluationProfile}`,
|
|
742
749
|
`Canonical next mandatory role is currently: ${nextRole}`,
|
|
743
750
|
`Canonical next mandatory action is currently: ${nextAction}`,
|
|
744
751
|
`Canonical continuation policy is currently: ${continuation}`,
|
|
745
752
|
`Canonical active slice is currently: ${activeSliceId}`,
|
|
753
|
+
`Canonical approved startup plan is currently: ${startupPlan.path} (${startupPlan.status})`,
|
|
746
754
|
`Canonical verification evidence artifact is currently: ${evidence.path} (${evidence.status})`,
|
|
747
755
|
"Do not trust pre-compaction memory over canonical files.",
|
|
748
756
|
"If the canonical state is ambiguous, inconsistent, missing, or stale after re-reading it, your first mandatory action is to dispatch completion-regrounder rather than guessing.",
|
|
@@ -757,6 +765,10 @@ function buildPostCompactionDriverInstructions(snapshot: CompletionStateSnapshot
|
|
|
757
765
|
if (activeWhyNow) lines.push(`Canonical active-slice why_now is currently: ${activeWhyNow}`);
|
|
758
766
|
if (implementationSurfaces.length > 0) lines.push(`Canonical implementation surfaces are currently: ${implementationSurfaces.join(", ")}`);
|
|
759
767
|
if (verificationCommands.length > 0) lines.push(`Canonical verification commands are currently: ${verificationCommands.join(" | ")}`);
|
|
768
|
+
if (startupPlan.source) lines.push(`Canonical approved startup plan source is currently: ${startupPlan.source}`);
|
|
769
|
+
if (startupPlan.plannedSurfaces.length > 0) lines.push(`Canonical approved startup plan surfaces are currently: ${startupPlan.plannedSurfaces.join(" | ")}`);
|
|
770
|
+
if (startupPlan.verificationIntent.length > 0) lines.push(`Canonical approved startup plan verification intent is currently: ${startupPlan.verificationIntent.join(" | ")}`);
|
|
771
|
+
lines.push(`Canonical approved startup plan summary is currently: ${startupPlan.summary}`);
|
|
760
772
|
if (evidence.subjectType) lines.push(`Canonical verification evidence subject is currently: ${evidence.subjectType}`);
|
|
761
773
|
if (evidence.outcome) lines.push(`Canonical verification evidence outcome is currently: ${evidence.outcome}`);
|
|
762
774
|
if (evidence.recordedAt) lines.push(`Canonical verification evidence recorded_at is currently: ${evidence.recordedAt}`);
|
|
@@ -847,6 +859,7 @@ function composeResumeCapsule(snapshot: CompletionStateSnapshot, sliceHistory: J
|
|
|
847
859
|
const verificationCommands = asStringArray(snapshot.active?.verification_commands);
|
|
848
860
|
const remainingBefore = asStringArray(snapshot.active?.remaining_contract_ids_before);
|
|
849
861
|
const evidence = verificationEvidenceContext(snapshot);
|
|
862
|
+
const startupPlan = startupPlanContext(snapshot);
|
|
850
863
|
const implementationSurfacesLine =
|
|
851
864
|
implementationSurfaces.length > 0 ? `- implementation_surfaces: ${implementationSurfaces.join(" | ")}` : undefined;
|
|
852
865
|
const verificationCommandsLine =
|
|
@@ -867,6 +880,7 @@ function composeResumeCapsule(snapshot: CompletionStateSnapshot, sliceHistory: J
|
|
|
867
880
|
activeSliceMatchesPlan: activeSliceMatchesPlan(snapshot),
|
|
868
881
|
activeSliceContractDrift: activeSliceContractDriftSummary(snapshot),
|
|
869
882
|
implementerHandoffSnapshot: handoffSnapshotState(snapshot.active),
|
|
883
|
+
startupPlan,
|
|
870
884
|
evidence,
|
|
871
885
|
activeSlice: {
|
|
872
886
|
sliceId: asString(snapshot.active?.slice_id) ?? asString(snapshot.activeSlice?.slice_id),
|
|
@@ -904,11 +918,11 @@ function completionKickoff(
|
|
|
904
918
|
: intent === "refocus" && missionAnchor
|
|
905
919
|
? `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
920
|
: "";
|
|
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${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.`;
|
|
921
|
+
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/startup-plan.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json before acting when they exist.\n- Treat .agent/startup-plan.json as the approved startup plan captured at /cook. completion-regrounder should use it as planning input, then derive canonical slices from current repo truth.\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
922
|
}
|
|
909
923
|
|
|
910
924
|
function completionResumePrompt(taskType: string, evaluationProfile: string): string {
|
|
911
|
-
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.`;
|
|
925
|
+
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/startup-plan.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json before acting.\n- Treat .agent/startup-plan.json as the approved workflow plan captured at /cook, then let completion-regrounder reconcile or rebuild canonical slices from repo truth when needed.\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.`;
|
|
912
926
|
}
|
|
913
927
|
|
|
914
928
|
export default function completionExtension(pi: ExtensionAPI) {
|
|
@@ -924,7 +938,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
924
938
|
structuredDiscussionFailureDetail: COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL,
|
|
925
939
|
mainChatRerunGuidance: COOK_MAIN_CHAT_RERUN_GUIDANCE,
|
|
926
940
|
cookCommandSpec: {
|
|
927
|
-
description: "/cook workflow:
|
|
941
|
+
description: "/cook workflow: capture the approved startup plan into .agent, let completion-regrounder split canonical slices, or resume the current workflow from canonical state",
|
|
928
942
|
},
|
|
929
943
|
buildContextProposalContinuationReason,
|
|
930
944
|
completionKickoff,
|
|
@@ -937,7 +951,6 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
937
951
|
completionTestWorkflowMissionOverride,
|
|
938
952
|
confirmContextProposal,
|
|
939
953
|
deriveCookContextProposal,
|
|
940
|
-
deriveCookStartupProposal,
|
|
941
954
|
emitCommandText,
|
|
942
955
|
finalizeContextProposalAnalysis,
|
|
943
956
|
getCtxCwd,
|
|
@@ -24,6 +24,26 @@ export type AdvisoryStartupBrief = {
|
|
|
24
24
|
evaluation_profile?: string;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
export type ApprovedStartupPlan = {
|
|
28
|
+
artifact_type: "completion-startup-plan";
|
|
29
|
+
schema_version: 1;
|
|
30
|
+
status: "approved";
|
|
31
|
+
source: AdvisoryStartupBrief["source"];
|
|
32
|
+
captured_at: string;
|
|
33
|
+
mission_anchor: string;
|
|
34
|
+
goal_text: string;
|
|
35
|
+
task_type?: string;
|
|
36
|
+
evaluation_profile?: string;
|
|
37
|
+
scope: string[];
|
|
38
|
+
constraints: string[];
|
|
39
|
+
acceptance: string[];
|
|
40
|
+
risks: string[];
|
|
41
|
+
notes: string[];
|
|
42
|
+
planned_surfaces: string[];
|
|
43
|
+
verification_intent: string[];
|
|
44
|
+
sequencing_hints: string[];
|
|
45
|
+
};
|
|
46
|
+
|
|
27
47
|
export function buildCookHandoffBoundaryReminder(): string {
|
|
28
48
|
return [
|
|
29
49
|
"You are in ordinary main chat unless the user explicitly runs /cook.",
|
|
@@ -33,10 +53,11 @@ export function buildCookHandoffBoundaryReminder(): string {
|
|
|
33
53
|
"In ordinary chat, do not load or follow completion-protocol, and do not call completion_role.",
|
|
34
54
|
"If the user wants direct implementation now, stay in ordinary chat and help directly instead of blocking on /cook.",
|
|
35
55
|
"If the user asks follow-up questions or wants to keep refining scope, continue helping naturally in ordinary chat.",
|
|
36
|
-
"If the user explicitly runs /cook, the extension should call a primary-agent
|
|
37
|
-
"Do not expect /cook to infer or guess startup intent from recent discussion alone
|
|
38
|
-
"Only provide a preview startup
|
|
39
|
-
"Any preview capsule is
|
|
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, 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
|
+
"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 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
|
+
"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.",
|
|
40
61
|
"When you continue in ordinary chat, do not pretend /cook already started and do not silently rewrite discussion into canonical workflow state.",
|
|
41
62
|
].join(" ");
|
|
42
63
|
}
|
|
@@ -85,7 +106,28 @@ function buildAdvisoryStartupBriefNotes(analysis: ContextProposalAnalysis): stri
|
|
|
85
106
|
...analysis.critique,
|
|
86
107
|
...analysis.possibleNoise.map((item) => `Possible noise: ${item}`),
|
|
87
108
|
];
|
|
88
|
-
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
|
+
}
|
|
111
|
+
|
|
112
|
+
function startupPlanSourceForProposal(source: ContextProposal["source"]): AdvisoryStartupBrief["source"] {
|
|
113
|
+
return source === "handoff_capsule"
|
|
114
|
+
? "primary_agent_handoff"
|
|
115
|
+
: source === "deferred_primary_agent_handoff"
|
|
116
|
+
? "deferred_primary_agent_handoff"
|
|
117
|
+
: "recent_discussion";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function extractDelimitedNoteValues(notes: string[], prefix: string): string[] {
|
|
121
|
+
return notes.flatMap((note) => {
|
|
122
|
+
if (!note.startsWith(prefix)) return [];
|
|
123
|
+
return note.slice(prefix.length).split("|").map((item) => item.trim()).filter(Boolean);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function extractSequencingHints(notes: string[]): string[] {
|
|
128
|
+
return notes.filter((note) =>
|
|
129
|
+
note.startsWith("First slice goal:") || note.startsWith("First slice non-goals:") || note.startsWith("Why this slice first:"),
|
|
130
|
+
);
|
|
89
131
|
}
|
|
90
132
|
|
|
91
133
|
export function buildAdvisoryStartupBrief(args: {
|
|
@@ -95,12 +137,7 @@ export function buildAdvisoryStartupBrief(args: {
|
|
|
95
137
|
}): AdvisoryStartupBrief {
|
|
96
138
|
return {
|
|
97
139
|
kind: "startup_brief",
|
|
98
|
-
source:
|
|
99
|
-
args.proposal.source === "handoff_capsule"
|
|
100
|
-
? "primary_agent_handoff"
|
|
101
|
-
: args.proposal.source === "deferred_primary_agent_handoff"
|
|
102
|
-
? "deferred_primary_agent_handoff"
|
|
103
|
-
: "recent_discussion",
|
|
140
|
+
source: startupPlanSourceForProposal(args.proposal.source),
|
|
104
141
|
confirmed: true,
|
|
105
142
|
captured_at: args.capturedAt ?? new Date().toISOString(),
|
|
106
143
|
goal_text: args.proposal.goalText,
|
|
@@ -115,6 +152,81 @@ export function buildAdvisoryStartupBrief(args: {
|
|
|
115
152
|
};
|
|
116
153
|
}
|
|
117
154
|
|
|
155
|
+
export function buildApprovedStartupPlan(args: {
|
|
156
|
+
proposal: Pick<ContextProposal, "goalText" | "mission" | "scope" | "constraints" | "acceptance" | "source">;
|
|
157
|
+
analysis: ContextProposalAnalysis;
|
|
158
|
+
missionAnchor?: string;
|
|
159
|
+
capturedAt?: string;
|
|
160
|
+
}): ApprovedStartupPlan {
|
|
161
|
+
const capturedAt = args.capturedAt ?? new Date().toISOString();
|
|
162
|
+
const notes = buildAdvisoryStartupBriefNotes(args.analysis);
|
|
163
|
+
return {
|
|
164
|
+
artifact_type: "completion-startup-plan",
|
|
165
|
+
schema_version: 1,
|
|
166
|
+
status: "approved",
|
|
167
|
+
source: startupPlanSourceForProposal(args.proposal.source),
|
|
168
|
+
captured_at: capturedAt,
|
|
169
|
+
mission_anchor: args.missionAnchor ?? args.proposal.mission,
|
|
170
|
+
goal_text: args.proposal.goalText,
|
|
171
|
+
task_type: args.analysis.taskType,
|
|
172
|
+
evaluation_profile: args.analysis.evaluationProfile,
|
|
173
|
+
scope: [...args.proposal.scope],
|
|
174
|
+
constraints: [...args.proposal.constraints],
|
|
175
|
+
acceptance: [...args.proposal.acceptance],
|
|
176
|
+
risks: [...args.analysis.risks],
|
|
177
|
+
notes: [...notes],
|
|
178
|
+
planned_surfaces: extractDelimitedNoteValues(notes, "Implementation surfaces:"),
|
|
179
|
+
verification_intent: extractDelimitedNoteValues(notes, "Verification commands:"),
|
|
180
|
+
sequencing_hints: extractSequencingHints(notes),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function buildApprovedStartupPlanMarkdown(plan: ApprovedStartupPlan): string {
|
|
185
|
+
const lines = [
|
|
186
|
+
"# Approved Startup Plan",
|
|
187
|
+
"",
|
|
188
|
+
`Mission anchor: ${plan.mission_anchor}`,
|
|
189
|
+
`Source: ${plan.source}`,
|
|
190
|
+
`Captured at: ${plan.captured_at}`,
|
|
191
|
+
];
|
|
192
|
+
if (plan.task_type) lines.push(`Task type: ${plan.task_type}`);
|
|
193
|
+
if (plan.evaluation_profile) lines.push(`Evaluation profile: ${plan.evaluation_profile}`);
|
|
194
|
+
lines.push("", "## Goal", "", plan.goal_text);
|
|
195
|
+
if (plan.scope.length > 0) {
|
|
196
|
+
lines.push("", "## Scope", "");
|
|
197
|
+
for (const item of plan.scope) lines.push(`- ${item}`);
|
|
198
|
+
}
|
|
199
|
+
if (plan.constraints.length > 0) {
|
|
200
|
+
lines.push("", "## Constraints", "");
|
|
201
|
+
for (const item of plan.constraints) lines.push(`- ${item}`);
|
|
202
|
+
}
|
|
203
|
+
if (plan.acceptance.length > 0) {
|
|
204
|
+
lines.push("", "## Acceptance", "");
|
|
205
|
+
for (const item of plan.acceptance) lines.push(`- ${item}`);
|
|
206
|
+
}
|
|
207
|
+
if (plan.risks.length > 0) {
|
|
208
|
+
lines.push("", "## Risks", "");
|
|
209
|
+
for (const item of plan.risks) lines.push(`- ${item}`);
|
|
210
|
+
}
|
|
211
|
+
if (plan.planned_surfaces.length > 0) {
|
|
212
|
+
lines.push("", "## Planned surfaces", "");
|
|
213
|
+
for (const item of plan.planned_surfaces) lines.push(`- ${item}`);
|
|
214
|
+
}
|
|
215
|
+
if (plan.verification_intent.length > 0) {
|
|
216
|
+
lines.push("", "## Verification intent", "");
|
|
217
|
+
for (const item of plan.verification_intent) lines.push(`- ${item}`);
|
|
218
|
+
}
|
|
219
|
+
if (plan.sequencing_hints.length > 0) {
|
|
220
|
+
lines.push("", "## Sequencing hints", "");
|
|
221
|
+
for (const item of plan.sequencing_hints) lines.push(`- ${item}`);
|
|
222
|
+
}
|
|
223
|
+
if (plan.notes.length > 0) {
|
|
224
|
+
lines.push("", "## Notes", "");
|
|
225
|
+
for (const item of plan.notes) lines.push(`- ${item}`);
|
|
226
|
+
}
|
|
227
|
+
return `${lines.join("\n")}\n`;
|
|
228
|
+
}
|
|
229
|
+
|
|
118
230
|
export function buildContextProposalCritiqueText(analysis: ContextProposalAnalysis): string {
|
|
119
231
|
const lines: string[] = [];
|
|
120
232
|
if (analysis.critique.length > 0) {
|
|
@@ -147,7 +259,7 @@ export function buildContextProposalCritiqueText(analysis: ContextProposalAnalys
|
|
|
147
259
|
for (const item of analysis.suppressedNegatedTopics) lines.push(`- ${item}`);
|
|
148
260
|
}
|
|
149
261
|
if (lines.length === 0) {
|
|
150
|
-
return "No additional operator notes or risks were derived for this startup
|
|
262
|
+
return "No additional operator notes or risks were derived for this startup plan.";
|
|
151
263
|
}
|
|
152
264
|
return lines.join("\n");
|
|
153
265
|
}
|
|
@@ -196,7 +308,7 @@ export function buildContextProposalConfirmationActions(mainChatRerunGuidance: s
|
|
|
196
308
|
{
|
|
197
309
|
id: "start",
|
|
198
310
|
label: "Start",
|
|
199
|
-
description: "Accept this startup
|
|
311
|
+
description: "Accept this startup plan and let /cook write or refocus canonical workflow state.",
|
|
200
312
|
},
|
|
201
313
|
{
|
|
202
314
|
id: "cancel",
|
|
@@ -216,8 +328,8 @@ export function buildContextProposalConfirmationLayout(args: {
|
|
|
216
328
|
}): ContextProposalConfirmationLayout {
|
|
217
329
|
return {
|
|
218
330
|
title: args.title,
|
|
219
|
-
intro: "Review the startup
|
|
220
|
-
proposalHeading: "Startup
|
|
331
|
+
intro: "Review the startup plan (mission, scope, constraints, acceptance, and notes/risks) plus the routing details before /cook writes canonical workflow state. This gate is approval-only: either Start it as-is or Cancel, discuss changes in the main chat, and rerun /cook.",
|
|
332
|
+
proposalHeading: "Startup plan",
|
|
221
333
|
proposalBody: buildContextProposalDisplayText(args.proposal),
|
|
222
334
|
critiqueHeading: "Notes and risks",
|
|
223
335
|
critiqueBody: buildContextProposalCritiqueText(args.analysis),
|
|
@@ -417,6 +529,15 @@ type CompletionVerificationEvidenceSummary = {
|
|
|
417
529
|
summary: string;
|
|
418
530
|
};
|
|
419
531
|
|
|
532
|
+
type CompletionStartupPlanSummary = {
|
|
533
|
+
path: string;
|
|
534
|
+
status: string;
|
|
535
|
+
source?: string;
|
|
536
|
+
plannedSurfaces: string[];
|
|
537
|
+
verificationIntent: string[];
|
|
538
|
+
summary: string;
|
|
539
|
+
};
|
|
540
|
+
|
|
420
541
|
export function buildSystemReminder(args: {
|
|
421
542
|
missionAnchor?: string;
|
|
422
543
|
taskType?: string;
|
|
@@ -440,11 +561,12 @@ export function buildSystemReminder(args: {
|
|
|
440
561
|
implementationSurfacesLine?: string;
|
|
441
562
|
verificationCommandsLine?: string;
|
|
442
563
|
evidence: CompletionVerificationEvidenceSummary;
|
|
564
|
+
startupPlan: CompletionStartupPlanSummary;
|
|
443
565
|
evaluationRoleReminderText?: string;
|
|
444
566
|
}): string {
|
|
445
567
|
const lines = [
|
|
446
568
|
"Completion workflow detected.",
|
|
447
|
-
"Canonical truth lives in .agent/state.json, .agent/plan.json, .agent/active-slice.json, .agent/slice-history.jsonl, .agent/stop-check-history.jsonl, and .agent/verification-evidence.json.",
|
|
569
|
+
"Canonical truth lives in .agent/state.json, .agent/startup-plan.json, .agent/plan.json, .agent/active-slice.json, .agent/slice-history.jsonl, .agent/stop-check-history.jsonl, and .agent/verification-evidence.json.",
|
|
448
570
|
`Mission anchor: ${args.missionAnchor ?? "(unknown)"}`,
|
|
449
571
|
`Task type: ${args.taskType ?? "(missing)"}`,
|
|
450
572
|
`Evaluation profile: ${args.evaluationProfile ?? "(missing)"}`,
|
|
@@ -461,6 +583,7 @@ export function buildSystemReminder(args: {
|
|
|
461
583
|
"Only stop for the user when continuation_policy is await_user_input, blocked, paused, or done.",
|
|
462
584
|
"If canonical state is stale, invalid, ambiguous, or missing, route to completion-regrounder.",
|
|
463
585
|
"When recovering from compaction, prefer a deterministic restart from canonical files over conversational inference.",
|
|
586
|
+
`Approved startup plan: ${args.startupPlan.path} (${args.startupPlan.status})`,
|
|
464
587
|
];
|
|
465
588
|
if (args.exactActiveContract) {
|
|
466
589
|
lines.push("Selected/in-progress/committed/done .agent/active-slice.json is the canonical implementation contract.");
|
|
@@ -474,6 +597,14 @@ export function buildSystemReminder(args: {
|
|
|
474
597
|
else if (args.implementationSurfaces.length > 0) lines.push(`Active implementation surfaces: ${args.implementationSurfaces.join(", ")}`);
|
|
475
598
|
if (args.verificationCommandsLine) lines.push(args.verificationCommandsLine);
|
|
476
599
|
else if (args.verificationCommands.length > 0) lines.push(`Active verification commands: ${args.verificationCommands.join(" | ")}`);
|
|
600
|
+
if (args.startupPlan.source) lines.push(`Approved startup plan source: ${args.startupPlan.source}`);
|
|
601
|
+
if (args.startupPlan.plannedSurfaces.length > 0) {
|
|
602
|
+
lines.push(`Approved startup plan surfaces: ${args.startupPlan.plannedSurfaces.join(" | ")}`);
|
|
603
|
+
}
|
|
604
|
+
if (args.startupPlan.verificationIntent.length > 0) {
|
|
605
|
+
lines.push(`Approved startup plan verification intent: ${args.startupPlan.verificationIntent.join(" | ")}`);
|
|
606
|
+
}
|
|
607
|
+
lines.push(`Approved startup plan summary: ${args.startupPlan.summary}`);
|
|
477
608
|
lines.push(`Verification evidence artifact: ${args.evidence.path} (${args.evidence.status})`);
|
|
478
609
|
if (args.evidence.subjectType) lines.push(`Verification evidence subject: ${args.evidence.subjectType}`);
|
|
479
610
|
if (args.evidence.outcome) lines.push(`Verification evidence outcome: ${args.evidence.outcome}`);
|
|
@@ -502,6 +633,7 @@ export function buildResumeCapsule(args: {
|
|
|
502
633
|
activeSliceMatchesPlan: "yes" | "no" | "unknown";
|
|
503
634
|
activeSliceContractDrift: string;
|
|
504
635
|
implementerHandoffSnapshot: "present" | "missing_or_unclear";
|
|
636
|
+
startupPlan: CompletionStartupPlanSummary;
|
|
505
637
|
evidence: CompletionVerificationEvidenceSummary;
|
|
506
638
|
activeSlice: {
|
|
507
639
|
sliceId?: string;
|
|
@@ -544,6 +676,14 @@ export function buildResumeCapsule(args: {
|
|
|
544
676
|
`implementer_handoff_snapshot: ${args.implementerHandoffSnapshot}`,
|
|
545
677
|
`history_counts: reviewed=${args.history.reviewed}, audited=${args.history.audited}, accepted=${args.history.accepted}, reopened=${args.history.reopened}, judgments=${args.history.judgments}`,
|
|
546
678
|
"",
|
|
679
|
+
"startup_plan:",
|
|
680
|
+
`- path: ${args.startupPlan.path}`,
|
|
681
|
+
`- status: ${args.startupPlan.status}`,
|
|
682
|
+
`- source: ${args.startupPlan.source ?? "(missing)"}`,
|
|
683
|
+
`- planned_surfaces: ${args.startupPlan.plannedSurfaces.length > 0 ? args.startupPlan.plannedSurfaces.join(" | ") : "(none)"}`,
|
|
684
|
+
`- verification_intent: ${args.startupPlan.verificationIntent.length > 0 ? args.startupPlan.verificationIntent.join(" | ") : "(none)"}`,
|
|
685
|
+
`- summary: ${args.startupPlan.summary}`,
|
|
686
|
+
"",
|
|
547
687
|
"verification_evidence:",
|
|
548
688
|
`- path: ${args.evidence.path}`,
|
|
549
689
|
`- status: ${args.evidence.status}`,
|
|
@@ -591,8 +731,9 @@ export function buildResumeCapsule(args: {
|
|
|
591
731
|
"- Treat this block as continuity support derived from canonical .agent state.",
|
|
592
732
|
"- For selected/in-progress/committed/done slices, .agent/active-slice.json is the canonical implementation contract and the selected plan slice must mirror it exactly.",
|
|
593
733
|
"- Preserve exact slice_id, goal, contract_ids, acceptance criteria, blocked_on, priority, why_now, implementation surfaces, verification commands, locked notes, must-fix findings, basis_commit, and before-slice counters where still true.",
|
|
734
|
+
"- .agent/startup-plan.json is the approved workflow plan captured at /cook entry. completion-regrounder must treat it as planning input, then reconcile canonical slices against repo truth instead of copying it blindly into plan.json.",
|
|
594
735
|
"- When populated, .agent/verification-evidence.json is the durable canonical verification record for the selected slice or current HEAD and should be consumed instead of temp-only artifacts or conversational summaries.",
|
|
595
|
-
"- After compaction, re-read .agent/state.json, .agent/plan.json, .agent/active-slice.json, .agent/slice-history.jsonl, .agent/stop-check-history.jsonl, and .agent/verification-evidence.json before resuming long-running completion work.",
|
|
736
|
+
"- After compaction, re-read .agent/state.json, .agent/startup-plan.json, .agent/plan.json, .agent/active-slice.json, .agent/slice-history.jsonl, .agent/stop-check-history.jsonl, and .agent/verification-evidence.json before resuming long-running completion work.",
|
|
596
737
|
"- Invoke completion-regrounder before continuing when requires_reground is true or unknown.",
|
|
597
738
|
"- Invoke completion-regrounder before continuing when next_mandatory_role or next_mandatory_action is unknown or ambiguous.",
|
|
598
739
|
"- Invoke completion-regrounder before continuing when active_slice_matches_plan is no, active_slice_contract_drift_fields is not none, or implementer_handoff_snapshot is missing_or_unclear.",
|