@linimin/pi-letscook 0.1.58 → 0.1.59
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/.agent/README.md +28 -0
- package/.agent/mission.md +8 -0
- package/.agent/profile.json +13 -0
- package/.agent/verify_completion_control_plane.sh +203 -0
- package/.agent/verify_completion_stop.sh +20 -0
- package/CHANGELOG.md +10 -0
- package/README.md +23 -19
- package/extensions/completion/driver.ts +47 -41
- package/extensions/completion/index.ts +29 -21
- package/extensions/completion/prompt-surfaces.ts +8 -6
- package/extensions/completion/proposal.ts +0 -15
- package/package.json +6 -1
- package/scripts/active-slice-contract-test.sh +93 -2
- package/scripts/canonical-evidence-artifact-test.sh +93 -2
- package/scripts/context-proposal-test.sh +553 -736
- package/scripts/refocus-test.sh +196 -28
- package/scripts/release-check.sh +50 -28
- package/scripts/smoke-test.sh +113 -10
- package/skills/cook-handoff-boundary/SKILL.md +11 -6
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
missionAnchorsStrictlyEquivalent,
|
|
26
26
|
normalizeMissionAnchorText,
|
|
27
27
|
resolveContextProposalConfirmationAction,
|
|
28
|
-
shouldTreatBareActiveWorkflowProposalAsClearRefocus,
|
|
29
28
|
stripCodeBlocks,
|
|
30
29
|
} from "./proposal";
|
|
31
30
|
import type {
|
|
@@ -128,15 +127,14 @@ type CookContextProposalResult = {
|
|
|
128
127
|
};
|
|
129
128
|
|
|
130
129
|
type ActiveWorkflowProposalAssessment = {
|
|
131
|
-
action: "continue" | "refocus" | "
|
|
130
|
+
action: "continue" | "refocus" | "blocked";
|
|
132
131
|
currentMissionAnchor: string;
|
|
133
132
|
proposal?: ContextProposal;
|
|
134
133
|
blockedFailureMessage?: string;
|
|
135
134
|
reason:
|
|
136
135
|
| "matching_mission"
|
|
137
|
-
| "
|
|
138
|
-
| "
|
|
139
|
-
| "ambiguous_discussion"
|
|
136
|
+
| "missing_explicit_handoff"
|
|
137
|
+
| "fresh_explicit_handoff"
|
|
140
138
|
| "fresh_explicit_handoff_not_startable";
|
|
141
139
|
};
|
|
142
140
|
|
|
@@ -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 new-workflow startup now requires a fresh valid explicit primary-agent handoff from recent ordinary-chat discussion; recent discussion alone no longer starts a workflow. Ask the primary agent to hand off explicitly 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,27 @@ async function promptContextProposalConfirmationAction(
|
|
|
374
372
|
});
|
|
375
373
|
}
|
|
376
374
|
|
|
375
|
+
async function deriveCookStartupProposal(
|
|
376
|
+
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
377
|
+
projectName: string,
|
|
378
|
+
): Promise<CookContextProposalResult> {
|
|
379
|
+
const recentMessages = collectRecentSessionMessages(ctx, { isRecord, asString, asNumber, isStaleContextError });
|
|
380
|
+
const explicitHandoff = assessLatestCookHandoffProposal(recentMessages, projectName, {
|
|
381
|
+
asString,
|
|
382
|
+
asStringArray,
|
|
383
|
+
assessMissionAnchor,
|
|
384
|
+
normalizeMissionAnchorText,
|
|
385
|
+
isWeakMissionAnchor,
|
|
386
|
+
missionAnchorsStrictlyEquivalent,
|
|
387
|
+
stripCodeBlocks,
|
|
388
|
+
});
|
|
389
|
+
if (explicitHandoff.status === "startable") return { proposal: explicitHandoff.proposal };
|
|
390
|
+
if (explicitHandoff.status === "fresh_but_not_startable") {
|
|
391
|
+
return { blockedFailureMessage: explicitHandoff.message };
|
|
392
|
+
}
|
|
393
|
+
return {};
|
|
394
|
+
}
|
|
395
|
+
|
|
377
396
|
async function deriveCookContextProposal(
|
|
378
397
|
ctx: { cwd: string; hasUI: boolean; ui: any; sessionManager: any; model?: any; modelRegistry?: any },
|
|
379
398
|
projectName: string,
|
|
@@ -396,19 +415,8 @@ async function deriveCookContextProposal(
|
|
|
396
415
|
`verification summary: ${asString(snapshot.verificationEvidence?.summary) ?? "(none)"}`,
|
|
397
416
|
]
|
|
398
417
|
: [];
|
|
399
|
-
const explicitHandoff =
|
|
400
|
-
|
|
401
|
-
asStringArray,
|
|
402
|
-
assessMissionAnchor,
|
|
403
|
-
normalizeMissionAnchorText,
|
|
404
|
-
isWeakMissionAnchor,
|
|
405
|
-
missionAnchorsStrictlyEquivalent,
|
|
406
|
-
stripCodeBlocks,
|
|
407
|
-
});
|
|
408
|
-
if (explicitHandoff.status === "startable") return { proposal: explicitHandoff.proposal };
|
|
409
|
-
if (explicitHandoff.status === "fresh_but_not_startable") {
|
|
410
|
-
return { blockedFailureMessage: explicitHandoff.message };
|
|
411
|
-
}
|
|
418
|
+
const explicitHandoff = await deriveCookStartupProposal(ctx, projectName);
|
|
419
|
+
if (explicitHandoff.proposal || explicitHandoff.blockedFailureMessage) return explicitHandoff;
|
|
412
420
|
return {
|
|
413
421
|
proposal: await deriveCookContextProposalFromRecentDiscussion(projectName, recentEntries, {
|
|
414
422
|
asString,
|
|
@@ -921,7 +929,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
921
929
|
structuredDiscussionFailureDetail: COOK_STRUCTURED_DISCUSSION_FAILURE_DETAIL,
|
|
922
930
|
mainChatRerunGuidance: COOK_MAIN_CHAT_RERUN_GUIDANCE,
|
|
923
931
|
cookCommandSpec: {
|
|
924
|
-
description: "/cook workflow:
|
|
932
|
+
description: "/cook workflow: start a new workflow or next round only from a fresh recent explicit primary-agent handoff, resume the current workflow from canonical state, or confirm an explicit replacement from the explicit /cook command",
|
|
925
933
|
},
|
|
926
934
|
buildContextProposalContinuationReason,
|
|
927
935
|
completionKickoff,
|
|
@@ -934,6 +942,7 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
934
942
|
completionTestWorkflowMissionOverride,
|
|
935
943
|
confirmContextProposal,
|
|
936
944
|
deriveCookContextProposal,
|
|
945
|
+
deriveCookStartupProposal,
|
|
937
946
|
emitCommandText,
|
|
938
947
|
finalizeContextProposalAnalysis,
|
|
939
948
|
getCtxCwd,
|
|
@@ -948,7 +957,6 @@ export default function completionExtension(pi: ExtensionAPI) {
|
|
|
948
957
|
scaffoldCompletionFiles,
|
|
949
958
|
shouldSkipDriverKickoffForTests,
|
|
950
959
|
shouldTestAutoContinueOnSessionStart,
|
|
951
|
-
shouldTreatBareActiveWorkflowProposalAsClearRefocus,
|
|
952
960
|
};
|
|
953
961
|
|
|
954
962
|
|
|
@@ -27,15 +27,17 @@ export type AdvisoryStartupBrief = {
|
|
|
27
27
|
export function buildCookHandoffBoundaryReminder(): string {
|
|
28
28
|
return [
|
|
29
29
|
"You are still in ordinary main chat before any explicit /cook workflow entry.",
|
|
30
|
-
"Use ordinary chat to clarify requirements, discuss tradeoffs,
|
|
30
|
+
"Use ordinary chat to clarify requirements, discuss tradeoffs, propose implementation approaches, and refine scope with the user.",
|
|
31
31
|
"/cook is the only explicit entrypoint into long-running completion workflow.",
|
|
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 —
|
|
33
|
-
"
|
|
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 — do not begin long-running product implementation in ordinary chat and do not edit tracked product files for that workflow-level task.",
|
|
33
|
+
"Instead, recommend /cook as the workflow boundary while keeping the conversation in ordinary chat until the user explicitly runs /cook.",
|
|
34
|
+
"If the user keeps asking follow-up questions or refining requirements before /cook, continue that ordinary-chat discussion normally instead of switching into a handoff-only refusal mode, but do not act as though /cook had already been invoked.",
|
|
34
35
|
"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
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
36
|
+
"If the task is workflow-worthy but that first slice is still vague, say that /cook will be the right next step once the first bounded slice is concrete enough, then keep refining in ordinary chat without emitting an implementation-ready capsule yet.",
|
|
37
|
+
"When handing off, explain that /cook can start a new workflow or next round only from a fresh valid explicit primary-agent handoff capsule from recent ordinary-chat discussion; otherwise it fails closed, while already-active workflows resume from canonical .agent state unless a fresh valid explicit handoff proposes replacement.",
|
|
38
|
+
"Once the task is implementation-ready, 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
39
|
"Use handoff_kind implementation_workflow_handoff for that implementation-ready capsule.",
|
|
40
|
+
"If later ordinary-chat discussion materially changes the startup brief before /cook runs, update or replace the capsule in a later assistant reply instead of pretending the workflow already started.",
|
|
39
41
|
"The capsule is startup intake for /cook only: do not present it as canonical .agent state, an active slice, or a persistent repo contract.",
|
|
40
42
|
"If the task is still ordinary Q&A, lightweight brainstorming, or a tiny one-off fix, continue normally without forcing /cook.",
|
|
41
43
|
].join(" ");
|
|
@@ -1246,7 +1246,6 @@ export function extractContextProposalFromStructuredSession(
|
|
|
1246
1246
|
|
|
1247
1247
|
const COOK_HANDOFF_BLOCK_REGEX = /```cook_handoff\s*([\s\S]*?)```/giu;
|
|
1248
1248
|
const COOK_HANDOFF_MAX_AGE_MS = 45 * 60 * 1000;
|
|
1249
|
-
const COOK_HANDOFF_MAX_LATER_NON_COMMAND_MESSAGES = 2;
|
|
1250
1249
|
const COOK_HANDOFF_NEGATIVE_MISSION_REGEX =
|
|
1251
1250
|
/(?:\b(?:do not|don't|dont|not|never|avoid|skip|refuse|recognize that|suppress|ignore|block|prevent)\b|(?:不要|別|别|勿|禁止|避免|忽略|阻止))/iu;
|
|
1252
1251
|
const COOK_HANDOFF_WORKFLOW_ONLY_ACCEPTANCE_REGEX =
|
|
@@ -1389,19 +1388,6 @@ function isStartableCookHandoffCapsule(
|
|
|
1389
1388
|
return cookHandoffStartabilityFailures(capsule, deps).length === 0;
|
|
1390
1389
|
}
|
|
1391
1390
|
|
|
1392
|
-
function laterMessagesInvalidateCookHandoff(
|
|
1393
|
-
laterMessages: RecentSessionMessage[],
|
|
1394
|
-
deps: Pick<ProposalParseDeps, "stripCodeBlocks">,
|
|
1395
|
-
): boolean {
|
|
1396
|
-
const laterNonCommandMessages = laterMessages.filter((entry) => !entry.isCommand);
|
|
1397
|
-
if (laterNonCommandMessages.length > COOK_HANDOFF_MAX_LATER_NON_COMMAND_MESSAGES) return true;
|
|
1398
|
-
return laterNonCommandMessages.some((entry) => {
|
|
1399
|
-
if (entry.role === "summary") return false;
|
|
1400
|
-
if (!hasRecentDiscussionImplementationIntent(entry.text, deps.stripCodeBlocks)) return false;
|
|
1401
|
-
return true;
|
|
1402
|
-
});
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
1391
|
function cookHandoffIsFreshEnough(capsule: CookHandoffCapsule, laterMessages: RecentSessionMessage[]): boolean {
|
|
1406
1392
|
const capturedAtMs = Date.parse(capsule.captured_at);
|
|
1407
1393
|
if (!Number.isFinite(capturedAtMs)) return false;
|
|
@@ -1484,7 +1470,6 @@ export function assessLatestCookHandoffProposal(
|
|
|
1484
1470
|
const capsule = capsules[capsuleIndex];
|
|
1485
1471
|
const laterMessages = recentMessages.slice(0, index);
|
|
1486
1472
|
if (!cookHandoffIsFreshEnough(capsule, laterMessages)) continue;
|
|
1487
|
-
if (laterMessagesInvalidateCookHandoff(laterMessages, deps)) continue;
|
|
1488
1473
|
const failures = cookHandoffStartabilityFailures(capsule, deps);
|
|
1489
1474
|
if (failures.length > 0) {
|
|
1490
1475
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linimin/pi-letscook",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.59",
|
|
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,
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
"subagent"
|
|
23
23
|
],
|
|
24
24
|
"files": [
|
|
25
|
+
".agent/README.md",
|
|
26
|
+
".agent/mission.md",
|
|
27
|
+
".agent/profile.json",
|
|
28
|
+
".agent/verify_completion_stop.sh",
|
|
29
|
+
".agent/verify_completion_control_plane.sh",
|
|
25
30
|
"extensions",
|
|
26
31
|
"skills",
|
|
27
32
|
"agents",
|
|
@@ -47,6 +47,49 @@ with session_path.open('w', encoding='utf-8') as fh:
|
|
|
47
47
|
PY
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
write_session_messages() {
|
|
51
|
+
local session_path="$1"
|
|
52
|
+
local cwd="$2"
|
|
53
|
+
local messages_json="$3"
|
|
54
|
+
python3 - "$session_path" "$cwd" "$messages_json" <<'PY'
|
|
55
|
+
import json
|
|
56
|
+
import sys
|
|
57
|
+
from pathlib import Path
|
|
58
|
+
|
|
59
|
+
session_path = Path(sys.argv[1])
|
|
60
|
+
cwd = sys.argv[2]
|
|
61
|
+
messages = json.loads(sys.argv[3])
|
|
62
|
+
session_path.parent.mkdir(parents=True, exist_ok=True)
|
|
63
|
+
entries = [
|
|
64
|
+
{
|
|
65
|
+
"type": "session",
|
|
66
|
+
"version": 3,
|
|
67
|
+
"id": "11111111-1111-4111-8111-111111111111",
|
|
68
|
+
"timestamp": "2026-01-01T00:00:00.000Z",
|
|
69
|
+
"cwd": cwd,
|
|
70
|
+
},
|
|
71
|
+
]
|
|
72
|
+
parent_id = None
|
|
73
|
+
for index, message in enumerate(messages, start=1):
|
|
74
|
+
entry_id = f"m{index:04d}"
|
|
75
|
+
entries.append({
|
|
76
|
+
"type": "message",
|
|
77
|
+
"id": entry_id,
|
|
78
|
+
"parentId": parent_id,
|
|
79
|
+
"timestamp": f"2026-01-01T00:00:{index:02d}.000Z",
|
|
80
|
+
"message": {
|
|
81
|
+
"role": message["role"],
|
|
82
|
+
"content": message["content"],
|
|
83
|
+
"timestamp": 1767225600000 + index * 1000,
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
parent_id = entry_id
|
|
87
|
+
with session_path.open('w', encoding='utf-8') as fh:
|
|
88
|
+
for entry in entries:
|
|
89
|
+
fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
90
|
+
PY
|
|
91
|
+
}
|
|
92
|
+
|
|
50
93
|
cd "$PKG_ROOT"
|
|
51
94
|
|
|
52
95
|
node <<'NODE'
|
|
@@ -94,11 +137,59 @@ NODE
|
|
|
94
137
|
ROOT="$TMPDIR/repo"
|
|
95
138
|
PROMPT="$TMPDIR/resume-prompt.txt"
|
|
96
139
|
BOOTSTRAP_SESSION="$TMPDIR/session-active-slice-bootstrap.jsonl"
|
|
97
|
-
|
|
140
|
+
BOOTSTRAP_MESSAGES="$(python3 - <<'PY'
|
|
141
|
+
import json
|
|
142
|
+
capsule = {
|
|
143
|
+
"kind": "cook_handoff",
|
|
144
|
+
"source": "primary_agent",
|
|
145
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
146
|
+
"source_turn_id": "m0002",
|
|
147
|
+
"mission": "Exercise active-slice contract parity.",
|
|
148
|
+
"scope": [
|
|
149
|
+
"Bootstrap canonical completion files for the active-slice contract fixture.",
|
|
150
|
+
"Keep the fixture on the shipped explicit-handoff startup path."
|
|
151
|
+
],
|
|
152
|
+
"constraints": [
|
|
153
|
+
"Use supported bare /cook startup only."
|
|
154
|
+
],
|
|
155
|
+
"acceptance": [
|
|
156
|
+
"Materialize .agent/profile.json, .agent/state.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json before the fixture rewrites them.",
|
|
157
|
+
"Keep scripts/active-slice-contract-test.sh aligned with the packaged startup contract."
|
|
158
|
+
],
|
|
159
|
+
"risks": [
|
|
160
|
+
"Active-slice fixture bootstrap must stay anchored to the fresh explicit handoff."
|
|
161
|
+
],
|
|
162
|
+
"notes": [
|
|
163
|
+
"This handoff exists only to scaffold canonical files before the fixture rewrites them for contract parity coverage."
|
|
164
|
+
],
|
|
165
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
166
|
+
"first_slice_goal": "Scaffold active-slice contract fixture files before rewriting them for parity verification.",
|
|
167
|
+
"first_slice_non_goals": [
|
|
168
|
+
"Do not broaden the fixture beyond active-slice contract surfaces."
|
|
169
|
+
],
|
|
170
|
+
"implementation_surfaces": [
|
|
171
|
+
".agent/active-slice.json",
|
|
172
|
+
"scripts/active-slice-contract-test.sh"
|
|
173
|
+
],
|
|
174
|
+
"verification_commands": [
|
|
175
|
+
"bash scripts/active-slice-contract-test.sh"
|
|
176
|
+
],
|
|
177
|
+
"why_this_slice_first": "The active-slice fixture cannot validate parity until canonical files exist.",
|
|
178
|
+
"task_type": "completion-workflow",
|
|
179
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
180
|
+
"why_cook_now": "The fixture bootstrap is concrete enough to scaffold canonical control-plane files."
|
|
181
|
+
}
|
|
182
|
+
messages = [
|
|
183
|
+
{"role": "user", "content": "Prepare the active-slice contract bootstrap fixture and tell me when it is ready for /cook."},
|
|
184
|
+
{"role": "assistant", "content": "The active-slice contract bootstrap fixture is ready for /cook. Run /cook to confirm it.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
185
|
+
]
|
|
186
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
187
|
+
PY
|
|
188
|
+
)"
|
|
98
189
|
mkdir -p "$ROOT"
|
|
99
190
|
cd "$ROOT"
|
|
100
191
|
git init -q
|
|
101
|
-
|
|
192
|
+
write_session_messages "$BOOTSTRAP_SESSION" "$ROOT" "$BOOTSTRAP_MESSAGES"
|
|
102
193
|
|
|
103
194
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
104
195
|
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
@@ -47,6 +47,49 @@ with session_path.open('w', encoding='utf-8') as fh:
|
|
|
47
47
|
PY
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
write_session_messages() {
|
|
51
|
+
local session_path="$1"
|
|
52
|
+
local cwd="$2"
|
|
53
|
+
local messages_json="$3"
|
|
54
|
+
python3 - "$session_path" "$cwd" "$messages_json" <<'PY'
|
|
55
|
+
import json
|
|
56
|
+
import sys
|
|
57
|
+
from pathlib import Path
|
|
58
|
+
|
|
59
|
+
session_path = Path(sys.argv[1])
|
|
60
|
+
cwd = sys.argv[2]
|
|
61
|
+
messages = json.loads(sys.argv[3])
|
|
62
|
+
session_path.parent.mkdir(parents=True, exist_ok=True)
|
|
63
|
+
entries = [
|
|
64
|
+
{
|
|
65
|
+
"type": "session",
|
|
66
|
+
"version": 3,
|
|
67
|
+
"id": "11111111-1111-4111-8111-111111111111",
|
|
68
|
+
"timestamp": "2026-01-01T00:00:00.000Z",
|
|
69
|
+
"cwd": cwd,
|
|
70
|
+
},
|
|
71
|
+
]
|
|
72
|
+
parent_id = None
|
|
73
|
+
for index, message in enumerate(messages, start=1):
|
|
74
|
+
entry_id = f"m{index:04d}"
|
|
75
|
+
entries.append({
|
|
76
|
+
"type": "message",
|
|
77
|
+
"id": entry_id,
|
|
78
|
+
"parentId": parent_id,
|
|
79
|
+
"timestamp": f"2026-01-01T00:00:{index:02d}.000Z",
|
|
80
|
+
"message": {
|
|
81
|
+
"role": message["role"],
|
|
82
|
+
"content": message["content"],
|
|
83
|
+
"timestamp": 1767225600000 + index * 1000,
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
parent_id = entry_id
|
|
87
|
+
with session_path.open('w', encoding='utf-8') as fh:
|
|
88
|
+
for entry in entries:
|
|
89
|
+
fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
90
|
+
PY
|
|
91
|
+
}
|
|
92
|
+
|
|
50
93
|
cleanup() {
|
|
51
94
|
if [[ -n "$CURRENT_EVIDENCE_BACKUP" && -f "$CURRENT_EVIDENCE_BACKUP" ]]; then
|
|
52
95
|
cp "$CURRENT_EVIDENCE_BACKUP" "$PKG_ROOT/.agent/verification-evidence.json"
|
|
@@ -139,11 +182,59 @@ bash .agent/verify_completion_control_plane.sh >/dev/null
|
|
|
139
182
|
ROOT="$TMPDIR/repo"
|
|
140
183
|
SYSTEM_REMINDER="$TMPDIR/system-reminder.txt"
|
|
141
184
|
BOOTSTRAP_SESSION="$TMPDIR/session-canonical-evidence-bootstrap.jsonl"
|
|
142
|
-
|
|
185
|
+
BOOTSTRAP_MESSAGES="$(python3 - <<'PY'
|
|
186
|
+
import json
|
|
187
|
+
capsule = {
|
|
188
|
+
"kind": "cook_handoff",
|
|
189
|
+
"source": "primary_agent",
|
|
190
|
+
"captured_at": "2026-01-01T00:00:02.000Z",
|
|
191
|
+
"source_turn_id": "m0002",
|
|
192
|
+
"mission": "Exercise canonical evidence fixture bootstrap.",
|
|
193
|
+
"scope": [
|
|
194
|
+
"Materialize canonical completion files for the evidence artifact fixture.",
|
|
195
|
+
"Keep the verification-evidence bootstrap on the supported explicit-handoff startup path."
|
|
196
|
+
],
|
|
197
|
+
"constraints": [
|
|
198
|
+
"Use supported bare /cook startup only."
|
|
199
|
+
],
|
|
200
|
+
"acceptance": [
|
|
201
|
+
"Scaffold .agent/profile.json, .agent/state.json, .agent/plan.json, .agent/active-slice.json, and .agent/verification-evidence.json before the fixture rewrites them.",
|
|
202
|
+
"Keep scripts/canonical-evidence-artifact-test.sh aligned with packaged bootstrap behavior."
|
|
203
|
+
],
|
|
204
|
+
"risks": [
|
|
205
|
+
"Evidence-artifact bootstrap must stay anchored to the fresh explicit handoff."
|
|
206
|
+
],
|
|
207
|
+
"notes": [
|
|
208
|
+
"This fixture exists only to scaffold canonical files before rewriting them for evidence parity coverage."
|
|
209
|
+
],
|
|
210
|
+
"handoff_kind": "implementation_workflow_handoff",
|
|
211
|
+
"first_slice_goal": "Scaffold canonical evidence-artifact fixture files before rewriting them for parity checks.",
|
|
212
|
+
"first_slice_non_goals": [
|
|
213
|
+
"Do not broaden the bootstrap fixture beyond the evidence-artifact surfaces."
|
|
214
|
+
],
|
|
215
|
+
"implementation_surfaces": [
|
|
216
|
+
".agent/verification-evidence.json",
|
|
217
|
+
"scripts/canonical-evidence-artifact-test.sh"
|
|
218
|
+
],
|
|
219
|
+
"verification_commands": [
|
|
220
|
+
"bash ./scripts/canonical-evidence-artifact-test.sh"
|
|
221
|
+
],
|
|
222
|
+
"why_this_slice_first": "The evidence-artifact fixture cannot validate fail-closed parity until canonical files exist.",
|
|
223
|
+
"task_type": "completion-workflow",
|
|
224
|
+
"evaluation_profile": "completion-rubric-v1",
|
|
225
|
+
"why_cook_now": "The fixture bootstrap is concrete enough to create canonical control-plane files."
|
|
226
|
+
}
|
|
227
|
+
messages = [
|
|
228
|
+
{"role": "user", "content": "Prepare the canonical evidence bootstrap fixture and tell me when it is ready for /cook."},
|
|
229
|
+
{"role": "assistant", "content": "The canonical evidence bootstrap fixture is ready for /cook. Run /cook to confirm it.\n\n```cook_handoff\n" + json.dumps(capsule, ensure_ascii=False, indent=2) + "\n```"},
|
|
230
|
+
]
|
|
231
|
+
print(json.dumps(messages, ensure_ascii=False))
|
|
232
|
+
PY
|
|
233
|
+
)"
|
|
143
234
|
mkdir -p "$ROOT"
|
|
144
235
|
cd "$ROOT"
|
|
145
236
|
git init -q
|
|
146
|
-
|
|
237
|
+
write_session_messages "$BOOTSTRAP_SESSION" "$ROOT" "$BOOTSTRAP_MESSAGES"
|
|
147
238
|
|
|
148
239
|
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
149
240
|
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|