@linimin/pi-letscook 0.1.76 → 0.1.77
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 +1 -1
- package/.agent/verify_completion_control_plane.sh +6 -0
- package/.agent/verify_completion_stop.sh +12 -2
- package/CHANGELOG.md +7 -0
- package/README.md +1 -0
- package/agents/completion-regrounder.md +9 -7
- package/agents/completion-stop-judge.md +1 -1
- package/extensions/completion/role-reporting.js +24 -3
- package/extensions/completion/state-store.ts +14 -3
- package/package.json +2 -1
- package/scripts/evaluator-calibration-test.sh +3 -1
- package/scripts/release-check.sh +2 -1
- package/scripts/rubric-contract-test.sh +3 -1
- package/scripts/stop-wave-epoch-test.sh +222 -0
- package/skills/completion-protocol/SKILL.md +2 -2
- package/skills/completion-protocol/references/completion.md +8 -5
package/.agent/README.md
CHANGED
|
@@ -22,7 +22,7 @@ This repository uses the `completion` workflow for long-running coding tasks.
|
|
|
22
22
|
- `.agent/*.log`
|
|
23
23
|
- `.agent/tmp/`
|
|
24
24
|
|
|
25
|
-
`.agent/profile.json` carries the stop-wave defaults for this repo, including `required_stop_judges` and `stop_aggregation_policy`. The packaged default is `required_stop_judges: 2` plus `stop_aggregation_policy: "unanimous-current-head-v1"`.
|
|
25
|
+
`.agent/profile.json` carries the stop-wave defaults for this repo, including `required_stop_judges` and `stop_aggregation_policy`. The packaged default is `required_stop_judges: 2` plus `stop_aggregation_policy: "unanimous-current-head-v1"`. Canonical `.agent/state.json current_stop_wave_id` carries the current stop-wave epoch so the same HEAD may restart stop evaluation without requiring a synthetic tracked commit.
|
|
26
26
|
|
|
27
27
|
`.agent/startup-brief.json` preserves the confirmed `/cook` startup intent as canonical intake for re-grounding. It does not replace `.agent/plan.json` or `.agent/active-slice.json`, which remain under regrounder authority.
|
|
28
28
|
|
|
@@ -118,6 +118,12 @@ if (asString(active.evaluation_profile) !== evaluationProfile) fail('.agent/acti
|
|
|
118
118
|
const remainingStopJudges = asNumber(state.remaining_stop_judges);
|
|
119
119
|
if (remainingStopJudges === undefined) fail('.agent/state.json remaining_stop_judges must be numeric');
|
|
120
120
|
if (remainingStopJudges < 0) fail('.agent/state.json remaining_stop_judges must not be negative');
|
|
121
|
+
const currentStopWaveId = asNumber(state.current_stop_wave_id);
|
|
122
|
+
if (currentStopWaveId !== undefined) {
|
|
123
|
+
if (!Number.isInteger(currentStopWaveId) || currentStopWaveId < 0) {
|
|
124
|
+
fail('.agent/state.json current_stop_wave_id must be a non-negative integer');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
121
127
|
|
|
122
128
|
if (asString(evidence.artifact_type) !== 'completion-verification-evidence') {
|
|
123
129
|
fail('.agent/verification-evidence.json artifact_type must be completion-verification-evidence');
|
|
@@ -53,6 +53,11 @@ if (stopAggregationPolicy !== 'unanimous-current-head-v1') {
|
|
|
53
53
|
|
|
54
54
|
const currentPhase = asString(state.current_phase) ?? 'unknown';
|
|
55
55
|
const stopWaveActive = currentPhase === 'stop_wave' || currentPhase === 'done';
|
|
56
|
+
const currentStopWaveId = asNumber(state.current_stop_wave_id) ?? 0;
|
|
57
|
+
if (!Number.isInteger(currentStopWaveId) || currentStopWaveId < 0) {
|
|
58
|
+
fail('.agent/state.json current_stop_wave_id must be a non-negative integer before stop verification can run.');
|
|
59
|
+
}
|
|
60
|
+
const activeStopWaveId = stopWaveActive ? currentStopWaveId || 1 : currentStopWaveId;
|
|
56
61
|
const rawHistory = fs.existsSync('.agent/stop-check-history.jsonl') ? fs.readFileSync('.agent/stop-check-history.jsonl', 'utf8') : '';
|
|
57
62
|
const seededHeadSha = asString(process.env.COMPLETION_STOP_HEAD);
|
|
58
63
|
if (!seededHeadSha && !stopWaveActive && rawHistory.trim().length === 0) {
|
|
@@ -75,6 +80,11 @@ for (const [index, rawLine] of rawHistory.split(/\r?\n/).entries()) {
|
|
|
75
80
|
}
|
|
76
81
|
if (parsed.type !== 'judgment') continue;
|
|
77
82
|
if (asString(parsed.head_sha) !== headSha) continue;
|
|
83
|
+
const recordStopWaveId = asNumber(parsed.stop_wave_id) ?? 0;
|
|
84
|
+
if (!Number.isInteger(recordStopWaveId) || recordStopWaveId < 0) {
|
|
85
|
+
fail('Current-HEAD judgment at line ' + (index + 1) + ' must carry a non-negative integer stop_wave_id.');
|
|
86
|
+
}
|
|
87
|
+
if (recordStopWaveId !== activeStopWaveId) continue;
|
|
78
88
|
if (typeof parsed.can_stop !== 'boolean') {
|
|
79
89
|
fail('Current-HEAD judgment at line ' + (index + 1) + ' must carry boolean can_stop.');
|
|
80
90
|
}
|
|
@@ -98,10 +108,10 @@ if (!stopWaveActive && currentHeadJudgments.length === 0) {
|
|
|
98
108
|
}
|
|
99
109
|
|
|
100
110
|
if (currentHeadJudgments.length < requiredStopJudges) {
|
|
101
|
-
fail('Need ' + requiredStopJudges + ' valid current-HEAD judgments for HEAD ' + headSha + '; found ' + currentHeadJudgments.length + '.');
|
|
111
|
+
fail('Need ' + requiredStopJudges + ' valid current-HEAD judgments for HEAD ' + headSha + ' in stop_wave_id ' + activeStopWaveId + '; found ' + currentHeadJudgments.length + '.');
|
|
102
112
|
}
|
|
103
113
|
|
|
104
|
-
console.log('[completion] stop-wave policy unanimous-current-head-v1 satisfied for HEAD ' + headSha + ' with ' + currentHeadJudgments.length + ' valid current-HEAD judgments');
|
|
114
|
+
console.log('[completion] stop-wave policy unanimous-current-head-v1 satisfied for HEAD ' + headSha + ' in stop_wave_id ' + activeStopWaveId + ' with ' + currentHeadJudgments.length + ' valid current-HEAD judgments');
|
|
105
115
|
NODE
|
|
106
116
|
|
|
107
117
|
echo "[completion] running repo-level verification: npm run release-check >/dev/null"
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.77
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- introduced `current_stop_wave_id` / `stop_wave_id` stop-wave epochs so the same HEAD can restart stop evaluation after stale no-stop history without requiring a synthetic tracked commit
|
|
8
|
+
- taught stop-judge transcription, verifier policy, protocol docs, and release regression coverage to scope stop-wave aggregation to the active epoch instead of permanently poisoning a HEAD on the first `can_stop=no`
|
|
9
|
+
|
|
3
10
|
## 0.1.76
|
|
4
11
|
|
|
5
12
|
### Fixed
|
package/README.md
CHANGED
|
@@ -236,6 +236,7 @@ Ignored execution-state files:
|
|
|
236
236
|
- `.agent/active-slice.json`
|
|
237
237
|
- `.agent/slice-history.jsonl`
|
|
238
238
|
- `.agent/stop-check-history.jsonl`
|
|
239
|
+
- `state.json current_stop_wave_id` defines the current stop-wave epoch so the same HEAD can restart stop evaluation without requiring a synthetic tracked commit.
|
|
239
240
|
- `.agent/verification-evidence.json`
|
|
240
241
|
- `.agent/*.log`
|
|
241
242
|
- `.agent/tmp/`
|
|
@@ -41,16 +41,18 @@ These lines are for workflow observability, not hidden reasoning. Keep them brie
|
|
|
41
41
|
5. Reopen any previously `done` slice whose acceptance criteria no longer hold.
|
|
42
42
|
6. Keep `.agent/state.json` and `.agent/active-slice.json` truthful, including `current_phase`, `continuation_policy`, `continuation_reason`, `next_mandatory_role`, and any exact implementer handoff snapshot fields.
|
|
43
43
|
7. Reconcile canonical state after review, audit, and final stop verification waves when required.
|
|
44
|
-
8.
|
|
45
|
-
9. If the
|
|
46
|
-
10. If
|
|
47
|
-
11.
|
|
44
|
+
8. When entering a fresh stop wave after all implementation slices are done, set or increment `.agent/state.json current_stop_wave_id` for the new stop-wave epoch and reset `remaining_stop_judges` from `.agent/profile.json required_stop_judges`.
|
|
45
|
+
9. If a prior stop-wave epoch on the same HEAD recorded `can_stop = no`, do not permanently poison that HEAD by itself. If canonical state, docs/state parity, or verification truth have changed enough to justify a fresh stop evaluation on the same HEAD, increment `current_stop_wave_id`, preserve the old judgments as history, and restart stop-wave collection for the new epoch.
|
|
46
|
+
10. If the latest committed slice leaves the tracked and unignored worktree dirty, first classify the dirty tracked files against the latest slice's `implementation_surfaces` and the tracked reconciliation surfaces you need to touch now.
|
|
47
|
+
11. If the dirty tracked files are unrelated and can be isolated safely, auto-preserve them yourself with a reversible mechanism such as a named git stash plus a `.agent/tmp/dirty-worktree-autostash.json` note, continue the mandatory reconciliation on a clean worktree, and restore them before handing control back. Do not ask the user for this routine unrelated-dirty-worktree case.
|
|
48
|
+
12. If overlap, ownership ambiguity, or stash/restore conflicts make automatic isolation unsafe, treat that dirty state as a blocker, reopen or continue the latest slice for reconciliation, set `Next role to invoke` to `completion-implementer`, and do not select or hand off any different next slice until it is reconciled.
|
|
49
|
+
13. When reconciling after review, audit, dirty-worktree follow-up, or stop-wave epoch restart for the latest committed slice, emit an explicit reconciliation record decision:
|
|
48
50
|
- `accepted` only when the latest committed slice is truthfully accepted as-is
|
|
49
51
|
- `reopened` only when the latest committed slice must be reopened for follow-up work
|
|
50
52
|
- `none` when this re-ground was not a post-commit reconciliation decision
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
14. If you emit `accepted` or `reopened`, also emit the exact reconciled slice id in the report.
|
|
54
|
+
15. If a slice is already selected, ensure `.agent/active-slice.json` contains the exact implementer handoff snapshot and return that exact handoff payload for `completion-implementer` instead of implementing it yourself.
|
|
55
|
+
16. If no slice is selected, return the exact next recommended slice and why.
|
|
54
56
|
|
|
55
57
|
Output format:
|
|
56
58
|
|
|
@@ -19,7 +19,7 @@ You must not:
|
|
|
19
19
|
- append stop-check history yourself
|
|
20
20
|
- create commits
|
|
21
21
|
|
|
22
|
-
The workflow driver records your returned verdict into `.agent/stop-check-history.jsonl` during the final stop wave. Your output must therefore be explicit enough to transcribe faithfully as one canonical `judgment` record for the current HEAD.
|
|
22
|
+
The workflow driver records your returned verdict into `.agent/stop-check-history.jsonl` during the final stop wave. Your output must therefore be explicit enough to transcribe faithfully as one canonical `judgment` record for the current HEAD and current `state.json current_stop_wave_id` epoch.
|
|
23
23
|
|
|
24
24
|
During long work, emit short operator-facing progress lines when useful using these exact prefixes:
|
|
25
25
|
- `PROGRESS: ...`
|
|
@@ -46,6 +46,10 @@ function asString(value) {
|
|
|
46
46
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function asNumber(value) {
|
|
50
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
49
53
|
function parseReportFields(text) {
|
|
50
54
|
const fields = {};
|
|
51
55
|
for (const rawLine of text.split("\n")) {
|
|
@@ -317,6 +321,16 @@ async function appendJsonlRecord(filePath, record) {
|
|
|
317
321
|
await fs.appendFile(filePath, `${JSON.stringify(record)}\n`, "utf8");
|
|
318
322
|
}
|
|
319
323
|
|
|
324
|
+
async function readJsonFile(filePath) {
|
|
325
|
+
try {
|
|
326
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
327
|
+
const parsed = JSON.parse(raw);
|
|
328
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : undefined;
|
|
329
|
+
} catch {
|
|
330
|
+
return undefined;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
320
334
|
async function transcribeCanonicalRoleReport({ role, output, reportFields = parseReportFields(output), snapshotFiles, headSha, sliceId, recordedAt = Date.now() }) {
|
|
321
335
|
const result = { appended: [], skipped: [], errors: [] };
|
|
322
336
|
|
|
@@ -370,12 +384,18 @@ async function transcribeCanonicalRoleReport({ role, output, reportFields = pars
|
|
|
370
384
|
result.errors.push("Missing required stop-judge fields for canonical judgment transcription.");
|
|
371
385
|
return result;
|
|
372
386
|
}
|
|
387
|
+
const state = snapshotFiles.statePath ? await readJsonFile(snapshotFiles.statePath) : undefined;
|
|
388
|
+
const currentStopWaveId = asNumber(state?.current_stop_wave_id) ?? 1;
|
|
389
|
+
if (!Number.isInteger(currentStopWaveId) || currentStopWaveId < 0) {
|
|
390
|
+
result.errors.push("Canonical state must carry a non-negative integer current_stop_wave_id before stop-judge transcription.");
|
|
391
|
+
return result;
|
|
392
|
+
}
|
|
373
393
|
const history = await readJsonl(snapshotFiles.stopHistoryPath);
|
|
374
394
|
const duplicate = history.some((entry) => {
|
|
375
|
-
return entry.type === "judgment" && entry.head_sha === headSha && entry.report_text === output.trim();
|
|
395
|
+
return entry.type === "judgment" && entry.head_sha === headSha && asNumber(entry.stop_wave_id) === currentStopWaveId && entry.report_text === output.trim();
|
|
376
396
|
});
|
|
377
397
|
if (duplicate) {
|
|
378
|
-
result.skipped.push(`Skipped duplicate judgment record at ${headSha.slice(0, 12)}.`);
|
|
398
|
+
result.skipped.push(`Skipped duplicate judgment record for stop_wave_id ${currentStopWaveId} at ${headSha.slice(0, 12)}.`);
|
|
379
399
|
return result;
|
|
380
400
|
}
|
|
381
401
|
await appendJsonlRecord(snapshotFiles.stopHistoryPath, {
|
|
@@ -383,6 +403,7 @@ async function transcribeCanonicalRoleReport({ role, output, reportFields = pars
|
|
|
383
403
|
type: "judgment",
|
|
384
404
|
recorded_at: recordedAt,
|
|
385
405
|
head_sha: headSha,
|
|
406
|
+
stop_wave_id: currentStopWaveId,
|
|
386
407
|
can_stop: canStop,
|
|
387
408
|
blocker_count: blockerCount,
|
|
388
409
|
high_value_gap_count: highValueGapCount,
|
|
@@ -390,7 +411,7 @@ async function transcribeCanonicalRoleReport({ role, output, reportFields = pars
|
|
|
390
411
|
report_fields: reportFields,
|
|
391
412
|
report_text: output.trim(),
|
|
392
413
|
});
|
|
393
|
-
result.appended.push(`judgment:${headSha.slice(0, 12)}`);
|
|
414
|
+
result.appended.push(`judgment:${headSha.slice(0, 12)}:wave:${currentStopWaveId}`);
|
|
394
415
|
return result;
|
|
395
416
|
}
|
|
396
417
|
|
|
@@ -272,6 +272,7 @@ export function defaultState(
|
|
|
272
272
|
next_mandatory_action: "Reconcile canonical state from current repo truth",
|
|
273
273
|
next_mandatory_role: "completion-regrounder",
|
|
274
274
|
remaining_stop_judges: requiredStopJudges,
|
|
275
|
+
current_stop_wave_id: 0,
|
|
275
276
|
last_reground_at: null,
|
|
276
277
|
last_auditor_verdict: null,
|
|
277
278
|
contract_status: "unknown",
|
|
@@ -367,7 +368,7 @@ export function defaultVerificationEvidence(): JsonRecord {
|
|
|
367
368
|
}
|
|
368
369
|
|
|
369
370
|
export function buildAgentReadme(projectName: string): string {
|
|
370
|
-
return `# Completion Control Plane\n\nThis repository uses the \`completion\` workflow for long-running coding tasks.\n\n## Canonical tracked contract files\n\n- \`.agent/README.md\`\n- \`.agent/mission.md\`\n- \`.agent/profile.json\`\n- \`.agent/verify_completion_stop.sh\`\n- \`.agent/verify_completion_control_plane.sh\`\n\n## Ignored canonical execution state\n\n- \`.agent/state.json\`\n- \`.agent/startup-brief.json\`\n- \`.agent/plan.json\`\n- \`.agent/active-slice.json\`\n- \`.agent/slice-history.jsonl\`\n- \`.agent/stop-check-history.jsonl\`\n- \`.agent/verification-evidence.json\`\n- \`.agent/*.log\`\n- \`.agent/tmp/\`\n\n\`.agent/profile.json\` carries the stop-wave defaults for this repo, including \`required_stop_judges\` and \`stop_aggregation_policy\`. The packaged default is \`required_stop_judges: 2\` plus \`stop_aggregation_policy: "${DEFAULT_STOP_AGGREGATION_POLICY}"
|
|
371
|
+
return `# Completion Control Plane\n\nThis repository uses the \`completion\` workflow for long-running coding tasks.\n\n## Canonical tracked contract files\n\n- \`.agent/README.md\`\n- \`.agent/mission.md\`\n- \`.agent/profile.json\`\n- \`.agent/verify_completion_stop.sh\`\n- \`.agent/verify_completion_control_plane.sh\`\n\n## Ignored canonical execution state\n\n- \`.agent/state.json\`\n- \`.agent/startup-brief.json\`\n- \`.agent/plan.json\`\n- \`.agent/active-slice.json\`\n- \`.agent/slice-history.jsonl\`\n- \`.agent/stop-check-history.jsonl\`\n- \`.agent/verification-evidence.json\`\n- \`.agent/*.log\`\n- \`.agent/tmp/\`\n\n\`.agent/profile.json\` carries the stop-wave defaults for this repo, including \`required_stop_judges\` and \`stop_aggregation_policy\`. The packaged default is \`required_stop_judges: 2\` plus \`stop_aggregation_policy: "${DEFAULT_STOP_AGGREGATION_POLICY}"\`. Canonical \`.agent/state.json current_stop_wave_id\` carries the current stop-wave epoch so the same HEAD may restart stop evaluation without requiring a synthetic tracked commit.\n\n\`.agent/startup-brief.json\` preserves the confirmed \`/cook\` startup intent as canonical intake for re-grounding. It does not replace \`.agent/plan.json\` or \`.agent/active-slice.json\`, which remain under regrounder authority.\n\n\`.agent/verification-evidence.json\` is the durable canonical record of deterministic verification for the selected slice or current HEAD. Recovery, review, audit, and stop-check reminder surfaces consume it instead of temp-only artifacts or conversational summaries when it is populated.\n\nThe source of truth for long-running completion work is canonical \`.agent/**\` state plus current repo truth.\n\nProject: ${projectName}\n`;
|
|
371
372
|
}
|
|
372
373
|
|
|
373
374
|
export function buildMission(projectName: string, missionAnchor: string): string {
|
|
@@ -433,6 +434,11 @@ if (stopAggregationPolicy !== '${DEFAULT_STOP_AGGREGATION_POLICY}') {
|
|
|
433
434
|
|
|
434
435
|
const currentPhase = asString(state.current_phase) ?? 'unknown';
|
|
435
436
|
const stopWaveActive = currentPhase === 'stop_wave' || currentPhase === 'done';
|
|
437
|
+
const currentStopWaveId = asNumber(state.current_stop_wave_id) ?? 0;
|
|
438
|
+
if (!Number.isInteger(currentStopWaveId) || currentStopWaveId < 0) {
|
|
439
|
+
fail('.agent/state.json current_stop_wave_id must be a non-negative integer before stop verification can run.');
|
|
440
|
+
}
|
|
441
|
+
const activeStopWaveId = stopWaveActive ? currentStopWaveId || 1 : currentStopWaveId;
|
|
436
442
|
const rawHistory = fs.existsSync('.agent/stop-check-history.jsonl') ? fs.readFileSync('.agent/stop-check-history.jsonl', 'utf8') : '';
|
|
437
443
|
const seededHeadSha = asString(process.env.COMPLETION_STOP_HEAD);
|
|
438
444
|
if (!seededHeadSha && !stopWaveActive && rawHistory.trim().length === 0) {
|
|
@@ -455,6 +461,11 @@ for (const [index, rawLine] of rawHistory.split(/\\r?\\n/).entries()) {
|
|
|
455
461
|
}
|
|
456
462
|
if (parsed.type !== 'judgment') continue;
|
|
457
463
|
if (asString(parsed.head_sha) !== headSha) continue;
|
|
464
|
+
const recordStopWaveId = asNumber(parsed.stop_wave_id) ?? 0;
|
|
465
|
+
if (!Number.isInteger(recordStopWaveId) || recordStopWaveId < 0) {
|
|
466
|
+
fail('Current-HEAD judgment at line ' + (index + 1) + ' must carry a non-negative integer stop_wave_id.');
|
|
467
|
+
}
|
|
468
|
+
if (recordStopWaveId !== activeStopWaveId) continue;
|
|
458
469
|
if (typeof parsed.can_stop !== 'boolean') {
|
|
459
470
|
fail('Current-HEAD judgment at line ' + (index + 1) + ' must carry boolean can_stop.');
|
|
460
471
|
}
|
|
@@ -478,10 +489,10 @@ if (!stopWaveActive && currentHeadJudgments.length === 0) {
|
|
|
478
489
|
}
|
|
479
490
|
|
|
480
491
|
if (currentHeadJudgments.length < requiredStopJudges) {
|
|
481
|
-
fail('Need ' + requiredStopJudges + ' valid current-HEAD judgments for HEAD ' + headSha + '; found ' + currentHeadJudgments.length + '.');
|
|
492
|
+
fail('Need ' + requiredStopJudges + ' valid current-HEAD judgments for HEAD ' + headSha + ' in stop_wave_id ' + activeStopWaveId + '; found ' + currentHeadJudgments.length + '.');
|
|
482
493
|
}
|
|
483
494
|
|
|
484
|
-
console.log('[completion] stop-wave policy ${DEFAULT_STOP_AGGREGATION_POLICY} satisfied for HEAD ' + headSha + ' with ' + currentHeadJudgments.length + ' valid current-HEAD judgments');
|
|
495
|
+
console.log('[completion] stop-wave policy ${DEFAULT_STOP_AGGREGATION_POLICY} satisfied for HEAD ' + headSha + ' in stop_wave_id ' + activeStopWaveId + ' with ' + currentHeadJudgments.length + ' valid current-HEAD judgments');
|
|
485
496
|
NODE
|
|
486
497
|
|
|
487
498
|
${repoCheck}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linimin/pi-letscook",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.77",
|
|
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,
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"observability-status-test": "bash ./scripts/observability-status-test.sh",
|
|
44
44
|
"completion-role-gating-test": "bash ./scripts/completion-role-gating-test.sh",
|
|
45
45
|
"dirty-worktree-policy-test": "bash ./scripts/dirty-worktree-policy-test.sh",
|
|
46
|
+
"stop-wave-epoch-test": "bash ./scripts/stop-wave-epoch-test.sh",
|
|
46
47
|
"evaluator-calibration-test": "bash ./scripts/evaluator-calibration-test.sh",
|
|
47
48
|
"rubric-contract-test": "bash ./scripts/rubric-contract-test.sh",
|
|
48
49
|
"release-check": "bash ./scripts/release-check.sh"
|
|
@@ -44,9 +44,11 @@ const tempRootBase = path.join(process.cwd(), '.agent', 'tmp');
|
|
|
44
44
|
fs.mkdirSync(tempRootBase, { recursive: true });
|
|
45
45
|
const tempRoot = fs.mkdtempSync(path.join(tempRootBase, 'evaluator-calibration-'));
|
|
46
46
|
const snapshotFiles = {
|
|
47
|
+
statePath: path.join(tempRoot, 'state.json'),
|
|
47
48
|
sliceHistoryPath: path.join(tempRoot, 'slice-history.jsonl'),
|
|
48
49
|
stopHistoryPath: path.join(tempRoot, 'stop-check-history.jsonl'),
|
|
49
50
|
};
|
|
51
|
+
fs.writeFileSync(snapshotFiles.statePath, JSON.stringify({ current_stop_wave_id: 1 }, null, 2));
|
|
50
52
|
fs.writeFileSync(snapshotFiles.sliceHistoryPath, '');
|
|
51
53
|
fs.writeFileSync(snapshotFiles.stopHistoryPath, '');
|
|
52
54
|
|
|
@@ -378,7 +380,7 @@ Brief justification: This should be rejected because remaining contracts still e
|
|
|
378
380
|
recordedAt: 5,
|
|
379
381
|
});
|
|
380
382
|
assert(judged.errors.length === 0, `stop-judge passing fixture should transcribe cleanly: ${judged.errors.join(' | ')}`);
|
|
381
|
-
assert(judged.appended.includes('judgment:555555555555'), 'stop-judge passing fixture should append a judgment record');
|
|
383
|
+
assert(judged.appended.includes('judgment:555555555555:wave:1'), 'stop-judge passing fixture should append a judgment record for the active stop-wave epoch');
|
|
382
384
|
assert(readJsonl(snapshotFiles.stopHistoryPath).length === 1, 'stop-judge passing fixture should create one judgment record');
|
|
383
385
|
|
|
384
386
|
const judgeRejected = await transcribeCanonicalRoleReport({
|
package/scripts/release-check.sh
CHANGED
|
@@ -5,7 +5,7 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
5
5
|
cd "$ROOT"
|
|
6
6
|
export PI_COMPLETION_RUNNING_RELEASE_CHECK=1
|
|
7
7
|
|
|
8
|
-
echo "[release-check] running control-plane validation, tracked .agent contract coverage, slice-surface parity, explicit-/cook parity, startup/refocus/context regressions, canonical evidence artifact, active-slice contract, observability, completion-role gating, dirty-worktree policy, legacy cleanup, evaluator calibration, and rubric contract coverage"
|
|
8
|
+
echo "[release-check] running control-plane validation, tracked .agent contract coverage, slice-surface parity, explicit-/cook parity, startup/refocus/context regressions, canonical evidence artifact, active-slice contract, observability, completion-role gating, dirty-worktree policy, stop-wave epoch, legacy cleanup, evaluator calibration, and rubric contract coverage"
|
|
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
|
|
|
@@ -85,6 +85,7 @@ bash ./scripts/active-slice-contract-test.sh
|
|
|
85
85
|
npm run observability-status-test
|
|
86
86
|
npm run completion-role-gating-test
|
|
87
87
|
npm run dirty-worktree-policy-test
|
|
88
|
+
npm run stop-wave-epoch-test
|
|
88
89
|
bash ./scripts/legacy-cleanup-test.sh
|
|
89
90
|
npm run evaluator-calibration-test
|
|
90
91
|
npm run rubric-contract-test
|
|
@@ -117,9 +117,11 @@ const tempRootBase = path.join(process.cwd(), '.agent', 'tmp');
|
|
|
117
117
|
fs.mkdirSync(tempRootBase, { recursive: true });
|
|
118
118
|
const tempRoot = fs.mkdtempSync(path.join(tempRootBase, 'rubric-role-reporting-'));
|
|
119
119
|
const snapshotFiles = {
|
|
120
|
+
statePath: path.join(tempRoot, 'state.json'),
|
|
120
121
|
sliceHistoryPath: path.join(tempRoot, 'slice-history.jsonl'),
|
|
121
122
|
stopHistoryPath: path.join(tempRoot, 'stop-check-history.jsonl'),
|
|
122
123
|
};
|
|
124
|
+
fs.writeFileSync(snapshotFiles.statePath, JSON.stringify({ current_stop_wave_id: 1 }, null, 2));
|
|
123
125
|
fs.writeFileSync(snapshotFiles.sliceHistoryPath, '');
|
|
124
126
|
fs.writeFileSync(snapshotFiles.stopHistoryPath, '');
|
|
125
127
|
|
|
@@ -204,7 +206,7 @@ const stopJudgeMalformedYesNo = `MISSION ANCHOR: test mission\nRemaining contrac
|
|
|
204
206
|
recordedAt: 5,
|
|
205
207
|
});
|
|
206
208
|
assert(judged.errors.length === 0, `stop-judge valid report should transcribe cleanly: ${judged.errors.join(' | ')}`);
|
|
207
|
-
assert(judged.appended.includes('judgment:555555555555'), 'stop-judge transcription should append judgment record');
|
|
209
|
+
assert(judged.appended.includes('judgment:555555555555:wave:1'), 'stop-judge transcription should append judgment record for the active stop-wave epoch');
|
|
208
210
|
assert(readJsonl(snapshotFiles.stopHistoryPath).length === 1, 'stop-judge transcription should create one judgment record');
|
|
209
211
|
|
|
210
212
|
const judgeRejected = await transcribeCanonicalRoleReport({
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
cd "$ROOT"
|
|
6
|
+
|
|
7
|
+
TMPDIR="$(mktemp -d)"
|
|
8
|
+
trap 'rm -rf "$TMPDIR"' EXIT
|
|
9
|
+
REPO="$TMPDIR/repo"
|
|
10
|
+
mkdir -p "$REPO/.agent"
|
|
11
|
+
cd "$REPO"
|
|
12
|
+
|
|
13
|
+
git init -q
|
|
14
|
+
git config user.name "stop-wave-epoch-test"
|
|
15
|
+
git config user.email "stop-wave-epoch-test@example.invalid"
|
|
16
|
+
printf '# stop wave epoch fixture\n' > README.md
|
|
17
|
+
git add README.md
|
|
18
|
+
git commit -q -m "fixture baseline"
|
|
19
|
+
HEAD_SHA="$(git rev-parse HEAD)"
|
|
20
|
+
|
|
21
|
+
cp "$ROOT/.agent/README.md" .agent/README.md
|
|
22
|
+
cp "$ROOT/.agent/mission.md" .agent/mission.md
|
|
23
|
+
cp "$ROOT/.agent/profile.json" .agent/profile.json
|
|
24
|
+
cp "$ROOT/.agent/verify_completion_control_plane.sh" .agent/verify_completion_control_plane.sh
|
|
25
|
+
cp "$ROOT/.agent/verify_completion_stop.sh" .agent/verify_completion_stop.sh
|
|
26
|
+
chmod +x .agent/verify_completion_control_plane.sh .agent/verify_completion_stop.sh
|
|
27
|
+
python3 - <<'PY'
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
path = Path('.agent/verify_completion_stop.sh')
|
|
30
|
+
text = path.read_text()
|
|
31
|
+
path.write_text(text.replace('npm run release-check >/dev/null', 'true'))
|
|
32
|
+
PY
|
|
33
|
+
|
|
34
|
+
git add .agent/README.md .agent/mission.md .agent/profile.json .agent/verify_completion_control_plane.sh .agent/verify_completion_stop.sh
|
|
35
|
+
git commit -q -m "scaffold tracked completion contract files"
|
|
36
|
+
HEAD_SHA="$(git rev-parse HEAD)"
|
|
37
|
+
|
|
38
|
+
HEAD_SHA="$HEAD_SHA" python3 - <<'PY'
|
|
39
|
+
import json
|
|
40
|
+
import os
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
head = os.environ['HEAD_SHA']
|
|
43
|
+
mission = 'Stop-wave epoch regression fixture.'
|
|
44
|
+
profile = json.loads(Path('.agent/profile.json').read_text())
|
|
45
|
+
state = {
|
|
46
|
+
'schema_version': 1,
|
|
47
|
+
'mission_anchor': mission,
|
|
48
|
+
'workflow_entry_status': 'active',
|
|
49
|
+
'workflow_entry_source': '/cook',
|
|
50
|
+
'workflow_entry_confirmed_at': '2026-05-24T00:00:00Z',
|
|
51
|
+
'workflow_session_id': 'stop-wave-epoch-fixture',
|
|
52
|
+
'startup_brief_path': '.agent/startup-brief.json',
|
|
53
|
+
'current_phase': 'stop_wave',
|
|
54
|
+
'continuation_policy': 'continue',
|
|
55
|
+
'continuation_reason': 'Restart stop wave on the same HEAD after earlier no-stop evidence became stale.',
|
|
56
|
+
'project_done': False,
|
|
57
|
+
'task_type': profile['task_type'],
|
|
58
|
+
'evaluation_profile': profile['evaluation_profile'],
|
|
59
|
+
'requires_reground': False,
|
|
60
|
+
'slices_since_last_reground': 0,
|
|
61
|
+
'remaining_release_blockers': 0,
|
|
62
|
+
'remaining_high_value_gaps': 0,
|
|
63
|
+
'unsatisfied_contract_ids': [],
|
|
64
|
+
'release_blocker_ids': [],
|
|
65
|
+
'next_mandatory_action': 'Collect stop-wave judgments for the restarted epoch.',
|
|
66
|
+
'next_mandatory_role': 'completion-stop-judge',
|
|
67
|
+
'remaining_stop_judges': 2,
|
|
68
|
+
'current_stop_wave_id': 2,
|
|
69
|
+
'last_reground_at': '2026-05-24T00:00:00Z',
|
|
70
|
+
'last_auditor_verdict': 'pass',
|
|
71
|
+
'contract_status': 'stop_wave_pending_judgments',
|
|
72
|
+
'latest_completed_slice': head,
|
|
73
|
+
'latest_verified_slice': head,
|
|
74
|
+
}
|
|
75
|
+
startup_brief = {
|
|
76
|
+
'schema_version': 1,
|
|
77
|
+
'artifact_type': 'completion-startup-brief',
|
|
78
|
+
'source': 'test',
|
|
79
|
+
'confirmed': True,
|
|
80
|
+
'confirmed_at': '2026-05-24T00:00:00Z',
|
|
81
|
+
'mission': mission,
|
|
82
|
+
'goal_text': f'Mission: {mission}',
|
|
83
|
+
'scope': [],
|
|
84
|
+
'constraints': [],
|
|
85
|
+
'acceptance': [],
|
|
86
|
+
'risks': [],
|
|
87
|
+
'notes': ['stop-wave epoch test fixture'],
|
|
88
|
+
'task_type': profile['task_type'],
|
|
89
|
+
'evaluation_profile': profile['evaluation_profile'],
|
|
90
|
+
}
|
|
91
|
+
plan = {
|
|
92
|
+
'schema_version': 1,
|
|
93
|
+
'mission_anchor': mission,
|
|
94
|
+
'task_type': profile['task_type'],
|
|
95
|
+
'evaluation_profile': profile['evaluation_profile'],
|
|
96
|
+
'last_reground_at': '2026-05-24T00:00:00Z',
|
|
97
|
+
'plan_basis': 'stop_wave_epoch_fixture',
|
|
98
|
+
'candidate_slices': [],
|
|
99
|
+
}
|
|
100
|
+
active = {
|
|
101
|
+
'schema_version': 1,
|
|
102
|
+
'mission_anchor': mission,
|
|
103
|
+
'task_type': profile['task_type'],
|
|
104
|
+
'evaluation_profile': profile['evaluation_profile'],
|
|
105
|
+
'status': 'idle',
|
|
106
|
+
'slice_id': None,
|
|
107
|
+
'goal': None,
|
|
108
|
+
'contract_ids': [],
|
|
109
|
+
'acceptance_criteria': [],
|
|
110
|
+
'priority': None,
|
|
111
|
+
'why_now': None,
|
|
112
|
+
'blocked_on': [],
|
|
113
|
+
'locked_notes': [],
|
|
114
|
+
'must_fix_findings': [],
|
|
115
|
+
'implementation_surfaces': [],
|
|
116
|
+
'verification_commands': [],
|
|
117
|
+
'basis_commit': None,
|
|
118
|
+
'remaining_contract_ids_before': [],
|
|
119
|
+
'release_blocker_count_before': None,
|
|
120
|
+
'high_value_gap_count_before': None,
|
|
121
|
+
}
|
|
122
|
+
evidence = {
|
|
123
|
+
'schema_version': 1,
|
|
124
|
+
'artifact_type': 'completion-verification-evidence',
|
|
125
|
+
'subject_type': 'none',
|
|
126
|
+
'slice_id': None,
|
|
127
|
+
'goal': None,
|
|
128
|
+
'contract_ids': [],
|
|
129
|
+
'basis_commit': None,
|
|
130
|
+
'head_sha': None,
|
|
131
|
+
'verification_commands': [],
|
|
132
|
+
'outcome': 'not_recorded',
|
|
133
|
+
'recorded_at': None,
|
|
134
|
+
'summary': 'No selected-slice verification evidence is required for the stop-wave epoch fixture.',
|
|
135
|
+
}
|
|
136
|
+
Path('.agent/state.json').write_text(json.dumps(state, indent=2) + '\n')
|
|
137
|
+
Path('.agent/startup-brief.json').write_text(json.dumps(startup_brief, indent=2) + '\n')
|
|
138
|
+
Path('.agent/plan.json').write_text(json.dumps(plan, indent=2) + '\n')
|
|
139
|
+
Path('.agent/active-slice.json').write_text(json.dumps(active, indent=2) + '\n')
|
|
140
|
+
Path('.agent/verification-evidence.json').write_text(json.dumps(evidence, indent=2) + '\n')
|
|
141
|
+
Path('.agent/stop-check-history.jsonl').write_text(json.dumps({
|
|
142
|
+
'schema_version': 1,
|
|
143
|
+
'type': 'judgment',
|
|
144
|
+
'recorded_at': 1,
|
|
145
|
+
'head_sha': head,
|
|
146
|
+
'stop_wave_id': 1,
|
|
147
|
+
'can_stop': False,
|
|
148
|
+
'blocker_count': 1,
|
|
149
|
+
'high_value_gap_count': 0,
|
|
150
|
+
}) + '\n')
|
|
151
|
+
Path('.agent/slice-history.jsonl').write_text('')
|
|
152
|
+
PY
|
|
153
|
+
|
|
154
|
+
HEAD_SHA="$HEAD_SHA" python3 - <<'PY'
|
|
155
|
+
import os, subprocess
|
|
156
|
+
combined = subprocess.run(['bash', '.agent/verify_completion_stop.sh'], text=True, capture_output=True)
|
|
157
|
+
text = combined.stdout + combined.stderr
|
|
158
|
+
assert combined.returncode != 0, 'expected stop verifier to fail before current stop-wave judgments are recorded'
|
|
159
|
+
assert f'Need 2 valid current-HEAD judgments for HEAD {os.environ["HEAD_SHA"]} in stop_wave_id 2; found 0.' in text, text
|
|
160
|
+
assert 'Current HEAD has a can_stop=no judgment' not in text, text
|
|
161
|
+
PY
|
|
162
|
+
|
|
163
|
+
HEAD_SHA="$HEAD_SHA" python3 - <<'PY'
|
|
164
|
+
import json, os
|
|
165
|
+
from pathlib import Path
|
|
166
|
+
head = os.environ['HEAD_SHA']
|
|
167
|
+
records = [
|
|
168
|
+
{
|
|
169
|
+
'schema_version': 1,
|
|
170
|
+
'type': 'judgment',
|
|
171
|
+
'recorded_at': 2,
|
|
172
|
+
'head_sha': head,
|
|
173
|
+
'stop_wave_id': 2,
|
|
174
|
+
'can_stop': True,
|
|
175
|
+
'blocker_count': 0,
|
|
176
|
+
'high_value_gap_count': 0,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
'schema_version': 1,
|
|
180
|
+
'type': 'judgment',
|
|
181
|
+
'recorded_at': 3,
|
|
182
|
+
'head_sha': head,
|
|
183
|
+
'stop_wave_id': 2,
|
|
184
|
+
'can_stop': True,
|
|
185
|
+
'blocker_count': 0,
|
|
186
|
+
'high_value_gap_count': 0,
|
|
187
|
+
},
|
|
188
|
+
]
|
|
189
|
+
with Path('.agent/stop-check-history.jsonl').open('a', encoding='utf8') as fh:
|
|
190
|
+
for record in records:
|
|
191
|
+
fh.write(json.dumps(record) + '\n')
|
|
192
|
+
PY
|
|
193
|
+
|
|
194
|
+
bash .agent/verify_completion_stop.sh >/dev/null
|
|
195
|
+
|
|
196
|
+
ROOT_PATH="$ROOT" node - <<'NODE'
|
|
197
|
+
const fs = require('node:fs');
|
|
198
|
+
const path = require('node:path');
|
|
199
|
+
const { parseReportFields, transcribeCanonicalRoleReport } = require(path.join(process.env.ROOT_PATH, 'extensions/completion/role-reporting.js'));
|
|
200
|
+
(async () => {
|
|
201
|
+
const report = `MISSION ANCHOR: epoch mission\nRemaining contract IDs: none\nRubric:\n- Contract coverage: pass - All implementation slices are accepted on HEAD.\n- Correctness risk: pass - No remaining blocker or high-value gap is evident.\n- Verification evidence: pass - Final verification passes for the current head.\n- Docs/state parity: pass - Docs, config, and canonical state match shipped behavior.\nCan the project stop now: yes\nExact remaining open top-level contract IDs: none\nBlocker count: 0\nHigh-value gap count: 0\nLatest completed slice commit: abcdef1234567890abcdef1234567890abcdef12\nDocs/config/runbooks match shipped behavior: yes\nTracked and unignored worktree is clean: yes\nBrief justification: Stop-wave epoch transcription should capture the active stop_wave_id.`;
|
|
202
|
+
const headSha = require('node:child_process').execFileSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
|
|
203
|
+
const result = await transcribeCanonicalRoleReport({
|
|
204
|
+
role: 'completion-stop-judge',
|
|
205
|
+
output: report,
|
|
206
|
+
reportFields: parseReportFields(report),
|
|
207
|
+
snapshotFiles: {
|
|
208
|
+
statePath: path.join(process.cwd(), '.agent/state.json'),
|
|
209
|
+
stopHistoryPath: path.join(process.cwd(), '.agent/stop-check-history.jsonl'),
|
|
210
|
+
sliceHistoryPath: path.join(process.cwd(), '.agent/slice-history.jsonl'),
|
|
211
|
+
},
|
|
212
|
+
headSha,
|
|
213
|
+
recordedAt: 4,
|
|
214
|
+
});
|
|
215
|
+
if (result.errors.length > 0) throw new Error(result.errors.join(' | '));
|
|
216
|
+
const lines = fs.readFileSync('.agent/stop-check-history.jsonl', 'utf8').trim().split('\n').map((line) => JSON.parse(line));
|
|
217
|
+
const last = lines[lines.length - 1];
|
|
218
|
+
if (last.stop_wave_id !== 2) throw new Error('transcribed stop judgment must include current stop_wave_id 2');
|
|
219
|
+
})();
|
|
220
|
+
NODE
|
|
221
|
+
|
|
222
|
+
echo "stop-wave epoch test passed"
|
|
@@ -36,7 +36,7 @@ This skill defines shared protocol facts only. Role-specific behavior belongs in
|
|
|
36
36
|
- Docs, config, and runbooks must stay truthful to shipped behavior.
|
|
37
37
|
- `.agent/verify_completion_stop.sh` is a generated repo-level baseline verifier. Onboarding should create a working version from current repo truth rather than an unconditional failing placeholder.
|
|
38
38
|
- The packaged default stop policy is `required_stop_judges: 2` plus `stop_aggregation_policy: "unanimous-current-head-v1"` in `.agent/profile.json`.
|
|
39
|
-
- Under `unanimous-current-head-v1`, only current-HEAD `judgment` records
|
|
39
|
+
- Under `unanimous-current-head-v1`, only current-HEAD `judgment` records from the current stop-wave epoch count. Canonical `state.json current_stop_wave_id` tracks that epoch, may be incremented to restart stop evaluation on the same `HEAD`, and repo-level stop verification must wait until the required current-HEAD judgments for the current epoch are recorded.
|
|
40
40
|
- Keep slice-specific proof in repo tests or deterministic checks. Refresh `.agent/verify_completion_stop.sh` only when the repo's top-level verification surfaces change or the verifier becomes stale.
|
|
41
41
|
- The workflow topology is flat and primary-driven: the main pi session remains the workflow root and invokes at most one completion role at a time.
|
|
42
42
|
- No completion role may invoke another completion role during the normal workflow.
|
|
@@ -86,7 +86,7 @@ If the workflow driver detects that the next mandatory action belongs to a compl
|
|
|
86
86
|
6. If the latest committed slice lacks an audit result, invoke `completion-auditor`.
|
|
87
87
|
7. If review or audit have returned and canonical reconciliation is needed, invoke `completion-regrounder`. `completion-regrounder` must not select or hand off a next slice while the latest committed slice leaves the tracked and unignored worktree dirty; instead it must reopen or continue that latest slice for reconciliation.
|
|
88
88
|
8. If all planned slices are done and final closure is being evaluated, invoke the required `completion-stop-judge` sessions directly.
|
|
89
|
-
9. After each required current-HEAD `completion-stop-judge` result is faithfully recorded, rerun `bash .agent/verify_completion_stop.sh` and invoke `completion-regrounder` for final stop reconciliation.
|
|
89
|
+
9. After each required current-HEAD `completion-stop-judge` result for the current `current_stop_wave_id` is faithfully recorded, rerun `bash .agent/verify_completion_stop.sh` and invoke `completion-regrounder` for final stop reconciliation.
|
|
90
90
|
|
|
91
91
|
The workflow driver must not substitute itself for any mandatory dispatch target above.
|
|
92
92
|
|
|
@@ -87,6 +87,7 @@ Required fields:
|
|
|
87
87
|
- `next_mandatory_action`
|
|
88
88
|
- `next_mandatory_role`
|
|
89
89
|
- `remaining_stop_judges`
|
|
90
|
+
- `current_stop_wave_id`
|
|
90
91
|
- `last_reground_at`
|
|
91
92
|
- `last_auditor_verdict`
|
|
92
93
|
- `contract_status`
|
|
@@ -250,6 +251,7 @@ Minimum record shape:
|
|
|
250
251
|
- `type`
|
|
251
252
|
- `recorded_at`
|
|
252
253
|
- `head_sha`
|
|
254
|
+
- `stop_wave_id`
|
|
253
255
|
- `can_stop`
|
|
254
256
|
- `blocker_count`
|
|
255
257
|
- `high_value_gap_count`
|
|
@@ -265,11 +267,12 @@ The packaged default stop policy is:
|
|
|
265
267
|
|
|
266
268
|
Policy meaning:
|
|
267
269
|
|
|
268
|
-
-
|
|
269
|
-
-
|
|
270
|
-
-
|
|
271
|
-
- fail closed if
|
|
272
|
-
-
|
|
270
|
+
- `state.json current_stop_wave_id` is the current stop-wave epoch for the current mission and may be incremented to restart stop evaluation on the same `HEAD`
|
|
271
|
+
- count only `judgment` records whose `head_sha` matches the current `HEAD` and whose `stop_wave_id` matches `state.json current_stop_wave_id`
|
|
272
|
+
- require at least two valid current-HEAD judgments for the current stop-wave epoch before repo-level stop verification may run
|
|
273
|
+
- fail closed if any current-HEAD judgment in the current stop-wave epoch has `can_stop = false`
|
|
274
|
+
- fail closed if a current-HEAD judgment in the current stop-wave epoch is malformed or carries non-zero blocker/high-value-gap counts
|
|
275
|
+
- rerun `bash .agent/verify_completion_stop.sh` only after the required current-HEAD judgments for the current stop-wave epoch are faithfully recorded, then hand final reconciliation back to `completion-regrounder`
|
|
273
276
|
|
|
274
277
|
## Structured Evaluation Rubric Foundation
|
|
275
278
|
|