@linimin/pi-letscook 0.1.65 → 0.1.67
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 +66 -17
- package/extensions/completion/index.ts +99 -21
- package/extensions/completion/policy-guards.ts +6 -1
- package/extensions/completion/prompt-surfaces.ts +157 -15
- package/extensions/completion/proposal.ts +21 -21
- package/extensions/completion/role-runner.ts +11 -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 +1 -1
- package/scripts/canonical-evidence-artifact-test.sh +49 -4
- package/scripts/context-proposal-test.sh +57 -58
- package/scripts/refocus-test.sh +17 -17
- package/scripts/release-check.sh +24 -11
- package/scripts/smoke-test.sh +70 -9
- package/skills/completion-protocol/SKILL.md +13 -5
- package/skills/completion-protocol/references/completion.md +37 -2
- package/skills/cook-handoff-boundary/SKILL.md +20 -16
|
@@ -18,7 +18,9 @@ import {
|
|
|
18
18
|
collectRecentDiscussionEntries,
|
|
19
19
|
collectRecentSessionMessages,
|
|
20
20
|
assessLatestCookHandoffProposal,
|
|
21
|
+
deriveCookContextProposalFromRecentDiscussion,
|
|
21
22
|
finalizeContextProposalAnalysis,
|
|
23
|
+
hasStructuredContextProposalSignal,
|
|
22
24
|
isWeakMissionAnchor,
|
|
23
25
|
missionAnchorsLikelyEquivalent,
|
|
24
26
|
missionAnchorsStrictlyEquivalent,
|
|
@@ -47,7 +49,8 @@ import {
|
|
|
47
49
|
maybeWriteContextProposalSnapshot,
|
|
48
50
|
} from "./prompt-surfaces";
|
|
49
51
|
import { toolCallBlockReason } from "./policy-guards";
|
|
50
|
-
import { analyzeContextProposalWithAgent,
|
|
52
|
+
import { analyzeContextProposalWithAgent, runCompletionRole } from "./role-runner";
|
|
53
|
+
import { generateCookHandoffWithAgent } from "./role-runner";
|
|
51
54
|
import {
|
|
52
55
|
applyLiveRoleEvent,
|
|
53
56
|
buildInlineRunningLines,
|
|
@@ -209,7 +212,7 @@ function maybeWriteTestSnapshot(targetPath: string | undefined, content: string)
|
|
|
209
212
|
|
|
210
213
|
const COOK_MAIN_CHAT_RERUN_GUIDANCE = "Discuss changes in the main chat and rerun /cook.";
|
|
211
214
|
const COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL =
|
|
212
|
-
"/cook failed closed because the
|
|
215
|
+
"/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
216
|
|
|
214
217
|
function isWorkflowDone(snapshot: CompletionStateSnapshot | undefined): boolean {
|
|
215
218
|
return asString(snapshot?.state?.continuation_policy) === "done";
|
|
@@ -418,10 +421,23 @@ async function deriveCookContextProposal(
|
|
|
418
421
|
`latest verified slice: ${asString(snapshot.state?.latest_verified_slice) ?? "(none)"}`,
|
|
419
422
|
`active slice goal: ${asString(snapshot.active?.goal) ?? "(none)"}`,
|
|
420
423
|
`active slice why_now: ${asString(snapshot.active?.why_now) ?? "(none)"}`,
|
|
424
|
+
`approved startup plan summary: ${asString(snapshot.startupPlan?.goal_text) ?? "(none)"}`,
|
|
421
425
|
`verification goal: ${asString(snapshot.verificationEvidence?.goal) ?? "(none)"}`,
|
|
422
426
|
`verification summary: ${asString(snapshot.verificationEvidence?.summary) ?? "(none)"}`,
|
|
423
427
|
]
|
|
424
428
|
: [];
|
|
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;
|
|
425
441
|
const raw = await generateCookHandoffWithAgent({
|
|
426
442
|
ctx,
|
|
427
443
|
projectName,
|
|
@@ -434,20 +450,49 @@ async function deriveCookContextProposal(
|
|
|
434
450
|
getCtxHasUI,
|
|
435
451
|
getCtxUi,
|
|
436
452
|
});
|
|
437
|
-
if (
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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 };
|
|
495
|
+
}
|
|
451
496
|
return {};
|
|
452
497
|
}
|
|
453
498
|
|
|
@@ -485,13 +530,19 @@ async function confirmContextProposal(
|
|
|
485
530
|
async function scaffoldCompletionFiles(
|
|
486
531
|
root: string,
|
|
487
532
|
missionAnchor: string,
|
|
488
|
-
options?: {
|
|
533
|
+
options?: {
|
|
534
|
+
analysis?: ContextProposalAnalysis;
|
|
535
|
+
continuationReason?: string;
|
|
536
|
+
advisoryStartupBrief?: JsonRecord;
|
|
537
|
+
approvedStartupPlan?: JsonRecord;
|
|
538
|
+
},
|
|
489
539
|
) {
|
|
490
540
|
const routing = finalizeContextProposalAnalysis(options?.analysis, [missionAnchor]);
|
|
491
541
|
return await scaffoldCompletionFilesOnDisk(root, missionAnchor, {
|
|
492
542
|
analysis: { taskType: routing.taskType, evaluationProfile: routing.evaluationProfile },
|
|
493
543
|
continuationReason: options?.continuationReason,
|
|
494
544
|
advisoryStartupBrief: options?.advisoryStartupBrief,
|
|
545
|
+
approvedStartupPlan: options?.approvedStartupPlan,
|
|
495
546
|
});
|
|
496
547
|
}
|
|
497
548
|
|
|
@@ -654,6 +705,20 @@ function verificationEvidenceContext(snapshot: CompletionStateSnapshot) {
|
|
|
654
705
|
};
|
|
655
706
|
}
|
|
656
707
|
|
|
708
|
+
function startupPlanContext(snapshot: CompletionStateSnapshot) {
|
|
709
|
+
const startupPlan = snapshot.startupPlan;
|
|
710
|
+
return {
|
|
711
|
+
path: path.relative(snapshot.files.root, snapshot.files.startupPlanPath) || ".agent/startup-plan.json",
|
|
712
|
+
status: startupPlan ? "present" : "missing",
|
|
713
|
+
source: asString(startupPlan?.source),
|
|
714
|
+
plannedSurfaces: asStringArray(startupPlan?.planned_surfaces),
|
|
715
|
+
verificationIntent: asStringArray(startupPlan?.verification_intent),
|
|
716
|
+
summary:
|
|
717
|
+
asString(startupPlan?.goal_text) ??
|
|
718
|
+
(startupPlan ? "Approved startup plan is present but its goal_text is missing." : "Approved startup plan is missing."),
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
657
722
|
function buildEvaluationRoleContextLines(snapshot: CompletionStateSnapshot, role: RubricEvaluationRole): string[] {
|
|
658
723
|
return buildExtractedEvaluationRoleContextLines(snapshot, role, {
|
|
659
724
|
asString,
|
|
@@ -684,6 +749,7 @@ function composeSystemReminder(snapshot: CompletionStateSnapshot, sliceHistory:
|
|
|
684
749
|
const exactActiveContract = activeCarriesExactHandoff(snapshot.active);
|
|
685
750
|
const activeContractDrift = activeSliceContractDriftSummary(snapshot);
|
|
686
751
|
const evidence = verificationEvidenceContext(snapshot);
|
|
752
|
+
const startupPlan = startupPlanContext(snapshot);
|
|
687
753
|
const activePriorityLine = activePriority !== undefined ? `Active slice priority: ${activePriority}` : undefined;
|
|
688
754
|
const activeWhyNowLine = activeWhyNow ? `Active slice why_now: ${activeWhyNow}` : undefined;
|
|
689
755
|
const implementationSurfacesLine =
|
|
@@ -713,6 +779,7 @@ function composeSystemReminder(snapshot: CompletionStateSnapshot, sliceHistory:
|
|
|
713
779
|
implementationSurfacesLine,
|
|
714
780
|
verificationCommandsLine,
|
|
715
781
|
evidence,
|
|
782
|
+
startupPlan,
|
|
716
783
|
evaluationRoleReminderText: isRubricEvaluationRole(nextRole) ? buildEvaluationRoleReminderText(snapshot, nextRole) : undefined,
|
|
717
784
|
});
|
|
718
785
|
}
|
|
@@ -732,17 +799,19 @@ function buildPostCompactionDriverInstructions(snapshot: CompletionStateSnapshot
|
|
|
732
799
|
const exactActiveContract = activeCarriesExactHandoff(snapshot.active);
|
|
733
800
|
const activeContractDrift = activeSliceContractDriftSummary(snapshot);
|
|
734
801
|
const evidence = verificationEvidenceContext(snapshot);
|
|
802
|
+
const startupPlan = startupPlanContext(snapshot);
|
|
735
803
|
const lines = [
|
|
736
804
|
"POST-COMPACTION RECOVERY MODE is active.",
|
|
737
805
|
`Compaction marker time: ${markerAt}`,
|
|
738
806
|
"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.",
|
|
807
|
+
"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
808
|
`Canonical task_type is currently: ${taskType}`,
|
|
741
809
|
`Canonical evaluation_profile is currently: ${evaluationProfile}`,
|
|
742
810
|
`Canonical next mandatory role is currently: ${nextRole}`,
|
|
743
811
|
`Canonical next mandatory action is currently: ${nextAction}`,
|
|
744
812
|
`Canonical continuation policy is currently: ${continuation}`,
|
|
745
813
|
`Canonical active slice is currently: ${activeSliceId}`,
|
|
814
|
+
`Canonical approved startup plan is currently: ${startupPlan.path} (${startupPlan.status})`,
|
|
746
815
|
`Canonical verification evidence artifact is currently: ${evidence.path} (${evidence.status})`,
|
|
747
816
|
"Do not trust pre-compaction memory over canonical files.",
|
|
748
817
|
"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 +826,10 @@ function buildPostCompactionDriverInstructions(snapshot: CompletionStateSnapshot
|
|
|
757
826
|
if (activeWhyNow) lines.push(`Canonical active-slice why_now is currently: ${activeWhyNow}`);
|
|
758
827
|
if (implementationSurfaces.length > 0) lines.push(`Canonical implementation surfaces are currently: ${implementationSurfaces.join(", ")}`);
|
|
759
828
|
if (verificationCommands.length > 0) lines.push(`Canonical verification commands are currently: ${verificationCommands.join(" | ")}`);
|
|
829
|
+
if (startupPlan.source) lines.push(`Canonical approved startup plan source is currently: ${startupPlan.source}`);
|
|
830
|
+
if (startupPlan.plannedSurfaces.length > 0) lines.push(`Canonical approved startup plan surfaces are currently: ${startupPlan.plannedSurfaces.join(" | ")}`);
|
|
831
|
+
if (startupPlan.verificationIntent.length > 0) lines.push(`Canonical approved startup plan verification intent is currently: ${startupPlan.verificationIntent.join(" | ")}`);
|
|
832
|
+
lines.push(`Canonical approved startup plan summary is currently: ${startupPlan.summary}`);
|
|
760
833
|
if (evidence.subjectType) lines.push(`Canonical verification evidence subject is currently: ${evidence.subjectType}`);
|
|
761
834
|
if (evidence.outcome) lines.push(`Canonical verification evidence outcome is currently: ${evidence.outcome}`);
|
|
762
835
|
if (evidence.recordedAt) lines.push(`Canonical verification evidence recorded_at is currently: ${evidence.recordedAt}`);
|
|
@@ -847,6 +920,7 @@ function composeResumeCapsule(snapshot: CompletionStateSnapshot, sliceHistory: J
|
|
|
847
920
|
const verificationCommands = asStringArray(snapshot.active?.verification_commands);
|
|
848
921
|
const remainingBefore = asStringArray(snapshot.active?.remaining_contract_ids_before);
|
|
849
922
|
const evidence = verificationEvidenceContext(snapshot);
|
|
923
|
+
const startupPlan = startupPlanContext(snapshot);
|
|
850
924
|
const implementationSurfacesLine =
|
|
851
925
|
implementationSurfaces.length > 0 ? `- implementation_surfaces: ${implementationSurfaces.join(" | ")}` : undefined;
|
|
852
926
|
const verificationCommandsLine =
|
|
@@ -867,6 +941,7 @@ function composeResumeCapsule(snapshot: CompletionStateSnapshot, sliceHistory: J
|
|
|
867
941
|
activeSliceMatchesPlan: activeSliceMatchesPlan(snapshot),
|
|
868
942
|
activeSliceContractDrift: activeSliceContractDriftSummary(snapshot),
|
|
869
943
|
implementerHandoffSnapshot: handoffSnapshotState(snapshot.active),
|
|
944
|
+
startupPlan,
|
|
870
945
|
evidence,
|
|
871
946
|
activeSlice: {
|
|
872
947
|
sliceId: asString(snapshot.active?.slice_id) ?? asString(snapshot.activeSlice?.slice_id),
|
|
@@ -904,11 +979,11 @@ function completionKickoff(
|
|
|
904
979
|
: intent === "refocus" && missionAnchor
|
|
905
980
|
? `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
981
|
: "";
|
|
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.`;
|
|
982
|
+
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
983
|
}
|
|
909
984
|
|
|
910
985
|
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.`;
|
|
986
|
+
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
987
|
}
|
|
913
988
|
|
|
914
989
|
export default function completionExtension(pi: ExtensionAPI) {
|
|
@@ -924,7 +999,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
924
999
|
structuredDiscussionFailureDetail: COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL,
|
|
925
1000
|
mainChatRerunGuidance: COOK_MAIN_CHAT_RERUN_GUIDANCE,
|
|
926
1001
|
cookCommandSpec: {
|
|
927
|
-
description: "/cook workflow:
|
|
1002
|
+
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
1003
|
},
|
|
929
1004
|
buildContextProposalContinuationReason,
|
|
930
1005
|
completionKickoff,
|
|
@@ -1055,11 +1130,13 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
1055
1130
|
const snapshot = await loadCompletionSnapshot(cwd);
|
|
1056
1131
|
const completionActive = Boolean(snapshot) && asString(snapshot?.state?.continuation_policy) !== "done";
|
|
1057
1132
|
const root = snapshot?.files.root ?? findRepoRoot(cwd) ?? cwd;
|
|
1133
|
+
const completionRoleDispatchAllowed = Boolean(role) || (hasCompletionRoutingActivation(snapshot) && isCompletionDriverPromptTurn(ctx));
|
|
1058
1134
|
const reason = toolCallBlockReason({
|
|
1059
1135
|
toolName: event.toolName,
|
|
1060
1136
|
input: isRecord(event.input) ? event.input : undefined,
|
|
1061
1137
|
role,
|
|
1062
1138
|
completionActive,
|
|
1139
|
+
completionRoleDispatchAllowed,
|
|
1063
1140
|
root,
|
|
1064
1141
|
});
|
|
1065
1142
|
if (reason) return { block: true, reason };
|
|
@@ -1074,6 +1151,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
1074
1151
|
"Use completion_role when driving the completion workflow and a mandatory completion role must act next.",
|
|
1075
1152
|
"Use completion_role only for completion-bootstrapper, completion-regrounder, completion-implementer, completion-reviewer, completion-auditor, or completion-stop-judge.",
|
|
1076
1153
|
"Do not use completion_role from inside a completion role; only the workflow driver may dispatch roles.",
|
|
1154
|
+
"Do not call completion_role from ordinary chat; it is reserved for explicit /cook workflow driver turns.",
|
|
1077
1155
|
],
|
|
1078
1156
|
parameters: Type.Object({
|
|
1079
1157
|
role: StringEnum(ROLE_NAMES, { description: "The completion role to invoke." }),
|
|
@@ -62,14 +62,19 @@ export function toolCallBlockReason(args: {
|
|
|
62
62
|
input?: JsonRecord;
|
|
63
63
|
role?: string;
|
|
64
64
|
completionActive: boolean;
|
|
65
|
+
completionRoleDispatchAllowed: boolean;
|
|
65
66
|
root: string;
|
|
66
67
|
}): string | undefined {
|
|
67
|
-
const { toolName, input, role, completionActive, root } = args;
|
|
68
|
+
const { toolName, input, role, completionActive, completionRoleDispatchAllowed, root } = args;
|
|
68
69
|
|
|
69
70
|
if (toolName === "completion_role" && role) {
|
|
70
71
|
return `Nested completion role dispatch is forbidden for ${role}.`;
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
if (toolName === "completion_role" && !completionRoleDispatchAllowed) {
|
|
75
|
+
return "completion_role may only be used from an explicit /cook workflow driver turn.";
|
|
76
|
+
}
|
|
77
|
+
|
|
73
78
|
if (toolName === "edit" || toolName === "write") {
|
|
74
79
|
const rawPath = asString(input?.path);
|
|
75
80
|
if (!rawPath) return undefined;
|
|
@@ -24,18 +24,40 @@ 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.",
|
|
30
50
|
"Ordinary chat may clarify requirements, discuss tradeoffs, refine scope, and directly implement requested repo changes, including multi-file work, when that is the most helpful response.",
|
|
31
51
|
"Do not proactively tell the user to run /cook just because a task looks workflow-worthy, and do not emit a ```cook_handoff``` capsule by default in ordinary chat.",
|
|
32
52
|
"/cook is optional workflow mode for resumability, review, audit, canonical .agent state, or deliberate multi-session control; it is not required just to edit repo files in ordinary chat.",
|
|
53
|
+
"In ordinary chat, do not load or follow completion-protocol, and do not call completion_role.",
|
|
33
54
|
"If the user wants direct implementation now, stay in ordinary chat and help directly instead of blocking on /cook.",
|
|
34
55
|
"If the user asks follow-up questions or wants to keep refining scope, continue helping naturally in ordinary chat.",
|
|
35
|
-
"If the user explicitly runs /cook, the extension should call a primary-agent
|
|
36
|
-
"Do not expect /cook to infer or guess startup intent from recent discussion alone; /cook should use
|
|
37
|
-
"Only provide a preview startup
|
|
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.",
|
|
58
|
+
"Only provide a preview startup plan or ```cook_handoff``` capsule in ordinary chat when the user explicitly asks for that preview behavior.",
|
|
38
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.",
|
|
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.",
|
|
39
61
|
"When you continue in ordinary chat, do not pretend /cook already started and do not silently rewrite discussion into canonical workflow state.",
|
|
40
62
|
].join(" ");
|
|
41
63
|
}
|
|
@@ -87,6 +109,27 @@ function buildAdvisoryStartupBriefNotes(analysis: ContextProposalAnalysis): stri
|
|
|
87
109
|
return notes.length > 0 ? notes : ["No additional operator notes were derived from recent discussion."];
|
|
88
110
|
}
|
|
89
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
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
90
133
|
export function buildAdvisoryStartupBrief(args: {
|
|
91
134
|
proposal: Pick<ContextProposal, "goalText" | "mission" | "scope" | "constraints" | "acceptance" | "source">;
|
|
92
135
|
analysis: ContextProposalAnalysis;
|
|
@@ -94,12 +137,7 @@ export function buildAdvisoryStartupBrief(args: {
|
|
|
94
137
|
}): AdvisoryStartupBrief {
|
|
95
138
|
return {
|
|
96
139
|
kind: "startup_brief",
|
|
97
|
-
source:
|
|
98
|
-
args.proposal.source === "handoff_capsule"
|
|
99
|
-
? "primary_agent_handoff"
|
|
100
|
-
: args.proposal.source === "deferred_primary_agent_handoff"
|
|
101
|
-
? "deferred_primary_agent_handoff"
|
|
102
|
-
: "recent_discussion",
|
|
140
|
+
source: startupPlanSourceForProposal(args.proposal.source),
|
|
103
141
|
confirmed: true,
|
|
104
142
|
captured_at: args.capturedAt ?? new Date().toISOString(),
|
|
105
143
|
goal_text: args.proposal.goalText,
|
|
@@ -114,6 +152,81 @@ export function buildAdvisoryStartupBrief(args: {
|
|
|
114
152
|
};
|
|
115
153
|
}
|
|
116
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
|
+
|
|
117
230
|
export function buildContextProposalCritiqueText(analysis: ContextProposalAnalysis): string {
|
|
118
231
|
const lines: string[] = [];
|
|
119
232
|
if (analysis.critique.length > 0) {
|
|
@@ -146,7 +259,7 @@ export function buildContextProposalCritiqueText(analysis: ContextProposalAnalys
|
|
|
146
259
|
for (const item of analysis.suppressedNegatedTopics) lines.push(`- ${item}`);
|
|
147
260
|
}
|
|
148
261
|
if (lines.length === 0) {
|
|
149
|
-
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.";
|
|
150
263
|
}
|
|
151
264
|
return lines.join("\n");
|
|
152
265
|
}
|
|
@@ -195,7 +308,7 @@ export function buildContextProposalConfirmationActions(mainChatRerunGuidance: s
|
|
|
195
308
|
{
|
|
196
309
|
id: "start",
|
|
197
310
|
label: "Start",
|
|
198
|
-
description: "Accept this startup
|
|
311
|
+
description: "Accept this startup plan and let /cook write or refocus canonical workflow state.",
|
|
199
312
|
},
|
|
200
313
|
{
|
|
201
314
|
id: "cancel",
|
|
@@ -215,8 +328,8 @@ export function buildContextProposalConfirmationLayout(args: {
|
|
|
215
328
|
}): ContextProposalConfirmationLayout {
|
|
216
329
|
return {
|
|
217
330
|
title: args.title,
|
|
218
|
-
intro: "Review the startup
|
|
219
|
-
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",
|
|
220
333
|
proposalBody: buildContextProposalDisplayText(args.proposal),
|
|
221
334
|
critiqueHeading: "Notes and risks",
|
|
222
335
|
critiqueBody: buildContextProposalCritiqueText(args.analysis),
|
|
@@ -416,6 +529,15 @@ type CompletionVerificationEvidenceSummary = {
|
|
|
416
529
|
summary: string;
|
|
417
530
|
};
|
|
418
531
|
|
|
532
|
+
type CompletionStartupPlanSummary = {
|
|
533
|
+
path: string;
|
|
534
|
+
status: string;
|
|
535
|
+
source?: string;
|
|
536
|
+
plannedSurfaces: string[];
|
|
537
|
+
verificationIntent: string[];
|
|
538
|
+
summary: string;
|
|
539
|
+
};
|
|
540
|
+
|
|
419
541
|
export function buildSystemReminder(args: {
|
|
420
542
|
missionAnchor?: string;
|
|
421
543
|
taskType?: string;
|
|
@@ -439,11 +561,12 @@ export function buildSystemReminder(args: {
|
|
|
439
561
|
implementationSurfacesLine?: string;
|
|
440
562
|
verificationCommandsLine?: string;
|
|
441
563
|
evidence: CompletionVerificationEvidenceSummary;
|
|
564
|
+
startupPlan: CompletionStartupPlanSummary;
|
|
442
565
|
evaluationRoleReminderText?: string;
|
|
443
566
|
}): string {
|
|
444
567
|
const lines = [
|
|
445
568
|
"Completion workflow detected.",
|
|
446
|
-
"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.",
|
|
447
570
|
`Mission anchor: ${args.missionAnchor ?? "(unknown)"}`,
|
|
448
571
|
`Task type: ${args.taskType ?? "(missing)"}`,
|
|
449
572
|
`Evaluation profile: ${args.evaluationProfile ?? "(missing)"}`,
|
|
@@ -460,6 +583,7 @@ export function buildSystemReminder(args: {
|
|
|
460
583
|
"Only stop for the user when continuation_policy is await_user_input, blocked, paused, or done.",
|
|
461
584
|
"If canonical state is stale, invalid, ambiguous, or missing, route to completion-regrounder.",
|
|
462
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})`,
|
|
463
587
|
];
|
|
464
588
|
if (args.exactActiveContract) {
|
|
465
589
|
lines.push("Selected/in-progress/committed/done .agent/active-slice.json is the canonical implementation contract.");
|
|
@@ -473,6 +597,14 @@ export function buildSystemReminder(args: {
|
|
|
473
597
|
else if (args.implementationSurfaces.length > 0) lines.push(`Active implementation surfaces: ${args.implementationSurfaces.join(", ")}`);
|
|
474
598
|
if (args.verificationCommandsLine) lines.push(args.verificationCommandsLine);
|
|
475
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}`);
|
|
476
608
|
lines.push(`Verification evidence artifact: ${args.evidence.path} (${args.evidence.status})`);
|
|
477
609
|
if (args.evidence.subjectType) lines.push(`Verification evidence subject: ${args.evidence.subjectType}`);
|
|
478
610
|
if (args.evidence.outcome) lines.push(`Verification evidence outcome: ${args.evidence.outcome}`);
|
|
@@ -501,6 +633,7 @@ export function buildResumeCapsule(args: {
|
|
|
501
633
|
activeSliceMatchesPlan: "yes" | "no" | "unknown";
|
|
502
634
|
activeSliceContractDrift: string;
|
|
503
635
|
implementerHandoffSnapshot: "present" | "missing_or_unclear";
|
|
636
|
+
startupPlan: CompletionStartupPlanSummary;
|
|
504
637
|
evidence: CompletionVerificationEvidenceSummary;
|
|
505
638
|
activeSlice: {
|
|
506
639
|
sliceId?: string;
|
|
@@ -543,6 +676,14 @@ export function buildResumeCapsule(args: {
|
|
|
543
676
|
`implementer_handoff_snapshot: ${args.implementerHandoffSnapshot}`,
|
|
544
677
|
`history_counts: reviewed=${args.history.reviewed}, audited=${args.history.audited}, accepted=${args.history.accepted}, reopened=${args.history.reopened}, judgments=${args.history.judgments}`,
|
|
545
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
|
+
"",
|
|
546
687
|
"verification_evidence:",
|
|
547
688
|
`- path: ${args.evidence.path}`,
|
|
548
689
|
`- status: ${args.evidence.status}`,
|
|
@@ -590,8 +731,9 @@ export function buildResumeCapsule(args: {
|
|
|
590
731
|
"- Treat this block as continuity support derived from canonical .agent state.",
|
|
591
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.",
|
|
592
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.",
|
|
593
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.",
|
|
594
|
-
"- 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.",
|
|
595
737
|
"- Invoke completion-regrounder before continuing when requires_reground is true or unknown.",
|
|
596
738
|
"- Invoke completion-regrounder before continuing when next_mandatory_role or next_mandatory_action is unknown or ambiguous.",
|
|
597
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.",
|