@linimin/pi-letscook 0.1.61 → 0.1.63
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 +7 -1
- package/README.md +28 -25
- package/extensions/completion/driver.ts +24 -44
- package/extensions/completion/index.ts +6 -73
- package/extensions/completion/prompt-surfaces.ts +9 -9
- package/package.json +1 -1
- package/scripts/context-proposal-test.sh +25 -29
- package/scripts/refocus-test.sh +4 -4
- package/scripts/release-check.sh +17 -21
- package/scripts/smoke-test.sh +8 -8
- package/skills/cook-handoff-boundary/SKILL.md +48 -51
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.1.62
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- made ordinary chat implementation-first again so the primary agent may directly edit repo files without requiring `/cook` when workflow state is unnecessary
|
|
8
|
+
- repositioned `/cook` as optional workflow mode for confirm-first startup, resumability, review/audit flow, and canonical `.agent/**` state rather than as a mandatory implementation boundary
|
|
9
|
+
- updated ordinary-chat boundary docs, reminders, and release-parity checks so they no longer tell the agent to block repo edits pending explicit `/cook`
|
|
4
10
|
|
|
5
11
|
## 0.1.61
|
|
6
12
|
|
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @linimin/pi-letscook
|
|
2
2
|
|
|
3
|
-
`/cook`
|
|
3
|
+
`/cook` is optional workflow mode for turning main-chat discussion about concrete repo changes into a resumable repo workflow stored in repo-local `.agent/**` state.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
You can still implement directly in ordinary chat when you do not need workflow state. Use `/cook` when you want confirm-first startup, resumability, review/audit flow, or canonical `.agent/**` control.
|
|
6
6
|
|
|
7
7
|
## Use it when
|
|
8
8
|
|
|
@@ -10,10 +10,12 @@
|
|
|
10
10
|
- you want one mission tracked in repo state instead of chat memory
|
|
11
11
|
- you want clear continue / refocus / next-round behavior
|
|
12
12
|
- you want review, audit, and verification tied to the repo
|
|
13
|
+
- you want a confirm-first workflow boundary before canonical state is written
|
|
13
14
|
|
|
14
15
|
## Skip it when
|
|
15
16
|
|
|
16
17
|
- you only need a one-off answer
|
|
18
|
+
- you want the agent to implement directly in ordinary chat
|
|
17
19
|
- you are brainstorming
|
|
18
20
|
- you are writing planning docs but are not ready to start concrete repo changes
|
|
19
21
|
|
|
@@ -30,10 +32,10 @@ Then run `/reload` in Pi.
|
|
|
30
32
|
1. Install the package:
|
|
31
33
|
`pi install npm:@linimin/pi-letscook`
|
|
32
34
|
2. Run `/reload` in Pi.
|
|
33
|
-
3. In the main chat,
|
|
34
|
-
4. When you want
|
|
35
|
-
5. Review the
|
|
36
|
-
6. Later, run `/cook` again to resume from canonical state or confirm a
|
|
35
|
+
3. In the main chat, either implement directly with the agent or refine the concrete repo change you want.
|
|
36
|
+
4. When you want workflow mode, run `/cook`.
|
|
37
|
+
5. Review the startup brief and choose **Start** or **Cancel**.
|
|
38
|
+
6. Later, run `/cook` again to resume from canonical state or confirm a primary-agent-authored replacement or next-round handoff.
|
|
37
39
|
|
|
38
40
|
```text
|
|
39
41
|
/cook
|
|
@@ -43,20 +45,21 @@ Then run `/reload` in Pi.
|
|
|
43
45
|
|
|
44
46
|
| If you want to... | Do this |
|
|
45
47
|
|---|---|
|
|
46
|
-
|
|
|
48
|
+
| Implement directly without workflow | Ask in ordinary chat and let the agent modify the repo directly |
|
|
49
|
+
| Start a tracked workflow | Ask the primary agent in ordinary chat to prepare the explicit `/cook` handoff, then run `/cook` |
|
|
47
50
|
| Continue the current workflow | Run `/cook` |
|
|
48
|
-
| Refocus or start the next round |
|
|
51
|
+
| Refocus or start the next round | Ask the primary agent in ordinary chat to prepare the fresh explicit `/cook` handoff for the new slice, then run `/cook` |
|
|
49
52
|
|
|
50
53
|
## What `/cook` expects
|
|
51
54
|
|
|
52
|
-
-
|
|
53
|
-
- a mission
|
|
55
|
+
- a fresh explicit primary-agent `cook_handoff` capsule for any new-workflow, next-round, or replacement startup
|
|
56
|
+
- a mission and first slice concrete enough for the primary agent to author the startup handoff directly
|
|
54
57
|
- acceptance and verification intent that can support a truthful first workflow round
|
|
55
58
|
- README/CHANGELOG updates still count as concrete repo changes
|
|
56
|
-
- assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts still do not count unless
|
|
57
|
-
-
|
|
59
|
+
- assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts still do not count unless the primary agent turns them into an explicit `cook_handoff` capsule
|
|
60
|
+
- `/cook` does not synthesize startup from recent discussion when handoff data is missing; the primary agent must provide the handoff
|
|
58
61
|
|
|
59
|
-
If
|
|
62
|
+
If no fresh explicit primary-agent handoff exists, `/cook` fails closed, leaves canonical `.agent/**` state unchanged, and tells you to ask the primary agent in the main chat to emit a fresh `cook_handoff` capsule before rerunning `/cook`.
|
|
60
63
|
|
|
61
64
|
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
65
|
|
|
@@ -64,20 +67,20 @@ If you pass inline arguments to `/cook`, it also fails closed and tells you to m
|
|
|
64
67
|
|
|
65
68
|
## Workflow entry
|
|
66
69
|
|
|
67
|
-
Only explicit `/cook` enters
|
|
70
|
+
Only explicit `/cook` enters workflow mode. Ordinary prompts stay in the main chat and go straight to the primary agent.
|
|
68
71
|
|
|
69
|
-
|
|
72
|
+
Ordinary chat can still directly implement repo changes. `/cook` is for the cases where you want workflow control rather than just implementation help, and the primary agent should prepare the handoff before you run it.
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
When you explicitly run `/cook`, it should consume the explicit primary-agent handoff you already prepared in ordinary chat, then ask you to **Start** or **Cancel** before rewriting canonical workflow state.
|
|
72
75
|
|
|
73
|
-
|
|
76
|
+
Explicit `/cook` capsules are the required startup intake for new-workflow, next-round, and replacement entry.
|
|
74
77
|
|
|
75
78
|
Important behavior:
|
|
76
|
-
- `/cook` is
|
|
77
|
-
- startup and next-round entry stay confirm-first, but they
|
|
78
|
-
- active workflows resume from canonical `.agent/**` state unless
|
|
79
|
+
- `/cook` is an optional workflow boundary and manual entry point
|
|
80
|
+
- startup and next-round entry stay confirm-first, but they start from explicit primary-agent handoff data rather than recent-discussion guessing
|
|
81
|
+
- active workflows resume from canonical `.agent/**` state unless a fresh explicit primary-agent handoff proposes a concrete replacement mission
|
|
79
82
|
- explicit slash commands other than `/cook` continue normally in the main chat
|
|
80
|
-
- ordinary main-chat discussion may clarify
|
|
83
|
+
- ordinary main-chat discussion may clarify, propose, or directly implement repo changes without entering workflow mode
|
|
81
84
|
|
|
82
85
|
## Typical examples
|
|
83
86
|
|
|
@@ -91,13 +94,13 @@ I want to add login redirect handling and tests.
|
|
|
91
94
|
|
|
92
95
|
## What happens when you run `/cook`
|
|
93
96
|
|
|
94
|
-
`/cook` first checks for a fresh explicit primary-agent handoff capsule
|
|
97
|
+
`/cook` first checks for a fresh explicit primary-agent handoff capsule. New-workflow entry, done-workflow next-round entry, and active-workflow replacement should use that handoff instead of guessing from recent discussion. If no fresh explicit handoff exists, `/cook` fails closed for startup/refocus and resumes canonical state only when continuing the existing workflow. None of this prevents ordinary-chat implementation when you choose not to enter workflow mode.
|
|
95
98
|
|
|
96
99
|
| Repo state | What you'll see |
|
|
97
100
|
|---|---|
|
|
98
|
-
| No workflow yet | `/cook`
|
|
99
|
-
| Active workflow exists | Usually a resume of the current workflow from canonical `.agent/**` state. If
|
|
100
|
-
| Previous workflow is `done` | `/cook` can
|
|
101
|
+
| No workflow yet | `/cook` consumes a fresh explicit primary-agent handoff and asks you to choose **Start** or **Cancel**. Missing, stale, planning-only, or non-startable handoffs fail closed. |
|
|
102
|
+
| Active workflow exists | Usually a resume of the current workflow from canonical `.agent/**` state. If a fresh explicit primary-agent handoff points to a different concrete replacement mission, `/cook` shows a chooser first and only rewrites canonical state after you confirm the replacement. Ambiguous or missing replacement handoff stays conservative. |
|
|
103
|
+
| Previous workflow is `done` | `/cook` can start the next implementation round only from a fresh explicit primary-agent handoff behind **Start** or **Cancel**. Missing, weak, or planning-only next-round handoffs fail closed. |
|
|
101
104
|
|
|
102
105
|
## Confirmation and fail-closed behavior
|
|
103
106
|
|
|
@@ -38,7 +38,7 @@ type ContextProposalAlternate = {
|
|
|
38
38
|
analysis: ContextProposalAnalysis;
|
|
39
39
|
goalText: string;
|
|
40
40
|
basisPreview: string;
|
|
41
|
-
source: "session" | "analyst" | "handoff_capsule"
|
|
41
|
+
source: "session" | "analyst" | "handoff_capsule";
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
type ContextProposal = ContextProposalAlternate & {
|
|
@@ -60,7 +60,7 @@ type CookContextProposalResult = {
|
|
|
60
60
|
blockedFailureMessage?: string;
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
function
|
|
63
|
+
function buildCookStartupBriefRequiredMessage(deps: CompletionDriverDeps, prefix?: string): string {
|
|
64
64
|
const requirement = deps.structuredDiscussionFailureDetail;
|
|
65
65
|
return prefix ? `${prefix} ${requirement}` : requirement;
|
|
66
66
|
}
|
|
@@ -72,10 +72,9 @@ type ActiveWorkflowProposalAssessment = {
|
|
|
72
72
|
blockedFailureMessage?: string;
|
|
73
73
|
reason:
|
|
74
74
|
| "matching_mission"
|
|
75
|
-
| "
|
|
76
|
-
| "
|
|
77
|
-
| "
|
|
78
|
-
| "replacement_not_startable";
|
|
75
|
+
| "missing_explicit_handoff"
|
|
76
|
+
| "fresh_explicit_handoff"
|
|
77
|
+
| "fresh_explicit_handoff_not_startable";
|
|
79
78
|
};
|
|
80
79
|
|
|
81
80
|
type ExistingWorkflowChooserOptions = {
|
|
@@ -321,23 +320,23 @@ async function assessActiveWorkflowProposalRouting(
|
|
|
321
320
|
): Promise<ActiveWorkflowProposalAssessment> {
|
|
322
321
|
const currentMission = currentMissionAnchor(snapshot);
|
|
323
322
|
const projectName = path.basename(snapshot.files.root);
|
|
324
|
-
const
|
|
325
|
-
if (
|
|
323
|
+
const explicitHandoff = await deps.deriveCookStartupProposal(ctx, projectName);
|
|
324
|
+
if (explicitHandoff.blockedFailureMessage) {
|
|
326
325
|
const assessment: ActiveWorkflowProposalAssessment = {
|
|
327
326
|
action: "blocked",
|
|
328
327
|
currentMissionAnchor: currentMission,
|
|
329
|
-
blockedFailureMessage:
|
|
330
|
-
reason: "
|
|
328
|
+
blockedFailureMessage: explicitHandoff.blockedFailureMessage,
|
|
329
|
+
reason: "fresh_explicit_handoff_not_startable",
|
|
331
330
|
};
|
|
332
331
|
deps.maybeWriteActiveWorkflowRoutingSnapshot(assessment);
|
|
333
332
|
return assessment;
|
|
334
333
|
}
|
|
335
|
-
const proposal =
|
|
334
|
+
const proposal = explicitHandoff.proposal;
|
|
336
335
|
if (!proposal) {
|
|
337
336
|
const assessment: ActiveWorkflowProposalAssessment = {
|
|
338
337
|
action: "continue",
|
|
339
338
|
currentMissionAnchor: currentMission,
|
|
340
|
-
reason: "
|
|
339
|
+
reason: "missing_explicit_handoff",
|
|
341
340
|
};
|
|
342
341
|
deps.maybeWriteActiveWorkflowRoutingSnapshot(assessment);
|
|
343
342
|
return assessment;
|
|
@@ -356,7 +355,7 @@ async function assessActiveWorkflowProposalRouting(
|
|
|
356
355
|
action: "refocus",
|
|
357
356
|
currentMissionAnchor: currentMission,
|
|
358
357
|
proposal,
|
|
359
|
-
reason:
|
|
358
|
+
reason: "fresh_explicit_handoff",
|
|
360
359
|
};
|
|
361
360
|
deps.maybeWriteActiveWorkflowRoutingSnapshot(assessment);
|
|
362
361
|
return assessment;
|
|
@@ -543,7 +542,7 @@ export async function runCookEntry(
|
|
|
543
542
|
}
|
|
544
543
|
const proposal = derived.proposal;
|
|
545
544
|
if (!proposal) {
|
|
546
|
-
deps.emitCommandText(ctx,
|
|
545
|
+
deps.emitCommandText(ctx, buildCookStartupBriefRequiredMessage(deps), "info");
|
|
547
546
|
return;
|
|
548
547
|
}
|
|
549
548
|
const decision = await deps.confirmContextProposal(ctx, proposal, {
|
|
@@ -588,7 +587,7 @@ export async function runCookEntry(
|
|
|
588
587
|
}
|
|
589
588
|
const proposal = derived.proposal;
|
|
590
589
|
if (!proposal) {
|
|
591
|
-
deps.emitCommandText(ctx,
|
|
590
|
+
deps.emitCommandText(ctx, buildCookStartupBriefRequiredMessage(deps, "The previous completion workflow is already done."), "info");
|
|
592
591
|
return;
|
|
593
592
|
}
|
|
594
593
|
const decision = await deps.confirmContextProposal(ctx, proposal, {
|
|
@@ -610,13 +609,7 @@ export async function runCookEntry(
|
|
|
610
609
|
buildAdvisoryStartupBrief({ proposal, analysis: decision.analysis }),
|
|
611
610
|
);
|
|
612
611
|
snapshot = (await loadCompletionSnapshot(snapshot.files.root)) ?? snapshot;
|
|
613
|
-
deps.emitCommandText(
|
|
614
|
-
ctx,
|
|
615
|
-
proposal.source === "handoff_capsule"
|
|
616
|
-
? `Started a new completion workflow round from explicit primary-agent handoff: ${decision.missionAnchor}`
|
|
617
|
-
: `Started a new completion workflow round from deferred primary-agent handoff: ${decision.missionAnchor}`,
|
|
618
|
-
"info",
|
|
619
|
-
);
|
|
612
|
+
deps.emitCommandText(ctx, `Started a new completion workflow round from explicit primary-agent handoff: ${decision.missionAnchor}`, "info");
|
|
620
613
|
} else {
|
|
621
614
|
const assessment = await assessActiveWorkflowProposalRouting(ctx, snapshot, deps);
|
|
622
615
|
if (assessment.action === "blocked") {
|
|
@@ -627,29 +620,20 @@ export async function runCookEntry(
|
|
|
627
620
|
await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps);
|
|
628
621
|
return;
|
|
629
622
|
}
|
|
630
|
-
const explicitReplacement = assessment.reason === "
|
|
631
|
-
const deferredReplacement = assessment.reason === "deferred_replacement";
|
|
623
|
+
const explicitReplacement = assessment.reason === "fresh_explicit_handoff";
|
|
632
624
|
const decision = await confirmExistingWorkflowProposal(ctx, snapshot, assessment.proposal, deps, {
|
|
633
625
|
intro: explicitReplacement
|
|
634
626
|
? "A fresh explicit primary-agent handoff proposes replacing the current workflow. Choose how /cook should proceed:"
|
|
635
|
-
:
|
|
636
|
-
? "A deferred primary-agent handoff synthesized from your recent discussion proposes replacing the current workflow. Choose how /cook should proceed:"
|
|
637
|
-
: "A replacement workflow is ready. Choose how /cook should proceed:",
|
|
627
|
+
: "A replacement workflow is ready. Choose how /cook should proceed:",
|
|
638
628
|
proposedMissionLabel: explicitReplacement
|
|
639
629
|
? "Proposed mission from explicit primary-agent handoff"
|
|
640
|
-
:
|
|
641
|
-
? "Proposed mission from deferred primary-agent handoff"
|
|
642
|
-
: "Proposed mission",
|
|
630
|
+
: "Proposed mission",
|
|
643
631
|
refocusChoiceLabel: explicitReplacement
|
|
644
632
|
? "Start new workflow from explicit primary-agent handoff\n\nReview the proposed replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state."
|
|
645
|
-
:
|
|
646
|
-
? "Start new workflow from deferred primary-agent handoff\n\nReview the proposed replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state."
|
|
647
|
-
: "Start new workflow\n\nReview the proposed replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state.",
|
|
633
|
+
: "Start new workflow\n\nReview the proposed replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state.",
|
|
648
634
|
alternateChoiceLabel: explicitReplacement
|
|
649
635
|
? "Start alternate workflow from explicit primary-agent handoff\n\nReview this alternate replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state."
|
|
650
|
-
:
|
|
651
|
-
? "Start alternate workflow from deferred primary-agent handoff\n\nReview this alternate replacement in a final Start/Cancel confirmation before /cook rewrites canonical workflow state."
|
|
652
|
-
: undefined,
|
|
636
|
+
: undefined,
|
|
653
637
|
comparison: "strict",
|
|
654
638
|
});
|
|
655
639
|
if (!decision) {
|
|
@@ -662,11 +646,9 @@ export async function runCookEntry(
|
|
|
662
646
|
}
|
|
663
647
|
const selectedProposal = decision.proposal;
|
|
664
648
|
const proposalDecision = await deps.confirmContextProposal(ctx, selectedProposal, {
|
|
665
|
-
title:
|
|
649
|
+
title: assessment.reason === "fresh_explicit_handoff"
|
|
666
650
|
? "Start the replacement workflow from this explicit startup brief?"
|
|
667
|
-
:
|
|
668
|
-
? "Start the replacement workflow from this deferred startup brief?"
|
|
669
|
-
: "Start the replacement workflow from this startup brief?",
|
|
651
|
+
: "Start the replacement workflow from this startup brief?",
|
|
670
652
|
});
|
|
671
653
|
if (!proposalDecision) {
|
|
672
654
|
deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled replacement workflow proposal", deps), "info");
|
|
@@ -686,11 +668,9 @@ export async function runCookEntry(
|
|
|
686
668
|
snapshot = (await loadCompletionSnapshot(snapshot.files.root)) ?? snapshot;
|
|
687
669
|
deps.emitCommandText(
|
|
688
670
|
ctx,
|
|
689
|
-
|
|
671
|
+
assessment.reason === "fresh_explicit_handoff"
|
|
690
672
|
? `Refocused completion mission from explicit primary-agent handoff to: ${proposalDecision.missionAnchor}`
|
|
691
|
-
:
|
|
692
|
-
? `Refocused completion mission from deferred primary-agent handoff to: ${proposalDecision.missionAnchor}`
|
|
693
|
-
: `Refocused completion mission to: ${proposalDecision.missionAnchor}`,
|
|
673
|
+
: `Refocused completion mission to: ${proposalDecision.missionAnchor}`,
|
|
694
674
|
"info",
|
|
695
675
|
);
|
|
696
676
|
}
|
|
@@ -135,10 +135,9 @@ type ActiveWorkflowProposalAssessment = {
|
|
|
135
135
|
blockedFailureMessage?: string;
|
|
136
136
|
reason:
|
|
137
137
|
| "matching_mission"
|
|
138
|
-
| "
|
|
139
|
-
| "
|
|
140
|
-
| "
|
|
141
|
-
| "replacement_not_startable";
|
|
138
|
+
| "missing_explicit_handoff"
|
|
139
|
+
| "fresh_explicit_handoff"
|
|
140
|
+
| "fresh_explicit_handoff_not_startable";
|
|
142
141
|
};
|
|
143
142
|
|
|
144
143
|
function completionTestWorkflowActionOverride(): "continue" | "refocus" | "cancel" | undefined {
|
|
@@ -212,7 +211,7 @@ function maybeWriteTestSnapshot(targetPath: string | undefined, content: string)
|
|
|
212
211
|
|
|
213
212
|
const COOK_MAIN_CHAT_RERUN_GUIDANCE = "Discuss changes in the main chat and rerun /cook.";
|
|
214
213
|
const COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL =
|
|
215
|
-
"/cook failed closed because
|
|
214
|
+
"/cook failed closed because starting workflow now requires a fresh explicit primary-agent handoff. Ask the primary agent in the main chat to emit a fresh ```cook_handoff``` capsule, then rerun /cook.";
|
|
216
215
|
|
|
217
216
|
function isWorkflowDone(snapshot: CompletionStateSnapshot | undefined): boolean {
|
|
218
217
|
return asString(snapshot?.state?.continuation_policy) === "done";
|
|
@@ -375,72 +374,6 @@ async function promptContextProposalConfirmationAction(
|
|
|
375
374
|
});
|
|
376
375
|
}
|
|
377
376
|
|
|
378
|
-
function stripCookHandoffBlocks(text: string): string {
|
|
379
|
-
return text.replace(COOK_HANDOFF_BLOCK_REGEX, " ").replace(/\s+/g, " ").trim();
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
async function deriveCookRecentDiscussionProposal(
|
|
383
|
-
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
384
|
-
projectName: string,
|
|
385
|
-
): Promise<ContextProposal | undefined> {
|
|
386
|
-
const recentMessages = collectRecentSessionMessages(ctx, { isRecord, asString, asNumber, isStaleContextError });
|
|
387
|
-
const recentEntries = recentMessages
|
|
388
|
-
.filter((entry) => (entry.role === "user" || entry.role === "custom") && !entry.isCommand)
|
|
389
|
-
.filter((entry) => !/```cook_handoff\b/i.test(entry.text))
|
|
390
|
-
.slice(0, 8)
|
|
391
|
-
.map((entry) => ({ role: entry.role, text: stripCookHandoffBlocks(entry.text) }))
|
|
392
|
-
.filter((entry) => entry.text.length > 0);
|
|
393
|
-
const snapshot = await loadCompletionSnapshot(getCtxCwd(ctx));
|
|
394
|
-
const workflowContextLines = snapshot
|
|
395
|
-
? [
|
|
396
|
-
`current mission anchor: ${asString(snapshot.state?.mission_anchor) ?? asString(snapshot.plan?.mission_anchor) ?? asString(snapshot.active?.mission_anchor) ?? "(none)"}`,
|
|
397
|
-
`continuation policy: ${asString(snapshot.state?.continuation_policy) ?? "(none)"}`,
|
|
398
|
-
`latest completed slice: ${asString(snapshot.state?.latest_completed_slice) ?? "(none)"}`,
|
|
399
|
-
`latest verified slice: ${asString(snapshot.state?.latest_verified_slice) ?? "(none)"}`,
|
|
400
|
-
`active slice goal: ${asString(snapshot.active?.goal) ?? "(none)"}`,
|
|
401
|
-
`active slice why_now: ${asString(snapshot.active?.why_now) ?? "(none)"}`,
|
|
402
|
-
`verification goal: ${asString(snapshot.verificationEvidence?.goal) ?? "(none)"}`,
|
|
403
|
-
`verification summary: ${asString(snapshot.verificationEvidence?.summary) ?? "(none)"}`,
|
|
404
|
-
]
|
|
405
|
-
: [];
|
|
406
|
-
const proposal = await deriveCookContextProposalFromRecentDiscussion(projectName, recentEntries, {
|
|
407
|
-
asString,
|
|
408
|
-
asStringArray,
|
|
409
|
-
workflowContext: snapshot
|
|
410
|
-
? {
|
|
411
|
-
currentMissionAnchor:
|
|
412
|
-
asString(snapshot.state?.mission_anchor) ?? asString(snapshot.plan?.mission_anchor) ?? asString(snapshot.active?.mission_anchor),
|
|
413
|
-
latestCompletedSlice: asString(snapshot.state?.latest_completed_slice),
|
|
414
|
-
latestVerifiedSlice: asString(snapshot.state?.latest_verified_slice),
|
|
415
|
-
activeSliceGoal: asString(snapshot.active?.goal),
|
|
416
|
-
activeSliceWhyNow: asString(snapshot.active?.why_now),
|
|
417
|
-
verificationGoal: asString(snapshot.verificationEvidence?.goal),
|
|
418
|
-
verificationSummary: asString(snapshot.verificationEvidence?.summary),
|
|
419
|
-
continuationPolicy: asString(snapshot.state?.continuation_policy),
|
|
420
|
-
}
|
|
421
|
-
: undefined,
|
|
422
|
-
analyzeContextProposal: async (entries) =>
|
|
423
|
-
await analyzeContextProposalWithAgent({
|
|
424
|
-
ctx,
|
|
425
|
-
projectName,
|
|
426
|
-
recentEntries: entries,
|
|
427
|
-
workflowContextLines,
|
|
428
|
-
liveRoleActivityByRoot,
|
|
429
|
-
completionStatusKey: COMPLETION_STATUS_KEY,
|
|
430
|
-
safeUiCall,
|
|
431
|
-
getCtxCwd,
|
|
432
|
-
getCtxHasUI,
|
|
433
|
-
getCtxUi,
|
|
434
|
-
}),
|
|
435
|
-
assessMissionAnchor,
|
|
436
|
-
isWeakMissionAnchor,
|
|
437
|
-
missionAnchorsStrictlyEquivalent,
|
|
438
|
-
normalizeMissionAnchorText,
|
|
439
|
-
stripCodeBlocks,
|
|
440
|
-
});
|
|
441
|
-
return retagContextProposalSource(proposal, "deferred_primary_agent_handoff");
|
|
442
|
-
}
|
|
443
|
-
|
|
444
377
|
async function deriveCookStartupProposal(
|
|
445
378
|
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
446
379
|
projectName: string,
|
|
@@ -459,7 +392,7 @@ async function deriveCookStartupProposal(
|
|
|
459
392
|
if (explicitHandoff.status === "fresh_but_not_startable") {
|
|
460
393
|
return { blockedFailureMessage: explicitHandoff.message };
|
|
461
394
|
}
|
|
462
|
-
return {
|
|
395
|
+
return {};
|
|
463
396
|
}
|
|
464
397
|
|
|
465
398
|
async function deriveCookContextProposal(
|
|
@@ -942,7 +875,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
942
875
|
structuredDiscussionFailureDetail: COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL,
|
|
943
876
|
mainChatRerunGuidance: COOK_MAIN_CHAT_RERUN_GUIDANCE,
|
|
944
877
|
cookCommandSpec: {
|
|
945
|
-
description: "/cook workflow:
|
|
878
|
+
description: "/cook workflow: start or replace workflow only from an explicit primary-agent handoff, or resume the current workflow from canonical state",
|
|
946
879
|
},
|
|
947
880
|
buildContextProposalContinuationReason,
|
|
948
881
|
completionKickoff,
|
|
@@ -26,17 +26,17 @@ export type AdvisoryStartupBrief = {
|
|
|
26
26
|
|
|
27
27
|
export function buildCookHandoffBoundaryReminder(): string {
|
|
28
28
|
return [
|
|
29
|
-
"You are
|
|
30
|
-
"
|
|
31
|
-
"/cook is the only explicit entrypoint into long-running completion workflow.",
|
|
29
|
+
"You are in ordinary main chat unless the user explicitly runs /cook.",
|
|
30
|
+
"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.",
|
|
32
31
|
"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.",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"If the user asks follow-up questions or wants to keep refining scope, continue helping in ordinary chat
|
|
36
|
-
"
|
|
37
|
-
"
|
|
32
|
+
"/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.",
|
|
33
|
+
"If the user wants direct implementation now, stay in ordinary chat and help directly instead of blocking on /cook.",
|
|
34
|
+
"If the user asks follow-up questions or wants to keep refining scope, continue helping naturally in ordinary chat.",
|
|
35
|
+
"If the user explicitly asks to enter /cook workflow, generate one fresh ```cook_handoff``` capsule in ordinary chat from the primary-agent view of the task, then tell the user to run /cook.",
|
|
36
|
+
"Do not expect /cook to infer or guess startup intent from recent discussion alone; /cook should consume the explicit primary-agent handoff instead.",
|
|
37
|
+
"Only provide a preview startup brief or ```cook_handoff``` capsule in ordinary chat when the user explicitly asks for that preview behavior or explicitly asks to enter /cook workflow.",
|
|
38
38
|
"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.",
|
|
39
|
-
"
|
|
39
|
+
"When you continue in ordinary chat, do not pretend /cook already started and do not silently rewrite discussion into canonical workflow state.",
|
|
40
40
|
].join(" ");
|
|
41
41
|
}
|
|
42
42
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linimin/pi-letscook",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.63",
|
|
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,
|
|
@@ -147,14 +147,15 @@ mkdir -p "$ROOT"
|
|
|
147
147
|
cd "$ROOT"
|
|
148
148
|
git init -q
|
|
149
149
|
|
|
150
|
-
# No workflow yet: bare /cook should
|
|
151
|
-
# even when
|
|
150
|
+
# No workflow yet: bare /cook should fail closed without a fresh explicit primary-agent handoff,
|
|
151
|
+
# even when recent discussion is fully structured.
|
|
152
152
|
SESSION_ZERO="$TMPDIR/session-zero.jsonl"
|
|
153
153
|
DISCUSSION_ZERO=$'Mission: Remove the completion status line while keeping the completion widget.\nScope:\n- Keep the non-running completion widget.\n- Suppress the widget while a completion role is active.\nConstraints:\n- Do not reintroduce any other completion status surface.\nAcceptance:\n- Update README to match the shipped behavior.\n- Keep observability regression coverage truthful.'
|
|
154
154
|
DISCUSSION_SNAPSHOT_ZERO="$TMPDIR/context-proposal-structured-fallback.json"
|
|
155
155
|
write_session "$SESSION_ZERO" "$ROOT" "$DISCUSSION_ZERO"
|
|
156
156
|
|
|
157
157
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
158
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
158
159
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO" \
|
|
159
160
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
160
161
|
pi --session "$SESSION_ZERO" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-structured-fallback.out" 2>"$TMPDIR/pi-completion-context-proposal-structured-fallback.err"
|
|
@@ -166,13 +167,9 @@ from pathlib import Path
|
|
|
166
167
|
|
|
167
168
|
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
168
169
|
snapshot = Path(sys.argv[3])
|
|
169
|
-
assert Path('.agent').exists(), '
|
|
170
|
-
assert snapshot.exists(), '
|
|
171
|
-
|
|
172
|
-
brief = json.loads(Path('.agent/state.json').read_text())['advisory_startup_brief']
|
|
173
|
-
assert proposal['source'] == 'deferred_primary_agent_handoff', 'structured startup should snapshot the deferred primary-agent handoff source'
|
|
174
|
-
assert brief['source'] == 'deferred_primary_agent_handoff', 'structured startup should record the deferred primary-agent handoff source in advisory intake'
|
|
175
|
-
assert 'Initialized completion control plane' in output, 'structured startup should initialize canonical workflow state'
|
|
170
|
+
assert not Path('.agent').exists(), 'missing explicit handoff should fail closed without writing canonical state'
|
|
171
|
+
assert not snapshot.exists(), 'missing explicit handoff should not emit a startup proposal snapshot'
|
|
172
|
+
assert 'fresh explicit primary-agent handoff' in output, 'missing explicit handoff should explain the explicit-handoff-only startup contract'
|
|
176
173
|
PY
|
|
177
174
|
|
|
178
175
|
rm -rf .agent
|
|
@@ -220,7 +217,7 @@ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
|
220
217
|
snapshot = Path(sys.argv[3])
|
|
221
218
|
assert not Path('.agent').exists(), 'user-authored faux handoff without supporting discussion should still fail closed without writing canonical state'
|
|
222
219
|
assert not snapshot.exists(), 'user-authored faux handoff should not emit a startup proposal snapshot'
|
|
223
|
-
assert '
|
|
220
|
+
assert 'fresh explicit primary-agent handoff' in output, 'user-authored faux handoff should still explain the explicit-handoff requirement'
|
|
224
221
|
PY
|
|
225
222
|
|
|
226
223
|
# No workflow yet: malformed or invalid assistant handoff capsules must also fail closed.
|
|
@@ -241,7 +238,7 @@ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
|
241
238
|
snapshot = Path(sys.argv[3])
|
|
242
239
|
assert not Path('.agent').exists(), 'invalid assistant handoff without supporting discussion should fail closed without writing canonical state'
|
|
243
240
|
assert not snapshot.exists(), 'invalid assistant handoff should not emit a startup proposal snapshot'
|
|
244
|
-
assert '
|
|
241
|
+
assert 'fresh explicit primary-agent handoff' in output, 'invalid assistant handoff should still explain the explicit-handoff requirement'
|
|
245
242
|
PY
|
|
246
243
|
|
|
247
244
|
# No workflow yet: a fresh explicit primary-agent handoff should still bootstrap canonical startup state.
|
|
@@ -389,7 +386,7 @@ assert routing['mode'] == 'bare', 'active bare /cook resume regression should sn
|
|
|
389
386
|
assert 'explicitGoal' not in routing, 'active bare /cook resume routing should not expose removed explicit-goal shim fields'
|
|
390
387
|
assert 'explicitGoalProvided' not in routing, 'active bare /cook resume routing should not expose removed explicit-goal shim fields'
|
|
391
388
|
assert routing['action'] == 'continue', 'active bare /cook should resume when no fresh explicit handoff exists'
|
|
392
|
-
assert routing['reason'] == '
|
|
389
|
+
assert routing['reason'] == 'missing_explicit_handoff', 'active bare /cook should explain that resume happened because no fresh explicit handoff existed'
|
|
393
390
|
assert routing['currentMissionAnchor'] == mission, 'resume routing should preserve the current mission anchor'
|
|
394
391
|
assert routing['proposedMissionAnchor'] is None, 'resume routing should not derive a replacement mission from recent discussion'
|
|
395
392
|
assert 'Resume the completion workflow from canonical state.' in resume, 'active bare /cook resume should still use the canonical resume prompt'
|
|
@@ -434,7 +431,7 @@ active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
|
434
431
|
|
|
435
432
|
assert routing['mode'] == 'bare', 'discussion-driven refocus removal should snapshot bare routing mode'
|
|
436
433
|
assert routing['action'] == 'continue', 'bare /cook should resume instead of deriving a replacement workflow from recent discussion'
|
|
437
|
-
assert routing['reason'] == '
|
|
434
|
+
assert routing['reason'] == 'missing_explicit_handoff', 'discussion-driven refocus removal should explain that no fresh explicit handoff existed'
|
|
438
435
|
assert routing['currentMissionAnchor'] == mission, 'discussion-driven refocus removal should preserve the current mission anchor'
|
|
439
436
|
assert routing['proposedMissionAnchor'] is None, 'discussion-driven refocus removal should not preserve a replacement mission from recent discussion'
|
|
440
437
|
assert 'Resume the completion workflow from canonical state.' in resume, 'discussion-driven refocus removal should still queue the canonical resume prompt'
|
|
@@ -508,7 +505,7 @@ active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
|
508
505
|
|
|
509
506
|
assert routing['mode'] == 'bare', 'summary-only active bare /cook regression should snapshot bare routing mode'
|
|
510
507
|
assert routing['action'] == 'continue', 'summary-only active bare /cook should resume rather than derive replacement startup'
|
|
511
|
-
assert routing['reason'] == '
|
|
508
|
+
assert routing['reason'] == 'missing_explicit_handoff', 'summary-only active bare /cook should explain that no fresh explicit handoff existed'
|
|
512
509
|
assert routing['currentMissionAnchor'] == mission, 'summary-only active bare /cook should preserve the current mission anchor'
|
|
513
510
|
assert routing['proposedMissionAnchor'] is None, 'summary-only active bare /cook should not derive a replacement mission from summary artifacts alone'
|
|
514
511
|
assert 'Resume the completion workflow from canonical state.' in resume, 'summary-only active bare /cook should still resume the canonical workflow'
|
|
@@ -613,7 +610,7 @@ after = {
|
|
|
613
610
|
|
|
614
611
|
assert routing['mode'] == 'bare', 'fresh non-startable explicit handoff should snapshot bare routing mode'
|
|
615
612
|
assert routing['action'] == 'blocked', 'fresh non-startable explicit handoff should fail closed for active bare /cook'
|
|
616
|
-
assert routing['reason'] == '
|
|
613
|
+
assert routing['reason'] == 'fresh_explicit_handoff_not_startable', 'fresh non-startable explicit handoff should keep the dedicated explicit-handoff fail-closed reason'
|
|
617
614
|
assert 'fresh explicit primary-agent handoff exists' in routing['blockedFailureMessage'], 'fresh non-startable explicit handoff should surface the dedicated fail-closed message'
|
|
618
615
|
assert 'acceptance is not anchored to concrete repo changes or verification' in routing['blockedFailureMessage'], 'fresh non-startable explicit handoff should explain why the capsule is not startable'
|
|
619
616
|
assert not resume_path.exists(), 'fresh non-startable explicit handoff should not queue a resume prompt'
|
|
@@ -708,14 +705,15 @@ assert not snapshot.exists(), 'verification-evidence overlap suppression should
|
|
|
708
705
|
assert '/cook failed closed' in output, 'verification-evidence overlap suppression should fail closed when the latest discussion only repeats verified work'
|
|
709
706
|
PY
|
|
710
707
|
|
|
711
|
-
# Completed workflow: bare /cook should
|
|
712
|
-
# even when
|
|
708
|
+
# Completed workflow: bare /cook should fail closed for next-round discussion-only startup too,
|
|
709
|
+
# even when the discussion is well structured.
|
|
713
710
|
SESSION_TWO_NORMALIZED="$TMPDIR/session-two-normalized.jsonl"
|
|
714
711
|
DISCUSSION_TWO_NORMALIZED=$'Mission: 開始實作這個方案\nScope:\n- Normalize bare /cook planning phrasing for the next workflow round.\n- Reset canonical state for the new implementation mission.\nConstraints:\n- Do not resume the completed workflow when the new round is clearly different.\nAcceptance:\n- Start a new round with the normalized mission anchor.'
|
|
715
712
|
DISCUSSION_SNAPSHOT_TWO_NORMALIZED="$TMPDIR/context-proposal-next-round-normalized.json"
|
|
716
713
|
write_session "$SESSION_TWO_NORMALIZED" "$ROOT" "$DISCUSSION_TWO_NORMALIZED"
|
|
717
714
|
|
|
718
715
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
716
|
+
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
719
717
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_TWO_NORMALIZED" \
|
|
720
718
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
721
719
|
pi --session "$SESSION_TWO_NORMALIZED" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-next-round-normalized.out" 2>"$TMPDIR/pi-completion-context-proposal-next-round-normalized.err"
|
|
@@ -730,12 +728,10 @@ snapshot = Path(sys.argv[3])
|
|
|
730
728
|
previous = sys.argv[4]
|
|
731
729
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
732
730
|
|
|
733
|
-
assert snapshot.exists(), 'done-workflow discussion-only startup should emit a proposal snapshot
|
|
734
|
-
|
|
735
|
-
assert state['
|
|
736
|
-
assert
|
|
737
|
-
assert proposal['source'] == 'deferred_primary_agent_handoff', 'done-workflow discussion-only startup should snapshot the deferred handoff source'
|
|
738
|
-
assert 'Started a new completion workflow round from deferred primary-agent handoff' in output, 'done-workflow discussion-only startup should report deferred next-round startup'
|
|
731
|
+
assert not snapshot.exists(), 'done-workflow discussion-only startup should not emit a proposal snapshot without a fresh explicit handoff'
|
|
732
|
+
assert state['mission_anchor'] == previous, 'done-workflow discussion-only startup should keep the completed mission anchor unchanged'
|
|
733
|
+
assert state['continuation_policy'] == 'done', 'done-workflow discussion-only startup should keep the workflow closed'
|
|
734
|
+
assert 'fresh explicit primary-agent handoff' in output, 'done-workflow discussion-only startup should explain the explicit-handoff-only entry contract'
|
|
739
735
|
PY
|
|
740
736
|
|
|
741
737
|
# Completed workflow: a fresh explicit primary-agent handoff should still start the next round.
|
|
@@ -954,8 +950,8 @@ after = {path.name: path.read_text() for path in tracked}
|
|
|
954
950
|
assert before == after, 'done /cook inline-args rejection should leave canonical files unchanged'
|
|
955
951
|
PY
|
|
956
952
|
|
|
957
|
-
# Completed workflow again: model-assisted discussion analysis alone should
|
|
958
|
-
#
|
|
953
|
+
# Completed workflow again: model-assisted discussion analysis alone should still fail closed
|
|
954
|
+
# without a fresh explicit primary-agent handoff.
|
|
959
955
|
mark_done
|
|
960
956
|
|
|
961
957
|
SESSION_FIVE="$TMPDIR/session-five.jsonl"
|
|
@@ -979,9 +975,9 @@ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
|
979
975
|
snapshot = Path(sys.argv[3])
|
|
980
976
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
981
977
|
|
|
982
|
-
assert snapshot.exists(), 'done-workflow analyst-only restart should emit a startup proposal snapshot'
|
|
983
|
-
assert state['continuation_policy'] == '
|
|
984
|
-
assert '
|
|
978
|
+
assert not snapshot.exists(), 'done-workflow analyst-only restart should not emit a startup proposal snapshot'
|
|
979
|
+
assert state['continuation_policy'] == 'done', 'done-workflow analyst-only restart should keep the workflow closed'
|
|
980
|
+
assert 'fresh explicit primary-agent handoff' in output, 'done-workflow analyst-only restart should explain the explicit-handoff-only startup contract'
|
|
985
981
|
PY
|
|
986
982
|
|
|
987
983
|
# Custom confirmation UI: start should render proposal content separately from approval-only Start/Cancel actions.
|
|
@@ -1527,7 +1523,7 @@ output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
|
1527
1523
|
|
|
1528
1524
|
assert not snapshot.exists(), 'stale handoff should not emit a startup proposal snapshot'
|
|
1529
1525
|
assert not Path('.agent').exists(), 'stale handoff should fail closed without writing canonical state'
|
|
1530
|
-
assert '
|
|
1526
|
+
assert 'fresh explicit primary-agent handoff' in output, 'stale handoff should explain that a fresh valid explicit handoff is required'
|
|
1531
1527
|
PY
|
|
1532
1528
|
|
|
1533
1529
|
# Negative handoff rationale: a non-startable capsule must not become the startup mission.
|
package/scripts/refocus-test.sh
CHANGED
|
@@ -313,7 +313,7 @@ assert routing['mode'] == 'bare', 'supported refocus should use bare active-work
|
|
|
313
313
|
assert 'explicitGoal' not in routing, 'supported bare refocus should not expose removed explicit-goal shim fields'
|
|
314
314
|
assert 'explicitGoalProvided' not in routing, 'supported bare refocus should not expose removed explicit-goal shim fields'
|
|
315
315
|
assert routing['action'] == 'refocus', 'supported bare /cook should classify as refocus when a fresh explicit handoff proposes a different mission'
|
|
316
|
-
assert routing['reason'] == '
|
|
316
|
+
assert routing['reason'] == 'fresh_explicit_handoff', 'supported bare /cook should record the explicit-handoff replacement reason'
|
|
317
317
|
assert routing['proposedMissionAnchor'] == new_anchor, 'explicit handoff routing snapshot should expose the replacement mission anchor'
|
|
318
318
|
assert routing['proposalSource'] == 'handoff_capsule', 'explicit handoff routing snapshot should preserve the handoff source'
|
|
319
319
|
PY
|
|
@@ -411,7 +411,7 @@ assert routing['mode'] == 'bare', 'bare /cook should snapshot bare active-workfl
|
|
|
411
411
|
assert 'explicitGoal' not in routing, 'bare chooser routing should not expose removed explicit-goal shim fields'
|
|
412
412
|
assert 'explicitGoalProvided' not in routing, 'bare chooser routing should not expose removed explicit-goal shim fields'
|
|
413
413
|
assert routing['action'] == 'refocus', 'fresh explicit replacement handoff should classify active bare /cook as refocus'
|
|
414
|
-
assert routing['reason'] == '
|
|
414
|
+
assert routing['reason'] == 'fresh_explicit_handoff', 'fresh explicit replacement handoff should record the explicit-handoff reason'
|
|
415
415
|
assert routing['currentMissionAnchor'] == updated_mission, 'explicit-handoff routing should keep the current mission anchor until the user approves replacement'
|
|
416
416
|
assert routing['proposedMissionAnchor'] == replacement_mission, 'explicit-handoff routing should expose the proposed replacement mission'
|
|
417
417
|
assert routing['proposalSource'] == 'handoff_capsule', 'explicit-handoff routing should preserve the handoff source'
|
|
@@ -454,7 +454,7 @@ assert state['mission_anchor'] == updated_mission, 'final Start/Cancel cancel sh
|
|
|
454
454
|
assert plan['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep plan.json unchanged'
|
|
455
455
|
assert active['mission_anchor'] == updated_mission, 'final Start/Cancel cancel should keep active-slice.json unchanged'
|
|
456
456
|
assert routing['action'] == 'refocus', 'final Start/Cancel cancel should still come from an explicit-handoff refocus classification'
|
|
457
|
-
assert routing['reason'] == '
|
|
457
|
+
assert routing['reason'] == 'fresh_explicit_handoff', 'final Start/Cancel cancel should preserve the explicit-handoff reason'
|
|
458
458
|
assert routing['currentMissionAnchor'] == updated_mission, 'final Start/Cancel cancel should keep the current mission anchor until the user approves replacement'
|
|
459
459
|
assert proposal['mission'] == replacement_mission, 'final Start/Cancel cancel should still prepare the replacement proposal before rewriting state'
|
|
460
460
|
assert proposal['source'] == 'handoff_capsule', 'final Start/Cancel cancel should preserve the explicit-handoff proposal source'
|
|
@@ -495,7 +495,7 @@ assert routing['mode'] == 'bare', 'accepted bare refocus should keep bare routin
|
|
|
495
495
|
assert 'explicitGoal' not in routing, 'accepted bare refocus should not expose removed explicit-goal shim fields'
|
|
496
496
|
assert 'explicitGoalProvided' not in routing, 'accepted bare refocus should not expose removed explicit-goal shim fields'
|
|
497
497
|
assert routing['action'] == 'refocus', 'accepted bare refocus should keep the explicit-handoff refocus classification'
|
|
498
|
-
assert routing['reason'] == '
|
|
498
|
+
assert routing['reason'] == 'fresh_explicit_handoff', 'accepted bare refocus should keep the explicit-handoff reason'
|
|
499
499
|
assert routing['currentMissionAnchor'] == 'Remove completion status line, keep widget.', 'accepted bare refocus should expose the original mission until Start is accepted'
|
|
500
500
|
assert routing['proposalSource'] == 'handoff_capsule', 'accepted bare refocus should preserve the explicit-handoff source'
|
|
501
501
|
assert new_anchor in mission_text, '.agent/mission.md did not update to the bare refocus mission anchor'
|
package/scripts/release-check.sh
CHANGED
|
@@ -9,47 +9,43 @@ echo "[release-check] running control-plane validation, tracked .agent contract
|
|
|
9
9
|
bash .agent/verify_completion_control_plane.sh
|
|
10
10
|
git ls-files --error-unmatch .agent/README.md .agent/mission.md .agent/profile.json .agent/verify_completion_stop.sh .agent/verify_completion_control_plane.sh >/dev/null
|
|
11
11
|
|
|
12
|
-
echo "[release-check] verifying public /cook parity and explicit-
|
|
12
|
+
echo "[release-check] verifying public /cook parity and explicit-handoff docs/help"
|
|
13
13
|
python3 - <<'PY'
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
16
|
checks = {
|
|
17
17
|
"README.md": [
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"-
|
|
23
|
-
"`/cook` first checks for a fresh explicit primary-agent handoff capsule as a compatibility intake path. If none is present, `/cook` synthesizes a startup brief from recent discussion using primary-agent-style context.",
|
|
24
|
-
"when a concrete replacement mission suggests replacing an active workflow, `/cook` shows a chooser before any canonical state rewrite",
|
|
18
|
+
"You can still implement directly in ordinary chat when you do not need workflow state.",
|
|
19
|
+
"When you explicitly run `/cook`, it should consume the explicit primary-agent handoff you already prepared in ordinary chat, then ask you to **Start** or **Cancel** before rewriting canonical workflow state.",
|
|
20
|
+
"Explicit `/cook` capsules are the required startup intake for new-workflow, next-round, and replacement entry.",
|
|
21
|
+
"`/cook` first checks for a fresh explicit primary-agent handoff capsule.",
|
|
22
|
+
"New-workflow entry, done-workflow next-round entry, and active-workflow replacement should use that handoff instead of guessing from recent discussion.",
|
|
25
23
|
],
|
|
26
24
|
"CHANGELOG.md": [
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"updated public parity and shipped package contents so the tracked `.agent` contract files are included in package tarballs and packaged smoke/release verification can scaffold canonical state truthfully",
|
|
25
|
+
"made `/cook` stop inferring startup handoffs from recent discussion so workflow startup and replacement now require fresh explicit primary-agent `cook_handoff` data",
|
|
26
|
+
"clarified that when a user explicitly chooses `/cook`, the primary agent must author the handoff in ordinary chat and `/cook` must consume that handoff instead of guessing",
|
|
30
27
|
],
|
|
31
28
|
"extensions/completion/prompt-surfaces.ts": [
|
|
32
|
-
'"/cook
|
|
33
|
-
'"Do not
|
|
34
|
-
'"Only provide a preview startup brief or ```cook_handoff``` capsule in ordinary chat when the user explicitly asks for that preview behavior."',
|
|
29
|
+
'"If the user explicitly asks to enter /cook workflow, generate one fresh ```cook_handoff``` capsule in ordinary chat from the primary-agent view of the task, then tell the user to run /cook."',
|
|
30
|
+
'"Do not expect /cook to infer or guess startup intent from recent discussion alone; /cook should consume the explicit primary-agent handoff instead."',
|
|
35
31
|
],
|
|
36
32
|
"extensions/completion/index.ts": [
|
|
37
|
-
'"/cook failed closed because
|
|
38
|
-
'description: "/cook workflow:
|
|
33
|
+
'"/cook failed closed because starting workflow now requires a fresh explicit primary-agent handoff. Ask the primary agent in the main chat to emit a fresh ```cook_handoff``` capsule, then rerun /cook."',
|
|
34
|
+
'description: "/cook workflow: start or replace workflow only from an explicit primary-agent handoff, or resume the current workflow from canonical state"',
|
|
39
35
|
],
|
|
40
36
|
}
|
|
41
37
|
|
|
42
38
|
forbidden = {
|
|
43
39
|
"README.md": [
|
|
44
|
-
"
|
|
45
|
-
"
|
|
40
|
+
"synthesizes a startup brief from recent discussion using primary-agent-style context",
|
|
41
|
+
"derive startup from explicit user `/cook` entry plus recent discussion when needed",
|
|
46
42
|
],
|
|
47
43
|
"extensions/completion/prompt-surfaces.ts": [
|
|
48
|
-
'"
|
|
44
|
+
'"If the user explicitly runs /cook, /cook will synthesize a startup brief from recent discussion using primary-agent-style context, then show Start/Cancel confirmation before canonical workflow state is rewritten."',
|
|
49
45
|
],
|
|
50
46
|
"extensions/completion/index.ts": [
|
|
51
|
-
'description: "/cook workflow:
|
|
52
|
-
'"/cook failed closed because
|
|
47
|
+
'description: "/cook workflow: optionally enter tracked workflow mode, synthesize a startup brief from explicit /cook entry, resume the current workflow from canonical state, or confirm a replacement mission"',
|
|
48
|
+
'"/cook failed closed because it could not derive a concrete startup brief from recent discussion. Clarify the mission, first slice, or verification intent in the main chat, then rerun /cook."',
|
|
53
49
|
],
|
|
54
50
|
}
|
|
55
51
|
|
package/scripts/smoke-test.sh
CHANGED
|
@@ -265,14 +265,14 @@ auto_resume = Path(sys.argv[5])
|
|
|
265
265
|
assert not reminder.exists(), 'ordinary non-/cook turn should not inject completion reminder solely from canonical state'
|
|
266
266
|
assert handoff.exists(), 'ordinary non-/cook turn should inject the /cook handoff boundary reminder'
|
|
267
267
|
handoff_text = handoff.read_text()
|
|
268
|
-
assert '
|
|
268
|
+
assert 'ordinary main chat unless the user explicitly runs /cook' in handoff_text, 'ordinary handoff reminder should preserve explicit /cook workflow entry'
|
|
269
|
+
assert 'directly implement requested repo changes, including multi-file work' in handoff_text, 'ordinary handoff reminder should allow direct ordinary-chat implementation'
|
|
269
270
|
assert 'Do not proactively tell the user to run /cook' in handoff_text, 'ordinary handoff reminder should keep ordinary chat neutral until explicit /cook entry'
|
|
270
|
-
assert '
|
|
271
|
-
assert '
|
|
272
|
-
assert '
|
|
273
|
-
assert '
|
|
274
|
-
assert '
|
|
275
|
-
assert 'Start/Cancel confirmation before canonical workflow state is rewritten' in handoff_text, 'ordinary handoff reminder should preserve confirm-first startup'
|
|
271
|
+
assert '/cook is optional workflow mode' in handoff_text, 'ordinary handoff reminder should position /cook as optional workflow mode'
|
|
272
|
+
assert 'If the user wants direct implementation now, stay in ordinary chat and help directly instead of blocking on /cook.' in handoff_text, 'ordinary handoff reminder should avoid blocking implementation on /cook'
|
|
273
|
+
assert 'generate one fresh ```cook_handoff``` capsule in ordinary chat from the primary-agent view of the task' in handoff_text, 'ordinary handoff reminder should require primary-agent authored handoff when the user explicitly chooses /cook'
|
|
274
|
+
assert 'Do not expect /cook to infer or guess startup intent from recent discussion alone' in handoff_text, 'ordinary handoff reminder should forbid /cook-side guessing'
|
|
275
|
+
assert 'do not silently rewrite discussion into canonical workflow state' in handoff_text, 'ordinary handoff reminder should preserve non-canonical ordinary-chat behavior'
|
|
276
276
|
assert not auto_resume.exists(), 'ordinary non-/cook turn should not queue auto-resume before /cook activation'
|
|
277
277
|
assert 'Skipped completion workflow auto-resume prompt (test mode)' not in output, 'ordinary non-/cook turn should not attempt auto-resume'
|
|
278
278
|
PY
|
|
@@ -301,7 +301,7 @@ assert f'- task_type: {expected_task_type}' in resume, 'resume prompt missing ca
|
|
|
301
301
|
assert f'- evaluation_profile: {expected_eval_profile}' in resume, 'resume prompt missing canonical evaluation_profile'
|
|
302
302
|
assert routing['mode'] == 'bare', 'active bare /cook should snapshot bare routing mode'
|
|
303
303
|
assert routing['action'] == 'continue', 'no-discussion active bare /cook should resume from canonical state without a concrete replacement mission'
|
|
304
|
-
assert routing['reason'] == '
|
|
304
|
+
assert routing['reason'] == 'missing_explicit_handoff', 'no-discussion active bare /cook should explain that resume happened because no fresh explicit handoff existed'
|
|
305
305
|
assert routing['currentMissionAnchor'] == state['mission_anchor'], 'resume routing snapshot should keep the current mission anchor'
|
|
306
306
|
assert routing['proposedMissionAnchor'] is None, 'no-discussion active bare /cook should not propose a replacement mission'
|
|
307
307
|
assert not chooser_path.exists(), 'active bare /cook resume should not open the chooser without a fresh explicit handoff'
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cook-handoff-boundary
|
|
3
|
-
description: Ordinary-chat
|
|
3
|
+
description: Ordinary-chat contract for treating `/cook` as an optional workflow mode while still requiring primary-agent-authored handoff data whenever the user explicitly chooses workflow mode.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# /cook Handoff Boundary
|
|
7
7
|
|
|
8
8
|
Load or summarize this contract when the primary agent is operating in ordinary main chat before the user has explicitly entered `/cook`.
|
|
9
9
|
|
|
10
|
-
This skill governs the
|
|
10
|
+
This skill governs the relationship between:
|
|
11
11
|
|
|
12
|
-
- ordinary main-chat discussion
|
|
13
|
-
-
|
|
12
|
+
- ordinary main-chat discussion and direct implementation
|
|
13
|
+
- optional transition into long-running completion workflow through `/cook`
|
|
14
14
|
|
|
15
15
|
## Core Contract
|
|
16
16
|
|
|
17
|
-
- Ordinary chat may be used to clarify requirements, discuss tradeoffs,
|
|
18
|
-
- `/cook` is
|
|
19
|
-
-
|
|
20
|
-
-
|
|
17
|
+
- Ordinary chat may be used to clarify requirements, discuss tradeoffs, refine scope, and directly implement requested repo changes.
|
|
18
|
+
- `/cook` is an explicit workflow entrypoint for users who want resumability, review, audit, or canonical `.agent/**` workflow state.
|
|
19
|
+
- `/cook` is optional. It is not required just because the work spans multiple files or looks substantial.
|
|
20
|
+
- Ordinary chat remains ordinary chat until the user explicitly chooses `/cook`.
|
|
21
21
|
|
|
22
22
|
## What Ordinary Chat May Do
|
|
23
23
|
|
|
@@ -27,53 +27,47 @@ The primary agent may:
|
|
|
27
27
|
- discuss tradeoffs
|
|
28
28
|
- refine scope and constraints
|
|
29
29
|
- summarize likely mission, acceptance, or risks
|
|
30
|
-
-
|
|
30
|
+
- directly edit repo files when that is the most helpful response
|
|
31
|
+
- complete multi-file implementation in ordinary chat when workflow state is unnecessary
|
|
31
32
|
|
|
32
33
|
The primary agent should not:
|
|
33
34
|
|
|
34
|
-
- proactively tell the user to run `/cook`
|
|
35
|
+
- proactively tell the user to run `/cook` just because the task looks workflow-worthy
|
|
35
36
|
- proactively emit a `cook_handoff` capsule by default
|
|
36
|
-
- act as though workflow has already started
|
|
37
|
-
- rewrite ordinary-chat discussion into canonical workflow state
|
|
37
|
+
- act as though workflow has already started when it has not
|
|
38
|
+
- silently rewrite ordinary-chat discussion into canonical workflow state
|
|
38
39
|
|
|
39
|
-
## When
|
|
40
|
+
## When `/cook` Is Helpful
|
|
40
41
|
|
|
41
|
-
The primary agent
|
|
42
|
+
The primary agent may mention `/cook` as an optional tool when it would genuinely help, for example when:
|
|
42
43
|
|
|
43
|
-
- the
|
|
44
|
-
- the
|
|
45
|
-
- the
|
|
46
|
-
- the
|
|
44
|
+
- the work should be resumable across sessions
|
|
45
|
+
- the user wants a tracked mission in canonical `.agent/**` state
|
|
46
|
+
- the task benefits from explicit review / audit / stop-wave flow
|
|
47
|
+
- the user wants a confirm-first workflow boundary before a long-running effort
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
But even in those cases:
|
|
49
50
|
|
|
50
|
-
-
|
|
51
|
-
-
|
|
51
|
+
- do not force `/cook`
|
|
52
|
+
- do not frame `/cook` as mandatory for direct repo edits
|
|
53
|
+
- continue helping directly in ordinary chat unless the user explicitly chooses workflow mode
|
|
52
54
|
|
|
53
|
-
## Required Behavior
|
|
55
|
+
## Required Behavior When The User Explicitly Chooses `/cook`
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
If the user explicitly asks to enter `/cook` workflow mode, the primary agent must:
|
|
56
58
|
|
|
57
|
-
-
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
59
|
+
- generate the handoff information itself in ordinary chat
|
|
60
|
+
- emit exactly one fresh `cook_handoff` capsule that captures the intended startup slice from the primary-agent view of the task
|
|
61
|
+
- tell the user to run `/cook` only after that explicit handoff exists
|
|
62
|
+
- not rely on `/cook` to infer, summarize, or guess the startup slice from recent discussion alone
|
|
61
63
|
|
|
62
|
-
|
|
64
|
+
In other words:
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
- primary agent authors the handoff
|
|
67
|
+
- `/cook` consumes and confirms that handoff
|
|
68
|
+
- `/cook` must not invent the mission from transcript guessing
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
- `/cook` will show Start / Cancel confirmation before canonical workflow state is rewritten
|
|
68
|
-
- that synthesized startup brief is advisory intake only, not canonical `.agent/**` state by itself
|
|
69
|
-
|
|
70
|
-
This means the primary agent does **not** need to proactively attach startup capsules during ordinary chat just because the task looks ready.
|
|
71
|
-
|
|
72
|
-
## Optional Preview Behavior
|
|
73
|
-
|
|
74
|
-
Only if the user explicitly asks for a preview startup brief or handoff capsule in ordinary chat may the primary agent provide one.
|
|
75
|
-
|
|
76
|
-
Optional preview capsule format:
|
|
70
|
+
## Required Capsule Format
|
|
77
71
|
|
|
78
72
|
````text
|
|
79
73
|
```cook_handoff
|
|
@@ -105,28 +99,31 @@ Optional preview capsule format:
|
|
|
105
99
|
Notes:
|
|
106
100
|
|
|
107
101
|
- `constraints` may be replaced or supplemented by `non_goals` when clearer.
|
|
108
|
-
- `first_slice_goal`, `first_slice_non_goals`, `implementation_surfaces`, `verification_commands`, and `why_this_slice_first` are required
|
|
109
|
-
- Any
|
|
102
|
+
- `first_slice_goal`, `first_slice_non_goals`, `implementation_surfaces`, `verification_commands`, and `why_this_slice_first` are required for an implementation-ready handoff.
|
|
103
|
+
- Any capsule is startup intake for `/cook` only. It is not canonical `.agent/**` state, not active-slice state, and not a second repo contract source.
|
|
110
104
|
|
|
111
|
-
Suggested wording:
|
|
105
|
+
Suggested wording when the user chooses workflow mode:
|
|
112
106
|
|
|
113
|
-
>
|
|
114
|
-
|
|
115
|
-
A short recap may include mission, scope, or acceptance, but that recap must not be presented as canonical plan state.
|
|
107
|
+
> Got it — since you want `/cook`, I’ll first prepare the explicit startup handoff here from the current task context. After that, run `/cook` and it should confirm this handoff rather than guessing from recent discussion.
|
|
116
108
|
|
|
117
109
|
## Forbidden Behaviors
|
|
118
110
|
|
|
119
111
|
Before the user explicitly runs `/cook`, the primary agent must not:
|
|
120
112
|
|
|
121
|
-
-
|
|
122
|
-
- modify tracked product files as part of that workflow-level task
|
|
123
|
-
- act as though `/cook` had already been invoked
|
|
113
|
+
- pretend `/cook` has already been invoked
|
|
124
114
|
- silently rewrite ordinary-chat discussion into active workflow state
|
|
125
|
-
-
|
|
115
|
+
- claim canonical `.agent/**` startup state exists when it does not
|
|
116
|
+
- refuse ordinary-chat implementation solely because `/cook` would also be possible
|
|
117
|
+
|
|
118
|
+
When the user does explicitly choose `/cook`, the system must not:
|
|
119
|
+
|
|
120
|
+
- let `/cook` invent the startup mission from recent discussion alone
|
|
121
|
+
- let `/cook` replace missing primary-agent handoff data with transcript guessing
|
|
122
|
+
- let `/cook` reopen or refocus workflow from guessed intent when no fresh explicit primary-agent handoff exists
|
|
126
123
|
|
|
127
124
|
## Relationship To `completion-protocol`
|
|
128
125
|
|
|
129
|
-
This skill is only about pre-`/cook` ordinary-chat
|
|
126
|
+
This skill is only about pre-`/cook` ordinary-chat behavior and explicit handoff preparation.
|
|
130
127
|
|
|
131
128
|
After the user explicitly enters `/cook`, the separate `completion-protocol` skill governs:
|
|
132
129
|
|