@linimin/pi-letscook 0.1.60 → 0.1.62
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 +11 -8
- package/README.md +35 -36
- package/extensions/completion/driver.ts +41 -28
- package/extensions/completion/index.ts +76 -63
- package/extensions/completion/prompt-surfaces.ts +16 -15
- package/extensions/completion/proposal.ts +28 -1
- package/package.json +1 -1
- package/scripts/context-proposal-test.sh +49 -154
- package/scripts/refocus-test.sh +4 -4
- package/scripts/release-check.sh +25 -33
- package/scripts/smoke-test.sh +65 -25
- package/skills/cook-handoff-boundary/SKILL.md +65 -36
package/CHANGELOG.md
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.1.62
|
|
4
4
|
|
|
5
|
-
|
|
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`
|
|
6
10
|
|
|
7
|
-
## 0.1.
|
|
11
|
+
## 0.1.61
|
|
8
12
|
|
|
9
13
|
### Changed
|
|
10
14
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
- kept
|
|
14
|
-
-
|
|
15
|
-
- updated public parity and packaged release verification so README/help/changelog/release-check all describe and gate the shipped mixed model truthfully, while still packaging the tracked `.agent` contract files
|
|
15
|
+
- removed proactive primary-agent `/cook` prompting and default ordinary-chat `cook_handoff` emission so main chat stays advisory until the user explicitly runs `/cook`
|
|
16
|
+
- changed bare `/cook` startup and done-workflow next-round entry to synthesize a deferred primary-agent startup brief from recent discussion instead of requiring a pre-authored explicit handoff capsule
|
|
17
|
+
- kept active-workflow bare `/cook` resumable from canonical `.agent/**` state by default while allowing `/cook` to confirm a concrete replacement mission derived from explicit entry context
|
|
18
|
+
- 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
|
|
16
19
|
|
|
17
20
|
## 0.1.58
|
|
18
21
|
|
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.
|
|
35
|
-
5. Review the startup brief and choose **Start** or **Cancel**.
|
|
36
|
-
6. Later, run `/cook` again to resume from canonical state or confirm
|
|
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 synthesized startup brief and choose **Start** or **Cancel**.
|
|
38
|
+
6. Later, run `/cook` again to resume from canonical state or confirm a synthesized replacement or next-round startup brief.
|
|
37
39
|
|
|
38
40
|
```text
|
|
39
41
|
/cook
|
|
@@ -43,64 +45,62 @@ 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 | Discuss the concrete repo change in the main chat, then run `/cook` when you want workflow to begin |
|
|
47
50
|
| Continue the current workflow | Run `/cook` |
|
|
48
|
-
| Refocus or start the next round | Discuss the new concrete repo change in the main chat, then run `/cook`
|
|
51
|
+
| Refocus or start the next round | Discuss the new concrete repo change in the main chat, then run `/cook` when you want a new startup brief synthesized |
|
|
49
52
|
|
|
50
53
|
## What `/cook` expects
|
|
51
54
|
|
|
52
|
-
- recent
|
|
53
|
-
-
|
|
55
|
+
- enough recent main-chat discussion for `/cook` to synthesize a concrete startup brief when you explicitly invoke it
|
|
56
|
+
- a mission that is concrete enough to anchor bounded repo work rather than planning-only discussion
|
|
57
|
+
- acceptance and verification intent that can support a truthful first workflow round
|
|
54
58
|
- README/CHANGELOG updates still count as concrete repo changes
|
|
55
|
-
- assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts still do not
|
|
56
|
-
-
|
|
57
|
-
- active-workflow replacement still stays conservative: `/cook` resumes from canonical state unless a fresh explicit handoff proposes a different concrete repo change and you confirm that replacement
|
|
59
|
+
- assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts still do not count unless they can be turned into a concrete startup brief
|
|
60
|
+
- optional explicit `cook_handoff` capsules may still be consumed as a compatibility intake path, but they are no longer required for new-workflow or next-round entry
|
|
58
61
|
|
|
59
|
-
If
|
|
62
|
+
If `/cook` cannot derive a concrete startup brief, it fails closed, leaves canonical `.agent/**` state unchanged, and tells you to refine the mission, first slice, or verification intent in the main chat before rerunning `/cook`.
|
|
60
63
|
|
|
61
|
-
If
|
|
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
|
|
|
63
66
|
If you pass inline arguments to `/cook`, it also fails closed and tells you to move that intent into the main chat before rerunning bare `/cook`.
|
|
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.
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
When you explicitly run `/cook`, it synthesizes a startup brief from recent discussion using primary-agent-style context, then asks you to **Start** or **Cancel** before rewriting canonical workflow state.
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
Bare `/cook` is still the canonical workflow boundary: it synthesizes the startup brief from recent ordinary-chat discussion at `/cook` time, then waits for **Start** or **Cancel** before any canonical `.agent/**` write.
|
|
76
|
+
Optional explicit `/cook` capsules may still be used as compatibility startup intake, but they are not required for new-workflow or next-round entry.
|
|
76
77
|
|
|
77
78
|
Important behavior:
|
|
78
|
-
- `/cook` is
|
|
79
|
-
- startup and next-round entry stay confirm-first
|
|
80
|
-
- active workflows resume from canonical `.agent/**` state unless
|
|
81
|
-
- any pre-`/cook` preview or capsule is explicit-request-only and non-canonical
|
|
79
|
+
- `/cook` is an optional workflow boundary and manual entry point
|
|
80
|
+
- startup and next-round entry stay confirm-first, deriving startup from explicit user `/cook` entry plus recent discussion when needed
|
|
81
|
+
- active workflows resume from canonical `.agent/**` state unless `/cook` synthesizes or receives a concrete replacement mission
|
|
82
82
|
- explicit slash commands other than `/cook` continue normally in the main chat
|
|
83
|
-
- ordinary main-chat discussion may clarify
|
|
83
|
+
- ordinary main-chat discussion may clarify, propose, or directly implement repo changes without entering workflow mode
|
|
84
84
|
|
|
85
85
|
## Typical examples
|
|
86
86
|
|
|
87
|
-
Start a new workflow from recent discussion:
|
|
87
|
+
Start a new workflow from recent main-chat discussion:
|
|
88
88
|
|
|
89
89
|
```text
|
|
90
90
|
I want to add login redirect handling and tests.
|
|
91
|
-
# discuss
|
|
91
|
+
# discuss/refine in main chat
|
|
92
92
|
/cook
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
## What happens when you run `/cook`
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
`/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. New-workflow entry and done-workflow next-round entry still fail closed when that synthesis is too weak or planning-only. When a workflow is already active and no concrete replacement mission is available, `/cook` resumes from canonical `.agent/**` state. None of this prevents ordinary-chat implementation when you choose not to enter workflow mode.
|
|
98
98
|
|
|
99
99
|
| Repo state | What you'll see |
|
|
100
100
|
|---|---|
|
|
101
|
-
| No workflow yet | `/cook` synthesizes a startup brief from recent discussion and
|
|
102
|
-
| Active workflow exists | Usually a resume of the current workflow from canonical `.agent/**` state. If a
|
|
103
|
-
| Previous workflow is `done` | `/cook`
|
|
101
|
+
| No workflow yet | `/cook` synthesizes a startup brief from recent discussion and asks you to choose **Start** or **Cancel**. A fresh explicit handoff capsule may still be used if present. Weak, unreliable, stale, planning-only, or non-startable intake still fails closed. |
|
|
102
|
+
| Active workflow exists | Usually a resume of the current workflow from canonical `.agent/**` state. If `/cook` finds a different concrete replacement mission from a compatibility capsule or deferred synthesis, it shows a chooser first and only rewrites canonical state after you confirm the replacement. Ambiguous intake stays conservative. |
|
|
103
|
+
| Previous workflow is `done` | `/cook` can synthesize the next implementation round from recent discussion behind **Start** or **Cancel**. Weak or planning-only next-round intake still fails closed. |
|
|
104
104
|
|
|
105
105
|
## Confirmation and fail-closed behavior
|
|
106
106
|
|
|
@@ -108,10 +108,9 @@ When no workflow is active, bare `/cook` synthesizes a startup brief from recent
|
|
|
108
108
|
|
|
109
109
|
- startup, next-round, and refocus proposals are approval-only
|
|
110
110
|
- actions are **Start** and **Cancel**
|
|
111
|
-
- **Cancel** is side-effect free:
|
|
111
|
+
- **Cancel** is side-effect free: discuss changes in the main chat and rerun `/cook`
|
|
112
112
|
- weak, ambiguous, stale, invalid, assistant-produced, or planning-only intake does not start a workflow
|
|
113
|
-
-
|
|
114
|
-
- when a fresh explicit handoff suggests replacing an active workflow, `/cook` shows a chooser before any canonical state rewrite
|
|
113
|
+
- when a concrete replacement mission suggests replacing an active workflow, `/cook` shows a chooser before any canonical state rewrite
|
|
115
114
|
|
|
116
115
|
When you accept startup or refocus, `/cook` persists the chosen workflow state in canonical `.agent/**` files before the re-ground round begins.
|
|
117
116
|
|
|
@@ -264,7 +263,7 @@ npm run rubric-contract-test
|
|
|
264
263
|
npm run release-check
|
|
265
264
|
```
|
|
266
265
|
|
|
267
|
-
`npm run release-check` is the broad packaged-release verifier. It begins with `bash .agent/verify_completion_control_plane.sh`, so missing or stale `.agent/verification-evidence.json` parity fails closed before the broader suite runs, then asserts the shipped
|
|
266
|
+
`npm run release-check` is the broad packaged-release verifier. It begins with `bash .agent/verify_completion_control_plane.sh`, so missing or stale `.agent/verification-evidence.json` parity fails closed before the broader suite runs, then asserts the shipped `/cook` public parity surfaces in `README.md`, `CHANGELOG.md`, and the `/cook` help/fail-closed copy in `extensions/completion/index.ts`, reruns the startup/refocus/context checks — including the critique-aware `/cook` confirmation regression and the smoke auto-resume prompt path — includes deterministic canonical evidence artifact coverage and includes deterministic active-slice contract coverage plus observability coverage, evaluator calibration, and the rubric-contract regression, and finishes with `npm pack --dry-run`.
|
|
268
267
|
|
|
269
268
|
The direct package-root verifier commands above intentionally self-isolate the repo-local extension when they shell back into `pi`, so you should not need to wrap them with `pi --no-extensions` even if `@linimin/pi-letscook` is also installed globally on the same machine.
|
|
270
269
|
|
|
@@ -38,7 +38,7 @@ type ContextProposalAlternate = {
|
|
|
38
38
|
analysis: ContextProposalAnalysis;
|
|
39
39
|
goalText: string;
|
|
40
40
|
basisPreview: string;
|
|
41
|
-
source: "session" | "analyst" | "handoff_capsule";
|
|
41
|
+
source: "session" | "analyst" | "handoff_capsule" | "deferred_primary_agent_handoff";
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
type ContextProposal = ContextProposalAlternate & {
|
|
@@ -60,9 +60,8 @@ type CookContextProposalResult = {
|
|
|
60
60
|
blockedFailureMessage?: string;
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
function
|
|
64
|
-
const requirement =
|
|
65
|
-
"/cook failed closed because recent discussion did not produce a clear execution-ready startup brief with Mission/Scope/Constraints/Acceptance for concrete repo changes. Clarify the concrete repo changes in the main chat and rerun /cook.";
|
|
63
|
+
function buildCookStartupDerivationFailureMessage(deps: CompletionDriverDeps, prefix?: string): string {
|
|
64
|
+
const requirement = deps.structuredDiscussionFailureDetail;
|
|
66
65
|
return prefix ? `${prefix} ${requirement}` : requirement;
|
|
67
66
|
}
|
|
68
67
|
|
|
@@ -73,9 +72,10 @@ type ActiveWorkflowProposalAssessment = {
|
|
|
73
72
|
blockedFailureMessage?: string;
|
|
74
73
|
reason:
|
|
75
74
|
| "matching_mission"
|
|
76
|
-
| "
|
|
77
|
-
| "
|
|
78
|
-
| "
|
|
75
|
+
| "no_replacement_proposal"
|
|
76
|
+
| "explicit_handoff_replacement"
|
|
77
|
+
| "deferred_replacement"
|
|
78
|
+
| "replacement_not_startable";
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
type ExistingWorkflowChooserOptions = {
|
|
@@ -321,23 +321,23 @@ async function assessActiveWorkflowProposalRouting(
|
|
|
321
321
|
): Promise<ActiveWorkflowProposalAssessment> {
|
|
322
322
|
const currentMission = currentMissionAnchor(snapshot);
|
|
323
323
|
const projectName = path.basename(snapshot.files.root);
|
|
324
|
-
const
|
|
325
|
-
if (
|
|
324
|
+
const derived = await deps.deriveCookContextProposal(ctx, projectName);
|
|
325
|
+
if (derived.blockedFailureMessage) {
|
|
326
326
|
const assessment: ActiveWorkflowProposalAssessment = {
|
|
327
327
|
action: "blocked",
|
|
328
328
|
currentMissionAnchor: currentMission,
|
|
329
|
-
blockedFailureMessage:
|
|
330
|
-
reason: "
|
|
329
|
+
blockedFailureMessage: derived.blockedFailureMessage,
|
|
330
|
+
reason: "replacement_not_startable",
|
|
331
331
|
};
|
|
332
332
|
deps.maybeWriteActiveWorkflowRoutingSnapshot(assessment);
|
|
333
333
|
return assessment;
|
|
334
334
|
}
|
|
335
|
-
const proposal =
|
|
335
|
+
const proposal = derived.proposal;
|
|
336
336
|
if (!proposal) {
|
|
337
337
|
const assessment: ActiveWorkflowProposalAssessment = {
|
|
338
338
|
action: "continue",
|
|
339
339
|
currentMissionAnchor: currentMission,
|
|
340
|
-
reason: "
|
|
340
|
+
reason: "no_replacement_proposal",
|
|
341
341
|
};
|
|
342
342
|
deps.maybeWriteActiveWorkflowRoutingSnapshot(assessment);
|
|
343
343
|
return assessment;
|
|
@@ -356,7 +356,7 @@ async function assessActiveWorkflowProposalRouting(
|
|
|
356
356
|
action: "refocus",
|
|
357
357
|
currentMissionAnchor: currentMission,
|
|
358
358
|
proposal,
|
|
359
|
-
reason: "
|
|
359
|
+
reason: proposal.source === "handoff_capsule" ? "explicit_handoff_replacement" : "deferred_replacement",
|
|
360
360
|
};
|
|
361
361
|
deps.maybeWriteActiveWorkflowRoutingSnapshot(assessment);
|
|
362
362
|
return assessment;
|
|
@@ -536,14 +536,14 @@ export async function runCookEntry(
|
|
|
536
536
|
if (!snapshot) {
|
|
537
537
|
const root = findRepoRoot(cwd) ?? cwd;
|
|
538
538
|
const projectName = path.basename(root);
|
|
539
|
-
const derived = await deps.
|
|
539
|
+
const derived = await deps.deriveCookStartupProposal(ctx, projectName);
|
|
540
540
|
if (derived.blockedFailureMessage) {
|
|
541
541
|
deps.emitCommandText(ctx, derived.blockedFailureMessage, "info");
|
|
542
542
|
return;
|
|
543
543
|
}
|
|
544
544
|
const proposal = derived.proposal;
|
|
545
545
|
if (!proposal) {
|
|
546
|
-
deps.emitCommandText(ctx,
|
|
546
|
+
deps.emitCommandText(ctx, buildCookStartupDerivationFailureMessage(deps), "info");
|
|
547
547
|
return;
|
|
548
548
|
}
|
|
549
549
|
const decision = await deps.confirmContextProposal(ctx, proposal, {
|
|
@@ -581,14 +581,14 @@ export async function runCookEntry(
|
|
|
581
581
|
if (!goal) {
|
|
582
582
|
if (workflowDone) {
|
|
583
583
|
const projectName = path.basename(snapshot.files.root);
|
|
584
|
-
const derived = await deps.
|
|
584
|
+
const derived = await deps.deriveCookStartupProposal(ctx, projectName);
|
|
585
585
|
if (derived.blockedFailureMessage) {
|
|
586
586
|
deps.emitCommandText(ctx, derived.blockedFailureMessage, "info");
|
|
587
587
|
return;
|
|
588
588
|
}
|
|
589
589
|
const proposal = derived.proposal;
|
|
590
590
|
if (!proposal) {
|
|
591
|
-
deps.emitCommandText(ctx,
|
|
591
|
+
deps.emitCommandText(ctx, buildCookStartupDerivationFailureMessage(deps, "The previous completion workflow is already done."), "info");
|
|
592
592
|
return;
|
|
593
593
|
}
|
|
594
594
|
const decision = await deps.confirmContextProposal(ctx, proposal, {
|
|
@@ -614,7 +614,7 @@ export async function runCookEntry(
|
|
|
614
614
|
ctx,
|
|
615
615
|
proposal.source === "handoff_capsule"
|
|
616
616
|
? `Started a new completion workflow round from explicit primary-agent handoff: ${decision.missionAnchor}`
|
|
617
|
-
: `Started a new completion workflow round from
|
|
617
|
+
: `Started a new completion workflow round from deferred primary-agent handoff: ${decision.missionAnchor}`,
|
|
618
618
|
"info",
|
|
619
619
|
);
|
|
620
620
|
} else {
|
|
@@ -627,20 +627,29 @@ export async function runCookEntry(
|
|
|
627
627
|
await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps);
|
|
628
628
|
return;
|
|
629
629
|
}
|
|
630
|
-
const explicitReplacement = assessment.reason === "
|
|
630
|
+
const explicitReplacement = assessment.reason === "explicit_handoff_replacement";
|
|
631
|
+
const deferredReplacement = assessment.reason === "deferred_replacement";
|
|
631
632
|
const decision = await confirmExistingWorkflowProposal(ctx, snapshot, assessment.proposal, deps, {
|
|
632
633
|
intro: explicitReplacement
|
|
633
634
|
? "A fresh explicit primary-agent handoff proposes replacing the current workflow. Choose how /cook should proceed:"
|
|
634
|
-
:
|
|
635
|
+
: deferredReplacement
|
|
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:",
|
|
635
638
|
proposedMissionLabel: explicitReplacement
|
|
636
639
|
? "Proposed mission from explicit primary-agent handoff"
|
|
637
|
-
:
|
|
640
|
+
: deferredReplacement
|
|
641
|
+
? "Proposed mission from deferred primary-agent handoff"
|
|
642
|
+
: "Proposed mission",
|
|
638
643
|
refocusChoiceLabel: explicitReplacement
|
|
639
644
|
? "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."
|
|
640
|
-
:
|
|
645
|
+
: deferredReplacement
|
|
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.",
|
|
641
648
|
alternateChoiceLabel: explicitReplacement
|
|
642
649
|
? "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."
|
|
643
|
-
:
|
|
650
|
+
: deferredReplacement
|
|
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,
|
|
644
653
|
comparison: "strict",
|
|
645
654
|
});
|
|
646
655
|
if (!decision) {
|
|
@@ -653,9 +662,11 @@ export async function runCookEntry(
|
|
|
653
662
|
}
|
|
654
663
|
const selectedProposal = decision.proposal;
|
|
655
664
|
const proposalDecision = await deps.confirmContextProposal(ctx, selectedProposal, {
|
|
656
|
-
title:
|
|
665
|
+
title: explicitReplacement
|
|
657
666
|
? "Start the replacement workflow from this explicit startup brief?"
|
|
658
|
-
:
|
|
667
|
+
: deferredReplacement
|
|
668
|
+
? "Start the replacement workflow from this deferred startup brief?"
|
|
669
|
+
: "Start the replacement workflow from this startup brief?",
|
|
659
670
|
});
|
|
660
671
|
if (!proposalDecision) {
|
|
661
672
|
deps.emitCommandText(ctx, buildCookCancellationMessage("Cancelled replacement workflow proposal", deps), "info");
|
|
@@ -675,9 +686,11 @@ export async function runCookEntry(
|
|
|
675
686
|
snapshot = (await loadCompletionSnapshot(snapshot.files.root)) ?? snapshot;
|
|
676
687
|
deps.emitCommandText(
|
|
677
688
|
ctx,
|
|
678
|
-
|
|
689
|
+
explicitReplacement
|
|
679
690
|
? `Refocused completion mission from explicit primary-agent handoff to: ${proposalDecision.missionAnchor}`
|
|
680
|
-
:
|
|
691
|
+
: deferredReplacement
|
|
692
|
+
? `Refocused completion mission from deferred primary-agent handoff to: ${proposalDecision.missionAnchor}`
|
|
693
|
+
: `Refocused completion mission to: ${proposalDecision.missionAnchor}`,
|
|
681
694
|
"info",
|
|
682
695
|
);
|
|
683
696
|
}
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
missionAnchorsStrictlyEquivalent,
|
|
26
26
|
normalizeMissionAnchorText,
|
|
27
27
|
resolveContextProposalConfirmationAction,
|
|
28
|
+
retagContextProposalSource,
|
|
28
29
|
stripCodeBlocks,
|
|
29
30
|
} from "./proposal";
|
|
30
31
|
import type {
|
|
@@ -107,6 +108,7 @@ type RubricEvaluationRole = (typeof RUBRIC_EVALUATION_ROLES)[number];
|
|
|
107
108
|
const liveRoleActivityByRoot = new Map<string, LiveRoleActivity>();
|
|
108
109
|
const activatedCompletionRoutingRoots = new Set<string>();
|
|
109
110
|
const LIVE_ROLE_HEARTBEAT_MS = 5_000;
|
|
111
|
+
const COOK_HANDOFF_BLOCK_REGEX = /```cook_handoff\s*[\s\S]*?```/giu;
|
|
110
112
|
|
|
111
113
|
function asBoolean(value: unknown): boolean | undefined {
|
|
112
114
|
return typeof value === "boolean" ? value : undefined;
|
|
@@ -133,9 +135,10 @@ type ActiveWorkflowProposalAssessment = {
|
|
|
133
135
|
blockedFailureMessage?: string;
|
|
134
136
|
reason:
|
|
135
137
|
| "matching_mission"
|
|
136
|
-
| "
|
|
137
|
-
| "
|
|
138
|
-
| "
|
|
138
|
+
| "no_replacement_proposal"
|
|
139
|
+
| "explicit_handoff_replacement"
|
|
140
|
+
| "deferred_replacement"
|
|
141
|
+
| "replacement_not_startable";
|
|
139
142
|
};
|
|
140
143
|
|
|
141
144
|
function completionTestWorkflowActionOverride(): "continue" | "refocus" | "cancel" | undefined {
|
|
@@ -209,7 +212,7 @@ function maybeWriteTestSnapshot(targetPath: string | undefined, content: string)
|
|
|
209
212
|
|
|
210
213
|
const COOK_MAIN_CHAT_RERUN_GUIDANCE = "Discuss changes in the main chat and rerun /cook.";
|
|
211
214
|
const COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL =
|
|
212
|
-
"/cook failed closed because
|
|
215
|
+
"/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.";
|
|
213
216
|
|
|
214
217
|
function isWorkflowDone(snapshot: CompletionStateSnapshot | undefined): boolean {
|
|
215
218
|
return asString(snapshot?.state?.continuation_policy) === "done";
|
|
@@ -372,6 +375,72 @@ async function promptContextProposalConfirmationAction(
|
|
|
372
375
|
});
|
|
373
376
|
}
|
|
374
377
|
|
|
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
|
+
|
|
375
444
|
async function deriveCookStartupProposal(
|
|
376
445
|
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
377
446
|
projectName: string,
|
|
@@ -390,70 +459,14 @@ async function deriveCookStartupProposal(
|
|
|
390
459
|
if (explicitHandoff.status === "fresh_but_not_startable") {
|
|
391
460
|
return { blockedFailureMessage: explicitHandoff.message };
|
|
392
461
|
}
|
|
393
|
-
return {};
|
|
462
|
+
return { proposal: await deriveCookRecentDiscussionProposal(ctx, projectName) };
|
|
394
463
|
}
|
|
395
464
|
|
|
396
465
|
async function deriveCookContextProposal(
|
|
397
466
|
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
398
467
|
projectName: string,
|
|
399
468
|
): Promise<CookContextProposalResult> {
|
|
400
|
-
|
|
401
|
-
const recentEntries = recentMessages
|
|
402
|
-
.filter((entry) => (entry.role === "user" || entry.role === "custom") && !entry.isCommand)
|
|
403
|
-
.slice(0, 8)
|
|
404
|
-
.map((entry) => ({ role: entry.role, text: entry.text }));
|
|
405
|
-
const snapshot = await loadCompletionSnapshot(getCtxCwd(ctx));
|
|
406
|
-
const workflowContextLines = snapshot
|
|
407
|
-
? [
|
|
408
|
-
`current mission anchor: ${asString(snapshot.state?.mission_anchor) ?? asString(snapshot.plan?.mission_anchor) ?? asString(snapshot.active?.mission_anchor) ?? "(none)"}`,
|
|
409
|
-
`continuation policy: ${asString(snapshot.state?.continuation_policy) ?? "(none)"}`,
|
|
410
|
-
`latest completed slice: ${asString(snapshot.state?.latest_completed_slice) ?? "(none)"}`,
|
|
411
|
-
`latest verified slice: ${asString(snapshot.state?.latest_verified_slice) ?? "(none)"}`,
|
|
412
|
-
`active slice goal: ${asString(snapshot.active?.goal) ?? "(none)"}`,
|
|
413
|
-
`active slice why_now: ${asString(snapshot.active?.why_now) ?? "(none)"}`,
|
|
414
|
-
`verification goal: ${asString(snapshot.verificationEvidence?.goal) ?? "(none)"}`,
|
|
415
|
-
`verification summary: ${asString(snapshot.verificationEvidence?.summary) ?? "(none)"}`,
|
|
416
|
-
]
|
|
417
|
-
: [];
|
|
418
|
-
const explicitHandoff = await deriveCookStartupProposal(ctx, projectName);
|
|
419
|
-
if (explicitHandoff.proposal || explicitHandoff.blockedFailureMessage) return explicitHandoff;
|
|
420
|
-
return {
|
|
421
|
-
proposal: await deriveCookContextProposalFromRecentDiscussion(projectName, recentEntries, {
|
|
422
|
-
asString,
|
|
423
|
-
asStringArray,
|
|
424
|
-
workflowContext: snapshot
|
|
425
|
-
? {
|
|
426
|
-
currentMissionAnchor:
|
|
427
|
-
asString(snapshot.state?.mission_anchor) ?? asString(snapshot.plan?.mission_anchor) ?? asString(snapshot.active?.mission_anchor),
|
|
428
|
-
latestCompletedSlice: asString(snapshot.state?.latest_completed_slice),
|
|
429
|
-
latestVerifiedSlice: asString(snapshot.state?.latest_verified_slice),
|
|
430
|
-
activeSliceGoal: asString(snapshot.active?.goal),
|
|
431
|
-
activeSliceWhyNow: asString(snapshot.active?.why_now),
|
|
432
|
-
verificationGoal: asString(snapshot.verificationEvidence?.goal),
|
|
433
|
-
verificationSummary: asString(snapshot.verificationEvidence?.summary),
|
|
434
|
-
continuationPolicy: asString(snapshot.state?.continuation_policy),
|
|
435
|
-
}
|
|
436
|
-
: undefined,
|
|
437
|
-
analyzeContextProposal: async (entries) =>
|
|
438
|
-
await analyzeContextProposalWithAgent({
|
|
439
|
-
ctx,
|
|
440
|
-
projectName,
|
|
441
|
-
recentEntries: entries,
|
|
442
|
-
workflowContextLines,
|
|
443
|
-
liveRoleActivityByRoot,
|
|
444
|
-
completionStatusKey: COMPLETION_STATUS_KEY,
|
|
445
|
-
safeUiCall,
|
|
446
|
-
getCtxCwd,
|
|
447
|
-
getCtxHasUI,
|
|
448
|
-
getCtxUi,
|
|
449
|
-
}),
|
|
450
|
-
assessMissionAnchor,
|
|
451
|
-
isWeakMissionAnchor,
|
|
452
|
-
missionAnchorsStrictlyEquivalent,
|
|
453
|
-
normalizeMissionAnchorText,
|
|
454
|
-
stripCodeBlocks,
|
|
455
|
-
}),
|
|
456
|
-
};
|
|
469
|
+
return await deriveCookStartupProposal(ctx, projectName);
|
|
457
470
|
}
|
|
458
471
|
|
|
459
472
|
async function confirmContextProposal(
|
|
@@ -929,7 +942,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
929
942
|
structuredDiscussionFailureDetail: COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL,
|
|
930
943
|
mainChatRerunGuidance: COOK_MAIN_CHAT_RERUN_GUIDANCE,
|
|
931
944
|
cookCommandSpec: {
|
|
932
|
-
description: "/cook workflow:
|
|
945
|
+
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",
|
|
933
946
|
},
|
|
934
947
|
buildContextProposalContinuationReason,
|
|
935
948
|
completionKickoff,
|
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
|
|
11
11
|
export type AdvisoryStartupBrief = {
|
|
12
12
|
kind: "startup_brief";
|
|
13
|
-
source: "recent_discussion" | "primary_agent_handoff";
|
|
13
|
+
source: "recent_discussion" | "primary_agent_handoff" | "deferred_primary_agent_handoff";
|
|
14
14
|
confirmed: true;
|
|
15
15
|
captured_at: string;
|
|
16
16
|
goal_text: string;
|
|
@@ -26,20 +26,16 @@ export type AdvisoryStartupBrief = {
|
|
|
26
26
|
|
|
27
27
|
export function buildCookHandoffBoundaryReminder(): string {
|
|
28
28
|
return [
|
|
29
|
-
"You are
|
|
30
|
-
"
|
|
31
|
-
"/cook
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"If the user
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"When handing off, explain that bare /cook will synthesize a startup brief from recent ordinary-chat discussion for a new workflow or next round, while already-active workflows resume from canonical .agent state unless the user explicitly chooses a replacement path backed by a fresh valid explicit handoff.",
|
|
38
|
-
"If the user explicitly asks for a /cook preview or capsule before running /cook, you may 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.",
|
|
39
|
-
"Use handoff_kind implementation_workflow_handoff for that opt-in preview capsule.",
|
|
40
|
-
"If later ordinary-chat discussion materially changes the startup brief before /cook runs, update or replace the preview capsule in a later assistant reply instead of pretending the workflow already started.",
|
|
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.",
|
|
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.",
|
|
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 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.",
|
|
36
|
+
"Only provide a preview startup brief or ```cook_handoff``` capsule in ordinary chat when the user explicitly asks for that preview behavior.",
|
|
41
37
|
"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.",
|
|
42
|
-
"
|
|
38
|
+
"When you continue in ordinary chat, do not pretend /cook already started and do not silently rewrite discussion into canonical workflow state.",
|
|
43
39
|
].join(" ");
|
|
44
40
|
}
|
|
45
41
|
|
|
@@ -97,7 +93,12 @@ export function buildAdvisoryStartupBrief(args: {
|
|
|
97
93
|
}): AdvisoryStartupBrief {
|
|
98
94
|
return {
|
|
99
95
|
kind: "startup_brief",
|
|
100
|
-
source:
|
|
96
|
+
source:
|
|
97
|
+
args.proposal.source === "handoff_capsule"
|
|
98
|
+
? "primary_agent_handoff"
|
|
99
|
+
: args.proposal.source === "deferred_primary_agent_handoff"
|
|
100
|
+
? "deferred_primary_agent_handoff"
|
|
101
|
+
: "recent_discussion",
|
|
101
102
|
confirmed: true,
|
|
102
103
|
captured_at: args.capturedAt ?? new Date().toISOString(),
|
|
103
104
|
goal_text: args.proposal.goalText,
|