@linimin/pi-letscook 0.1.63 → 0.1.65
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/README.md +16 -16
- package/extensions/completion/driver.ts +7 -6
- package/extensions/completion/index.ts +54 -5
- package/extensions/completion/prompt-surfaces.ts +3 -3
- package/extensions/completion/role-runner.ts +105 -0
- package/package.json +1 -1
- package/scripts/context-proposal-test.sh +137 -34
- package/scripts/refocus-test.sh +77 -0
- package/scripts/release-check.sh +13 -15
- package/scripts/smoke-test.sh +1 -1
- package/skills/cook-handoff-boundary/SKILL.md +20 -16
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ Then run `/reload` in Pi.
|
|
|
35
35
|
3. In the main chat, either implement directly with the agent or refine the concrete repo change you want.
|
|
36
36
|
4. When you want workflow mode, run `/cook`.
|
|
37
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
|
|
38
|
+
6. Later, run `/cook` again to resume from canonical state or confirm a replacement or next-round handoff.
|
|
39
39
|
|
|
40
40
|
```text
|
|
41
41
|
/cook
|
|
@@ -46,20 +46,20 @@ Then run `/reload` in Pi.
|
|
|
46
46
|
| If you want to... | Do this |
|
|
47
47
|
|---|---|
|
|
48
48
|
| Implement directly without workflow | Ask in ordinary chat and let the agent modify the repo directly |
|
|
49
|
-
| Start a tracked workflow |
|
|
49
|
+
| Start a tracked workflow | Discuss the concrete repo change in ordinary chat, then run `/cook` when you want workflow mode |
|
|
50
50
|
| Continue the current workflow | Run `/cook` |
|
|
51
|
-
| Refocus or start the next round |
|
|
51
|
+
| Refocus or start the next round | Discuss the new concrete repo change in ordinary chat, then run `/cook` when you want workflow mode |
|
|
52
52
|
|
|
53
53
|
## What `/cook` expects
|
|
54
54
|
|
|
55
|
-
-
|
|
56
|
-
- a mission and first slice concrete enough for the primary
|
|
55
|
+
- enough current task context for a primary-agent handoff synthesis step to produce a concrete workflow startup handoff
|
|
56
|
+
- a mission and first slice concrete enough for the primary-agent handoff step to author a truthful implementation-startable handoff
|
|
57
57
|
- acceptance and verification intent that can support a truthful first workflow round
|
|
58
58
|
- README/CHANGELOG updates still count as concrete repo changes
|
|
59
|
-
- assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts still do not count unless the primary
|
|
60
|
-
- `/cook`
|
|
59
|
+
- assistant-produced summaries and plan/spec/design-doc/proposal-only artifacts still do not count unless the primary-agent handoff step turns them into a concrete `cook_handoff` capsule
|
|
60
|
+
- `/cook` first prefers a fresh explicit `cook_handoff` capsule when one already exists, but otherwise calls the primary-agent handoff synthesis step in the same `/cook` entry
|
|
61
61
|
|
|
62
|
-
If
|
|
62
|
+
If the primary-agent handoff step still cannot prepare a concrete handoff, `/cook` 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`.
|
|
63
63
|
|
|
64
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.
|
|
65
65
|
|
|
@@ -71,14 +71,14 @@ Only explicit `/cook` enters workflow mode. Ordinary prompts stay in the main ch
|
|
|
71
71
|
|
|
72
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.
|
|
73
73
|
|
|
74
|
-
When you explicitly run `/cook`, it
|
|
74
|
+
When you explicitly run `/cook`, it first checks for a fresh explicit primary-agent handoff. If one is missing, it calls a same-entry primary-agent handoff synthesis step from the current task context, then asks you to **Start** or **Cancel** before rewriting canonical workflow state.
|
|
75
75
|
|
|
76
|
-
Explicit `/cook` capsules are
|
|
76
|
+
Explicit `/cook` capsules are still valid startup intake, but they are no longer the only path because `/cook` can synthesize the primary-agent handoff in the same entry when needed.
|
|
77
77
|
|
|
78
78
|
Important behavior:
|
|
79
79
|
- `/cook` is an optional workflow boundary and manual entry point
|
|
80
|
-
- startup and next-round entry stay confirm-first,
|
|
81
|
-
- active workflows resume from canonical `.agent/**` state unless a
|
|
80
|
+
- startup and next-round entry stay confirm-first, using explicit primary-agent handoff data when present and otherwise running the primary-agent handoff synthesis step in the same `/cook` entry
|
|
81
|
+
- active workflows resume from canonical `.agent/**` state unless a concrete replacement handoff is available or synthesized in the same `/cook` entry
|
|
82
82
|
- explicit slash commands other than `/cook` continue normally in the main chat
|
|
83
83
|
- ordinary main-chat discussion may clarify, propose, or directly implement repo changes without entering workflow mode
|
|
84
84
|
|
|
@@ -94,13 +94,13 @@ I want to add login redirect handling and tests.
|
|
|
94
94
|
|
|
95
95
|
## What happens when you run `/cook`
|
|
96
96
|
|
|
97
|
-
`/cook` first checks for a fresh explicit primary-agent handoff capsule. New-workflow entry
|
|
97
|
+
`/cook` first checks for a fresh explicit primary-agent handoff capsule. New-workflow entry and done-workflow next-round entry use that handoff when it already exists; otherwise `/cook` calls a same-entry primary-agent handoff synthesis step, then immediately continues to Start / Cancel if the generated handoff is concrete enough. Active workflows still resume canonical state by default unless a concrete replacement handoff is available or synthesized in the same `/cook` entry. 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` consumes a fresh explicit primary-agent handoff
|
|
102
|
-
| Active workflow exists | Usually a resume of the current workflow from canonical `.agent/**` state. If a
|
|
103
|
-
| Previous workflow is `done` | `/cook` can start the next implementation round
|
|
101
|
+
| No workflow yet | `/cook` consumes a fresh explicit primary-agent handoff when one already exists, or synthesizes one from the primary-agent view in the same entry, then asks you to choose **Start** or **Cancel**. Stale, planning-only, or non-startable handoffs still fail closed. |
|
|
102
|
+
| Active workflow exists | Usually a resume of the current workflow from canonical `.agent/**` state. If a concrete replacement handoff exists already or is synthesized in the same `/cook` entry and points to a different 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 from a fresh explicit primary-agent handoff or from the same-entry primary-agent handoff synthesis step behind **Start** or **Cancel**. Weak or planning-only next-round handoffs still fail closed. |
|
|
104
104
|
|
|
105
105
|
## Confirmation and fail-closed behavior
|
|
106
106
|
|
|
@@ -182,6 +182,7 @@ function buildCookStructuredDiscussionFailureMessage(deps: CompletionDriverDeps,
|
|
|
182
182
|
return prefix ? `${prefix} ${deps.structuredDiscussionFailureDetail}` : deps.structuredDiscussionFailureDetail;
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
|
|
185
186
|
export function completionContinuationFingerprint(snapshot: CompletionStateSnapshot): string | undefined {
|
|
186
187
|
if (asString(snapshot.state?.continuation_policy) !== "continue") return undefined;
|
|
187
188
|
const nextMandatoryRole = asString(snapshot.state?.next_mandatory_role);
|
|
@@ -320,18 +321,18 @@ async function assessActiveWorkflowProposalRouting(
|
|
|
320
321
|
): Promise<ActiveWorkflowProposalAssessment> {
|
|
321
322
|
const currentMission = currentMissionAnchor(snapshot);
|
|
322
323
|
const projectName = path.basename(snapshot.files.root);
|
|
323
|
-
const
|
|
324
|
-
if (
|
|
324
|
+
const proposalResult = await deps.deriveCookContextProposal(ctx, projectName);
|
|
325
|
+
if (proposalResult.blockedFailureMessage) {
|
|
325
326
|
const assessment: ActiveWorkflowProposalAssessment = {
|
|
326
327
|
action: "blocked",
|
|
327
328
|
currentMissionAnchor: currentMission,
|
|
328
|
-
blockedFailureMessage:
|
|
329
|
+
blockedFailureMessage: proposalResult.blockedFailureMessage,
|
|
329
330
|
reason: "fresh_explicit_handoff_not_startable",
|
|
330
331
|
};
|
|
331
332
|
deps.maybeWriteActiveWorkflowRoutingSnapshot(assessment);
|
|
332
333
|
return assessment;
|
|
333
334
|
}
|
|
334
|
-
const proposal =
|
|
335
|
+
const proposal = proposalResult.proposal;
|
|
335
336
|
if (!proposal) {
|
|
336
337
|
const assessment: ActiveWorkflowProposalAssessment = {
|
|
337
338
|
action: "continue",
|
|
@@ -535,7 +536,7 @@ export async function runCookEntry(
|
|
|
535
536
|
if (!snapshot) {
|
|
536
537
|
const root = findRepoRoot(cwd) ?? cwd;
|
|
537
538
|
const projectName = path.basename(root);
|
|
538
|
-
const derived = await deps.
|
|
539
|
+
const derived = await deps.deriveCookContextProposal(ctx, projectName);
|
|
539
540
|
if (derived.blockedFailureMessage) {
|
|
540
541
|
deps.emitCommandText(ctx, derived.blockedFailureMessage, "info");
|
|
541
542
|
return;
|
|
@@ -580,7 +581,7 @@ export async function runCookEntry(
|
|
|
580
581
|
if (!goal) {
|
|
581
582
|
if (workflowDone) {
|
|
582
583
|
const projectName = path.basename(snapshot.files.root);
|
|
583
|
-
const derived = await deps.
|
|
584
|
+
const derived = await deps.deriveCookContextProposal(ctx, projectName);
|
|
584
585
|
if (derived.blockedFailureMessage) {
|
|
585
586
|
deps.emitCommandText(ctx, derived.blockedFailureMessage, "info");
|
|
586
587
|
return;
|
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
assessMissionAnchor,
|
|
18
18
|
collectRecentDiscussionEntries,
|
|
19
19
|
collectRecentSessionMessages,
|
|
20
|
-
deriveCookContextProposalFromRecentDiscussion,
|
|
21
20
|
assessLatestCookHandoffProposal,
|
|
22
21
|
finalizeContextProposalAnalysis,
|
|
23
22
|
isWeakMissionAnchor,
|
|
@@ -25,7 +24,6 @@ import {
|
|
|
25
24
|
missionAnchorsStrictlyEquivalent,
|
|
26
25
|
normalizeMissionAnchorText,
|
|
27
26
|
resolveContextProposalConfirmationAction,
|
|
28
|
-
retagContextProposalSource,
|
|
29
27
|
stripCodeBlocks,
|
|
30
28
|
} from "./proposal";
|
|
31
29
|
import type {
|
|
@@ -49,7 +47,7 @@ import {
|
|
|
49
47
|
maybeWriteContextProposalSnapshot,
|
|
50
48
|
} from "./prompt-surfaces";
|
|
51
49
|
import { toolCallBlockReason } from "./policy-guards";
|
|
52
|
-
import { analyzeContextProposalWithAgent, runCompletionRole } from "./role-runner";
|
|
50
|
+
import { analyzeContextProposalWithAgent, generateCookHandoffWithAgent, runCompletionRole } from "./role-runner";
|
|
53
51
|
import {
|
|
54
52
|
applyLiveRoleEvent,
|
|
55
53
|
buildInlineRunningLines,
|
|
@@ -211,7 +209,7 @@ function maybeWriteTestSnapshot(targetPath: string | undefined, content: string)
|
|
|
211
209
|
|
|
212
210
|
const COOK_MAIN_CHAT_RERUN_GUIDANCE = "Discuss changes in the main chat and rerun /cook.";
|
|
213
211
|
const COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL =
|
|
214
|
-
"/cook failed closed because
|
|
212
|
+
"/cook failed closed because the primary-agent handoff step could not prepare a concrete startup handoff from the current task context. Clarify the mission, first slice, or verification intent in the main chat, then rerun /cook.";
|
|
215
213
|
|
|
216
214
|
function isWorkflowDone(snapshot: CompletionStateSnapshot | undefined): boolean {
|
|
217
215
|
return asString(snapshot?.state?.continuation_policy) === "done";
|
|
@@ -374,6 +372,10 @@ async function promptContextProposalConfirmationAction(
|
|
|
374
372
|
});
|
|
375
373
|
}
|
|
376
374
|
|
|
375
|
+
function stripCookHandoffBlocks(text: string): string {
|
|
376
|
+
return text.replace(COOK_HANDOFF_BLOCK_REGEX, " ").replace(/\s+/g, " ").trim();
|
|
377
|
+
}
|
|
378
|
+
|
|
377
379
|
async function deriveCookStartupProposal(
|
|
378
380
|
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
379
381
|
projectName: string,
|
|
@@ -399,7 +401,54 @@ async function deriveCookContextProposal(
|
|
|
399
401
|
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
400
402
|
projectName: string,
|
|
401
403
|
): Promise<CookContextProposalResult> {
|
|
402
|
-
|
|
404
|
+
const explicit = await deriveCookStartupProposal(ctx, projectName);
|
|
405
|
+
if (explicit.proposal || explicit.blockedFailureMessage) return explicit;
|
|
406
|
+
const recentMessages = collectRecentSessionMessages(ctx, { isRecord, asString, asNumber, isStaleContextError });
|
|
407
|
+
const recentEntries = recentMessages
|
|
408
|
+
.filter((entry) => !entry.isCommand && (entry.role === "user" || entry.role === "assistant" || entry.role === "custom" || entry.role === "summary"))
|
|
409
|
+
.slice(0, 12)
|
|
410
|
+
.map((entry) => ({ role: entry.role, text: stripCookHandoffBlocks(entry.text) }))
|
|
411
|
+
.filter((entry) => entry.text.length > 0);
|
|
412
|
+
const snapshot = await loadCompletionSnapshot(getCtxCwd(ctx));
|
|
413
|
+
const workflowContextLines = snapshot
|
|
414
|
+
? [
|
|
415
|
+
`current mission anchor: ${asString(snapshot.state?.mission_anchor) ?? asString(snapshot.plan?.mission_anchor) ?? asString(snapshot.active?.mission_anchor) ?? "(none)"}`,
|
|
416
|
+
`continuation policy: ${asString(snapshot.state?.continuation_policy) ?? "(none)"}`,
|
|
417
|
+
`latest completed slice: ${asString(snapshot.state?.latest_completed_slice) ?? "(none)"}`,
|
|
418
|
+
`latest verified slice: ${asString(snapshot.state?.latest_verified_slice) ?? "(none)"}`,
|
|
419
|
+
`active slice goal: ${asString(snapshot.active?.goal) ?? "(none)"}`,
|
|
420
|
+
`active slice why_now: ${asString(snapshot.active?.why_now) ?? "(none)"}`,
|
|
421
|
+
`verification goal: ${asString(snapshot.verificationEvidence?.goal) ?? "(none)"}`,
|
|
422
|
+
`verification summary: ${asString(snapshot.verificationEvidence?.summary) ?? "(none)"}`,
|
|
423
|
+
]
|
|
424
|
+
: [];
|
|
425
|
+
const raw = await generateCookHandoffWithAgent({
|
|
426
|
+
ctx,
|
|
427
|
+
projectName,
|
|
428
|
+
recentEntries,
|
|
429
|
+
workflowContextLines,
|
|
430
|
+
liveRoleActivityByRoot,
|
|
431
|
+
completionStatusKey: COMPLETION_STATUS_KEY,
|
|
432
|
+
safeUiCall,
|
|
433
|
+
getCtxCwd,
|
|
434
|
+
getCtxHasUI,
|
|
435
|
+
getCtxUi,
|
|
436
|
+
});
|
|
437
|
+
if (!raw) return {};
|
|
438
|
+
const generated = assessLatestCookHandoffProposal([
|
|
439
|
+
{ role: "assistant", text: raw, messageId: "generated-primary-agent-handoff", timestampMs: Date.now(), isCommand: false },
|
|
440
|
+
], projectName, {
|
|
441
|
+
asString,
|
|
442
|
+
asStringArray,
|
|
443
|
+
assessMissionAnchor,
|
|
444
|
+
normalizeMissionAnchorText,
|
|
445
|
+
isWeakMissionAnchor,
|
|
446
|
+
missionAnchorsStrictlyEquivalent,
|
|
447
|
+
stripCodeBlocks,
|
|
448
|
+
});
|
|
449
|
+
if (generated.status === "startable") return { proposal: generated.proposal };
|
|
450
|
+
if (generated.status === "fresh_but_not_startable") return { blockedFailureMessage: generated.message };
|
|
451
|
+
return {};
|
|
403
452
|
}
|
|
404
453
|
|
|
405
454
|
async function confirmContextProposal(
|
|
@@ -32,9 +32,9 @@ export function buildCookHandoffBoundaryReminder(): string {
|
|
|
32
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
33
|
"If the user wants direct implementation now, stay in ordinary chat and help directly instead of blocking on /cook.",
|
|
34
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
|
|
36
|
-
"Do not expect /cook to infer or guess startup intent from recent discussion alone; /cook should
|
|
37
|
-
"Only provide a preview startup brief or ```cook_handoff``` capsule in ordinary chat when the user explicitly asks for that preview behavior
|
|
35
|
+
"If the user explicitly runs /cook, the extension should call a primary-agent handoff synthesis step from the current task context, then show Start/Cancel confirmation without making the user rerun /cook.",
|
|
36
|
+
"Do not expect /cook to infer or guess startup intent from recent discussion alone; /cook should use explicit primary-agent handoff data, whether it already exists or is synthesized in the same /cook entry.",
|
|
37
|
+
"Only provide a preview startup brief or ```cook_handoff``` capsule in ordinary chat when the user explicitly asks for that preview behavior.",
|
|
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(" ");
|
|
@@ -67,6 +67,8 @@ export type AnalyzeContextProposalWithAgentParams = {
|
|
|
67
67
|
getCtxUi: <T extends { ui: any }>(ctx: T) => any | undefined;
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
+
export type GenerateCookHandoffWithAgentParams = AnalyzeContextProposalWithAgentParams;
|
|
71
|
+
|
|
70
72
|
const AGENT_HOME = path.join(os.homedir(), ".pi", "agent");
|
|
71
73
|
const EXTENSION_DIR = typeof __dirname === "string" ? __dirname : process.cwd();
|
|
72
74
|
const PACKAGE_ROOT_CANDIDATE = path.resolve(EXTENSION_DIR, "..", "..");
|
|
@@ -94,6 +96,18 @@ const CONTEXT_PROPOSAL_ANALYST_SYSTEM_PROMPT = [
|
|
|
94
96
|
const STARTUP_ANALYST_ROLE = "cook-proposal-analyst";
|
|
95
97
|
const ANALYST_HEARTBEAT_MS = 5_000;
|
|
96
98
|
|
|
99
|
+
const PRIMARY_AGENT_HANDOFF_SYSTEM_PROMPT = [
|
|
100
|
+
"You are the primary agent preparing an explicit /cook handoff after the user already chose workflow mode.",
|
|
101
|
+
"Return either exactly one fenced ```cook_handoff JSON block or one brief plain sentence explaining why no concrete handoff can be prepared.",
|
|
102
|
+
"If you can prepare a handoff, the JSON must use kind cook_handoff, source primary_agent, and handoff_kind implementation_workflow_handoff.",
|
|
103
|
+
"When the user has clearly accepted a concrete assistant-proposed slice, carry that slice forward into the handoff instead of broadening or re-guessing the mission.",
|
|
104
|
+
"Do not make /cook infer or rediscover the mission from recent discussion later; author the handoff now from the primary-agent view of the task.",
|
|
105
|
+
"Do not emit markdown commentary before or after the capsule.",
|
|
106
|
+
"If the task is not concrete enough for implementation workflow, do not invent the slice.",
|
|
107
|
+
"A valid implementation-ready handoff must include mission, scope, constraints or non_goals, acceptance, risks, notes, first_slice_goal, first_slice_non_goals, implementation_surfaces, verification_commands, and why_this_slice_first.",
|
|
108
|
+
].join(" ");
|
|
109
|
+
const PRIMARY_AGENT_HANDOFF_ROLE = "cook-primary-agent-handoff";
|
|
110
|
+
|
|
97
111
|
class StartupAnalystOverlay extends Container {
|
|
98
112
|
private readonly border: DynamicBorder;
|
|
99
113
|
private readonly title: Text;
|
|
@@ -309,6 +323,97 @@ async function runContextProposalAnalystSubprocess(params: AnalyzeContextProposa
|
|
|
309
323
|
return await run();
|
|
310
324
|
}
|
|
311
325
|
|
|
326
|
+
function serializeRecentConversationEntries(entries: RecentDiscussionEntry[]): string {
|
|
327
|
+
return entries
|
|
328
|
+
.slice()
|
|
329
|
+
.reverse()
|
|
330
|
+
.map((entry, index) => `[${index + 1}] ${entry.role.toUpperCase()}\n${entry.text}`)
|
|
331
|
+
.join("\n\n");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function buildPrimaryAgentHandoffPrompt(projectName: string, recentEntries: RecentDiscussionEntry[], workflowContextLines: string[] = []): string {
|
|
335
|
+
const lines = [
|
|
336
|
+
`Project: ${projectName}`,
|
|
337
|
+
"",
|
|
338
|
+
"Recent session transcript:",
|
|
339
|
+
serializeRecentConversationEntries(recentEntries),
|
|
340
|
+
];
|
|
341
|
+
if (workflowContextLines.length > 0) lines.push("", "Canonical workflow context:", ...workflowContextLines);
|
|
342
|
+
lines.push(
|
|
343
|
+
"",
|
|
344
|
+
"Task:",
|
|
345
|
+
"The user explicitly invoked /cook. Prepare the primary-agent handoff that /cook should consume immediately for Start/Cancel confirmation.",
|
|
346
|
+
);
|
|
347
|
+
return lines.join("\n");
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async function runPrimaryAgentHandoffSubprocess(params: GenerateCookHandoffWithAgentParams): Promise<string | undefined> {
|
|
351
|
+
const { ctx, projectName, recentEntries } = params;
|
|
352
|
+
const modelArg = contextProposalAnalystModelArg(ctx.model);
|
|
353
|
+
if (!modelArg) return undefined;
|
|
354
|
+
const cwd = params.getCtxCwd(ctx);
|
|
355
|
+
const runCwd = findCompletionRoot(cwd) ?? findRepoRoot(cwd) ?? cwd;
|
|
356
|
+
const prompt = buildPrimaryAgentHandoffPrompt(projectName, recentEntries, params.workflowContextLines ?? []);
|
|
357
|
+
const systemPromptTemp = await writeTempFile(runCwd, "pi-cook-primary-agent-handoff-", PRIMARY_AGENT_HANDOFF_SYSTEM_PROMPT);
|
|
358
|
+
const args: string[] = ["--mode", "json", "-p", "--no-session", "--no-extensions", "--append-system-prompt", systemPromptTemp.filePath, "--model", modelArg, prompt];
|
|
359
|
+
const invocation = getPiInvocation(args);
|
|
360
|
+
const liveActivity = createLiveRoleActivity(PRIMARY_AGENT_HANDOFF_ROLE);
|
|
361
|
+
liveActivity.progress = "Preparing primary-agent /cook handoff";
|
|
362
|
+
liveActivity.currentAction = "Authoring explicit startup handoff from current task context";
|
|
363
|
+
liveActivity.assistantSummary = liveActivity.progress;
|
|
364
|
+
try {
|
|
365
|
+
const output = await new Promise<string | undefined>((resolve) => {
|
|
366
|
+
const proc = spawn(invocation.command, invocation.args, {
|
|
367
|
+
cwd: runCwd,
|
|
368
|
+
env: process.env,
|
|
369
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
370
|
+
shell: false,
|
|
371
|
+
});
|
|
372
|
+
let buffer = "";
|
|
373
|
+
const messages: RoleMessage[] = [];
|
|
374
|
+
const processLine = (line: string) => {
|
|
375
|
+
if (!line.trim()) return;
|
|
376
|
+
try {
|
|
377
|
+
const event = JSON.parse(line) as JsonRecord;
|
|
378
|
+
applyLiveRoleEvent(liveActivity, event, messages);
|
|
379
|
+
} catch {
|
|
380
|
+
// ignore malformed lines
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
proc.stdout.on("data", (chunk) => {
|
|
384
|
+
buffer += chunk.toString();
|
|
385
|
+
const lines = buffer.split("\n");
|
|
386
|
+
buffer = lines.pop() ?? "";
|
|
387
|
+
for (const line of lines) processLine(line);
|
|
388
|
+
});
|
|
389
|
+
proc.stderr.on("data", () => {
|
|
390
|
+
// ignore stderr unless no assistant output arrives
|
|
391
|
+
});
|
|
392
|
+
proc.on("close", (code) => {
|
|
393
|
+
if (buffer.trim()) processLine(buffer);
|
|
394
|
+
resolve(code === 0 ? liveActivity.lastAssistantText?.trim() || undefined : undefined);
|
|
395
|
+
});
|
|
396
|
+
proc.on("error", () => resolve(undefined));
|
|
397
|
+
});
|
|
398
|
+
return output;
|
|
399
|
+
} finally {
|
|
400
|
+
await fsp.rm(systemPromptTemp.dir, { recursive: true, force: true });
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export async function generateCookHandoffWithAgent(params: GenerateCookHandoffWithAgentParams): Promise<string | undefined> {
|
|
405
|
+
if (process.env.PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS === "1") return undefined;
|
|
406
|
+
const testOutput = asString(process.env.PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT);
|
|
407
|
+
if (testOutput) return testOutput;
|
|
408
|
+
if (params.recentEntries.length === 0) return undefined;
|
|
409
|
+
try {
|
|
410
|
+
return await runPrimaryAgentHandoffSubprocess(params);
|
|
411
|
+
} catch (error) {
|
|
412
|
+
console.warn("[completion] primary-agent handoff generation failed", error);
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
312
417
|
export async function analyzeContextProposalWithAgent(params: AnalyzeContextProposalWithAgentParams): Promise<ContextProposal | undefined> {
|
|
313
418
|
if (process.env.PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST === "1") return undefined;
|
|
314
419
|
const testOutput = asString(process.env.PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linimin/pi-letscook",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.65",
|
|
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,18 +147,54 @@ mkdir -p "$ROOT"
|
|
|
147
147
|
cd "$ROOT"
|
|
148
148
|
git init -q
|
|
149
149
|
|
|
150
|
-
# No workflow yet: bare /cook should
|
|
151
|
-
#
|
|
150
|
+
# No workflow yet: bare /cook should be able to generate a primary-agent handoff in the same entry,
|
|
151
|
+
# then continue directly to startup confirmation.
|
|
152
152
|
SESSION_ZERO="$TMPDIR/session-zero.jsonl"
|
|
153
|
-
DISCUSSION_ZERO=$'Mission: Remove the completion status line while keeping the completion widget
|
|
153
|
+
DISCUSSION_ZERO=$'Mission: Remove the completion status line while keeping the completion widget.
|
|
154
|
+
Scope:
|
|
155
|
+
- Keep the non-running completion widget.
|
|
156
|
+
- Suppress the widget while a completion role is active.
|
|
157
|
+
Constraints:
|
|
158
|
+
- Do not reintroduce any other completion status surface.
|
|
159
|
+
Acceptance:
|
|
160
|
+
- Update README to match the shipped behavior.
|
|
161
|
+
- Keep observability regression coverage truthful.'
|
|
154
162
|
DISCUSSION_SNAPSHOT_ZERO="$TMPDIR/context-proposal-structured-fallback.json"
|
|
163
|
+
GENERATED_HANDOFF_ZERO="$(python3 - <<'PY'
|
|
164
|
+
import json
|
|
165
|
+
capsule = {
|
|
166
|
+
"kind": "cook_handoff",
|
|
167
|
+
"source": "primary_agent",
|
|
168
|
+
"mission": "Remove the completion status line while keeping the completion widget.",
|
|
169
|
+
"scope": [
|
|
170
|
+
"Keep the non-running completion widget.",
|
|
171
|
+
"Suppress the widget while a completion role is active."
|
|
172
|
+
],
|
|
173
|
+
"constraints": [
|
|
174
|
+
"Do not reintroduce any other completion status surface."
|
|
175
|
+
],
|
|
176
|
+
"acceptance": [
|
|
177
|
+
"Update README to match the shipped behavior.",
|
|
178
|
+
"Keep observability regression coverage truthful."
|
|
179
|
+
],
|
|
180
|
+
"risks": [],
|
|
181
|
+
"notes": ["Generated by the primary-agent handoff step triggered from /cook."],
|
|
182
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
183
|
+
"first_slice_goal": "Remove the completion status line while preserving the widget behavior.",
|
|
184
|
+
"first_slice_non_goals": ["Do not reintroduce another status surface."],
|
|
185
|
+
"implementation_surfaces": ["extensions/completion/index.ts", "README.md"],
|
|
186
|
+
"verification_commands": ["npm run smoke-test"],
|
|
187
|
+
"why_this_slice_first": "This slice is already concrete and bounded enough to start workflow safely.",
|
|
188
|
+
"task_type": "completion-workflow",
|
|
189
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
190
|
+
"why_cook_now": "The user explicitly chose workflow mode for this bounded implementation slice."
|
|
191
|
+
}
|
|
192
|
+
print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
|
|
193
|
+
PY
|
|
194
|
+
)"
|
|
155
195
|
write_session "$SESSION_ZERO" "$ROOT" "$DISCUSSION_ZERO"
|
|
156
196
|
|
|
157
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept
|
|
158
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
159
|
-
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO" \
|
|
160
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
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"
|
|
197
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$GENERATED_HANDOFF_ZERO" PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_ZERO" PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 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"
|
|
162
198
|
|
|
163
199
|
python3 - "$TMPDIR/pi-completion-context-proposal-structured-fallback.out" "$TMPDIR/pi-completion-context-proposal-structured-fallback.err" "$DISCUSSION_SNAPSHOT_ZERO" <<'PY'
|
|
164
200
|
import json
|
|
@@ -167,9 +203,13 @@ from pathlib import Path
|
|
|
167
203
|
|
|
168
204
|
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
169
205
|
snapshot = Path(sys.argv[3])
|
|
170
|
-
assert
|
|
171
|
-
assert
|
|
172
|
-
|
|
206
|
+
assert Path('.agent').exists(), 'primary-agent handoff generation should scaffold canonical state in the same /cook entry'
|
|
207
|
+
assert snapshot.exists(), 'primary-agent handoff generation should emit a startup proposal snapshot'
|
|
208
|
+
proposal = json.loads(snapshot.read_text())
|
|
209
|
+
brief = json.loads(Path('.agent/state.json').read_text())['advisory_startup_brief']
|
|
210
|
+
assert proposal['source'] == 'handoff_capsule', 'generated primary-agent handoff should be consumed as handoff capsule startup source'
|
|
211
|
+
assert brief['source'] == 'primary_agent_handoff', 'generated primary-agent handoff should record primary_agent_handoff advisory intake'
|
|
212
|
+
assert 'Initialized completion control plane' in output, 'same-entry primary-agent handoff generation should initialize canonical workflow state'
|
|
173
213
|
PY
|
|
174
214
|
|
|
175
215
|
rm -rf .agent
|
|
@@ -206,6 +246,7 @@ PY
|
|
|
206
246
|
write_session_messages "$SESSION_ZERO_USER_AUTHORED" "$ROOT" "$USER_AUTHORED_MESSAGES_ZERO"
|
|
207
247
|
|
|
208
248
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$USER_AUTHORED_SNAPSHOT_ZERO" \
|
|
249
|
+
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
209
250
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
210
251
|
pi --session "$SESSION_ZERO_USER_AUTHORED" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-user-authored.out" 2>"$TMPDIR/pi-completion-context-proposal-user-authored.err"
|
|
211
252
|
|
|
@@ -217,7 +258,7 @@ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
|
217
258
|
snapshot = Path(sys.argv[3])
|
|
218
259
|
assert not Path('.agent').exists(), 'user-authored faux handoff without supporting discussion should still fail closed without writing canonical state'
|
|
219
260
|
assert not snapshot.exists(), 'user-authored faux handoff should not emit a startup proposal snapshot'
|
|
220
|
-
assert '
|
|
261
|
+
assert 'primary-agent handoff step could not prepare a concrete startup handoff' in output, 'user-authored faux handoff should fail closed when primary-agent handoff generation yields no handoff'
|
|
221
262
|
PY
|
|
222
263
|
|
|
223
264
|
# No workflow yet: malformed or invalid assistant handoff capsules must also fail closed.
|
|
@@ -227,6 +268,7 @@ INVALID_MESSAGES_ZERO='[{"role":"assistant","content":"This is not a valid start
|
|
|
227
268
|
write_session_messages "$SESSION_ZERO_INVALID" "$ROOT" "$INVALID_MESSAGES_ZERO"
|
|
228
269
|
|
|
229
270
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$INVALID_SNAPSHOT_ZERO" \
|
|
271
|
+
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
230
272
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
231
273
|
pi --session "$SESSION_ZERO_INVALID" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-invalid-handoff.out" 2>"$TMPDIR/pi-completion-context-proposal-invalid-handoff.err"
|
|
232
274
|
|
|
@@ -238,7 +280,7 @@ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
|
238
280
|
snapshot = Path(sys.argv[3])
|
|
239
281
|
assert not Path('.agent').exists(), 'invalid assistant handoff without supporting discussion should fail closed without writing canonical state'
|
|
240
282
|
assert not snapshot.exists(), 'invalid assistant handoff should not emit a startup proposal snapshot'
|
|
241
|
-
assert '
|
|
283
|
+
assert 'primary-agent handoff step could not prepare a concrete startup handoff' in output, 'invalid assistant handoff should fail closed when no valid handoff can be prepared'
|
|
242
284
|
PY
|
|
243
285
|
|
|
244
286
|
# No workflow yet: a fresh explicit primary-agent handoff should still bootstrap canonical startup state.
|
|
@@ -361,6 +403,7 @@ CONTINUE_PROPOSAL_ONE="$TMPDIR/unexpected-active-continue-proposal.json"
|
|
|
361
403
|
write_session "$SESSION_ONE_CONTINUE" "$ROOT" "$DISCUSSION_ONE_CONTINUE"
|
|
362
404
|
|
|
363
405
|
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
406
|
+
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
364
407
|
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$CONTINUE_ROUTING_ONE" \
|
|
365
408
|
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$CONTINUE_RESUME_PROMPT_ONE" \
|
|
366
409
|
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$CONTINUE_CHOOSER_ONE" \
|
|
@@ -408,6 +451,7 @@ DISCUSSION_REFOCUS_PROPOSAL_ONE="$TMPDIR/unexpected-active-discussion-refocus-pr
|
|
|
408
451
|
write_session "$SESSION_ONE_DISCUSSION_REFOCUS" "$ROOT" "$DISCUSSION_ONE_DISCUSSION_REFOCUS"
|
|
409
452
|
|
|
410
453
|
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
454
|
+
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
411
455
|
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$DISCUSSION_REFOCUS_ROUTING_ONE" \
|
|
412
456
|
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$DISCUSSION_REFOCUS_RESUME_ONE" \
|
|
413
457
|
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$DISCUSSION_REFOCUS_CHOOSER_ONE" \
|
|
@@ -482,6 +526,7 @@ with session_path.open('w', encoding='utf-8') as fh:
|
|
|
482
526
|
PY
|
|
483
527
|
|
|
484
528
|
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
529
|
+
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
485
530
|
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$SUMMARY_ROUTING_ONE" \
|
|
486
531
|
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$SUMMARY_RESUME_PROMPT_ONE" \
|
|
487
532
|
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$SUMMARY_CHOOSER_ONE" \
|
|
@@ -643,6 +688,7 @@ write_session "$SESSION_TWO_COMPLETED_SUPPRESS" "$ROOT" "$DISCUSSION_TWO_COMPLET
|
|
|
643
688
|
|
|
644
689
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
645
690
|
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
691
|
+
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
646
692
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_TWO_COMPLETED_SUPPRESS" \
|
|
647
693
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
648
694
|
pi --session "$SESSION_TWO_COMPLETED_SUPPRESS" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-next-round-completed-suppress.out" 2>"$TMPDIR/pi-completion-context-proposal-next-round-completed-suppress.err"
|
|
@@ -660,7 +706,7 @@ state = json.loads(Path('.agent/state.json').read_text())
|
|
|
660
706
|
assert state['mission_anchor'] == expected, 'completed-topic suppression should keep the done workflow mission anchor unchanged'
|
|
661
707
|
assert state['continuation_policy'] == 'done', 'completed-topic suppression should keep the workflow closed'
|
|
662
708
|
assert not snapshot.exists(), 'completed-topic suppression should not emit a proposal snapshot when the latest discussion only repeats finished work'
|
|
663
|
-
assert '
|
|
709
|
+
assert 'primary-agent handoff step could not prepare a concrete startup handoff' in output, 'completed-topic suppression should fail closed when no concrete primary-agent handoff can be prepared'
|
|
664
710
|
PY
|
|
665
711
|
|
|
666
712
|
# Completed workflow: bare /cook should also suppress proposals that merely restate canonical
|
|
@@ -691,6 +737,7 @@ write_session "$SESSION_TWO_VERIFIED_SUPPRESS" "$ROOT" "$DISCUSSION_TWO_VERIFIED
|
|
|
691
737
|
|
|
692
738
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
693
739
|
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
740
|
+
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
694
741
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_TWO_VERIFIED_SUPPRESS" \
|
|
695
742
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
696
743
|
pi --session "$SESSION_TWO_VERIFIED_SUPPRESS" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-next-round-verified-suppress.out" 2>"$TMPDIR/pi-completion-context-proposal-next-round-verified-suppress.err"
|
|
@@ -702,7 +749,7 @@ from pathlib import Path
|
|
|
702
749
|
output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
703
750
|
snapshot = Path(sys.argv[3])
|
|
704
751
|
assert not snapshot.exists(), 'verification-evidence overlap suppression should not emit a proposal snapshot for already verified work'
|
|
705
|
-
assert '
|
|
752
|
+
assert 'primary-agent handoff step could not prepare a concrete startup handoff' in output, 'verification-evidence overlap suppression should fail closed when no concrete primary-agent handoff can be prepared'
|
|
706
753
|
PY
|
|
707
754
|
|
|
708
755
|
# Completed workflow: bare /cook should fail closed for next-round discussion-only startup too,
|
|
@@ -712,11 +759,38 @@ DISCUSSION_TWO_NORMALIZED=$'Mission: 開始實作這個方案\nScope:\n- Normali
|
|
|
712
759
|
DISCUSSION_SNAPSHOT_TWO_NORMALIZED="$TMPDIR/context-proposal-next-round-normalized.json"
|
|
713
760
|
write_session "$SESSION_TWO_NORMALIZED" "$ROOT" "$DISCUSSION_TWO_NORMALIZED"
|
|
714
761
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
762
|
+
GENERATED_HANDOFF_TWO_NORMALIZED="$(python3 - <<'PY'
|
|
763
|
+
import json
|
|
764
|
+
capsule = {
|
|
765
|
+
"kind": "cook_handoff",
|
|
766
|
+
"source": "primary_agent",
|
|
767
|
+
"mission": "Start the next workflow round with a normalized implementation mission.",
|
|
768
|
+
"scope": [
|
|
769
|
+
"Reset canonical state for the new implementation mission.",
|
|
770
|
+
"Keep the next round distinct from the completed workflow."
|
|
771
|
+
],
|
|
772
|
+
"constraints": [
|
|
773
|
+
"Do not resume the completed workflow when the new round is clearly different."
|
|
774
|
+
],
|
|
775
|
+
"acceptance": [
|
|
776
|
+
"Reset canonical state back to reground for the new mission.",
|
|
777
|
+
"Preserve the tracked completion control-plane files."
|
|
778
|
+
],
|
|
779
|
+
"risks": [],
|
|
780
|
+
"notes": ["Generated by the primary-agent handoff step triggered from /cook."],
|
|
781
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
782
|
+
"first_slice_goal": "Bootstrap the next workflow round from the normalized implementation mission.",
|
|
783
|
+
"first_slice_non_goals": ["Do not reopen finished slices from the previous workflow."],
|
|
784
|
+
"implementation_surfaces": ["extensions/completion/driver.ts", "scripts/context-proposal-test.sh"],
|
|
785
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
786
|
+
"why_this_slice_first": "The user explicitly chose workflow mode for a bounded next-round restart.",
|
|
787
|
+
"task_type": "completion-workflow",
|
|
788
|
+
"evaluation_profile": "completion-rubric-v1"
|
|
789
|
+
}
|
|
790
|
+
print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
|
|
791
|
+
PY
|
|
792
|
+
)"
|
|
793
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$GENERATED_HANDOFF_TWO_NORMALIZED" PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_TWO_NORMALIZED" PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 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"
|
|
720
794
|
|
|
721
795
|
python3 - "$TMPDIR/pi-completion-context-proposal-next-round-normalized.out" "$TMPDIR/pi-completion-context-proposal-next-round-normalized.err" "$DISCUSSION_SNAPSHOT_TWO_NORMALIZED" "$CURRENT_DONE_MISSION" <<'PY'
|
|
722
796
|
import json
|
|
@@ -727,11 +801,12 @@ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
|
727
801
|
snapshot = Path(sys.argv[3])
|
|
728
802
|
previous = sys.argv[4]
|
|
729
803
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
assert
|
|
733
|
-
assert state['
|
|
734
|
-
assert '
|
|
804
|
+
if snapshot.exists():
|
|
805
|
+
proposal = json.loads(snapshot.read_text())
|
|
806
|
+
assert proposal['source'] == 'handoff_capsule', 'done-workflow generated startup should snapshot the primary-agent handoff source'
|
|
807
|
+
assert state['mission_anchor'] != previous, 'done-workflow discussion-only startup should advance to the new mission anchor'
|
|
808
|
+
assert state['continuation_policy'] == 'continue', 'done-workflow discussion-only startup should reopen workflow state'
|
|
809
|
+
assert 'Started a new completion workflow round from explicit primary-agent handoff' in output, 'done-workflow generated startup should report explicit primary-agent handoff startup'
|
|
735
810
|
PY
|
|
736
811
|
|
|
737
812
|
# Completed workflow: a fresh explicit primary-agent handoff should still start the next round.
|
|
@@ -960,11 +1035,37 @@ ANALYST_OUTPUT_FIVE='{"mission":"Use a proposal analyst to summarize natural dis
|
|
|
960
1035
|
DISCUSSION_SNAPSHOT_FIVE="$TMPDIR/context-proposal-analyst-restart-rejected.json"
|
|
961
1036
|
write_session "$SESSION_FIVE" "$ROOT" "$DISCUSSION_FIVE"
|
|
962
1037
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1038
|
+
GENERATED_HANDOFF_FIVE="$(python3 - <<'PY'
|
|
1039
|
+
import json
|
|
1040
|
+
capsule = {
|
|
1041
|
+
"kind": "cook_handoff",
|
|
1042
|
+
"source": "primary_agent",
|
|
1043
|
+
"mission": "Use the analyst-backed parser follow-up as the next workflow round.",
|
|
1044
|
+
"scope": [
|
|
1045
|
+
"Keep the discussion-derived mission anchored once it is clear.",
|
|
1046
|
+
"Drop stale scope from earlier turns."
|
|
1047
|
+
],
|
|
1048
|
+
"constraints": [
|
|
1049
|
+
"Do not rewrite the parser."
|
|
1050
|
+
],
|
|
1051
|
+
"acceptance": [
|
|
1052
|
+
"Add a regression test."
|
|
1053
|
+
],
|
|
1054
|
+
"risks": [],
|
|
1055
|
+
"notes": ["Generated by the primary-agent handoff step triggered from /cook."],
|
|
1056
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
1057
|
+
"first_slice_goal": "Land the regression-test-backed parser follow-up without rewriting the parser.",
|
|
1058
|
+
"first_slice_non_goals": ["Do not broaden the mission with stale scope."],
|
|
1059
|
+
"implementation_surfaces": ["scripts/context-proposal-test.sh"],
|
|
1060
|
+
"verification_commands": ["npm run context-proposal-test"],
|
|
1061
|
+
"why_this_slice_first": "The user explicitly chose workflow mode and the primary agent can already bound the first slice.",
|
|
1062
|
+
"task_type": "completion-workflow",
|
|
1063
|
+
"evaluation_profile": "completion-rubric-v1"
|
|
1064
|
+
}
|
|
1065
|
+
print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
|
|
1066
|
+
PY
|
|
1067
|
+
)"
|
|
1068
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept PI_COMPLETION_CONTEXT_PROPOSAL_ANALYST_OUTPUT="$ANALYST_OUTPUT_FIVE" PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$GENERATED_HANDOFF_FIVE" PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$DISCUSSION_SNAPSHOT_FIVE" PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 pi --session "$SESSION_FIVE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-context-proposal-analyst.out" 2>"$TMPDIR/pi-completion-context-proposal-analyst.err"
|
|
968
1069
|
|
|
969
1070
|
python3 - "$TMPDIR/pi-completion-context-proposal-analyst.out" "$TMPDIR/pi-completion-context-proposal-analyst.err" "$DISCUSSION_SNAPSHOT_FIVE" <<'PY'
|
|
970
1071
|
import json
|
|
@@ -975,9 +1076,10 @@ output = Path(sys.argv[1]).read_text() + Path(sys.argv[2]).read_text()
|
|
|
975
1076
|
snapshot = Path(sys.argv[3])
|
|
976
1077
|
state = json.loads(Path('.agent/state.json').read_text())
|
|
977
1078
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
assert '
|
|
1079
|
+
if snapshot.exists():
|
|
1080
|
+
pass
|
|
1081
|
+
assert state['continuation_policy'] == 'continue', 'done-workflow analyst-backed primary-agent handoff should reopen the workflow'
|
|
1082
|
+
assert 'Started a new completion workflow round from explicit primary-agent handoff' in output, 'done-workflow analyst-backed startup should report explicit primary-agent handoff startup'
|
|
981
1083
|
PY
|
|
982
1084
|
|
|
983
1085
|
# Custom confirmation UI: start should render proposal content separately from approval-only Start/Cancel actions.
|
|
@@ -1511,6 +1613,7 @@ write_session_messages "$HANDOFF_SESSION_STALE" "$HANDOFF_ROOT_STALE" "$HANDOFF_
|
|
|
1511
1613
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
1512
1614
|
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
1513
1615
|
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$HANDOFF_SNAPSHOT_STALE" \
|
|
1616
|
+
PI_COMPLETION_DISABLE_PRIMARY_HANDOFF_SYNTHESIS=1 \
|
|
1514
1617
|
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
1515
1618
|
pi --session "$HANDOFF_SESSION_STALE" -e "$PKG_ROOT" -p "/cook" >"$TMPDIR/pi-completion-handoff-stale.out" 2>"$TMPDIR/pi-completion-handoff-stale.err"
|
|
1516
1619
|
|
|
@@ -1523,7 +1626,7 @@ output = Path(sys.argv[2]).read_text() + Path(sys.argv[3]).read_text()
|
|
|
1523
1626
|
|
|
1524
1627
|
assert not snapshot.exists(), 'stale handoff should not emit a startup proposal snapshot'
|
|
1525
1628
|
assert not Path('.agent').exists(), 'stale handoff should fail closed without writing canonical state'
|
|
1526
|
-
assert '
|
|
1629
|
+
assert 'primary-agent handoff step could not prepare a concrete startup handoff' in output, 'stale handoff should fail closed when the synthesized handoff step produces nothing'
|
|
1527
1630
|
PY
|
|
1528
1631
|
|
|
1529
1632
|
# Negative handoff rationale: a non-startable capsule must not become the startup mission.
|
package/scripts/refocus-test.sh
CHANGED
|
@@ -519,4 +519,81 @@ assert plan['plan_basis'] == 'user_refocus', 'plan.json plan_basis should be use
|
|
|
519
519
|
assert active['status'] == 'idle', 'active-slice.json status should reset to idle after bare refocus'
|
|
520
520
|
PY
|
|
521
521
|
|
|
522
|
+
SYNTH_REPLACEMENT_SESSION="$TMPDIR/session-synthesized-active-replacement.jsonl"
|
|
523
|
+
SYNTH_REPLACEMENT_ROUTING="$TMPDIR/synthesized-active-replacement-routing.json"
|
|
524
|
+
SYNTH_REPLACEMENT_PROPOSAL="$TMPDIR/synthesized-active-replacement-proposal.json"
|
|
525
|
+
write_session "$SYNTH_REPLACEMENT_SESSION" "$TMPDIR" "Please replace the current workflow with the synthesized replacement mission when I run /cook."
|
|
526
|
+
|
|
527
|
+
SYNTH_REPLACEMENT_HANDOFF="$(python3 - <<'PY'
|
|
528
|
+
import json
|
|
529
|
+
capsule = {
|
|
530
|
+
"kind": "cook_handoff",
|
|
531
|
+
"source": "primary_agent",
|
|
532
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
533
|
+
"source_turn_id": "generated-primary-agent-handoff",
|
|
534
|
+
"mission": "Exercise same-entry active-workflow replacement synthesis.",
|
|
535
|
+
"scope": [
|
|
536
|
+
"Generate the replacement handoff inside the same /cook entry.",
|
|
537
|
+
"Keep the chooser and final Start/Cancel confirmation truthful."
|
|
538
|
+
],
|
|
539
|
+
"constraints": [
|
|
540
|
+
"Do not rewrite canonical state before the final Start confirmation."
|
|
541
|
+
],
|
|
542
|
+
"acceptance": [
|
|
543
|
+
"Replace the active workflow using the synthesized primary-agent handoff.",
|
|
544
|
+
"Keep deterministic coverage for same-entry active replacement."
|
|
545
|
+
],
|
|
546
|
+
"risks": [],
|
|
547
|
+
"notes": [
|
|
548
|
+
"This replacement is synthesized during /cook rather than pre-authored in the transcript."
|
|
549
|
+
],
|
|
550
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
551
|
+
"first_slice_goal": "Exercise same-entry active-workflow replacement synthesis.",
|
|
552
|
+
"first_slice_non_goals": [],
|
|
553
|
+
"implementation_surfaces": [
|
|
554
|
+
"scripts/refocus-test.sh"
|
|
555
|
+
],
|
|
556
|
+
"verification_commands": [
|
|
557
|
+
"npm run refocus-test"
|
|
558
|
+
],
|
|
559
|
+
"why_this_slice_first": "Active replacement should work when the primary-agent handoff is synthesized in the same /cook entry.",
|
|
560
|
+
"task_type": "completion-workflow",
|
|
561
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
562
|
+
"why_cook_now": "The user explicitly chose workflow mode and the replacement handoff can be synthesized immediately."
|
|
563
|
+
}
|
|
564
|
+
print("```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```")
|
|
565
|
+
PY
|
|
566
|
+
)"
|
|
567
|
+
|
|
568
|
+
PI_COMPLETION_PRIMARY_HANDOFF_OUTPUT="$SYNTH_REPLACEMENT_HANDOFF" \
|
|
569
|
+
PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
|
|
570
|
+
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
571
|
+
PI_COMPLETION_TEST_CONTEXT_PROPOSAL_PATH="$SYNTH_REPLACEMENT_PROPOSAL" \
|
|
572
|
+
PI_COMPLETION_TEST_ACTIVE_WORKFLOW_ROUTING_PATH="$SYNTH_REPLACEMENT_ROUTING" \
|
|
573
|
+
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
574
|
+
pi --session "$SYNTH_REPLACEMENT_SESSION" -e "$PKG_ROOT" -p "/cook" \
|
|
575
|
+
>"$TMPDIR/pi-completion-synthesized-active-replacement.out" 2>"$TMPDIR/pi-completion-synthesized-active-replacement.err"
|
|
576
|
+
|
|
577
|
+
python3 - "$SYNTH_REPLACEMENT_PROPOSAL" "$SYNTH_REPLACEMENT_ROUTING" <<'PY'
|
|
578
|
+
import json
|
|
579
|
+
import sys
|
|
580
|
+
from pathlib import Path
|
|
581
|
+
|
|
582
|
+
new_anchor = 'Exercise same-entry active-workflow replacement synthesis.'
|
|
583
|
+
proposal = json.loads(Path(sys.argv[1]).read_text())
|
|
584
|
+
routing = json.loads(Path(sys.argv[2]).read_text())
|
|
585
|
+
state = json.loads(Path('.agent/state.json').read_text())
|
|
586
|
+
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
587
|
+
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
588
|
+
|
|
589
|
+
assert proposal['mission'] == new_anchor, 'same-entry synthesized replacement should preserve the replacement proposal mission'
|
|
590
|
+
assert routing['action'] == 'refocus', 'same-entry synthesized replacement should classify active bare /cook as refocus'
|
|
591
|
+
assert routing['reason'] == 'fresh_explicit_handoff', 'same-entry synthesized replacement should reuse the explicit-handoff routing reason because /cook synthesized an explicit handoff'
|
|
592
|
+
assert routing['proposalSource'] == 'handoff_capsule', 'same-entry synthesized replacement should surface the synthesized handoff as a handoff capsule source'
|
|
593
|
+
assert state['mission_anchor'] == new_anchor, 'state.json mission_anchor mismatch after same-entry synthesized refocus'
|
|
594
|
+
assert plan['mission_anchor'] == new_anchor, 'plan.json mission_anchor mismatch after same-entry synthesized refocus'
|
|
595
|
+
assert active['mission_anchor'] == new_anchor, 'active-slice.json mission_anchor mismatch after same-entry synthesized refocus'
|
|
596
|
+
assert state['continuation_reason'].startswith('User refocused workflow via /cook:'), 'same-entry synthesized replacement should record the /cook refocus continuation reason'
|
|
597
|
+
PY
|
|
598
|
+
|
|
522
599
|
echo "refocus test passed: $TMPDIR"
|
package/scripts/release-check.sh
CHANGED
|
@@ -9,43 +9,41 @@ 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
|
|
12
|
+
echo "[release-check] verifying public /cook parity and primary-agent-handoff docs/help"
|
|
13
13
|
python3 - <<'PY'
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
16
|
checks = {
|
|
17
17
|
"README.md": [
|
|
18
18
|
"You can still implement directly in ordinary chat when you do not need workflow state.",
|
|
19
|
-
"When you explicitly run `/cook`, it
|
|
20
|
-
"
|
|
21
|
-
"`/cook`
|
|
22
|
-
"New-workflow entry, done-workflow next-round entry, and active-workflow replacement should use that handoff instead of guessing from recent discussion.",
|
|
19
|
+
"When you explicitly run `/cook`, it first checks for a fresh explicit primary-agent handoff.",
|
|
20
|
+
"If one is missing, it calls a same-entry primary-agent handoff synthesis step from the current task context, then asks you to **Start** or **Cancel** before rewriting canonical workflow state.",
|
|
21
|
+
"Explicit `/cook` capsules are still valid startup intake, but they are no longer the only path because `/cook` can synthesize the primary-agent handoff in the same entry when needed.",
|
|
23
22
|
],
|
|
24
23
|
"CHANGELOG.md": [
|
|
25
|
-
"
|
|
26
|
-
"
|
|
24
|
+
"changed `/cook` entry again so it now calls a same-entry primary-agent handoff synthesis step when no fresh explicit handoff exists, then proceeds directly to Start / Cancel without requiring a second `/cook` run",
|
|
25
|
+
"kept `/cook` from guessing startup purely from recent discussion by requiring that same-entry synthesis step to produce explicit primary-agent handoff data before workflow state can start",
|
|
27
26
|
],
|
|
28
27
|
"extensions/completion/prompt-surfaces.ts": [
|
|
29
|
-
'"If the user explicitly
|
|
30
|
-
'"Do not expect /cook to infer or guess startup intent from recent discussion alone; /cook should
|
|
28
|
+
'"If the user explicitly runs /cook, the extension should call a primary-agent handoff synthesis step from the current task context, then show Start/Cancel confirmation without making the user rerun /cook."',
|
|
29
|
+
'"Do not expect /cook to infer or guess startup intent from recent discussion alone; /cook should use explicit primary-agent handoff data, whether it already exists or is synthesized in the same /cook entry."',
|
|
31
30
|
],
|
|
32
31
|
"extensions/completion/index.ts": [
|
|
33
|
-
'"/cook failed closed because
|
|
32
|
+
'"/cook failed closed because the primary-agent handoff step could not prepare a concrete startup handoff from the current task context. Clarify the mission, first slice, or verification intent in the main chat, then rerun /cook."',
|
|
34
33
|
'description: "/cook workflow: start or replace workflow only from an explicit primary-agent handoff, or resume the current workflow from canonical state"',
|
|
35
34
|
],
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
forbidden = {
|
|
39
38
|
"README.md": [
|
|
40
|
-
"
|
|
41
|
-
"
|
|
39
|
+
"asks the primary agent to prepare one in the main chat and leaves canonical state unchanged until you rerun /cook",
|
|
40
|
+
"Explicit `/cook` capsules are the required startup intake for new-workflow, next-round, and replacement entry.",
|
|
42
41
|
],
|
|
43
42
|
"extensions/completion/prompt-surfaces.ts": [
|
|
44
|
-
'"If the user explicitly
|
|
43
|
+
'"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."',
|
|
45
44
|
],
|
|
46
45
|
"extensions/completion/index.ts": [
|
|
47
|
-
'
|
|
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."',
|
|
46
|
+
'"/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."',
|
|
49
47
|
],
|
|
50
48
|
}
|
|
51
49
|
|
package/scripts/smoke-test.sh
CHANGED
|
@@ -270,7 +270,7 @@ assert 'directly implement requested repo changes, including multi-file work' in
|
|
|
270
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'
|
|
271
271
|
assert '/cook is optional workflow mode' in handoff_text, 'ordinary handoff reminder should position /cook as optional workflow mode'
|
|
272
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 '
|
|
273
|
+
assert 'the extension should call a primary-agent handoff synthesis step from the current task context' in handoff_text, 'ordinary handoff reminder should describe same-entry primary-agent handoff synthesis for /cook'
|
|
274
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
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'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cook-handoff-boundary
|
|
3
|
-
description: Ordinary-chat contract for treating `/cook` as an optional workflow mode while
|
|
3
|
+
description: Ordinary-chat contract for treating `/cook` as an optional workflow mode while requiring `/cook` to use primary-agent-authored handoff data instead of guessing from recent discussion.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# /cook Handoff Boundary
|
|
@@ -54,20 +54,24 @@ But even in those cases:
|
|
|
54
54
|
|
|
55
55
|
## Required Behavior When The User Explicitly Chooses `/cook`
|
|
56
56
|
|
|
57
|
-
If the user explicitly
|
|
57
|
+
If the user explicitly runs or clearly chooses `/cook` workflow mode, the system behavior should be:
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
1. check for a fresh explicit primary-agent `cook_handoff`
|
|
60
|
+
2. if none exists, call a primary-agent handoff synthesis step immediately from the current task context
|
|
61
|
+
3. use that handoff to show Start / Cancel confirmation in the same `/cook` entry
|
|
62
|
+
4. write canonical workflow state only after Start
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
That means:
|
|
65
65
|
|
|
66
|
-
-
|
|
67
|
-
- `/cook`
|
|
68
|
-
- `/cook`
|
|
66
|
+
- `/cook` must not infer or guess the startup slice from recent discussion alone
|
|
67
|
+
- `/cook` should use primary-agent-authored handoff data
|
|
68
|
+
- `/cook` should not require a manual rerun just to consume a handoff it can synthesize immediately from the primary-agent view
|
|
69
69
|
|
|
70
|
-
##
|
|
70
|
+
## Optional Preview Behavior
|
|
71
|
+
|
|
72
|
+
Only if the user explicitly asks for a preview startup brief or handoff capsule in ordinary chat may the primary agent provide one.
|
|
73
|
+
|
|
74
|
+
Optional preview capsule format:
|
|
71
75
|
|
|
72
76
|
````text
|
|
73
77
|
```cook_handoff
|
|
@@ -102,9 +106,9 @@ Notes:
|
|
|
102
106
|
- `first_slice_goal`, `first_slice_non_goals`, `implementation_surfaces`, `verification_commands`, and `why_this_slice_first` are required for an implementation-ready handoff.
|
|
103
107
|
- 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.
|
|
104
108
|
|
|
105
|
-
Suggested wording
|
|
109
|
+
Suggested wording:
|
|
106
110
|
|
|
107
|
-
>
|
|
111
|
+
> We can continue directly in ordinary chat if you want. If you prefer workflow mode, run `/cook` and it should use a primary-agent handoff for Start / Cancel confirmation rather than guessing from recent discussion.
|
|
108
112
|
|
|
109
113
|
## Forbidden Behaviors
|
|
110
114
|
|
|
@@ -118,12 +122,12 @@ Before the user explicitly runs `/cook`, the primary agent must not:
|
|
|
118
122
|
When the user does explicitly choose `/cook`, the system must not:
|
|
119
123
|
|
|
120
124
|
- let `/cook` invent the startup mission from recent discussion alone
|
|
121
|
-
- let `/cook` replace missing
|
|
122
|
-
-
|
|
125
|
+
- let `/cook` replace missing handoff data with generic transcript guessing
|
|
126
|
+
- require a second `/cook` invocation when same-entry primary-agent handoff synthesis is possible
|
|
123
127
|
|
|
124
128
|
## Relationship To `completion-protocol`
|
|
125
129
|
|
|
126
|
-
This skill is only about pre-`/cook` ordinary-chat behavior and
|
|
130
|
+
This skill is only about pre-`/cook` ordinary-chat behavior and `/cook` handoff expectations.
|
|
127
131
|
|
|
128
132
|
After the user explicitly enters `/cook`, the separate `completion-protocol` skill governs:
|
|
129
133
|
|