@linimin/pi-letscook 0.1.57 → 0.1.58
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 +12 -2
- package/README.md +9 -4
- package/extensions/completion/driver.ts +43 -7
- package/extensions/completion/index.ts +58 -41
- package/extensions/completion/prompt-surfaces.ts +4 -1
- package/extensions/completion/proposal.ts +136 -15
- package/package.json +1 -1
- package/scripts/context-proposal-test.sh +197 -4
- package/scripts/release-check.sh +11 -4
- package/scripts/smoke-test.sh +2 -0
- package/skills/cook-handoff-boundary/SKILL.md +10 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,12 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.58
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- tightened implementation-ready explicit `/cook` handoffs so fresh capsules must already carry a bounded first slice, repo-change-oriented acceptance, implementation surfaces, verification commands, and why-that-slice-first structure before workflow startup
|
|
10
|
+
- made fresh explicit but non-startable `/cook` handoffs fail closed with a dedicated operator message instead of falling back to broader recent discussion or silently drifting into planning
|
|
11
|
+
- expanded regressions and public parity so valid, vague, stale, done-workflow, and negative explicit-handoff cases stay truthful across runtime behavior, docs, and `npm run release-check`
|
|
12
|
+
|
|
5
13
|
## 0.1.57
|
|
6
14
|
|
|
7
15
|
### Changed
|
|
8
16
|
|
|
9
|
-
- made explicit primary-agent `/cook` handoff the preferred startup-intake path by teaching ordinary-chat handoff turns to emit a structured `cook_handoff` capsule and letting `/cook` prefer that capsule over broad context re-inference when it is fresh and
|
|
10
|
-
-
|
|
17
|
+
- made explicit primary-agent `/cook` handoff the preferred startup-intake path by teaching ordinary-chat handoff turns to emit a structured `cook_handoff` capsule and letting `/cook` prefer that capsule over broad context re-inference when it is fresh, valid, and implementation-startable
|
|
18
|
+
- tightened implementation-ready explicit handoffs so the structured capsule must already carry a bounded `first_slice_goal`, repo-change-oriented acceptance, `implementation_surfaces`, `verification_commands`, and `why_this_slice_first` before `/cook` will start workflow from it
|
|
19
|
+
- kept the pre-`/cook` handoff capsule as advisory startup intake only, not canonical `.agent/**` workflow state, while still using context-derived startup as the fallback only when no fresh explicit handoff is blocking startup
|
|
20
|
+
- kept context-derived startup as a fallback only when there is no fresh explicit handoff blocking startup, so stale or invalidated capsules can still fall back to recent discussion while fresh non-startable handoffs fail closed instead of silently rewriting canonical state
|
|
11
21
|
- made finished-workflow suppression stay a safety layer instead of a replacement mission when a fresh explicit `/cook` handoff exists, and blocked negative rejection/suppression text from becoming a Startable startup mission
|
|
12
22
|
- removed inline `/cook` arguments from the shipped entry path again so explicit bare `/cook` is the only public command, and fail closed when recent discussion is insufficient or unreliable
|
|
13
23
|
- added a pre-`/cook` ordinary-chat handoff boundary so the primary agent is instructed to stop at `/cook` once a task has matured into completion-workflow scope instead of starting long-running implementation directly in ordinary chat
|
package/README.md
CHANGED
|
@@ -50,13 +50,16 @@ Then run `/reload` in Pi.
|
|
|
50
50
|
## What `/cook` expects
|
|
51
51
|
|
|
52
52
|
- preferably a fresh explicit primary-agent `/cook` handoff capsule from the immediately preceding ordinary-chat turn
|
|
53
|
-
-
|
|
53
|
+
- for that handoff capsule to start workflow immediately, it must already be implementation-startable: a bounded `first_slice_goal`, repo-change-oriented acceptance, `implementation_surfaces`, `verification_commands`, and `why_this_slice_first`
|
|
54
|
+
- otherwise, when no fresh explicit handoff is blocking startup, recent main-chat discussion about concrete repo changes
|
|
54
55
|
- enough detail to derive a startup brief with mission, scope, constraints or non-goals, acceptance, and notes or risks
|
|
55
56
|
- README/CHANGELOG updates still count as concrete repo changes
|
|
56
57
|
- assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts still do not count unless they include the explicit structured `/cook` handoff capsule
|
|
57
58
|
|
|
58
59
|
If no fresh valid handoff exists and recent discussion is missing, weak, ambiguous, assistant-produced, or only describes planning artifacts instead of concrete repo changes, `/cook` fails closed, leaves canonical `.agent/**` state unchanged, and tells you to clarify the mission in the main chat before rerunning `/cook`.
|
|
59
60
|
|
|
61
|
+
If a fresh explicit handoff exists but is still workflow-worthy rather than implementation-startable, `/cook` also fails closed instead of silently treating that capsule as planning support or canonical workflow state.
|
|
62
|
+
|
|
60
63
|
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`.
|
|
61
64
|
|
|
62
65
|
## Workflow entry
|
|
@@ -67,6 +70,8 @@ If a task has clearly matured into completion-workflow scope, the primary agent
|
|
|
67
70
|
|
|
68
71
|
That handoff should include an explicit structured `/cook` capsule in the assistant reply so `/cook` can confirm the already-formed mission instead of re-deriving it from broad ambient context.
|
|
69
72
|
|
|
73
|
+
The preferred capsule is still advisory startup intake, not canonical workflow state, and it only counts as implementation-ready when it already names the first bounded slice, repo-change-oriented acceptance, implementation surfaces, and verification commands.
|
|
74
|
+
|
|
70
75
|
Important behavior:
|
|
71
76
|
- `/cook` is the canonical workflow boundary and manual entry point
|
|
72
77
|
- startup, refocus, and next-round routing stay confirm-first; nothing silently starts a workflow
|
|
@@ -84,13 +89,13 @@ I want to add login redirect handling and tests.
|
|
|
84
89
|
|
|
85
90
|
## What happens when you run `/cook`
|
|
86
91
|
|
|
87
|
-
`/cook` first looks for a fresh explicit primary-agent handoff capsule. If one is valid, `/cook` builds the startup brief from that handoff and only uses recent discussion as validation or supplemental notes.
|
|
92
|
+
`/cook` first looks for a fresh explicit primary-agent handoff capsule. If one is valid and implementation-startable, `/cook` builds the startup brief from that handoff and only uses recent discussion as validation or supplemental notes. `/cook` falls back to deriving a startup brief from recent discussion only when no fresh explicit handoff is blocking startup—for example, when there is no fresh capsule or only stale or invalidated capsules—before showing the existing approval-only Start/Cancel gate.
|
|
88
93
|
|
|
89
94
|
| Repo state | What you'll see |
|
|
90
95
|
|---|---|
|
|
91
|
-
| No workflow yet | If a fresh explicit handoff capsule exists, a startup brief built from that handoff. Otherwise a startup brief built from recent main-chat discussion. You choose **Start** or **Cancel**. Weak, unreliable, stale,
|
|
96
|
+
| No workflow yet | If a fresh explicit handoff capsule exists and is implementation-startable, a startup brief built from that handoff. Otherwise, when no fresh explicit handoff is blocking startup, a startup brief built from recent main-chat discussion. You choose **Start** or **Cancel**. Weak, unreliable, stale, planning-only, or non-startable explicit-handoff intake fails closed. |
|
|
92
97
|
| Active workflow exists | Usually a resume of the current workflow. If a fresh explicit handoff capsule or recent discussion clearly points to a different concrete repo change, `/cook` shows a chooser first and only rewrites canonical state after you confirm the new startup brief. Ambiguous intake stays conservative. |
|
|
93
|
-
| Previous workflow is `done` | A fresh explicit handoff capsule can still start the next implementation round behind **Start** or **Cancel**. Without
|
|
98
|
+
| Previous workflow is `done` | A fresh explicit handoff capsule can still start the next implementation round behind **Start** or **Cancel**. Without a fresh explicit handoff blocking startup, `/cook` can fall back to recent discussion. Discussion that only restates already-finished work still fails closed. |
|
|
94
99
|
|
|
95
100
|
## Confirmation and fail-closed behavior
|
|
96
101
|
|
|
@@ -38,7 +38,7 @@ type ContextProposalAlternate = {
|
|
|
38
38
|
analysis: ContextProposalAnalysis;
|
|
39
39
|
goalText: string;
|
|
40
40
|
basisPreview: string;
|
|
41
|
-
source: "session" | "analyst";
|
|
41
|
+
source: "session" | "analyst" | "handoff_capsule";
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
type ContextProposal = ContextProposalAlternate & {
|
|
@@ -55,11 +55,22 @@ type ExistingWorkflowDecision =
|
|
|
55
55
|
| { action: "continue"; currentMissionAnchor: string }
|
|
56
56
|
| { action: "refocus"; currentMissionAnchor: string; missionAnchor: string; proposal: ContextProposal };
|
|
57
57
|
|
|
58
|
+
type CookContextProposalResult = {
|
|
59
|
+
proposal?: ContextProposal;
|
|
60
|
+
blockedFailureMessage?: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
58
63
|
type ActiveWorkflowProposalAssessment = {
|
|
59
|
-
action: "continue" | "refocus" | "unclear";
|
|
64
|
+
action: "continue" | "refocus" | "unclear" | "blocked";
|
|
60
65
|
currentMissionAnchor: string;
|
|
61
66
|
proposal?: ContextProposal;
|
|
62
|
-
|
|
67
|
+
blockedFailureMessage?: string;
|
|
68
|
+
reason:
|
|
69
|
+
| "matching_mission"
|
|
70
|
+
| "clear_refocus"
|
|
71
|
+
| "missing_proposal"
|
|
72
|
+
| "ambiguous_discussion"
|
|
73
|
+
| "fresh_explicit_handoff_not_startable";
|
|
63
74
|
};
|
|
64
75
|
|
|
65
76
|
type ExistingWorkflowChooserOptions = {
|
|
@@ -110,7 +121,7 @@ export type CompletionDriverDeps = {
|
|
|
110
121
|
missionAnchor?: string,
|
|
111
122
|
) => string;
|
|
112
123
|
completionResumePrompt: (taskType: string, evaluationProfile: string) => string;
|
|
113
|
-
deriveCookContextProposal: (ctx: DriverContext, projectName: string) => Promise<
|
|
124
|
+
deriveCookContextProposal: (ctx: DriverContext, projectName: string) => Promise<CookContextProposalResult>;
|
|
114
125
|
confirmContextProposal: (
|
|
115
126
|
ctx: { hasUI: boolean; ui: any },
|
|
116
127
|
proposal: ContextProposal,
|
|
@@ -305,7 +316,18 @@ async function assessActiveWorkflowProposalRouting(
|
|
|
305
316
|
): Promise<ActiveWorkflowProposalAssessment> {
|
|
306
317
|
const currentMission = currentMissionAnchor(snapshot);
|
|
307
318
|
const projectName = path.basename(snapshot.files.root);
|
|
308
|
-
const
|
|
319
|
+
const derived = await deps.deriveCookContextProposal(ctx, projectName);
|
|
320
|
+
if (derived.blockedFailureMessage) {
|
|
321
|
+
const assessment: ActiveWorkflowProposalAssessment = {
|
|
322
|
+
action: "blocked",
|
|
323
|
+
currentMissionAnchor: currentMission,
|
|
324
|
+
blockedFailureMessage: derived.blockedFailureMessage,
|
|
325
|
+
reason: "fresh_explicit_handoff_not_startable",
|
|
326
|
+
};
|
|
327
|
+
deps.maybeWriteActiveWorkflowRoutingSnapshot(assessment);
|
|
328
|
+
return assessment;
|
|
329
|
+
}
|
|
330
|
+
const proposal = derived.proposal;
|
|
309
331
|
if (!proposal) {
|
|
310
332
|
const assessment: ActiveWorkflowProposalAssessment = {
|
|
311
333
|
action: "unclear",
|
|
@@ -519,7 +541,12 @@ export async function runCookEntry(
|
|
|
519
541
|
if (!snapshot) {
|
|
520
542
|
const root = findRepoRoot(cwd) ?? cwd;
|
|
521
543
|
const projectName = path.basename(root);
|
|
522
|
-
const
|
|
544
|
+
const derived = await deps.deriveCookContextProposal(ctx, projectName);
|
|
545
|
+
if (derived.blockedFailureMessage) {
|
|
546
|
+
deps.emitCommandText(ctx, derived.blockedFailureMessage, "info");
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const proposal = derived.proposal;
|
|
523
550
|
if (!proposal) {
|
|
524
551
|
deps.emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(deps), "info");
|
|
525
552
|
return;
|
|
@@ -559,7 +586,12 @@ export async function runCookEntry(
|
|
|
559
586
|
if (!goal) {
|
|
560
587
|
if (workflowDone) {
|
|
561
588
|
const projectName = path.basename(snapshot.files.root);
|
|
562
|
-
const
|
|
589
|
+
const derived = await deps.deriveCookContextProposal(ctx, projectName);
|
|
590
|
+
if (derived.blockedFailureMessage) {
|
|
591
|
+
deps.emitCommandText(ctx, derived.blockedFailureMessage, "info");
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const proposal = derived.proposal;
|
|
563
595
|
if (!proposal) {
|
|
564
596
|
deps.emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(deps, "The previous completion workflow is already done."), "info");
|
|
565
597
|
return;
|
|
@@ -586,6 +618,10 @@ export async function runCookEntry(
|
|
|
586
618
|
deps.emitCommandText(ctx, `Started a new completion workflow round from recent discussion: ${decision.missionAnchor}`, "info");
|
|
587
619
|
} else {
|
|
588
620
|
const assessment = await assessActiveWorkflowProposalRouting(ctx, snapshot, deps);
|
|
621
|
+
if (assessment.action === "blocked") {
|
|
622
|
+
deps.emitCommandText(ctx, assessment.blockedFailureMessage ?? buildCookStructuredDiscussionFailureMessage(deps), "info");
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
589
625
|
if (!assessment.proposal || assessment.action === "continue") {
|
|
590
626
|
await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps);
|
|
591
627
|
return;
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
collectRecentDiscussionEntries,
|
|
19
19
|
collectRecentSessionMessages,
|
|
20
20
|
deriveCookContextProposalFromRecentDiscussion,
|
|
21
|
-
|
|
21
|
+
assessLatestCookHandoffProposal,
|
|
22
22
|
finalizeContextProposalAnalysis,
|
|
23
23
|
isWeakMissionAnchor,
|
|
24
24
|
missionAnchorsLikelyEquivalent,
|
|
@@ -122,11 +122,22 @@ function candidateSlices(plan: JsonRecord | undefined): JsonRecord[] {
|
|
|
122
122
|
return Array.isArray(slices) ? slices.filter(isRecord) : [];
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
type CookContextProposalResult = {
|
|
126
|
+
proposal?: ContextProposal;
|
|
127
|
+
blockedFailureMessage?: string;
|
|
128
|
+
};
|
|
129
|
+
|
|
125
130
|
type ActiveWorkflowProposalAssessment = {
|
|
126
|
-
action: "continue" | "refocus" | "unclear";
|
|
131
|
+
action: "continue" | "refocus" | "unclear" | "blocked";
|
|
127
132
|
currentMissionAnchor: string;
|
|
128
133
|
proposal?: ContextProposal;
|
|
129
|
-
|
|
134
|
+
blockedFailureMessage?: string;
|
|
135
|
+
reason:
|
|
136
|
+
| "matching_mission"
|
|
137
|
+
| "clear_refocus"
|
|
138
|
+
| "missing_proposal"
|
|
139
|
+
| "ambiguous_discussion"
|
|
140
|
+
| "fresh_explicit_handoff_not_startable";
|
|
130
141
|
};
|
|
131
142
|
|
|
132
143
|
function completionTestWorkflowActionOverride(): "continue" | "refocus" | "cancel" | undefined {
|
|
@@ -271,6 +282,7 @@ function maybeWriteActiveWorkflowRoutingSnapshot(assessment: ActiveWorkflowPropo
|
|
|
271
282
|
action: assessment.action,
|
|
272
283
|
reason: assessment.reason,
|
|
273
284
|
currentMissionAnchor: assessment.currentMissionAnchor,
|
|
285
|
+
blockedFailureMessage: assessment.blockedFailureMessage ?? null,
|
|
274
286
|
proposedMissionAnchor: assessment.proposal?.mission ?? null,
|
|
275
287
|
proposalSource: assessment.proposal?.source ?? null,
|
|
276
288
|
possibleNoise: assessment.proposal?.analysis.possibleNoise ?? [],
|
|
@@ -365,7 +377,7 @@ async function promptContextProposalConfirmationAction(
|
|
|
365
377
|
async function deriveCookContextProposal(
|
|
366
378
|
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
367
379
|
projectName: string,
|
|
368
|
-
): Promise<
|
|
380
|
+
): Promise<CookContextProposalResult> {
|
|
369
381
|
const recentMessages = collectRecentSessionMessages(ctx, { isRecord, asString, asNumber, isStaleContextError });
|
|
370
382
|
const recentEntries = recentMessages
|
|
371
383
|
.filter((entry) => (entry.role === "user" || entry.role === "custom") && !entry.isCommand)
|
|
@@ -384,7 +396,7 @@ async function deriveCookContextProposal(
|
|
|
384
396
|
`verification summary: ${asString(snapshot.verificationEvidence?.summary) ?? "(none)"}`,
|
|
385
397
|
]
|
|
386
398
|
: [];
|
|
387
|
-
const explicitHandoff =
|
|
399
|
+
const explicitHandoff = assessLatestCookHandoffProposal(recentMessages, projectName, {
|
|
388
400
|
asString,
|
|
389
401
|
asStringArray,
|
|
390
402
|
assessMissionAnchor,
|
|
@@ -393,42 +405,47 @@ async function deriveCookContextProposal(
|
|
|
393
405
|
missionAnchorsStrictlyEquivalent,
|
|
394
406
|
stripCodeBlocks,
|
|
395
407
|
});
|
|
396
|
-
if (explicitHandoff) return explicitHandoff;
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
408
|
+
if (explicitHandoff.status === "startable") return { proposal: explicitHandoff.proposal };
|
|
409
|
+
if (explicitHandoff.status === "fresh_but_not_startable") {
|
|
410
|
+
return { blockedFailureMessage: explicitHandoff.message };
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
proposal: await deriveCookContextProposalFromRecentDiscussion(projectName, recentEntries, {
|
|
414
|
+
asString,
|
|
415
|
+
asStringArray,
|
|
416
|
+
workflowContext: snapshot
|
|
417
|
+
? {
|
|
418
|
+
currentMissionAnchor:
|
|
419
|
+
asString(snapshot.state?.mission_anchor) ?? asString(snapshot.plan?.mission_anchor) ?? asString(snapshot.active?.mission_anchor),
|
|
420
|
+
latestCompletedSlice: asString(snapshot.state?.latest_completed_slice),
|
|
421
|
+
latestVerifiedSlice: asString(snapshot.state?.latest_verified_slice),
|
|
422
|
+
activeSliceGoal: asString(snapshot.active?.goal),
|
|
423
|
+
activeSliceWhyNow: asString(snapshot.active?.why_now),
|
|
424
|
+
verificationGoal: asString(snapshot.verificationEvidence?.goal),
|
|
425
|
+
verificationSummary: asString(snapshot.verificationEvidence?.summary),
|
|
426
|
+
continuationPolicy: asString(snapshot.state?.continuation_policy),
|
|
427
|
+
}
|
|
428
|
+
: undefined,
|
|
429
|
+
analyzeContextProposal: async (entries) =>
|
|
430
|
+
await analyzeContextProposalWithAgent({
|
|
431
|
+
ctx,
|
|
432
|
+
projectName,
|
|
433
|
+
recentEntries: entries,
|
|
434
|
+
workflowContextLines,
|
|
435
|
+
liveRoleActivityByRoot,
|
|
436
|
+
completionStatusKey: COMPLETION_STATUS_KEY,
|
|
437
|
+
safeUiCall,
|
|
438
|
+
getCtxCwd,
|
|
439
|
+
getCtxHasUI,
|
|
440
|
+
getCtxUi,
|
|
441
|
+
}),
|
|
442
|
+
assessMissionAnchor,
|
|
443
|
+
isWeakMissionAnchor,
|
|
444
|
+
missionAnchorsStrictlyEquivalent,
|
|
445
|
+
normalizeMissionAnchorText,
|
|
446
|
+
stripCodeBlocks,
|
|
447
|
+
}),
|
|
448
|
+
};
|
|
432
449
|
}
|
|
433
450
|
|
|
434
451
|
async function confirmContextProposal(
|
|
@@ -31,8 +31,11 @@ export function buildCookHandoffBoundaryReminder(): string {
|
|
|
31
31
|
"/cook is the only explicit entrypoint into long-running completion workflow.",
|
|
32
32
|
"When you judge that the task has matured into completion-workflow scope — for example the user has clearly shifted from exploration into implementation intent, you have just produced a concrete plan or proposal whose next step would naturally be implementation, or the task spans multiple files, steps, or verification surfaces — stop short of long-running implementation and tell the user to run /cook.",
|
|
33
33
|
"At that handoff point, do not begin long-running product implementation in ordinary chat, do not edit tracked product files for that workflow-level task, and do not act as though /cook had already been invoked.",
|
|
34
|
+
"Distinguish a workflow-worthy handoff from an implementation-ready handoff: only emit the implementation-ready capsule when the first bounded implementation slice is concrete enough to start immediately.",
|
|
34
35
|
"When handing off, explain that /cook will first look for a fresh explicit primary-agent handoff capsule and otherwise fall back to recent discussion.",
|
|
35
|
-
"
|
|
36
|
+
"If the task is workflow-worthy but that first slice is still vague, tell the user to run /cook without emitting an implementation-ready capsule yet.",
|
|
37
|
+
"Otherwise append one exact fenced block in the same assistant reply using ```cook_handoff ... ``` JSON with kind/source/handoff_kind plus mission, scope, constraints or non_goals, acceptance, risks, notes, captured_at, source_turn_id, first_slice_goal, first_slice_non_goals, implementation_surfaces, verification_commands, why_this_slice_first, and optional task_type/evaluation_profile/why_cook_now.",
|
|
38
|
+
"Use handoff_kind implementation_workflow_handoff for that implementation-ready capsule.",
|
|
36
39
|
"The capsule is startup intake for /cook only: do not present it as canonical .agent state, an active slice, or a persistent repo contract.",
|
|
37
40
|
"If the task is still ordinary Q&A, lightweight brainstorming, or a tiny one-off fix, continue normally without forcing /cook.",
|
|
38
41
|
].join(" ");
|
|
@@ -59,12 +59,30 @@ export type CookHandoffCapsule = {
|
|
|
59
59
|
acceptance: string[];
|
|
60
60
|
risks: string[];
|
|
61
61
|
notes: string[];
|
|
62
|
-
handoff_kind: "
|
|
62
|
+
handoff_kind: "implementation_workflow_handoff";
|
|
63
|
+
first_slice_goal: string;
|
|
64
|
+
first_slice_non_goals: string[];
|
|
65
|
+
implementation_surfaces: string[];
|
|
66
|
+
verification_commands: string[];
|
|
67
|
+
why_this_slice_first: string;
|
|
63
68
|
task_type?: string;
|
|
64
69
|
evaluation_profile?: string;
|
|
65
70
|
why_cook_now?: string;
|
|
66
71
|
};
|
|
67
72
|
|
|
73
|
+
export type CookHandoffProposalAssessment =
|
|
74
|
+
| {
|
|
75
|
+
status: "none";
|
|
76
|
+
}
|
|
77
|
+
| {
|
|
78
|
+
status: "startable";
|
|
79
|
+
proposal: ContextProposal;
|
|
80
|
+
}
|
|
81
|
+
| {
|
|
82
|
+
status: "fresh_but_not_startable";
|
|
83
|
+
message: string;
|
|
84
|
+
};
|
|
85
|
+
|
|
68
86
|
export type ContextProposalDecision = {
|
|
69
87
|
missionAnchor: string;
|
|
70
88
|
goalText: string;
|
|
@@ -1231,6 +1249,12 @@ const COOK_HANDOFF_MAX_AGE_MS = 45 * 60 * 1000;
|
|
|
1231
1249
|
const COOK_HANDOFF_MAX_LATER_NON_COMMAND_MESSAGES = 2;
|
|
1232
1250
|
const COOK_HANDOFF_NEGATIVE_MISSION_REGEX =
|
|
1233
1251
|
/(?:\b(?:do not|don't|dont|not|never|avoid|skip|refuse|recognize that|suppress|ignore|block|prevent)\b|(?:不要|別|别|勿|禁止|避免|忽略|阻止))/iu;
|
|
1252
|
+
const COOK_HANDOFF_WORKFLOW_ONLY_ACCEPTANCE_REGEX =
|
|
1253
|
+
/(?:\b(?:confirm|discuss|clarify|decide|review|align(?: on)?|agree(?: on)?|explain|summari(?:s|z)e|describe|plan|proposal|spec(?:ification)?|design(?: doc(?:ument)?)?|next step|handoff|workflow|readiness)\b|(?:確認|确认|討論|讨论|釐清|厘清|決定|决定|審查|审查|對齊|对齐|同意|说明|說明|總結|总结|描述|規劃|规划|提案|方案|工作流|就緒|就绪))/iu;
|
|
1254
|
+
const COOK_HANDOFF_VERIFICATION_ACCEPTANCE_REGEX =
|
|
1255
|
+
/(?:\b(?:test|tests|testing|verify|verification|validated?|regression|coverage|assert(?:ion)?s?|check|checks|smoke|snapshot(?:s)?)\b|(?:測試|测试|驗證|验证|回歸|回归|覆蓋|覆盖|斷言|断言|檢查|检查|快照))/iu;
|
|
1256
|
+
const COOK_HANDOFF_VERIFICATION_ACTION_REGEX =
|
|
1257
|
+
/(?:\b(?:add|update|keep|run|rerun|cover|verify|validate|check|assert|exercise|prove)\b|(?:新增|更新|保持|執行|执行|重跑|覆蓋|覆盖|驗證|验证|檢查|检查|斷言|断言|證明|证明))/iu;
|
|
1234
1258
|
|
|
1235
1259
|
function parseCookHandoffCapsulesFromText(
|
|
1236
1260
|
text: string,
|
|
@@ -1251,15 +1275,20 @@ function parseCookHandoffCapsulesFromText(
|
|
|
1251
1275
|
if (!localIsRecord(parsed)) continue;
|
|
1252
1276
|
if (deps.asString(parsed.kind) !== "cook_handoff") continue;
|
|
1253
1277
|
if (deps.asString(parsed.source) !== "primary_agent") continue;
|
|
1254
|
-
if (deps.asString(parsed.handoff_kind) !== "
|
|
1278
|
+
if (deps.asString(parsed.handoff_kind) !== "implementation_workflow_handoff") continue;
|
|
1255
1279
|
const mission = deps.asString(parsed.mission);
|
|
1256
|
-
|
|
1280
|
+
const firstSliceGoal = deps.asString(parsed.first_slice_goal ?? parsed.firstSliceGoal);
|
|
1281
|
+
const whyThisSliceFirst = deps.asString(parsed.why_this_slice_first ?? parsed.whyThisSliceFirst);
|
|
1282
|
+
if (!mission || !firstSliceGoal || !whyThisSliceFirst) continue;
|
|
1257
1283
|
const scope = deps.asStringArray(parsed.scope);
|
|
1258
1284
|
const constraints = deps.asStringArray(parsed.constraints);
|
|
1259
1285
|
const nonGoals = deps.asStringArray(parsed.non_goals ?? parsed.nonGoals);
|
|
1260
1286
|
const acceptance = deps.asStringArray(parsed.acceptance);
|
|
1261
1287
|
const risks = deps.asStringArray(parsed.risks);
|
|
1262
1288
|
const notes = deps.asStringArray(parsed.notes);
|
|
1289
|
+
const firstSliceNonGoals = deps.asStringArray(parsed.first_slice_non_goals ?? parsed.firstSliceNonGoals);
|
|
1290
|
+
const implementationSurfaces = deps.asStringArray(parsed.implementation_surfaces ?? parsed.implementationSurfaces);
|
|
1291
|
+
const verificationCommands = deps.asStringArray(parsed.verification_commands ?? parsed.verificationCommands);
|
|
1263
1292
|
const capturedAt = deps.asString(parsed.captured_at) ?? (timestampMs ? new Date(timestampMs).toISOString() : undefined);
|
|
1264
1293
|
const sourceTurnId = deps.asString(parsed.source_turn_id) ?? messageId;
|
|
1265
1294
|
if (!capturedAt || !sourceTurnId) continue;
|
|
@@ -1275,7 +1304,12 @@ function parseCookHandoffCapsulesFromText(
|
|
|
1275
1304
|
acceptance,
|
|
1276
1305
|
risks,
|
|
1277
1306
|
notes,
|
|
1278
|
-
handoff_kind: "
|
|
1307
|
+
handoff_kind: "implementation_workflow_handoff",
|
|
1308
|
+
first_slice_goal: firstSliceGoal,
|
|
1309
|
+
first_slice_non_goals: firstSliceNonGoals,
|
|
1310
|
+
implementation_surfaces: implementationSurfaces,
|
|
1311
|
+
verification_commands: verificationCommands,
|
|
1312
|
+
why_this_slice_first: whyThisSliceFirst,
|
|
1279
1313
|
task_type: deps.asString(parsed.task_type),
|
|
1280
1314
|
evaluation_profile: deps.asString(parsed.evaluation_profile),
|
|
1281
1315
|
why_cook_now: deps.asString(parsed.why_cook_now),
|
|
@@ -1285,20 +1319,74 @@ function parseCookHandoffCapsulesFromText(
|
|
|
1285
1319
|
}
|
|
1286
1320
|
|
|
1287
1321
|
function buildCookHandoffBasisPreview(capsule: CookHandoffCapsule): string {
|
|
1288
|
-
const parts = [
|
|
1322
|
+
const parts = [
|
|
1323
|
+
capsule.mission,
|
|
1324
|
+
...capsule.scope,
|
|
1325
|
+
...capsule.constraints,
|
|
1326
|
+
...capsule.non_goals,
|
|
1327
|
+
...capsule.acceptance,
|
|
1328
|
+
`first_slice_goal: ${capsule.first_slice_goal}`,
|
|
1329
|
+
...capsule.first_slice_non_goals.map((item) => `first_slice_non_goals: ${item}`),
|
|
1330
|
+
...capsule.implementation_surfaces.map((item) => `implementation_surfaces: ${item}`),
|
|
1331
|
+
...capsule.verification_commands.map((item) => `verification_commands: ${item}`),
|
|
1332
|
+
`why_this_slice_first: ${capsule.why_this_slice_first}`,
|
|
1333
|
+
];
|
|
1289
1334
|
if (capsule.why_cook_now) parts.push(`why_cook_now: ${capsule.why_cook_now}`);
|
|
1290
1335
|
return parts.join("\n").trim();
|
|
1291
1336
|
}
|
|
1292
1337
|
|
|
1338
|
+
function cookHandoffAcceptanceItemIsRepoChangeOrVerificationOriented(item: string): boolean {
|
|
1339
|
+
const normalized = normalizeProposalLine(item);
|
|
1340
|
+
if (!normalized) return false;
|
|
1341
|
+
if (hasExplicitPlanningOnlyDeliverable([normalized])) return false;
|
|
1342
|
+
if (hasClearNoImplementationSignal([normalized])) return false;
|
|
1343
|
+
if (implementationMissionSourceCandidateText(normalized)) return true;
|
|
1344
|
+
if (COOK_HANDOFF_WORKFLOW_ONLY_ACCEPTANCE_REGEX.test(normalized)) return false;
|
|
1345
|
+
return COOK_HANDOFF_VERIFICATION_ACCEPTANCE_REGEX.test(normalized) && COOK_HANDOFF_VERIFICATION_ACTION_REGEX.test(normalized);
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
function cookHandoffAcceptanceIsRepoChangeOriented(capsule: CookHandoffCapsule): boolean {
|
|
1349
|
+
if (capsule.acceptance.length === 0) return false;
|
|
1350
|
+
return capsule.acceptance.some((item) => cookHandoffAcceptanceItemIsRepoChangeOrVerificationOriented(item));
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
function cookHandoffStartabilityFailures(
|
|
1354
|
+
capsule: CookHandoffCapsule,
|
|
1355
|
+
deps: Pick<ProposalParseDeps, "normalizeMissionAnchorText" | "isWeakMissionAnchor">,
|
|
1356
|
+
): string[] {
|
|
1357
|
+
const failures: string[] = [];
|
|
1358
|
+
const mission = deps.normalizeMissionAnchorText(capsule.mission);
|
|
1359
|
+
if (!mission || deps.isWeakMissionAnchor(mission)) failures.push("mission is missing a concrete implementation anchor");
|
|
1360
|
+
else if (COOK_HANDOFF_NEGATIVE_MISSION_REGEX.test(mission)) failures.push("mission is negative or workflow-suppression-only");
|
|
1361
|
+
if (capsule.scope.length === 0) failures.push("scope is empty");
|
|
1362
|
+
if (capsule.acceptance.length === 0) failures.push("acceptance is empty");
|
|
1363
|
+
else if (!cookHandoffAcceptanceIsRepoChangeOriented(capsule)) {
|
|
1364
|
+
failures.push("acceptance is not anchored to concrete repo changes or verification");
|
|
1365
|
+
}
|
|
1366
|
+
const firstSliceGoal = deps.normalizeMissionAnchorText(capsule.first_slice_goal);
|
|
1367
|
+
if (!firstSliceGoal || deps.isWeakMissionAnchor(firstSliceGoal) || COOK_HANDOFF_NEGATIVE_MISSION_REGEX.test(firstSliceGoal)) {
|
|
1368
|
+
failures.push("first_slice_goal is not a bounded implementation slice");
|
|
1369
|
+
} else if (hasExplicitPlanningOnlyDeliverable([capsule.first_slice_goal]) || hasClearNoImplementationSignal([capsule.first_slice_goal])) {
|
|
1370
|
+
failures.push("first_slice_goal is planning-only instead of a repo-change slice");
|
|
1371
|
+
}
|
|
1372
|
+
if (capsule.implementation_surfaces.length === 0) failures.push("implementation_surfaces is empty");
|
|
1373
|
+
if (capsule.verification_commands.length === 0) failures.push("verification_commands is empty");
|
|
1374
|
+
return failures;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
function buildNonStartableCookHandoffMessage(failures: string[]): string {
|
|
1378
|
+
return [
|
|
1379
|
+
"/cook failed closed because a fresh explicit primary-agent handoff exists, but it is not concrete enough to start implementation workflow yet.",
|
|
1380
|
+
"Tighten the handoff in the main chat so it names a bounded first implementation slice, repo-change-oriented acceptance, implementation_surfaces, and verification_commands, then rerun /cook.",
|
|
1381
|
+
`Blocking details: ${failures.join("; ")}.`,
|
|
1382
|
+
].join(" ");
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1293
1385
|
function isStartableCookHandoffCapsule(
|
|
1294
1386
|
capsule: CookHandoffCapsule,
|
|
1295
1387
|
deps: Pick<ProposalParseDeps, "normalizeMissionAnchorText" | "isWeakMissionAnchor">,
|
|
1296
1388
|
): boolean {
|
|
1297
|
-
|
|
1298
|
-
if (!mission || deps.isWeakMissionAnchor(mission)) return false;
|
|
1299
|
-
if (COOK_HANDOFF_NEGATIVE_MISSION_REGEX.test(mission)) return false;
|
|
1300
|
-
if (capsule.scope.length === 0 || capsule.acceptance.length === 0) return false;
|
|
1301
|
-
return true;
|
|
1389
|
+
return cookHandoffStartabilityFailures(capsule, deps).length === 0;
|
|
1302
1390
|
}
|
|
1303
1391
|
|
|
1304
1392
|
function laterMessagesInvalidateCookHandoff(
|
|
@@ -1347,6 +1435,11 @@ function buildContextProposalFromCookHandoffCapsule(
|
|
|
1347
1435
|
evaluationProfile: capsule.evaluation_profile,
|
|
1348
1436
|
critique: [
|
|
1349
1437
|
...capsule.notes,
|
|
1438
|
+
`First slice goal: ${capsule.first_slice_goal}`,
|
|
1439
|
+
...(capsule.first_slice_non_goals.length > 0 ? [`First slice non-goals: ${capsule.first_slice_non_goals.join(" | ")}`] : []),
|
|
1440
|
+
...(capsule.implementation_surfaces.length > 0 ? [`Implementation surfaces: ${capsule.implementation_surfaces.join(" | ")}`] : []),
|
|
1441
|
+
...(capsule.verification_commands.length > 0 ? [`Verification commands: ${capsule.verification_commands.join(" | ")}`] : []),
|
|
1442
|
+
`Why this slice first: ${capsule.why_this_slice_first}`,
|
|
1350
1443
|
...(capsule.why_cook_now ? [`Primary-agent /cook handoff rationale: ${capsule.why_cook_now}`] : []),
|
|
1351
1444
|
],
|
|
1352
1445
|
risks: capsule.risks,
|
|
@@ -1355,7 +1448,19 @@ function buildContextProposalFromCookHandoffCapsule(
|
|
|
1355
1448
|
suppressedCompletedTopics: [],
|
|
1356
1449
|
suppressedNegatedTopics: [],
|
|
1357
1450
|
},
|
|
1358
|
-
[
|
|
1451
|
+
[
|
|
1452
|
+
mission,
|
|
1453
|
+
goalText,
|
|
1454
|
+
capsule.mission,
|
|
1455
|
+
...capsule.scope,
|
|
1456
|
+
...constraints,
|
|
1457
|
+
...capsule.acceptance,
|
|
1458
|
+
capsule.first_slice_goal,
|
|
1459
|
+
...capsule.first_slice_non_goals,
|
|
1460
|
+
...capsule.implementation_surfaces,
|
|
1461
|
+
...capsule.verification_commands,
|
|
1462
|
+
capsule.why_this_slice_first,
|
|
1463
|
+
],
|
|
1359
1464
|
),
|
|
1360
1465
|
goalText,
|
|
1361
1466
|
basisPreview: buildCookHandoffBasisPreview(capsule),
|
|
@@ -1365,11 +1470,11 @@ function buildContextProposalFromCookHandoffCapsule(
|
|
|
1365
1470
|
return finalizeContextProposal(proposal, projectName, deps);
|
|
1366
1471
|
}
|
|
1367
1472
|
|
|
1368
|
-
export function
|
|
1473
|
+
export function assessLatestCookHandoffProposal(
|
|
1369
1474
|
recentMessages: RecentSessionMessage[],
|
|
1370
1475
|
projectName: string,
|
|
1371
1476
|
deps: ProposalParseDeps,
|
|
1372
|
-
):
|
|
1477
|
+
): CookHandoffProposalAssessment {
|
|
1373
1478
|
for (let index = 0; index < recentMessages.length; index += 1) {
|
|
1374
1479
|
const entry = recentMessages[index];
|
|
1375
1480
|
if (entry.role !== "assistant" || entry.isCommand) continue;
|
|
@@ -1380,11 +1485,27 @@ export function extractLatestCookHandoffProposal(
|
|
|
1380
1485
|
const laterMessages = recentMessages.slice(0, index);
|
|
1381
1486
|
if (!cookHandoffIsFreshEnough(capsule, laterMessages)) continue;
|
|
1382
1487
|
if (laterMessagesInvalidateCookHandoff(laterMessages, deps)) continue;
|
|
1488
|
+
const failures = cookHandoffStartabilityFailures(capsule, deps);
|
|
1489
|
+
if (failures.length > 0) {
|
|
1490
|
+
return {
|
|
1491
|
+
status: "fresh_but_not_startable",
|
|
1492
|
+
message: buildNonStartableCookHandoffMessage(failures),
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1383
1495
|
const proposal = buildContextProposalFromCookHandoffCapsule(capsule, projectName, deps);
|
|
1384
|
-
if (proposal) return proposal;
|
|
1496
|
+
if (proposal) return { status: "startable", proposal };
|
|
1385
1497
|
}
|
|
1386
1498
|
}
|
|
1387
|
-
return
|
|
1499
|
+
return { status: "none" };
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
export function extractLatestCookHandoffProposal(
|
|
1503
|
+
recentMessages: RecentSessionMessage[],
|
|
1504
|
+
projectName: string,
|
|
1505
|
+
deps: ProposalParseDeps,
|
|
1506
|
+
): ContextProposal | undefined {
|
|
1507
|
+
const assessment = assessLatestCookHandoffProposal(recentMessages, projectName, deps);
|
|
1508
|
+
return assessment.status === "startable" ? assessment.proposal : undefined;
|
|
1388
1509
|
}
|
|
1389
1510
|
|
|
1390
1511
|
export async function deriveCookContextProposalFromRecentDiscussion(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linimin/pi-letscook",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.58",
|
|
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,
|
|
@@ -1433,7 +1433,19 @@ capsule = {
|
|
|
1433
1433
|
"notes": [
|
|
1434
1434
|
"Keep the startup brief aligned with the explicit primary-agent plan."
|
|
1435
1435
|
],
|
|
1436
|
-
"handoff_kind": "
|
|
1436
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1437
|
+
"first_slice_goal": "Land the redirect callback fix and its regression coverage.",
|
|
1438
|
+
"first_slice_non_goals": [
|
|
1439
|
+
"Do not refactor the broader auth flow."
|
|
1440
|
+
],
|
|
1441
|
+
"implementation_surfaces": [
|
|
1442
|
+
"src/auth/redirect.ts",
|
|
1443
|
+
"tests/auth/redirect.spec.ts"
|
|
1444
|
+
],
|
|
1445
|
+
"verification_commands": [
|
|
1446
|
+
"npm test -- redirect.spec.ts"
|
|
1447
|
+
],
|
|
1448
|
+
"why_this_slice_first": "The redirect callback bug is already bounded enough to start implementation safely.",
|
|
1437
1449
|
"task_type": "completion-workflow",
|
|
1438
1450
|
"evaluation_profile": "completion-rubric-v1",
|
|
1439
1451
|
"why_cook_now": "The implementation plan is concrete and ready for repo changes."
|
|
@@ -1465,9 +1477,166 @@ assert snapshot['mission'] == 'Fix login redirect callback behavior.', 'explicit
|
|
|
1465
1477
|
assert state['mission_anchor'] == 'Fix login redirect callback behavior.', 'explicit handoff startup should use the handoff mission as canonical mission_anchor'
|
|
1466
1478
|
assert state['advisory_startup_brief']['source'] == 'primary_agent_handoff', 'explicit handoff startup should preserve the advisory intake source'
|
|
1467
1479
|
assert state['advisory_startup_brief']['risks'] == ['Stale auth discussion could broaden the startup brief if the handoff is ignored.'], 'explicit handoff startup should preserve handoff risks'
|
|
1480
|
+
assert 'First slice goal: Land the redirect callback fix and its regression coverage.' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve first_slice_goal in advisory notes'
|
|
1481
|
+
assert 'First slice non-goals: Do not refactor the broader auth flow.' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve first_slice_non_goals in advisory notes'
|
|
1482
|
+
assert 'Implementation surfaces: src/auth/redirect.ts | tests/auth/redirect.spec.ts' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve implementation_surfaces in advisory notes'
|
|
1483
|
+
assert 'Verification commands: npm test -- redirect.spec.ts' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve verification_commands in advisory notes'
|
|
1484
|
+
assert 'Why this slice first: The redirect callback bug is already bounded enough to start implementation safely.' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve why_this_slice_first in advisory notes'
|
|
1468
1485
|
assert 'Primary-agent /cook handoff rationale: The implementation plan is concrete and ready for repo changes.' in state['advisory_startup_brief']['notes'], 'explicit handoff startup should preserve why_cook_now as notes'
|
|
1469
1486
|
PY
|
|
1470
1487
|
|
|
1488
|
+
# Fresh but non-startable explicit handoff: /cook should fail closed instead of falling back
|
|
1489
|
+
# to a broad recent-discussion startup brief when the explicit capsule is still too vague.
|
|
1490
|
+
HANDOFF_ROOT_VAGUE="$TMPDIR/handoff-root-vague"
|
|
1491
|
+
mkdir -p "$HANDOFF_ROOT_VAGUE"
|
|
1492
|
+
cd "$HANDOFF_ROOT_VAGUE"
|
|
1493
|
+
git init -q
|
|
1494
|
+
|
|
1495
|
+
HANDOFF_SESSION_VAGUE="$TMPDIR/handoff-session-vague.jsonl"
|
|
1496
|
+
HANDOFF_SNAPSHOT_VAGUE="$TMPDIR/handoff-proposal-vague.json"
|
|
1497
|
+
HANDOFF_MESSAGES_VAGUE="$(python3 - <<'PY'
|
|
1498
|
+
import json
|
|
1499
|
+
capsule = {
|
|
1500
|
+
"kind": "cook_handoff",
|
|
1501
|
+
"source": "primary_agent",
|
|
1502
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1503
|
+
"source_turn_id": "m0002",
|
|
1504
|
+
"mission": "Fix login redirect callback behavior.",
|
|
1505
|
+
"scope": [
|
|
1506
|
+
"Update the callback redirect decision logic."
|
|
1507
|
+
],
|
|
1508
|
+
"constraints": [
|
|
1509
|
+
"Do not refactor the broader auth flow."
|
|
1510
|
+
],
|
|
1511
|
+
"acceptance": [
|
|
1512
|
+
"Confirm the final implementation breakdown before coding."
|
|
1513
|
+
],
|
|
1514
|
+
"risks": [
|
|
1515
|
+
"Broad recent context could be reused if the vague explicit handoff is ignored."
|
|
1516
|
+
],
|
|
1517
|
+
"notes": [
|
|
1518
|
+
"This handoff is still too vague to start implementation directly."
|
|
1519
|
+
],
|
|
1520
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1521
|
+
"first_slice_goal": "Patch the callback redirect decision logic.",
|
|
1522
|
+
"first_slice_non_goals": [
|
|
1523
|
+
"Do not refactor the broader auth flow."
|
|
1524
|
+
],
|
|
1525
|
+
"implementation_surfaces": [],
|
|
1526
|
+
"verification_commands": [],
|
|
1527
|
+
"why_this_slice_first": "The callback redirect path is the likely first slice, but the handoff still lacks execution detail.",
|
|
1528
|
+
"task_type": "completion-workflow",
|
|
1529
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
1530
|
+
"why_cook_now": "The task is workflow-worthy, but the implementation slice is not concrete enough yet."
|
|
1531
|
+
}
|
|
1532
|
+
recent_discussion = "Mission: Fix login redirect callback behavior.\nScope:\n- Update the callback redirect decision logic.\nConstraints:\n- Do not refactor the broader auth flow.\nAcceptance:\n- Add a regression test for returning to the requested page."
|
|
1533
|
+
messages = [
|
|
1534
|
+
{"role": "user", "content": recent_discussion},
|
|
1535
|
+
{"role": "assistant", "content": "This follow-up might soon be ready for /cook.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
1536
|
+
]
|
|
1537
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1538
|
+
PY
|
|
1539
|
+
)"
|
|
1540
|
+
write_session_messages "$HANDOFF_SESSION_VAGUE" "$HANDOFF_ROOT_VAGUE" "$HANDOFF_MESSAGES_VAGUE"
|
|
1541
|
+
|
|
1542
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
1543
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_VAGUE" \
|
|
1544
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1545
|
+
pi --session "$HANDOFF_SESSION_VAGUE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-vague.out" 2>"$TMPDIR/pi-completion-handoff-vague.err"
|
|
1546
|
+
|
|
1547
|
+
python3 - "$HANDOFF_SNAPSHOT_VAGUE" "$TMPDIR/pi-completion-handoff-vague.out" "$TMPDIR/pi-completion-handoff-vague.err" <<'PY'
|
|
1548
|
+
import sys
|
|
1549
|
+
from pathlib import Path
|
|
1550
|
+
|
|
1551
|
+
snapshot = Path(sys.argv[1])
|
|
1552
|
+
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
1553
|
+
|
|
1554
|
+
assert not snapshot.exists(), 'fresh non-startable handoff should not emit a startup proposal snapshot'
|
|
1555
|
+
assert not Path('.agent').exists(), 'fresh non-startable handoff should fail closed without writing canonical state'
|
|
1556
|
+
assert 'fresh explicit primary-agent handoff exists' in output, 'fresh non-startable handoff should explain that the explicit capsule blocked startup'
|
|
1557
|
+
assert 'acceptance is not anchored to concrete repo changes or verification' in output, 'fresh non-startable handoff should explain the workflow-only acceptance failure'
|
|
1558
|
+
assert 'implementation_surfaces is empty' in output, 'fresh non-startable handoff should explain the missing implementation_surfaces requirement'
|
|
1559
|
+
assert 'verification_commands is empty' in output, 'fresh non-startable handoff should explain the missing verification_commands requirement'
|
|
1560
|
+
PY
|
|
1561
|
+
|
|
1562
|
+
# Fresh explicit handoff with complete first-slice fields but vague acceptance: /cook should still fail closed
|
|
1563
|
+
# with the dedicated explicit-handoff message instead of bootstrapping canonical state.
|
|
1564
|
+
HANDOFF_ROOT_VAGUE_ACCEPTANCE="$TMPDIR/handoff-root-vague-acceptance"
|
|
1565
|
+
mkdir -p "$HANDOFF_ROOT_VAGUE_ACCEPTANCE"
|
|
1566
|
+
cd "$HANDOFF_ROOT_VAGUE_ACCEPTANCE"
|
|
1567
|
+
git init -q
|
|
1568
|
+
|
|
1569
|
+
HANDOFF_SESSION_VAGUE_ACCEPTANCE="$TMPDIR/handoff-session-vague-acceptance.jsonl"
|
|
1570
|
+
HANDOFF_SNAPSHOT_VAGUE_ACCEPTANCE="$TMPDIR/handoff-proposal-vague-acceptance.json"
|
|
1571
|
+
HANDOFF_MESSAGES_VAGUE_ACCEPTANCE="$(python3 - <<'PY'
|
|
1572
|
+
import json
|
|
1573
|
+
capsule = {
|
|
1574
|
+
"kind": "cook_handoff",
|
|
1575
|
+
"source": "primary_agent",
|
|
1576
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
1577
|
+
"source_turn_id": "m0002",
|
|
1578
|
+
"mission": "Fix login redirect callback behavior.",
|
|
1579
|
+
"scope": [
|
|
1580
|
+
"Update the callback redirect decision logic.",
|
|
1581
|
+
"Preserve the broader auth flow."
|
|
1582
|
+
],
|
|
1583
|
+
"constraints": [
|
|
1584
|
+
"Do not refactor the broader auth flow."
|
|
1585
|
+
],
|
|
1586
|
+
"acceptance": [
|
|
1587
|
+
"Current behavior stays understandable."
|
|
1588
|
+
],
|
|
1589
|
+
"risks": [
|
|
1590
|
+
"Broad recent context could be reused if the vague explicit handoff is ignored."
|
|
1591
|
+
],
|
|
1592
|
+
"notes": [
|
|
1593
|
+
"This handoff includes first-slice fields but still lacks concrete acceptance."
|
|
1594
|
+
],
|
|
1595
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1596
|
+
"first_slice_goal": "Land the redirect callback fix and its regression coverage.",
|
|
1597
|
+
"first_slice_non_goals": [
|
|
1598
|
+
"Do not refactor the broader auth flow."
|
|
1599
|
+
],
|
|
1600
|
+
"implementation_surfaces": [
|
|
1601
|
+
"src/auth/redirect.ts",
|
|
1602
|
+
"tests/auth/redirect.spec.ts"
|
|
1603
|
+
],
|
|
1604
|
+
"verification_commands": [
|
|
1605
|
+
"npm test -- redirect.spec.ts"
|
|
1606
|
+
],
|
|
1607
|
+
"why_this_slice_first": "The redirect callback bug is already bounded enough to start implementation safely once acceptance is concrete.",
|
|
1608
|
+
"task_type": "completion-workflow",
|
|
1609
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
1610
|
+
"why_cook_now": "The task is workflow-worthy, but the acceptance still needs concrete repo-change detail."
|
|
1611
|
+
}
|
|
1612
|
+
recent_discussion = "Mission: Fix login redirect callback behavior.\nScope:\n- Update the callback redirect decision logic.\nConstraints:\n- Do not refactor the broader auth flow.\nAcceptance:\n- Add a regression test for returning to the requested page."
|
|
1613
|
+
messages = [
|
|
1614
|
+
{"role": "user", "content": recent_discussion},
|
|
1615
|
+
{"role": "assistant", "content": "This follow-up might soon be ready for /cook.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
1616
|
+
]
|
|
1617
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
1618
|
+
PY
|
|
1619
|
+
)"
|
|
1620
|
+
write_session_messages "$HANDOFF_SESSION_VAGUE_ACCEPTANCE" "$HANDOFF_ROOT_VAGUE_ACCEPTANCE" "$HANDOFF_MESSAGES_VAGUE_ACCEPTANCE"
|
|
1621
|
+
|
|
1622
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
1623
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_VAGUE_ACCEPTANCE" \
|
|
1624
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1625
|
+
pi --session "$HANDOFF_SESSION_VAGUE_ACCEPTANCE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-vague-acceptance.out" 2>"$TMPDIR/pi-completion-handoff-vague-acceptance.err"
|
|
1626
|
+
|
|
1627
|
+
python3 - "$HANDOFF_SNAPSHOT_VAGUE_ACCEPTANCE" "$TMPDIR/pi-completion-handoff-vague-acceptance.out" "$TMPDIR/pi-completion-handoff-vague-acceptance.err" <<'PY'
|
|
1628
|
+
import sys
|
|
1629
|
+
from pathlib import Path
|
|
1630
|
+
|
|
1631
|
+
snapshot = Path(sys.argv[1])
|
|
1632
|
+
output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
1633
|
+
|
|
1634
|
+
assert not snapshot.exists(), 'fresh explicit handoff with vague acceptance should not emit a startup proposal snapshot'
|
|
1635
|
+
assert not Path('.agent').exists(), 'fresh explicit handoff with vague acceptance should fail closed without writing canonical state'
|
|
1636
|
+
assert 'fresh explicit primary-agent handoff exists' in output, 'fresh explicit handoff with vague acceptance should use the dedicated explicit-handoff fail-closed message'
|
|
1637
|
+
assert 'acceptance is not anchored to concrete repo changes or verification' in output, 'fresh explicit handoff with vague acceptance should explain the vague acceptance failure'
|
|
1638
|
+
PY
|
|
1639
|
+
|
|
1471
1640
|
# Done workflow + fresh handoff: the fresh explicit handoff should override done-state suppression and start the new round.
|
|
1472
1641
|
HANDOFF_ROOT_DONE="$TMPDIR/handoff-root-done"
|
|
1473
1642
|
mkdir -p "$HANDOFF_ROOT_DONE"
|
|
@@ -1509,7 +1678,19 @@ capsule = {
|
|
|
1509
1678
|
"notes": [
|
|
1510
1679
|
"This is a fresh implementation round, not a summary of the finished workflow."
|
|
1511
1680
|
],
|
|
1512
|
-
"handoff_kind": "
|
|
1681
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1682
|
+
"first_slice_goal": "Patch the callback edge case and cover it with a focused regression test.",
|
|
1683
|
+
"first_slice_non_goals": [
|
|
1684
|
+
"Do not turn done-state suppression into the startup mission."
|
|
1685
|
+
],
|
|
1686
|
+
"implementation_surfaces": [
|
|
1687
|
+
"src/auth/redirect.ts",
|
|
1688
|
+
"tests/auth/redirect-edge.spec.ts"
|
|
1689
|
+
],
|
|
1690
|
+
"verification_commands": [
|
|
1691
|
+
"npm test -- redirect-edge.spec.ts"
|
|
1692
|
+
],
|
|
1693
|
+
"why_this_slice_first": "The new callback edge case is the smallest fresh implementation slice after the prior round closed.",
|
|
1513
1694
|
"task_type": "completion-workflow",
|
|
1514
1695
|
"evaluation_profile": "completion-rubric-v1",
|
|
1515
1696
|
"why_cook_now": "A new implementation-ready edge case was identified after the previous round closed."
|
|
@@ -1541,6 +1722,8 @@ assert snapshot['mission'] == 'Reopen the login redirect work for the callback e
|
|
|
1541
1722
|
assert state['mission_anchor'] == 'Reopen the login redirect work for the callback edge case.', 'done-workflow handoff should override done-state suppression with the fresh mission'
|
|
1542
1723
|
assert state['continuation_policy'] == 'continue', 'done-workflow handoff should reopen canonical workflow state for the new round'
|
|
1543
1724
|
assert state['advisory_startup_brief']['source'] == 'primary_agent_handoff', 'done-workflow handoff should preserve the handoff advisory source'
|
|
1725
|
+
assert 'First slice goal: Patch the callback edge case and cover it with a focused regression test.' in state['advisory_startup_brief']['notes'], 'done-workflow handoff should preserve first_slice_goal in advisory notes'
|
|
1726
|
+
assert 'Verification commands: npm test -- redirect-edge.spec.ts' in state['advisory_startup_brief']['notes'], 'done-workflow handoff should preserve verification_commands in advisory notes'
|
|
1544
1727
|
PY
|
|
1545
1728
|
|
|
1546
1729
|
# Stale handoff: later discussion should invalidate the older handoff capsule and fall back to the newer discussion mission.
|
|
@@ -1564,7 +1747,12 @@ capsule = {
|
|
|
1564
1747
|
"acceptance": ["Add the original callback regression test."],
|
|
1565
1748
|
"risks": [],
|
|
1566
1749
|
"notes": [],
|
|
1567
|
-
"handoff_kind": "
|
|
1750
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1751
|
+
"first_slice_goal": "Ship the original login callback follow-up.",
|
|
1752
|
+
"first_slice_non_goals": ["Do not refactor the auth stack."],
|
|
1753
|
+
"implementation_surfaces": ["src/auth/login-redirect.ts"],
|
|
1754
|
+
"verification_commands": ["npm test -- login-redirect.spec.ts"],
|
|
1755
|
+
"why_this_slice_first": "The original callback follow-up was the first bounded implementation slice before later discussion replaced it."
|
|
1568
1756
|
}
|
|
1569
1757
|
newer_discussion = "Mission: Ship logout redirect consistency instead.\nScope:\n- Update the logout redirect path.\nConstraints:\n- Leave the login callback flow unchanged.\nAcceptance:\n- Add a logout redirect regression test."
|
|
1570
1758
|
messages = [
|
|
@@ -1618,7 +1806,12 @@ capsule = {
|
|
|
1618
1806
|
"acceptance": ["Explain that the finished workflow should stay closed."],
|
|
1619
1807
|
"risks": [],
|
|
1620
1808
|
"notes": [],
|
|
1621
|
-
"handoff_kind": "
|
|
1809
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1810
|
+
"first_slice_goal": "Keep the finished workflow closed.",
|
|
1811
|
+
"first_slice_non_goals": ["Do not start repo changes."],
|
|
1812
|
+
"implementation_surfaces": ["docs/workflow-status.md"],
|
|
1813
|
+
"verification_commands": ["npm test -- workflow-status"],
|
|
1814
|
+
"why_this_slice_first": "This is the only bounded next step being proposed, even though the mission itself is invalid."
|
|
1622
1815
|
}
|
|
1623
1816
|
messages = [
|
|
1624
1817
|
{"role": "user", "content": "Should we reopen the finished workflow?"},
|
package/scripts/release-check.sh
CHANGED
|
@@ -3,6 +3,7 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
5
|
cd "$ROOT"
|
|
6
|
+
export PI_COMPLETION_RUNNING_RELEASE_CHECK=1
|
|
6
7
|
|
|
7
8
|
echo "[release-check] running control-plane validation, tracked .agent contract coverage, slice-surface parity, explicit-/cook parity, startup/refocus/context regressions, canonical evidence artifact, active-slice contract, observability, legacy cleanup, evaluator calibration, and rubric contract coverage"
|
|
8
9
|
bash .agent/verify_completion_control_plane.sh
|
|
@@ -19,18 +20,24 @@ checks = {
|
|
|
19
20
|
"Only explicit `/cook` enters the workflow. Ordinary prompts stay in the main chat and go straight to the primary agent.",
|
|
20
21
|
"If a task has clearly matured into completion-workflow scope, the primary agent should hand you off to `/cook` instead of starting long-running implementation directly in ordinary chat.",
|
|
21
22
|
"That handoff should include an explicit structured `/cook` capsule in the assistant reply so `/cook` can confirm the already-formed mission instead of re-deriving it from broad ambient context.",
|
|
22
|
-
"
|
|
23
|
+
"for that handoff capsule to start workflow immediately, it must already be implementation-startable: a bounded `first_slice_goal`, repo-change-oriented acceptance, `implementation_surfaces`, `verification_commands`, and `why_this_slice_first`",
|
|
24
|
+
"The preferred capsule is still advisory startup intake, not canonical workflow state, and it only counts as implementation-ready when it already names the first bounded slice, repo-change-oriented acceptance, implementation surfaces, and verification commands.",
|
|
25
|
+
"`/cook` first looks for a fresh explicit primary-agent handoff capsule. If one is valid and implementation-startable, `/cook` builds the startup brief from that handoff and only uses recent discussion as validation or supplemental notes.",
|
|
26
|
+
"`/cook` falls back to deriving a startup brief from recent discussion only when no fresh explicit handoff is blocking startup—for example, when there is no fresh capsule or only stale or invalidated capsules—before showing the existing approval-only Start/Cancel gate.",
|
|
23
27
|
"The pre-`/cook` handoff capsule itself is not canonical workflow state. It is only startup intake for `/cook`.",
|
|
24
28
|
],
|
|
25
29
|
"CHANGELOG.md": [
|
|
26
|
-
"made explicit primary-agent `/cook` handoff the preferred startup-intake path by teaching ordinary-chat handoff turns to emit a structured `cook_handoff` capsule and letting `/cook` prefer that capsule over broad context re-inference when it is fresh and
|
|
27
|
-
"
|
|
30
|
+
"made explicit primary-agent `/cook` handoff the preferred startup-intake path by teaching ordinary-chat handoff turns to emit a structured `cook_handoff` capsule and letting `/cook` prefer that capsule over broad context re-inference when it is fresh, valid, and implementation-startable",
|
|
31
|
+
"tightened implementation-ready explicit handoffs so the structured capsule must already carry a bounded `first_slice_goal`, repo-change-oriented acceptance, `implementation_surfaces`, `verification_commands`, and `why_this_slice_first` before `/cook` will start workflow from it",
|
|
32
|
+
"kept the pre-`/cook` handoff capsule as advisory startup intake only, not canonical `.agent/**` workflow state, while still using context-derived startup as the fallback only when no fresh explicit handoff is blocking startup",
|
|
33
|
+
"kept context-derived startup as a fallback only when there is no fresh explicit handoff blocking startup, so stale or invalidated capsules can still fall back to recent discussion while fresh non-startable handoffs fail closed instead of silently rewriting canonical state",
|
|
28
34
|
"made finished-workflow suppression stay a safety layer instead of a replacement mission when a fresh explicit `/cook` handoff exists, and blocked negative rejection/suppression text from becoming a Startable startup mission",
|
|
29
35
|
],
|
|
30
36
|
"extensions/completion/prompt-surfaces.ts": [
|
|
31
37
|
'"/cook is the only explicit entrypoint into long-running completion workflow."',
|
|
32
38
|
'"When you judge that the task has matured into completion-workflow scope',
|
|
33
|
-
'"
|
|
39
|
+
'"Distinguish a workflow-worthy handoff from an implementation-ready handoff: only emit the implementation-ready capsule when the first bounded implementation slice is concrete enough to start immediately."',
|
|
40
|
+
'"Otherwise append one exact fenced block in the same assistant reply using ```cook_handoff ... ``` JSON with kind/source/handoff_kind plus mission, scope, constraints or non_goals, acceptance, risks, notes, captured_at, source_turn_id, first_slice_goal, first_slice_non_goals, implementation_surfaces, verification_commands, why_this_slice_first, and optional task_type/evaluation_profile/why_cook_now."',
|
|
34
41
|
'"The capsule is startup intake for /cook only: do not present it as canonical .agent state',
|
|
35
42
|
],
|
|
36
43
|
}
|
package/scripts/smoke-test.sh
CHANGED
|
@@ -171,6 +171,8 @@ handoff_text = handoff.read_text()
|
|
|
171
171
|
assert '/cook is the only explicit entrypoint into long-running completion workflow.' in handoff_text, 'ordinary handoff reminder should preserve the explicit /cook workflow boundary'
|
|
172
172
|
assert 'stop short of long-running implementation and tell the user to run /cook.' in handoff_text, 'ordinary handoff reminder should require primary-agent handoff before implementation'
|
|
173
173
|
assert '```cook_handoff ... ``` JSON' in handoff_text, 'ordinary handoff reminder should require the explicit structured /cook handoff capsule'
|
|
174
|
+
assert 'implementation_workflow_handoff' in handoff_text, 'ordinary handoff reminder should require the implementation-ready handoff kind'
|
|
175
|
+
assert 'first_slice_goal, first_slice_non_goals, implementation_surfaces, verification_commands, why_this_slice_first' in handoff_text, 'ordinary handoff reminder should require first-slice startability fields'
|
|
174
176
|
assert 'The capsule is startup intake for /cook only' in handoff_text, 'ordinary handoff reminder should keep the capsule non-canonical'
|
|
175
177
|
assert not auto_resume.exists(), 'ordinary non-/cook turn should not queue auto-resume before /cook activation'
|
|
176
178
|
assert 'Skipped completion workflow auto-resume prompt (test mode)' not in output, 'ordinary non-/cook turn should not attempt auto-resume'
|
|
@@ -36,7 +36,8 @@ When the task is judged ready for completion workflow, the primary agent must:
|
|
|
36
36
|
- not edit tracked product files in ordinary chat for that workflow-level task
|
|
37
37
|
- tell the user to run `/cook`
|
|
38
38
|
- explain that `/cook` will first look for a fresh explicit primary-agent handoff and otherwise fall back to recent discussion before asking for confirmation
|
|
39
|
-
-
|
|
39
|
+
- distinguish a workflow-worthy handoff from an implementation-ready handoff
|
|
40
|
+
- only append an implementation-ready `/cook` handoff capsule when the first bounded implementation slice is concrete enough to start immediately
|
|
40
41
|
|
|
41
42
|
Required capsule format:
|
|
42
43
|
|
|
@@ -50,10 +51,16 @@ Required capsule format:
|
|
|
50
51
|
"mission": "<startable implementation mission>",
|
|
51
52
|
"scope": ["..."],
|
|
52
53
|
"constraints": ["..."],
|
|
54
|
+
"non_goals": ["..."],
|
|
53
55
|
"acceptance": ["..."],
|
|
54
56
|
"risks": ["..."],
|
|
55
57
|
"notes": ["..."],
|
|
56
|
-
"handoff_kind": "
|
|
58
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
59
|
+
"first_slice_goal": "<bounded first slice goal>",
|
|
60
|
+
"first_slice_non_goals": ["..."],
|
|
61
|
+
"implementation_surfaces": ["path/or/surface"],
|
|
62
|
+
"verification_commands": ["npm test -- example"],
|
|
63
|
+
"why_this_slice_first": "<why this first slice should start the workflow>",
|
|
57
64
|
"task_type": "completion-workflow",
|
|
58
65
|
"evaluation_profile": "completion-rubric-v1",
|
|
59
66
|
"why_cook_now": "<why the task is ready for /cook now>"
|
|
@@ -64,6 +71,7 @@ Required capsule format:
|
|
|
64
71
|
Notes:
|
|
65
72
|
|
|
66
73
|
- `constraints` may be replaced or supplemented by `non_goals` when clearer.
|
|
74
|
+
- `first_slice_goal`, `first_slice_non_goals`, `implementation_surfaces`, `verification_commands`, and `why_this_slice_first` are required only for implementation-ready handoffs; if the work is workflow-worthy but that first slice is still vague, tell the user to run `/cook` without emitting this implementation-ready capsule.
|
|
67
75
|
- The mission must be positively startable implementation work; do not use rejection or suppression text as the mission.
|
|
68
76
|
- The capsule is startup intake for `/cook` only. It is not canonical `.agent/**` state, not active-slice state, and not a second repo contract source.
|
|
69
77
|
|