@linimin/pi-letscook 0.1.56 → 0.1.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -1
- package/README.md +18 -8
- package/extensions/completion/driver.ts +43 -7
- package/extensions/completion/index.ts +65 -32
- package/extensions/completion/prompt-surfaces.ts +9 -4
- package/extensions/completion/proposal.ts +339 -13
- package/package.json +1 -1
- package/scripts/context-proposal-test.sh +482 -0
- package/scripts/release-check.sh +15 -7
- package/scripts/smoke-test.sh +4 -0
- package/skills/cook-handoff-boundary/SKILL.md +40 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,9 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.58
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- tightened implementation-ready explicit `/cook` handoffs so fresh capsules must already carry a bounded first slice, repo-change-oriented acceptance, implementation surfaces, verification commands, and why-that-slice-first structure before workflow startup
|
|
10
|
+
- made fresh explicit but non-startable `/cook` handoffs fail closed with a dedicated operator message instead of falling back to broader recent discussion or silently drifting into planning
|
|
11
|
+
- expanded regressions and public parity so valid, vague, stale, done-workflow, and negative explicit-handoff cases stay truthful across runtime behavior, docs, and `npm run release-check`
|
|
12
|
+
|
|
13
|
+
## 0.1.57
|
|
14
|
+
|
|
5
15
|
### Changed
|
|
6
16
|
|
|
7
|
-
- made `/cook`
|
|
17
|
+
- made explicit primary-agent `/cook` handoff the preferred startup-intake path by teaching ordinary-chat handoff turns to emit a structured `cook_handoff` capsule and letting `/cook` prefer that capsule over broad context re-inference when it is fresh, valid, and implementation-startable
|
|
18
|
+
- tightened implementation-ready explicit handoffs so the structured capsule must already carry a bounded `first_slice_goal`, repo-change-oriented acceptance, `implementation_surfaces`, `verification_commands`, and `why_this_slice_first` before `/cook` will start workflow from it
|
|
19
|
+
- kept the pre-`/cook` handoff capsule as advisory startup intake only, not canonical `.agent/**` workflow state, while still using context-derived startup as the fallback only when no fresh explicit handoff is blocking startup
|
|
20
|
+
- kept context-derived startup as a fallback only when there is no fresh explicit handoff blocking startup, so stale or invalidated capsules can still fall back to recent discussion while fresh non-startable handoffs fail closed instead of silently rewriting canonical state
|
|
21
|
+
- made finished-workflow suppression stay a safety layer instead of a replacement mission when a fresh explicit `/cook` handoff exists, and blocked negative rejection/suppression text from becoming a Startable startup mission
|
|
8
22
|
- removed inline `/cook` arguments from the shipped entry path again so explicit bare `/cook` is the only public command, and fail closed when recent discussion is insufficient or unreliable
|
|
9
23
|
- added a pre-`/cook` ordinary-chat handoff boundary so the primary agent is instructed to stop at `/cook` once a task has matured into completion-workflow scope instead of starting long-running implementation directly in ordinary chat
|
|
10
24
|
|
package/README.md
CHANGED
|
@@ -49,12 +49,16 @@ Then run `/reload` in Pi.
|
|
|
49
49
|
|
|
50
50
|
## What `/cook` expects
|
|
51
51
|
|
|
52
|
-
-
|
|
52
|
+
- preferably a fresh explicit primary-agent `/cook` handoff capsule from the immediately preceding ordinary-chat turn
|
|
53
|
+
- for that handoff capsule to start workflow immediately, it must already be implementation-startable: a bounded `first_slice_goal`, repo-change-oriented acceptance, `implementation_surfaces`, `verification_commands`, and `why_this_slice_first`
|
|
54
|
+
- otherwise, when no fresh explicit handoff is blocking startup, recent main-chat discussion about concrete repo changes
|
|
53
55
|
- enough detail to derive a startup brief with mission, scope, constraints or non-goals, acceptance, and notes or risks
|
|
54
56
|
- README/CHANGELOG updates still count as concrete repo changes
|
|
55
|
-
- assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts do not
|
|
57
|
+
- assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts still do not count unless they include the explicit structured `/cook` handoff capsule
|
|
56
58
|
|
|
57
|
-
If recent discussion is missing, weak, ambiguous, assistant-produced, or only describes planning artifacts instead of concrete repo changes, `/cook` fails closed, leaves canonical `.agent/**` state unchanged, and tells you to clarify the mission in the main chat before rerunning `/cook`.
|
|
59
|
+
If no fresh valid handoff exists and recent discussion is missing, weak, ambiguous, assistant-produced, or only describes planning artifacts instead of concrete repo changes, `/cook` fails closed, leaves canonical `.agent/**` state unchanged, and tells you to clarify the mission in the main chat before rerunning `/cook`.
|
|
60
|
+
|
|
61
|
+
If a fresh explicit handoff exists but is still workflow-worthy rather than implementation-startable, `/cook` also fails closed instead of silently treating that capsule as planning support or canonical workflow state.
|
|
58
62
|
|
|
59
63
|
If you pass inline arguments to `/cook`, it also fails closed and tells you to move that intent into the main chat before rerunning bare `/cook`.
|
|
60
64
|
|
|
@@ -64,6 +68,10 @@ Only explicit `/cook` enters the workflow. Ordinary prompts stay in the main cha
|
|
|
64
68
|
|
|
65
69
|
If a task has clearly matured into completion-workflow scope, the primary agent should hand you off to `/cook` instead of starting long-running implementation directly in ordinary chat.
|
|
66
70
|
|
|
71
|
+
That handoff should include an explicit structured `/cook` capsule in the assistant reply so `/cook` can confirm the already-formed mission instead of re-deriving it from broad ambient context.
|
|
72
|
+
|
|
73
|
+
The preferred capsule is still advisory startup intake, not canonical workflow state, and it only counts as implementation-ready when it already names the first bounded slice, repo-change-oriented acceptance, implementation surfaces, and verification commands.
|
|
74
|
+
|
|
67
75
|
Important behavior:
|
|
68
76
|
- `/cook` is the canonical workflow boundary and manual entry point
|
|
69
77
|
- startup, refocus, and next-round routing stay confirm-first; nothing silently starts a workflow
|
|
@@ -81,13 +89,13 @@ I want to add login redirect handling and tests.
|
|
|
81
89
|
|
|
82
90
|
## What happens when you run `/cook`
|
|
83
91
|
|
|
84
|
-
`/cook` first
|
|
92
|
+
`/cook` first looks for a fresh explicit primary-agent handoff capsule. If one is valid and implementation-startable, `/cook` builds the startup brief from that handoff and only uses recent discussion as validation or supplemental notes. `/cook` falls back to deriving a startup brief from recent discussion only when no fresh explicit handoff is blocking startup—for example, when there is no fresh capsule or only stale or invalidated capsules—before showing the existing approval-only Start/Cancel gate.
|
|
85
93
|
|
|
86
94
|
| Repo state | What you'll see |
|
|
87
95
|
|---|---|
|
|
88
|
-
| No workflow yet |
|
|
89
|
-
| Active workflow exists | Usually a resume of the current workflow. If recent discussion clearly points to a different concrete repo change, `/cook` shows a chooser first and only rewrites canonical state after you confirm the new startup brief. Ambiguous
|
|
90
|
-
| Previous workflow is `done` | A
|
|
96
|
+
| No workflow yet | If a fresh explicit handoff capsule exists and is implementation-startable, a startup brief built from that handoff. Otherwise, when no fresh explicit handoff is blocking startup, a startup brief built from recent main-chat discussion. You choose **Start** or **Cancel**. Weak, unreliable, stale, planning-only, or non-startable explicit-handoff intake fails closed. |
|
|
97
|
+
| Active workflow exists | Usually a resume of the current workflow. If a fresh explicit handoff capsule or recent discussion clearly points to a different concrete repo change, `/cook` shows a chooser first and only rewrites canonical state after you confirm the new startup brief. Ambiguous intake stays conservative. |
|
|
98
|
+
| Previous workflow is `done` | A fresh explicit handoff capsule can still start the next implementation round behind **Start** or **Cancel**. Without a fresh explicit handoff blocking startup, `/cook` can fall back to recent discussion. Discussion that only restates already-finished work still fails closed. |
|
|
91
99
|
|
|
92
100
|
## Confirmation and fail-closed behavior
|
|
93
101
|
|
|
@@ -96,13 +104,15 @@ I want to add login redirect handling and tests.
|
|
|
96
104
|
- startup, next-round, and refocus proposals are approval-only
|
|
97
105
|
- actions are **Start** and **Cancel**
|
|
98
106
|
- **Cancel** is side-effect free: discuss changes in the main chat and rerun `/cook`
|
|
99
|
-
- weak, ambiguous, assistant-produced, or planning-only
|
|
107
|
+
- weak, ambiguous, stale, invalid, assistant-produced, or planning-only intake does not start a workflow
|
|
100
108
|
- when recent discussion suggests a different workflow, `/cook` shows a chooser before any canonical state rewrite
|
|
101
109
|
|
|
102
110
|
When you accept startup or refocus, `/cook` persists the chosen workflow state in canonical `.agent/**` files before the re-ground round begins.
|
|
103
111
|
|
|
104
112
|
The confirmed startup brief is also preserved there as advisory intake for later re-grounding. It does not replace `.agent/plan.json` or `.agent/active-slice.json`, which remain under regrounder authority.
|
|
105
113
|
|
|
114
|
+
The pre-`/cook` handoff capsule itself is not canonical workflow state. It is only startup intake for `/cook`.
|
|
115
|
+
|
|
106
116
|
## Observability
|
|
107
117
|
|
|
108
118
|
When canonical `.agent/**` state exists and no role is actively running, the extension shows a completion widget sourced from that state. The widget summarizes:
|
|
@@ -38,7 +38,7 @@ type ContextProposalAlternate = {
|
|
|
38
38
|
analysis: ContextProposalAnalysis;
|
|
39
39
|
goalText: string;
|
|
40
40
|
basisPreview: string;
|
|
41
|
-
source: "session" | "analyst";
|
|
41
|
+
source: "session" | "analyst" | "handoff_capsule";
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
type ContextProposal = ContextProposalAlternate & {
|
|
@@ -55,11 +55,22 @@ type ExistingWorkflowDecision =
|
|
|
55
55
|
| { action: "continue"; currentMissionAnchor: string }
|
|
56
56
|
| { action: "refocus"; currentMissionAnchor: string; missionAnchor: string; proposal: ContextProposal };
|
|
57
57
|
|
|
58
|
+
type CookContextProposalResult = {
|
|
59
|
+
proposal?: ContextProposal;
|
|
60
|
+
blockedFailureMessage?: string;
|
|
61
|
+
};
|
|
62
|
+
|
|
58
63
|
type ActiveWorkflowProposalAssessment = {
|
|
59
|
-
action: "continue" | "refocus" | "unclear";
|
|
64
|
+
action: "continue" | "refocus" | "unclear" | "blocked";
|
|
60
65
|
currentMissionAnchor: string;
|
|
61
66
|
proposal?: ContextProposal;
|
|
62
|
-
|
|
67
|
+
blockedFailureMessage?: string;
|
|
68
|
+
reason:
|
|
69
|
+
| "matching_mission"
|
|
70
|
+
| "clear_refocus"
|
|
71
|
+
| "missing_proposal"
|
|
72
|
+
| "ambiguous_discussion"
|
|
73
|
+
| "fresh_explicit_handoff_not_startable";
|
|
63
74
|
};
|
|
64
75
|
|
|
65
76
|
type ExistingWorkflowChooserOptions = {
|
|
@@ -110,7 +121,7 @@ export type CompletionDriverDeps = {
|
|
|
110
121
|
missionAnchor?: string,
|
|
111
122
|
) => string;
|
|
112
123
|
completionResumePrompt: (taskType: string, evaluationProfile: string) => string;
|
|
113
|
-
deriveCookContextProposal: (ctx: DriverContext, projectName: string) => Promise<
|
|
124
|
+
deriveCookContextProposal: (ctx: DriverContext, projectName: string) => Promise<CookContextProposalResult>;
|
|
114
125
|
confirmContextProposal: (
|
|
115
126
|
ctx: { hasUI: boolean; ui: any },
|
|
116
127
|
proposal: ContextProposal,
|
|
@@ -305,7 +316,18 @@ async function assessActiveWorkflowProposalRouting(
|
|
|
305
316
|
): Promise<ActiveWorkflowProposalAssessment> {
|
|
306
317
|
const currentMission = currentMissionAnchor(snapshot);
|
|
307
318
|
const projectName = path.basename(snapshot.files.root);
|
|
308
|
-
const
|
|
319
|
+
const derived = await deps.deriveCookContextProposal(ctx, projectName);
|
|
320
|
+
if (derived.blockedFailureMessage) {
|
|
321
|
+
const assessment: ActiveWorkflowProposalAssessment = {
|
|
322
|
+
action: "blocked",
|
|
323
|
+
currentMissionAnchor: currentMission,
|
|
324
|
+
blockedFailureMessage: derived.blockedFailureMessage,
|
|
325
|
+
reason: "fresh_explicit_handoff_not_startable",
|
|
326
|
+
};
|
|
327
|
+
deps.maybeWriteActiveWorkflowRoutingSnapshot(assessment);
|
|
328
|
+
return assessment;
|
|
329
|
+
}
|
|
330
|
+
const proposal = derived.proposal;
|
|
309
331
|
if (!proposal) {
|
|
310
332
|
const assessment: ActiveWorkflowProposalAssessment = {
|
|
311
333
|
action: "unclear",
|
|
@@ -519,7 +541,12 @@ export async function runCookEntry(
|
|
|
519
541
|
if (!snapshot) {
|
|
520
542
|
const root = findRepoRoot(cwd) ?? cwd;
|
|
521
543
|
const projectName = path.basename(root);
|
|
522
|
-
const
|
|
544
|
+
const derived = await deps.deriveCookContextProposal(ctx, projectName);
|
|
545
|
+
if (derived.blockedFailureMessage) {
|
|
546
|
+
deps.emitCommandText(ctx, derived.blockedFailureMessage, "info");
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const proposal = derived.proposal;
|
|
523
550
|
if (!proposal) {
|
|
524
551
|
deps.emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(deps), "info");
|
|
525
552
|
return;
|
|
@@ -559,7 +586,12 @@ export async function runCookEntry(
|
|
|
559
586
|
if (!goal) {
|
|
560
587
|
if (workflowDone) {
|
|
561
588
|
const projectName = path.basename(snapshot.files.root);
|
|
562
|
-
const
|
|
589
|
+
const derived = await deps.deriveCookContextProposal(ctx, projectName);
|
|
590
|
+
if (derived.blockedFailureMessage) {
|
|
591
|
+
deps.emitCommandText(ctx, derived.blockedFailureMessage, "info");
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const proposal = derived.proposal;
|
|
563
595
|
if (!proposal) {
|
|
564
596
|
deps.emitCommandText(ctx, buildCookStructuredDiscussionFailureMessage(deps, "The previous completion workflow is already done."), "info");
|
|
565
597
|
return;
|
|
@@ -586,6 +618,10 @@ export async function runCookEntry(
|
|
|
586
618
|
deps.emitCommandText(ctx, `Started a new completion workflow round from recent discussion: ${decision.missionAnchor}`, "info");
|
|
587
619
|
} else {
|
|
588
620
|
const assessment = await assessActiveWorkflowProposalRouting(ctx, snapshot, deps);
|
|
621
|
+
if (assessment.action === "blocked") {
|
|
622
|
+
deps.emitCommandText(ctx, assessment.blockedFailureMessage ?? buildCookStructuredDiscussionFailureMessage(deps), "info");
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
589
625
|
if (!assessment.proposal || assessment.action === "continue") {
|
|
590
626
|
await resumeActiveWorkflowFromCanonicalState(pi, ctx, snapshot, deps);
|
|
591
627
|
return;
|
|
@@ -16,7 +16,9 @@ import {
|
|
|
16
16
|
import {
|
|
17
17
|
assessMissionAnchor,
|
|
18
18
|
collectRecentDiscussionEntries,
|
|
19
|
+
collectRecentSessionMessages,
|
|
19
20
|
deriveCookContextProposalFromRecentDiscussion,
|
|
21
|
+
assessLatestCookHandoffProposal,
|
|
20
22
|
finalizeContextProposalAnalysis,
|
|
21
23
|
isWeakMissionAnchor,
|
|
22
24
|
missionAnchorsLikelyEquivalent,
|
|
@@ -120,11 +122,22 @@ function candidateSlices(plan: JsonRecord | undefined): JsonRecord[] {
|
|
|
120
122
|
return Array.isArray(slices) ? slices.filter(isRecord) : [];
|
|
121
123
|
}
|
|
122
124
|
|
|
125
|
+
type CookContextProposalResult = {
|
|
126
|
+
proposal?: ContextProposal;
|
|
127
|
+
blockedFailureMessage?: string;
|
|
128
|
+
};
|
|
129
|
+
|
|
123
130
|
type ActiveWorkflowProposalAssessment = {
|
|
124
|
-
action: "continue" | "refocus" | "unclear";
|
|
131
|
+
action: "continue" | "refocus" | "unclear" | "blocked";
|
|
125
132
|
currentMissionAnchor: string;
|
|
126
133
|
proposal?: ContextProposal;
|
|
127
|
-
|
|
134
|
+
blockedFailureMessage?: string;
|
|
135
|
+
reason:
|
|
136
|
+
| "matching_mission"
|
|
137
|
+
| "clear_refocus"
|
|
138
|
+
| "missing_proposal"
|
|
139
|
+
| "ambiguous_discussion"
|
|
140
|
+
| "fresh_explicit_handoff_not_startable";
|
|
128
141
|
};
|
|
129
142
|
|
|
130
143
|
function completionTestWorkflowActionOverride(): "continue" | "refocus" | "cancel" | undefined {
|
|
@@ -269,6 +282,7 @@ function maybeWriteActiveWorkflowRoutingSnapshot(assessment: ActiveWorkflowPropo
|
|
|
269
282
|
action: assessment.action,
|
|
270
283
|
reason: assessment.reason,
|
|
271
284
|
currentMissionAnchor: assessment.currentMissionAnchor,
|
|
285
|
+
blockedFailureMessage: assessment.blockedFailureMessage ?? null,
|
|
272
286
|
proposedMissionAnchor: assessment.proposal?.mission ?? null,
|
|
273
287
|
proposalSource: assessment.proposal?.source ?? null,
|
|
274
288
|
possibleNoise: assessment.proposal?.analysis.possibleNoise ?? [],
|
|
@@ -363,8 +377,12 @@ async function promptContextProposalConfirmationAction(
|
|
|
363
377
|
async function deriveCookContextProposal(
|
|
364
378
|
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
365
379
|
projectName: string,
|
|
366
|
-
): Promise<
|
|
367
|
-
const
|
|
380
|
+
): Promise<CookContextProposalResult> {
|
|
381
|
+
const recentMessages = collectRecentSessionMessages(ctx, { isRecord, asString, asNumber, isStaleContextError });
|
|
382
|
+
const recentEntries = recentMessages
|
|
383
|
+
.filter((entry) => (entry.role === "user" || entry.role === "custom") && !entry.isCommand)
|
|
384
|
+
.slice(0, 8)
|
|
385
|
+
.map((entry) => ({ role: entry.role, text: entry.text }));
|
|
368
386
|
const snapshot = await loadCompletionSnapshot(getCtxCwd(ctx));
|
|
369
387
|
const workflowContextLines = snapshot
|
|
370
388
|
? [
|
|
@@ -378,41 +396,56 @@ async function deriveCookContextProposal(
|
|
|
378
396
|
`verification summary: ${asString(snapshot.verificationEvidence?.summary) ?? "(none)"}`,
|
|
379
397
|
]
|
|
380
398
|
: [];
|
|
381
|
-
|
|
399
|
+
const explicitHandoff = assessLatestCookHandoffProposal(recentMessages, projectName, {
|
|
382
400
|
asString,
|
|
383
401
|
asStringArray,
|
|
384
|
-
workflowContext: snapshot
|
|
385
|
-
? {
|
|
386
|
-
currentMissionAnchor:
|
|
387
|
-
asString(snapshot.state?.mission_anchor) ?? asString(snapshot.plan?.mission_anchor) ?? asString(snapshot.active?.mission_anchor),
|
|
388
|
-
latestCompletedSlice: asString(snapshot.state?.latest_completed_slice),
|
|
389
|
-
latestVerifiedSlice: asString(snapshot.state?.latest_verified_slice),
|
|
390
|
-
activeSliceGoal: asString(snapshot.active?.goal),
|
|
391
|
-
activeSliceWhyNow: asString(snapshot.active?.why_now),
|
|
392
|
-
verificationGoal: asString(snapshot.verificationEvidence?.goal),
|
|
393
|
-
verificationSummary: asString(snapshot.verificationEvidence?.summary),
|
|
394
|
-
continuationPolicy: asString(snapshot.state?.continuation_policy),
|
|
395
|
-
}
|
|
396
|
-
: undefined,
|
|
397
|
-
analyzeContextProposal: async (entries) =>
|
|
398
|
-
await analyzeContextProposalWithAgent({
|
|
399
|
-
ctx,
|
|
400
|
-
projectName,
|
|
401
|
-
recentEntries: entries,
|
|
402
|
-
workflowContextLines,
|
|
403
|
-
liveRoleActivityByRoot,
|
|
404
|
-
completionStatusKey: COMPLETION_STATUS_KEY,
|
|
405
|
-
safeUiCall,
|
|
406
|
-
getCtxCwd,
|
|
407
|
-
getCtxHasUI,
|
|
408
|
-
getCtxUi,
|
|
409
|
-
}),
|
|
410
402
|
assessMissionAnchor,
|
|
403
|
+
normalizeMissionAnchorText,
|
|
411
404
|
isWeakMissionAnchor,
|
|
412
405
|
missionAnchorsStrictlyEquivalent,
|
|
413
|
-
normalizeMissionAnchorText,
|
|
414
406
|
stripCodeBlocks,
|
|
415
407
|
});
|
|
408
|
+
if (explicitHandoff.status === "startable") return { proposal: explicitHandoff.proposal };
|
|
409
|
+
if (explicitHandoff.status === "fresh_but_not_startable") {
|
|
410
|
+
return { blockedFailureMessage: explicitHandoff.message };
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
proposal: await deriveCookContextProposalFromRecentDiscussion(projectName, recentEntries, {
|
|
414
|
+
asString,
|
|
415
|
+
asStringArray,
|
|
416
|
+
workflowContext: snapshot
|
|
417
|
+
? {
|
|
418
|
+
currentMissionAnchor:
|
|
419
|
+
asString(snapshot.state?.mission_anchor) ?? asString(snapshot.plan?.mission_anchor) ?? asString(snapshot.active?.mission_anchor),
|
|
420
|
+
latestCompletedSlice: asString(snapshot.state?.latest_completed_slice),
|
|
421
|
+
latestVerifiedSlice: asString(snapshot.state?.latest_verified_slice),
|
|
422
|
+
activeSliceGoal: asString(snapshot.active?.goal),
|
|
423
|
+
activeSliceWhyNow: asString(snapshot.active?.why_now),
|
|
424
|
+
verificationGoal: asString(snapshot.verificationEvidence?.goal),
|
|
425
|
+
verificationSummary: asString(snapshot.verificationEvidence?.summary),
|
|
426
|
+
continuationPolicy: asString(snapshot.state?.continuation_policy),
|
|
427
|
+
}
|
|
428
|
+
: undefined,
|
|
429
|
+
analyzeContextProposal: async (entries) =>
|
|
430
|
+
await analyzeContextProposalWithAgent({
|
|
431
|
+
ctx,
|
|
432
|
+
projectName,
|
|
433
|
+
recentEntries: entries,
|
|
434
|
+
workflowContextLines,
|
|
435
|
+
liveRoleActivityByRoot,
|
|
436
|
+
completionStatusKey: COMPLETION_STATUS_KEY,
|
|
437
|
+
safeUiCall,
|
|
438
|
+
getCtxCwd,
|
|
439
|
+
getCtxHasUI,
|
|
440
|
+
getCtxUi,
|
|
441
|
+
}),
|
|
442
|
+
assessMissionAnchor,
|
|
443
|
+
isWeakMissionAnchor,
|
|
444
|
+
missionAnchorsStrictlyEquivalent,
|
|
445
|
+
normalizeMissionAnchorText,
|
|
446
|
+
stripCodeBlocks,
|
|
447
|
+
}),
|
|
448
|
+
};
|
|
416
449
|
}
|
|
417
450
|
|
|
418
451
|
async function confirmContextProposal(
|
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
|
|
11
11
|
export type AdvisoryStartupBrief = {
|
|
12
12
|
kind: "startup_brief";
|
|
13
|
-
source: "recent_discussion";
|
|
13
|
+
source: "recent_discussion" | "primary_agent_handoff";
|
|
14
14
|
confirmed: true;
|
|
15
15
|
captured_at: string;
|
|
16
16
|
goal_text: string;
|
|
@@ -31,7 +31,12 @@ export function buildCookHandoffBoundaryReminder(): string {
|
|
|
31
31
|
"/cook is the only explicit entrypoint into long-running completion workflow.",
|
|
32
32
|
"When you judge that the task has matured into completion-workflow scope — for example the user has clearly shifted from exploration into implementation intent, you have just produced a concrete plan or proposal whose next step would naturally be implementation, or the task spans multiple files, steps, or verification surfaces — stop short of long-running implementation and tell the user to run /cook.",
|
|
33
33
|
"At that handoff point, do not begin long-running product implementation in ordinary chat, do not edit tracked product files for that workflow-level task, and do not act as though /cook had already been invoked.",
|
|
34
|
-
"
|
|
34
|
+
"Distinguish a workflow-worthy handoff from an implementation-ready handoff: only emit the implementation-ready capsule when the first bounded implementation slice is concrete enough to start immediately.",
|
|
35
|
+
"When handing off, explain that /cook will first look for a fresh explicit primary-agent handoff capsule and otherwise fall back to recent discussion.",
|
|
36
|
+
"If the task is workflow-worthy but that first slice is still vague, tell the user to run /cook without emitting an implementation-ready capsule yet.",
|
|
37
|
+
"Otherwise append one exact fenced block in the same assistant reply using ```cook_handoff ... ``` JSON with kind/source/handoff_kind plus mission, scope, constraints or non_goals, acceptance, risks, notes, captured_at, source_turn_id, first_slice_goal, first_slice_non_goals, implementation_surfaces, verification_commands, why_this_slice_first, and optional task_type/evaluation_profile/why_cook_now.",
|
|
38
|
+
"Use handoff_kind implementation_workflow_handoff for that implementation-ready capsule.",
|
|
39
|
+
"The capsule is startup intake for /cook only: do not present it as canonical .agent state, an active slice, or a persistent repo contract.",
|
|
35
40
|
"If the task is still ordinary Q&A, lightweight brainstorming, or a tiny one-off fix, continue normally without forcing /cook.",
|
|
36
41
|
].join(" ");
|
|
37
42
|
}
|
|
@@ -84,13 +89,13 @@ function buildAdvisoryStartupBriefNotes(analysis: ContextProposalAnalysis): stri
|
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
export function buildAdvisoryStartupBrief(args: {
|
|
87
|
-
proposal: Pick<ContextProposal, "goalText" | "mission" | "scope" | "constraints" | "acceptance">;
|
|
92
|
+
proposal: Pick<ContextProposal, "goalText" | "mission" | "scope" | "constraints" | "acceptance" | "source">;
|
|
88
93
|
analysis: ContextProposalAnalysis;
|
|
89
94
|
capturedAt?: string;
|
|
90
95
|
}): AdvisoryStartupBrief {
|
|
91
96
|
return {
|
|
92
97
|
kind: "startup_brief",
|
|
93
|
-
source: "recent_discussion",
|
|
98
|
+
source: args.proposal.source === "handoff_capsule" ? "primary_agent_handoff" : "recent_discussion",
|
|
94
99
|
confirmed: true,
|
|
95
100
|
captured_at: args.capturedAt ?? new Date().toISOString(),
|
|
96
101
|
goal_text: args.proposal.goalText,
|