@attalabs/vinaya 0.24.1 → 0.26.0
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 +26 -3
- package/aeg-root/contracts/architect-planner.md +5 -5
- package/aeg-root/contracts/developer-reviewer.md +10 -7
- package/aeg-root/contracts/planner-developer.md +142 -0
- package/aeg-root/contracts/reviewer-archivist.md +2 -2
- package/aeg-root/contracts/tranche-archivist-planner.md +12 -11
- package/aeg-root/enforcement.md +25 -11
- package/aeg-root/process.md +53 -75
- package/aeg-root/roles/archivist.md +3 -3
- package/aeg-root/roles/developer.md +30 -24
- package/aeg-root/roles/planner.md +100 -21
- package/aeg-root/roles/principal.md +14 -11
- package/aeg-root/roles/reviewer.md +33 -17
- package/aeg-root/roles/security.md +28 -13
- package/aeg-root/roles/tranche-archivist.md +2 -2
- package/aeg-root/skills/aeg/SKILL.md +5 -5
- package/aeg-root/skills/aeg-roles/SKILL.md +7 -7
- package/aeg-root/state-machine.md +36 -35
- package/aeg-root/task-model.md +3 -3
- package/aeg-root/templates/brief-template.md +22 -6
- package/aeg-root/templates/issue-rationale-template.md +35 -2
- package/aeg-root/templates/pr-report-template.md +5 -13
- package/aeg-root/tranche-model.md +21 -21
- package/dist/checks/bin/check-body-bare-digits.js +684 -142
- package/dist/checks/bin/check-branch-topology.js +741 -88
- package/dist/checks/bin/check-brief-shape.js +708 -83
- package/dist/checks/bin/check-changeset-coverage.js +1445 -149
- package/dist/checks/bin/check-closes-n.js +741 -88
- package/dist/checks/bin/check-coherence.js +808 -149
- package/dist/checks/bin/check-dead-branch-push.js +617 -81
- package/dist/checks/bin/check-dispatch-readiness.js +811 -152
- package/dist/checks/bin/check-doc-coverage-push.js +1445 -149
- package/dist/checks/bin/check-doc-coverage.js +1447 -151
- package/dist/checks/bin/check-doctrine-no-procedures.js +684 -142
- package/dist/checks/bin/check-doctrine-portability.js +1445 -149
- package/dist/checks/bin/check-evidence-fresh.js +684 -142
- package/dist/checks/bin/check-exec-bits.js +1445 -149
- package/dist/checks/bin/check-first-push-dispatch.js +808 -149
- package/dist/checks/bin/check-issue-assignment.js +741 -88
- package/dist/checks/bin/check-main-branch-refusal.js +618 -82
- package/dist/checks/bin/check-no-disk-state.js +617 -81
- package/dist/checks/bin/check-pr-premise-reassert.js +5401 -0
- package/dist/checks/bin/check-pr-report-density.js +617 -81
- package/dist/checks/bin/check-quoted-command.js +1443 -147
- package/dist/checks/bin/check-reader-resolvable-prose.js +1481 -153
- package/dist/checks/bin/check-registry-gates.js +659 -85
- package/dist/checks/bin/check-retired-vocabulary.js +1443 -147
- package/dist/checks/bin/check-review-gate.js +758 -145
- package/dist/checks/bin/check-single-plan-pr.js +617 -81
- package/dist/checks/bin/check-surface-scope.js +5722 -0
- package/dist/checks/bin/check-test-plan.js +617 -81
- package/dist/checks/bin/check-token-collection-wired.js +617 -81
- package/dist/checks/bin/check-token-report.js +629 -88
- package/dist/checks/bin/check-workspace-escape.js +1443 -147
- package/dist/index.js +8548 -4335
- package/package.json +1 -1
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/brief-render.ts +2 -2
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/main-branch-refusal.ts +26 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/1q96_modules_@clerk_nextjs_dist_esm_app-router_client_keyless-creator-reader_0lom2js.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0053k9k._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0112h-k._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0o771t1._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__1pndh3_._.js → [root-of-the-server]__0p8q38b._.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0puovz5._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1hs0dcu._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1wc4-ip._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_03x_w6q._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0gvm3og._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0lwxg63._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0wxycau._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1n0cnq-._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/packages_ui_topbar_index_tsx_1h1gs1y._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3m1kgax7j2vgs.js → 0bn8c8v5q429o.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0doqwpd81sjzn.js → 0keji7wvbe1d1.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1maoxbrl3pv5d.css +1 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1uogpj5w2n5ju.js → 3-6a3tinbdb-7.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0zebsmmk1bvnb.js → 3_9xytdmiv1xi.js} +4 -4
- package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
- package/aeg-root/contracts/brief-developer.md +0 -141
- package/aeg-root/contracts/planner-brief.md +0 -130
- package/aeg-root/roles/brief-author.md +0 -114
- package/aeg-root/skills/brief-authoring/SKILL.md +0 -509
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/3qc67qhcvbg0o.css +0 -1
- /package/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/{aeg-core → vinaya/studio-standalone/_node_modules/@attalabs/aeg-core}/bin/verify-coherence.ts +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_buildManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_clientMiddlewareManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_ssgManifest.js +0 -0
|
@@ -27,6 +27,15 @@ var GATE_AUDIENCE = {
|
|
|
27
27
|
internal: "A composite that shells this repo's own `typecheck`/`lint`/`test`/`build` scripts by name. Those names are this monorepo's toolchain (bun + turbo + biome), not an adopter's; the shipped equivalent of its intent is `vinaya check --all`, which is portable by construction."
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
|
+
var NON_GATE_BINS = [
|
|
31
|
+
"archive-task",
|
|
32
|
+
"assign-task-issue",
|
|
33
|
+
"dead-branch-audit",
|
|
34
|
+
"eval-agent-compliance",
|
|
35
|
+
"open-issue",
|
|
36
|
+
"open-pr",
|
|
37
|
+
"report-tokens"
|
|
38
|
+
];
|
|
30
39
|
function isShipped(a) {
|
|
31
40
|
return "shippedAs" in a;
|
|
32
41
|
}
|
|
@@ -387,7 +396,7 @@ var LABELS = [
|
|
|
387
396
|
id: "vinaya/needs:brief-correction",
|
|
388
397
|
category: "needs",
|
|
389
398
|
form: "literal",
|
|
390
|
-
carries: "Waiting on the
|
|
399
|
+
carries: "Waiting on the Planner — the brief contradicts the surface it describes."
|
|
391
400
|
},
|
|
392
401
|
{
|
|
393
402
|
key: "waiver-docs",
|
|
@@ -1938,13 +1947,18 @@ function parseInlineFieldList(section) {
|
|
|
1938
1947
|
}
|
|
1939
1948
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
1940
1949
|
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
1941
|
-
|
|
1950
|
+
var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1951
|
+
function firstFiveLines(comment) {
|
|
1942
1952
|
return comment.split(`
|
|
1943
|
-
`).slice(0,
|
|
1953
|
+
`).slice(0, 5).join(`
|
|
1944
1954
|
`);
|
|
1945
1955
|
}
|
|
1946
1956
|
function extractHeadSha(comment) {
|
|
1947
|
-
const m =
|
|
1957
|
+
const m = firstFiveLines(comment).match(HEAD_SHA_PATTERN);
|
|
1958
|
+
return m ? m[1].toLowerCase() : null;
|
|
1959
|
+
}
|
|
1960
|
+
function extractObjectivesVersion(comment) {
|
|
1961
|
+
const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
|
|
1948
1962
|
return m ? m[1].toLowerCase() : null;
|
|
1949
1963
|
}
|
|
1950
1964
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
@@ -1953,21 +1967,24 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1953
1967
|
return {
|
|
1954
1968
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
1955
1969
|
headSha: null,
|
|
1970
|
+
objectivesVersion: null,
|
|
1956
1971
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1957
1972
|
};
|
|
1958
1973
|
}
|
|
1959
1974
|
const latest = candidates[candidates.length - 1];
|
|
1960
|
-
const m =
|
|
1975
|
+
const m = firstFiveLines(latest).match(valuePattern);
|
|
1961
1976
|
if (!m) {
|
|
1962
1977
|
return {
|
|
1963
|
-
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first
|
|
1978
|
+
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
|
|
1964
1979
|
headSha: null,
|
|
1965
|
-
|
|
1980
|
+
objectivesVersion: null,
|
|
1981
|
+
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
|
|
1966
1982
|
};
|
|
1967
1983
|
}
|
|
1968
1984
|
return {
|
|
1969
1985
|
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
1970
1986
|
headSha: extractHeadSha(latest),
|
|
1987
|
+
objectivesVersion: extractObjectivesVersion(latest),
|
|
1971
1988
|
danglingNote: null
|
|
1972
1989
|
};
|
|
1973
1990
|
}
|
|
@@ -2324,8 +2341,105 @@ function overrideActive(opts) {
|
|
|
2324
2341
|
return true;
|
|
2325
2342
|
return false;
|
|
2326
2343
|
}
|
|
2327
|
-
// ../../packages/aeg-core/src/
|
|
2344
|
+
// ../../packages/aeg-core/src/objectives.ts
|
|
2328
2345
|
import { createHash as createHash2 } from "node:crypto";
|
|
2346
|
+
var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
|
|
2347
|
+
var NEXT_HEADING_RE = /^##[ \t]/m;
|
|
2348
|
+
var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
|
|
2349
|
+
function hasBacktickedPath(text) {
|
|
2350
|
+
let i = 0;
|
|
2351
|
+
while (i < text.length) {
|
|
2352
|
+
const start = text.indexOf("`", i);
|
|
2353
|
+
if (start === -1)
|
|
2354
|
+
return false;
|
|
2355
|
+
const end = text.indexOf("`", start + 1);
|
|
2356
|
+
if (end === -1)
|
|
2357
|
+
return false;
|
|
2358
|
+
if (text.slice(start + 1, end).includes("/"))
|
|
2359
|
+
return true;
|
|
2360
|
+
i = end + 1;
|
|
2361
|
+
}
|
|
2362
|
+
return false;
|
|
2363
|
+
}
|
|
2364
|
+
var MIN_WORDS_OUTSIDE_BACKTICKS = 3;
|
|
2365
|
+
function stripObjectiveBackticks(text) {
|
|
2366
|
+
return text.replace(/`[^`\n]*`/g, " ");
|
|
2367
|
+
}
|
|
2368
|
+
function wordCount(text) {
|
|
2369
|
+
return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
|
|
2370
|
+
}
|
|
2371
|
+
function objectivesSectionText(body) {
|
|
2372
|
+
const heading = HEADING_RE.exec(body);
|
|
2373
|
+
if (!heading)
|
|
2374
|
+
return null;
|
|
2375
|
+
const afterHeading = body.slice(heading.index + heading[0].length);
|
|
2376
|
+
const next = NEXT_HEADING_RE.exec(afterHeading);
|
|
2377
|
+
return next ? afterHeading.slice(0, next.index) : afterHeading;
|
|
2378
|
+
}
|
|
2379
|
+
function hasObjectivesHeading(body) {
|
|
2380
|
+
return HEADING_RE.test(body);
|
|
2381
|
+
}
|
|
2382
|
+
function objectivesOf(body) {
|
|
2383
|
+
const section = objectivesSectionText(body);
|
|
2384
|
+
if (section === null) {
|
|
2385
|
+
return { ok: false, errors: ["no `## Objectives` heading found in the body."] };
|
|
2386
|
+
}
|
|
2387
|
+
const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
2388
|
+
if (lines.length === 0) {
|
|
2389
|
+
return { ok: false, errors: ["the `## Objectives` section has no `O<n>.` lines."] };
|
|
2390
|
+
}
|
|
2391
|
+
const objectives = [];
|
|
2392
|
+
const errors = [];
|
|
2393
|
+
for (const line of lines) {
|
|
2394
|
+
const m = OBJECTIVE_LINE_RE.exec(line);
|
|
2395
|
+
if (!m) {
|
|
2396
|
+
errors.push(`"${line}" is not a well-formed objective line — expected \`O<n>. <sentence>\`, e.g. \`O1. …\`.`);
|
|
2397
|
+
continue;
|
|
2398
|
+
}
|
|
2399
|
+
const n = m[1];
|
|
2400
|
+
const text = m[2].trim();
|
|
2401
|
+
if (text.length === 0) {
|
|
2402
|
+
errors.push(`O${n} has no sentence — every objective is one observable sentence.`);
|
|
2403
|
+
continue;
|
|
2404
|
+
}
|
|
2405
|
+
if (hasBacktickedPath(text) && wordCount(stripObjectiveBackticks(text)) < MIN_WORDS_OUTSIDE_BACKTICKS) {
|
|
2406
|
+
errors.push(`O${n} is little more than a file path — an objective states an observable outcome, never a bare path (the Planner maps it to files).`);
|
|
2407
|
+
continue;
|
|
2408
|
+
}
|
|
2409
|
+
objectives.push({ id: `O${n}`, text });
|
|
2410
|
+
}
|
|
2411
|
+
for (let i = 0;i < objectives.length; i++) {
|
|
2412
|
+
const expected = i + 1;
|
|
2413
|
+
const actual = Number.parseInt(objectives[i].id.slice(1), 10);
|
|
2414
|
+
if (actual !== expected) {
|
|
2415
|
+
errors.push(`numbering is not contiguous from O1 — expected O${expected}, found ${objectives[i].id}.`);
|
|
2416
|
+
break;
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
if (errors.length > 0)
|
|
2420
|
+
return { ok: false, errors };
|
|
2421
|
+
return { ok: true, objectives };
|
|
2422
|
+
}
|
|
2423
|
+
function normalizeText(text) {
|
|
2424
|
+
return text.trim().replace(/\s+/g, " ");
|
|
2425
|
+
}
|
|
2426
|
+
function objectivesVersion(objectives) {
|
|
2427
|
+
const normalized = objectives.map((o) => `${o.id} ${normalizeText(o.text)}`).join(`
|
|
2428
|
+
`);
|
|
2429
|
+
return createHash2("sha256").update(normalized).digest("hex");
|
|
2430
|
+
}
|
|
2431
|
+
function isIssueNotFoundError(err) {
|
|
2432
|
+
const stderr = err?.stderr;
|
|
2433
|
+
const haystack = [
|
|
2434
|
+
err?.message ?? "",
|
|
2435
|
+
typeof stderr === "string" ? stderr : stderr?.toString() ?? ""
|
|
2436
|
+
].join(`
|
|
2437
|
+
`);
|
|
2438
|
+
return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
// ../../packages/aeg-core/src/premise-check.ts
|
|
2442
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2329
2443
|
var ASSERTION_KINDS = new Set(["contains", "absent", "sha256"]);
|
|
2330
2444
|
function isPremiseHeader(line) {
|
|
2331
2445
|
const stripped = line.replace(/[*#]/g, "").trim();
|
|
@@ -2396,7 +2510,7 @@ function checkPremises(assertions, fileReader) {
|
|
|
2396
2510
|
} else if (a.kind === "absent" && content.includes(a.value)) {
|
|
2397
2511
|
failures.push(`premise-check: ${a.path} now contains "${a.value}", but the brief pinned it absent — the premise has moved; re-dig before proceeding.`);
|
|
2398
2512
|
} else if (a.kind === "sha256") {
|
|
2399
|
-
const actual =
|
|
2513
|
+
const actual = createHash3("sha256").update(content).digest("hex");
|
|
2400
2514
|
if (actual !== a.value.toLowerCase()) {
|
|
2401
2515
|
failures.push(`premise-check: ${a.path} sha256 mismatch (brief pinned ${a.value}, file is now ${actual}) — the file changed since this brief was authored; re-dig before proceeding.`);
|
|
2402
2516
|
}
|
|
@@ -2501,18 +2615,14 @@ function checkTestPlanExclusivity(prBody) {
|
|
|
2501
2615
|
function checkPrincipalPlaceholder(prBody) {
|
|
2502
2616
|
const region = testPlanRegion(prBody);
|
|
2503
2617
|
const lineRe = /^-\s*\[[ xX]\]\s*\*{2}\[principal\]\*{2}(.*)$/gim;
|
|
2618
|
+
const errors = [];
|
|
2504
2619
|
for (const m of region.matchAll(lineRe)) {
|
|
2505
2620
|
const content = m[1] ?? "";
|
|
2506
2621
|
if (/^\s*None\b/i.test(content)) {
|
|
2507
|
-
|
|
2508
|
-
status: "fail",
|
|
2509
|
-
errors: [
|
|
2510
|
-
'brief-validation Test Plan shape: a `**[principal]**` checkbox item is a "None" placeholder — if there is no principal-runnable surface, omit the item entirely; an untickable placeholder box blocks the merge gate forever.'
|
|
2511
|
-
]
|
|
2512
|
-
};
|
|
2622
|
+
errors.push(`brief-validation Test Plan shape: a \`**[principal]**\` checkbox item is a "None" placeholder ("${content.trim()}") — if there is no principal-runnable surface, omit the item entirely; an untickable placeholder box blocks the merge gate forever.`);
|
|
2513
2623
|
}
|
|
2514
2624
|
}
|
|
2515
|
-
return { status: "pass", errors: [] };
|
|
2625
|
+
return errors.length > 0 ? { status: "fail", errors } : { status: "pass", errors: [] };
|
|
2516
2626
|
}
|
|
2517
2627
|
function checkSurfaceMap(prBody) {
|
|
2518
2628
|
return headingCheck(prBody, "(?:technical\\s+)?surface map", "Technical surface map");
|
|
@@ -2757,8 +2867,55 @@ function checkDefeatCases(prBody) {
|
|
|
2757
2867
|
]
|
|
2758
2868
|
};
|
|
2759
2869
|
}
|
|
2870
|
+
var PART_CITATION_RE = /Part\s+\d+\s*\(([^)]*)\)/gi;
|
|
2871
|
+
var OBJECTIVE_REF_RE = /O(\d+)/g;
|
|
2872
|
+
function citedObjectiveIds(section6) {
|
|
2873
|
+
const ids = new Set;
|
|
2874
|
+
for (const m of section6.matchAll(PART_CITATION_RE)) {
|
|
2875
|
+
for (const r of m[1].matchAll(OBJECTIVE_REF_RE))
|
|
2876
|
+
ids.add(Number.parseInt(r[1], 10));
|
|
2877
|
+
}
|
|
2878
|
+
return ids;
|
|
2879
|
+
}
|
|
2880
|
+
function checkObjectivesCopy(prBody, issueObjectives) {
|
|
2881
|
+
const parsed = objectivesOf(prBody);
|
|
2882
|
+
if (!parsed.ok) {
|
|
2883
|
+
return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives copy: ${e}`) };
|
|
2884
|
+
}
|
|
2885
|
+
if (objectivesVersion(parsed.objectives) !== objectivesVersion(issueObjectives)) {
|
|
2886
|
+
return {
|
|
2887
|
+
status: "fail",
|
|
2888
|
+
errors: [
|
|
2889
|
+
"brief-validation objectives copy: the brief's `## Objectives` section does not match the Issue's — copy the Issue's `## Objectives` section (aeg-root/roles/developer.md)."
|
|
2890
|
+
]
|
|
2891
|
+
};
|
|
2892
|
+
}
|
|
2893
|
+
return { status: "pass", errors: [] };
|
|
2894
|
+
}
|
|
2895
|
+
function checkObjectivesCoverage(prBody) {
|
|
2896
|
+
const parsed = objectivesOf(prBody);
|
|
2897
|
+
if (!parsed.ok) {
|
|
2898
|
+
return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives coverage: ${e}`) };
|
|
2899
|
+
}
|
|
2900
|
+
const declaredIds = new Set(parsed.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
|
|
2901
|
+
const section6 = extractNumberedSection(prBody, 6) ?? "";
|
|
2902
|
+
const cited = citedObjectiveIds(section6);
|
|
2903
|
+
const maxDeclared = Math.max(...declaredIds, 0);
|
|
2904
|
+
const errors = [];
|
|
2905
|
+
for (const id of declaredIds) {
|
|
2906
|
+
if (!cited.has(id)) {
|
|
2907
|
+
errors.push(`brief-validation objectives coverage: O${id} is not cited by any Part in §6 — every objective must be cited by at least one Part.`);
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
for (const id of cited) {
|
|
2911
|
+
if (!declaredIds.has(id)) {
|
|
2912
|
+
errors.push(`brief-validation objectives coverage: a Part in §6 cites O${id}, but the Objectives section ends at O${maxDeclared}.`);
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
|
|
2916
|
+
}
|
|
2760
2917
|
function checkBriefSections(prBody, readTier, options = {}) {
|
|
2761
|
-
const { requireClosesN = true, consumersOf = () => [] } = options;
|
|
2918
|
+
const { requireClosesN = true, consumersOf = () => [], issueObjectives } = options;
|
|
2762
2919
|
const results = [
|
|
2763
2920
|
checkTierField(prBody, readTier),
|
|
2764
2921
|
checkTestPlan(prBody),
|
|
@@ -2776,10 +2933,23 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2776
2933
|
checkCommandsCarryOutput(prBody),
|
|
2777
2934
|
checkConsumerTests(prBody, consumersOf),
|
|
2778
2935
|
checkDefeatCases(prBody),
|
|
2936
|
+
...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
|
|
2779
2937
|
...requireClosesN ? [checkClosesN(prBody)] : []
|
|
2780
2938
|
];
|
|
2781
2939
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2782
2940
|
}
|
|
2941
|
+
var AEG_BRIEF_V1_MARKER = "<!-- aeg:brief:v1 -->";
|
|
2942
|
+
function contentAfterTwoLines(body) {
|
|
2943
|
+
const first = body.indexOf(`
|
|
2944
|
+
`);
|
|
2945
|
+
if (first === -1)
|
|
2946
|
+
return "";
|
|
2947
|
+
const second = body.indexOf(`
|
|
2948
|
+
`, first + 1);
|
|
2949
|
+
if (second === -1)
|
|
2950
|
+
return "";
|
|
2951
|
+
return body.slice(second + 1);
|
|
2952
|
+
}
|
|
2783
2953
|
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
2784
2954
|
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
2785
2955
|
var STATIC_PORTABLE_PREFIXES = [
|
|
@@ -2974,14 +3144,14 @@ function checkSectionDensity(prBody, heading) {
|
|
|
2974
3144
|
]
|
|
2975
3145
|
};
|
|
2976
3146
|
}
|
|
2977
|
-
function
|
|
2978
|
-
return checkSectionDensity(prBody, "
|
|
3147
|
+
function checkDecisionsDensity(prBody) {
|
|
3148
|
+
return checkSectionDensity(prBody, "Decisions");
|
|
2979
3149
|
}
|
|
2980
3150
|
function checkScopeDensity(prBody) {
|
|
2981
3151
|
return checkSectionDensity(prBody, "Scope");
|
|
2982
3152
|
}
|
|
2983
3153
|
function checkPrReportDensity(prBody) {
|
|
2984
|
-
const results = [
|
|
3154
|
+
const results = [checkDecisionsDensity(prBody), checkScopeDensity(prBody)];
|
|
2985
3155
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2986
3156
|
}
|
|
2987
3157
|
// ../../packages/aeg-core/src/issue-validation.ts
|
|
@@ -3013,6 +3183,75 @@ function checkIssueRationale(body) {
|
|
|
3013
3183
|
}
|
|
3014
3184
|
return { status: errors.length > 0 ? "fail" : "pass", errors };
|
|
3015
3185
|
}
|
|
3186
|
+
var OBJECTIVES_SINCE_ISSUE = 404;
|
|
3187
|
+
function checkIssueObjectives(body, issueNumber) {
|
|
3188
|
+
if (issueNumber !== null && issueNumber < OBJECTIVES_SINCE_ISSUE)
|
|
3189
|
+
return { status: "pass", errors: [] };
|
|
3190
|
+
const result = objectivesOf(body);
|
|
3191
|
+
if (result.ok)
|
|
3192
|
+
return { status: "pass", errors: [] };
|
|
3193
|
+
return { status: "fail", errors: result.errors.map((e) => `issue-validation objectives: ${e}`) };
|
|
3194
|
+
}
|
|
3195
|
+
function topLevelSectionText(body, headingName) {
|
|
3196
|
+
const headingRe = new RegExp(`^##[ \\t]*${headingName}[ \\t]*$`, "im");
|
|
3197
|
+
const heading = headingRe.exec(body);
|
|
3198
|
+
if (!heading)
|
|
3199
|
+
return null;
|
|
3200
|
+
const afterHeading = body.slice(heading.index + heading[0].length);
|
|
3201
|
+
const next = /^##[ \t]/m.exec(afterHeading);
|
|
3202
|
+
return next ? afterHeading.slice(0, next.index) : afterHeading;
|
|
3203
|
+
}
|
|
3204
|
+
function looksLikeFilePath(entry2) {
|
|
3205
|
+
const stripped = entry2.replace(/\/\*\*?$/, "");
|
|
3206
|
+
const lastSegment = stripped.split("/").pop() ?? stripped;
|
|
3207
|
+
const extMatch = /\.[A-Za-z0-9]{1,6}$/.exec(lastSegment);
|
|
3208
|
+
return extMatch !== null && extMatch.index > 0;
|
|
3209
|
+
}
|
|
3210
|
+
function splitGlobList(raw) {
|
|
3211
|
+
return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
3212
|
+
}
|
|
3213
|
+
function parseIssueSurface(body) {
|
|
3214
|
+
const section = topLevelSectionText(body, "Surface");
|
|
3215
|
+
if (section === null)
|
|
3216
|
+
return { ok: false, errors: ["no `## Surface` heading found in the body."] };
|
|
3217
|
+
const inLine = /^in:\s*(.+)$/im.exec(section);
|
|
3218
|
+
const outLine = /^out:\s*(.+)$/im.exec(section);
|
|
3219
|
+
const errors = [];
|
|
3220
|
+
if (!inLine)
|
|
3221
|
+
errors.push("`## Surface` has no `in:` line.");
|
|
3222
|
+
if (!outLine)
|
|
3223
|
+
errors.push("`## Surface` has no `out:` line.");
|
|
3224
|
+
if (errors.length > 0)
|
|
3225
|
+
return { ok: false, errors };
|
|
3226
|
+
const inGlobs = splitGlobList(inLine[1]);
|
|
3227
|
+
const outGlobs = splitGlobList(outLine[1]);
|
|
3228
|
+
for (const g of [...inGlobs, ...outGlobs]) {
|
|
3229
|
+
if (looksLikeFilePath(g)) {
|
|
3230
|
+
errors.push(`\`${g}\` in \`## Surface\` looks like a file path — Surface entries are directory-level globs, never file paths.`);
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
if (inGlobs.length === 0)
|
|
3234
|
+
errors.push("`## Surface`'s `in:` line resolved to zero globs.");
|
|
3235
|
+
if (errors.length > 0)
|
|
3236
|
+
return { ok: false, errors };
|
|
3237
|
+
return { ok: true, value: { in: inGlobs, out: outGlobs } };
|
|
3238
|
+
}
|
|
3239
|
+
function globCoversPath(glob, path) {
|
|
3240
|
+
const g = glob.replace(/\/\*\*?$/, "").replace(/\/+$/, "");
|
|
3241
|
+
const p = path.replace(/\/+$/, "");
|
|
3242
|
+
return g === p || g.startsWith(`${p}/`) || p.startsWith(`${g}/`);
|
|
3243
|
+
}
|
|
3244
|
+
function checkSurfaceScope(changedFiles, outGlobs) {
|
|
3245
|
+
if (outGlobs.length === 0)
|
|
3246
|
+
return { ok: true };
|
|
3247
|
+
const violations = [];
|
|
3248
|
+
for (const file of changedFiles) {
|
|
3249
|
+
const glob = outGlobs.find((g) => globCoversPath(g, file));
|
|
3250
|
+
if (glob)
|
|
3251
|
+
violations.push({ file, glob });
|
|
3252
|
+
}
|
|
3253
|
+
return violations.length > 0 ? { ok: false, violations } : { ok: true };
|
|
3254
|
+
}
|
|
3016
3255
|
function isTaskIssueLabelSet(labels) {
|
|
3017
3256
|
return hasLabel("tranche", labels);
|
|
3018
3257
|
}
|
|
@@ -3077,6 +3316,8 @@ function checkProjectsRegistered(body, _labels, registeredNames) {
|
|
|
3077
3316
|
}
|
|
3078
3317
|
return errors.length > 0 ? { status: "fail", errors } : { status: "pass", errors: [] };
|
|
3079
3318
|
}
|
|
3319
|
+
var DOC_PATH_RE = /(?:(?:aeg-root|apps|packages|specs|docs|tools|\.claude|\.github)\/[\w./@-]*\.(?:md|mdx)|\.claude\/(?:skills|rules)\/[\w./-]+|\b(?:docs-index|decisions|projects|state-machine|enforcement|process|README|CLAUDE)\.md\b|\b[\w-]+-(?:spec|decisions|backlog)\.md\b)/i;
|
|
3320
|
+
var DOC_PATH_RE_GLOBAL = new RegExp(DOC_PATH_RE.source, "gi");
|
|
3080
3321
|
|
|
3081
3322
|
// ../../packages/aeg-core/src/coherence-checks.ts
|
|
3082
3323
|
var COHERENCE_ENFORCED_FROM = "2026-07-01";
|
|
@@ -3274,7 +3515,8 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
|
|
|
3274
3515
|
continue;
|
|
3275
3516
|
const errors = [
|
|
3276
3517
|
...checkIssueRationale(issue.body).errors,
|
|
3277
|
-
...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors
|
|
3518
|
+
...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
|
|
3519
|
+
...checkIssueObjectives(issue.body, issue.number).errors
|
|
3278
3520
|
];
|
|
3279
3521
|
if (errors.length === 0)
|
|
3280
3522
|
continue;
|
|
@@ -3389,6 +3631,27 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
3389
3631
|
}
|
|
3390
3632
|
return { ok: true, expectedIssue };
|
|
3391
3633
|
}
|
|
3634
|
+
// ../../packages/aeg-core/src/archive-task.ts
|
|
3635
|
+
function closesRefs(text) {
|
|
3636
|
+
return [...new Set([...text.matchAll(/Closes #(\d+)/gi)].map((m) => Number(m[1])))];
|
|
3637
|
+
}
|
|
3638
|
+
function extractIssue(body) {
|
|
3639
|
+
const stripped = stripCode(body);
|
|
3640
|
+
const bodyNums = closesRefs(stripped);
|
|
3641
|
+
const anchored = anchoredRegion(stripped, "CLOSES");
|
|
3642
|
+
if (anchored !== null) {
|
|
3643
|
+
const anchorNums = closesRefs(anchored);
|
|
3644
|
+
if (anchorNums.length === 0)
|
|
3645
|
+
return { issue: null, extraIssues: bodyNums, outsideHeader: false };
|
|
3646
|
+
const issue2 = anchorNums[0];
|
|
3647
|
+
return { issue: issue2, extraIssues: bodyNums.filter((n) => n !== issue2), outsideHeader: false };
|
|
3648
|
+
}
|
|
3649
|
+
const headerNums = closesRefs(headerRegion(stripped));
|
|
3650
|
+
if (bodyNums.length === 0)
|
|
3651
|
+
return { issue: null, extraIssues: [], outsideHeader: false };
|
|
3652
|
+
const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
|
|
3653
|
+
return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
|
|
3654
|
+
}
|
|
3392
3655
|
// ../../packages/aeg-core/src/review-gate.ts
|
|
3393
3656
|
function isBoundToHead(extraction, headSha) {
|
|
3394
3657
|
if (!extraction.headSha)
|
|
@@ -3407,6 +3670,11 @@ function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
|
|
|
3407
3670
|
function isBoundToPatch(extraction, headSha, patchIdOf) {
|
|
3408
3671
|
return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
|
|
3409
3672
|
}
|
|
3673
|
+
function isBoundToObjectives(extraction, currentVersion) {
|
|
3674
|
+
if (currentVersion === null)
|
|
3675
|
+
return true;
|
|
3676
|
+
return extraction.objectivesVersion === currentVersion;
|
|
3677
|
+
}
|
|
3410
3678
|
function checkReviewGate(input) {
|
|
3411
3679
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
3412
3680
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -3433,7 +3701,9 @@ function checkReviewGate(input) {
|
|
|
3433
3701
|
const securityClean = security.value === "PASS";
|
|
3434
3702
|
const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
|
|
3435
3703
|
const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
|
|
3436
|
-
|
|
3704
|
+
const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
|
|
3705
|
+
const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
|
|
3706
|
+
if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && securityClean && securityBound && securityObjectivesBound && mechanicalChecksClean) {
|
|
3437
3707
|
return {
|
|
3438
3708
|
verdict: "pass",
|
|
3439
3709
|
reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}, and every reported mechanical check is green.`,
|
|
@@ -3445,11 +3715,15 @@ function checkReviewGate(input) {
|
|
|
3445
3715
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
3446
3716
|
} else if (!codeReviewBound) {
|
|
3447
3717
|
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3718
|
+
} else if (!codeReviewObjectivesBound) {
|
|
3719
|
+
problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3448
3720
|
}
|
|
3449
3721
|
if (!securityClean) {
|
|
3450
3722
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
3451
3723
|
} else if (!securityBound) {
|
|
3452
3724
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3725
|
+
} else if (!securityObjectivesBound) {
|
|
3726
|
+
problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3453
3727
|
}
|
|
3454
3728
|
if (!mechanicalChecksClean) {
|
|
3455
3729
|
problems.push(reportedMechanicalChecks.length === 0 ? "no mechanical checks have reported for this head yet" : `mechanical check(s) not green: ${reportedMechanicalChecks.filter((c) => c.bucket !== "pass").map((c) => `${c.name} (${c.bucket})`).join(", ")}`);
|
|
@@ -3573,16 +3847,33 @@ function checkG1(rows, existsFn) {
|
|
|
3573
3847
|
return { check: "G1", status: findings.length > 0 ? "fail" : "pass", findings };
|
|
3574
3848
|
}
|
|
3575
3849
|
var SCAFFOLD_PLACEHOLDER = "[undocumented — fill in why]";
|
|
3850
|
+
var NON_GATE_BIN_NAMES = NON_GATE_BINS;
|
|
3851
|
+
var TWIN_CANDIDATE_EXCEPTIONS = {
|
|
3852
|
+
"apps/cli/src/checks/bin/check-dead-branch-push.ts": 'Ring 0 "Pushing to a branch whose pull request already resolved" (implementation: check-push-target.ts)',
|
|
3853
|
+
"apps/cli/src/checks/bin/check-issue-assignment.ts": `Ring 0 "A task branch's first push (Issue self-assignment)" (implementation: assign-task-issue.ts)`,
|
|
3854
|
+
"apps/cli/src/checks/bin/check-closes-n.ts": 'Ring 1 "Closes linkage" (implementation: verify-coherence.ts)'
|
|
3855
|
+
};
|
|
3856
|
+
var NON_GATE_HOOK_SCRIPTS = [".claude/hooks/track-transcript.sh"];
|
|
3576
3857
|
function checkG2(rows, candidateFiles) {
|
|
3577
3858
|
const implementations = new Set(rows.map((r) => r.implementation).filter((p) => p !== ""));
|
|
3859
|
+
const claimedNames = new Set(rows.flatMap((r) => claimedCheckNames(r.implementation)));
|
|
3578
3860
|
const findings = [];
|
|
3579
3861
|
for (const path of candidateFiles) {
|
|
3580
|
-
if (
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3862
|
+
if (implementations.has(path))
|
|
3863
|
+
continue;
|
|
3864
|
+
if (path.startsWith(AEG_CORE_BIN_PATH_PREFIX) && NON_GATE_BIN_NAMES.includes(basenameNoExt(path)))
|
|
3865
|
+
continue;
|
|
3866
|
+
if (NON_GATE_HOOK_SCRIPTS.includes(path))
|
|
3867
|
+
continue;
|
|
3868
|
+
if (path in TWIN_CANDIDATE_EXCEPTIONS)
|
|
3869
|
+
continue;
|
|
3870
|
+
const candidateNames = claimedCheckNames(path);
|
|
3871
|
+
if (candidateNames.length > 0 && candidateNames.some((n) => claimedNames.has(n)))
|
|
3872
|
+
continue;
|
|
3873
|
+
findings.push({
|
|
3874
|
+
path,
|
|
3875
|
+
reason: `"${path}" is not named as the implementation of any row in enforcement.md's ring tables`
|
|
3876
|
+
});
|
|
3586
3877
|
}
|
|
3587
3878
|
for (const row of rows) {
|
|
3588
3879
|
const carriesPlaceholder = [row.summary, row.description, row.spec].some((cell) => cell === SCAFFOLD_PLACEHOLDER);
|
|
@@ -3594,7 +3885,7 @@ function checkG2(rows, candidateFiles) {
|
|
|
3594
3885
|
});
|
|
3595
3886
|
}
|
|
3596
3887
|
}
|
|
3597
|
-
return { check: "G2", status: findings.length > 0 ? "
|
|
3888
|
+
return { check: "G2", status: findings.length > 0 ? "fail" : "pass", findings };
|
|
3598
3889
|
}
|
|
3599
3890
|
function checkG3(ring0Rows, crossingFiles) {
|
|
3600
3891
|
const ring0Implementations = new Set(ring0Rows.map((r) => r.implementation));
|
|
@@ -3685,55 +3976,20 @@ function checkG6(rows, registeredCheckNames) {
|
|
|
3685
3976
|
}
|
|
3686
3977
|
return { check: "G6", status: findings.length > 0 ? "fail" : "pass", findings };
|
|
3687
3978
|
}
|
|
3688
|
-
// ../../packages/aeg-core/src/
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
return {
|
|
3699
|
-
verdict: "refuse",
|
|
3700
|
-
reason: `Branch \`${branch}\` has a CLOSED PR (#${prNumber}) that was never merged — pushing here would resurrect abandoned/rejected work. Open a new task branch instead.`
|
|
3701
|
-
};
|
|
3702
|
-
}
|
|
3703
|
-
if (prState === "OPEN") {
|
|
3704
|
-
return {
|
|
3705
|
-
verdict: "allow",
|
|
3706
|
-
reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
|
|
3707
|
-
};
|
|
3708
|
-
}
|
|
3709
|
-
if (prState === "NONE") {
|
|
3710
|
-
return {
|
|
3711
|
-
verdict: "allow",
|
|
3712
|
-
reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
|
|
3713
|
-
};
|
|
3714
|
-
}
|
|
3715
|
-
return {
|
|
3716
|
-
verdict: "allow",
|
|
3717
|
-
reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
|
|
3718
|
-
};
|
|
3719
|
-
}
|
|
3720
|
-
// ../../packages/aeg-core/src/baseline-capture.ts
|
|
3721
|
-
function captureBaseline(counts, capturedAt) {
|
|
3722
|
-
return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
|
|
3979
|
+
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
3980
|
+
var PRODUCT_SLUG_SCOPE = [
|
|
3981
|
+
"apps/cli/src",
|
|
3982
|
+
".github/workflows",
|
|
3983
|
+
".vinaya",
|
|
3984
|
+
"apps/cli/README.md",
|
|
3985
|
+
"packages/sources/README.md"
|
|
3986
|
+
];
|
|
3987
|
+
function isUnderOrEqual(path, prefix) {
|
|
3988
|
+
return path === prefix || path.startsWith(`${prefix}/`);
|
|
3723
3989
|
}
|
|
3724
|
-
function
|
|
3725
|
-
|
|
3726
|
-
const baselineEntry = baseline.find((b) => b.tool === c.tool);
|
|
3727
|
-
const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
|
|
3728
|
-
return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
|
|
3729
|
-
});
|
|
3730
|
-
return {
|
|
3731
|
-
withinBudget: perTool.every((t) => t.delta <= 0),
|
|
3732
|
-
delta: perTool.reduce((sum, t) => sum + t.delta, 0),
|
|
3733
|
-
perTool
|
|
3734
|
-
};
|
|
3990
|
+
function isProductScopeFile(path) {
|
|
3991
|
+
return PRODUCT_SLUG_SCOPE.some((prefix) => isUnderOrEqual(path, prefix));
|
|
3735
3992
|
}
|
|
3736
|
-
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
3737
3993
|
var SHIPS_PREFIX = "aeg-root/";
|
|
3738
3994
|
function shipsArchivePrefix(shipsPrefix) {
|
|
3739
3995
|
return `${shipsPrefix}tranches/completed/`;
|
|
@@ -3753,6 +4009,8 @@ function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix, shipsPr
|
|
|
3753
4009
|
}
|
|
3754
4010
|
if (isSpecFile2(path) || isClaudeMdFile(path))
|
|
3755
4011
|
return "internal";
|
|
4012
|
+
if (isProductScopeFile(path))
|
|
4013
|
+
return "product";
|
|
3756
4014
|
return null;
|
|
3757
4015
|
}
|
|
3758
4016
|
var SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
|
|
@@ -3765,6 +4023,9 @@ function stripNonProse(path, content) {
|
|
|
3765
4023
|
}
|
|
3766
4024
|
return content;
|
|
3767
4025
|
}
|
|
4026
|
+
function stripNonProseForProduct(path, content) {
|
|
4027
|
+
return path.endsWith(".md") ? stripNonProse(path, content) : content;
|
|
4028
|
+
}
|
|
3768
4029
|
function lineAt(content, index) {
|
|
3769
4030
|
let line = 1;
|
|
3770
4031
|
for (let i = 0;i < index; i++) {
|
|
@@ -3789,12 +4050,19 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
|
|
|
3789
4050
|
{ pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug" },
|
|
3790
4051
|
...legacyPattern ? [{ pattern: legacyPattern, what: "an internal tranche slug", group: 2 }] : []
|
|
3791
4052
|
];
|
|
4053
|
+
const productPatterns = [
|
|
4054
|
+
{ pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug in product code" }
|
|
4055
|
+
];
|
|
3792
4056
|
for (const file of files) {
|
|
3793
4057
|
const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
|
|
3794
|
-
if (!cls ||
|
|
4058
|
+
if (!cls || cls === "internal")
|
|
3795
4059
|
continue;
|
|
3796
|
-
|
|
3797
|
-
|
|
4060
|
+
if (cls !== "product" && !SWEPT_CLASSES.has(cls))
|
|
4061
|
+
continue;
|
|
4062
|
+
const blocking = cls === "product";
|
|
4063
|
+
const scrubbed = blocking ? stripNonProseForProduct(file.path, file.content) : stripNonProse(file.path, file.content);
|
|
4064
|
+
const patternsToRun = blocking ? productPatterns : patterns;
|
|
4065
|
+
for (const { pattern, what, group } of patternsToRun) {
|
|
3798
4066
|
pattern.lastIndex = 0;
|
|
3799
4067
|
let match = pattern.exec(scrubbed);
|
|
3800
4068
|
while (match !== null) {
|
|
@@ -3802,7 +4070,8 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
|
|
|
3802
4070
|
findings.push({
|
|
3803
4071
|
file: file.path,
|
|
3804
4072
|
line: lineAt(scrubbed, match.index),
|
|
3805
|
-
message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve
|
|
4073
|
+
message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve`,
|
|
4074
|
+
blocking
|
|
3806
4075
|
});
|
|
3807
4076
|
match = pattern.exec(scrubbed);
|
|
3808
4077
|
}
|
|
@@ -3852,7 +4121,8 @@ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, read
|
|
|
3852
4121
|
findings.push({
|
|
3853
4122
|
file: file.path,
|
|
3854
4123
|
line,
|
|
3855
|
-
message: `uses coined term "${term}" without defining it inline or linking the glossary
|
|
4124
|
+
message: `uses coined term "${term}" without defining it inline or linking the glossary`,
|
|
4125
|
+
blocking: false
|
|
3856
4126
|
});
|
|
3857
4127
|
}
|
|
3858
4128
|
}
|
|
@@ -3875,6 +4145,7 @@ function parseGlossaryTerms(glossaryContent) {
|
|
|
3875
4145
|
}
|
|
3876
4146
|
return terms;
|
|
3877
4147
|
}
|
|
4148
|
+
|
|
3878
4149
|
// ../../packages/aeg-core/src/quoted-command.ts
|
|
3879
4150
|
var QUOTED_COMMAND_SHIPS_PREFIX = "aeg-root/";
|
|
3880
4151
|
var QUOTED_COMMAND_SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
|
|
@@ -3969,6 +4240,54 @@ function evaluateCitedQuotes(citedQuotes, citedFileContents) {
|
|
|
3969
4240
|
}
|
|
3970
4241
|
return findings;
|
|
3971
4242
|
}
|
|
4243
|
+
// ../../packages/aeg-core/src/dead-branch-push-guard.ts
|
|
4244
|
+
function checkDeadBranchPush(input) {
|
|
4245
|
+
const { branch, prState, prNumber } = input;
|
|
4246
|
+
if (prState === "MERGED") {
|
|
4247
|
+
return {
|
|
4248
|
+
verdict: "refuse",
|
|
4249
|
+
reason: `Branch \`${branch}\` already has a MERGED PR (#${prNumber}) — this branch's work is done. Push a new task branch instead of pushing onto an already-resolved one.`
|
|
4250
|
+
};
|
|
4251
|
+
}
|
|
4252
|
+
if (prState === "CLOSED") {
|
|
4253
|
+
return {
|
|
4254
|
+
verdict: "refuse",
|
|
4255
|
+
reason: `Branch \`${branch}\` has a CLOSED PR (#${prNumber}) that was never merged — pushing here would resurrect abandoned/rejected work. Open a new task branch instead.`
|
|
4256
|
+
};
|
|
4257
|
+
}
|
|
4258
|
+
if (prState === "OPEN") {
|
|
4259
|
+
return {
|
|
4260
|
+
verdict: "allow",
|
|
4261
|
+
reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
|
|
4262
|
+
};
|
|
4263
|
+
}
|
|
4264
|
+
if (prState === "NONE") {
|
|
4265
|
+
return {
|
|
4266
|
+
verdict: "allow",
|
|
4267
|
+
reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
|
|
4268
|
+
};
|
|
4269
|
+
}
|
|
4270
|
+
return {
|
|
4271
|
+
verdict: "allow",
|
|
4272
|
+
reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
|
|
4273
|
+
};
|
|
4274
|
+
}
|
|
4275
|
+
// ../../packages/aeg-core/src/baseline-capture.ts
|
|
4276
|
+
function captureBaseline(counts, capturedAt) {
|
|
4277
|
+
return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
|
|
4278
|
+
}
|
|
4279
|
+
function compareToBaseline(current, baseline) {
|
|
4280
|
+
const perTool = current.map((c) => {
|
|
4281
|
+
const baselineEntry = baseline.find((b) => b.tool === c.tool);
|
|
4282
|
+
const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
|
|
4283
|
+
return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
|
|
4284
|
+
});
|
|
4285
|
+
return {
|
|
4286
|
+
withinBudget: perTool.every((t) => t.delta <= 0),
|
|
4287
|
+
delta: perTool.reduce((sum, t) => sum + t.delta, 0),
|
|
4288
|
+
perTool
|
|
4289
|
+
};
|
|
4290
|
+
}
|
|
3972
4291
|
// ../../packages/aeg-core/src/retired-vocabulary.ts
|
|
3973
4292
|
var RETIRED_PATTERNS = [
|
|
3974
4293
|
String.raw`\bD-[0-9]{3}\b`,
|
|
@@ -4438,6 +4757,223 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
|
|
|
4438
4757
|
}
|
|
4439
4758
|
return findings;
|
|
4440
4759
|
}
|
|
4760
|
+
// ../../packages/aeg-core/src/log/schema.ts
|
|
4761
|
+
import { z } from "zod";
|
|
4762
|
+
var ROLE_VALUES = [
|
|
4763
|
+
"planner",
|
|
4764
|
+
"developer",
|
|
4765
|
+
"code-reviewer",
|
|
4766
|
+
"security",
|
|
4767
|
+
"principal",
|
|
4768
|
+
"archivist",
|
|
4769
|
+
"architect"
|
|
4770
|
+
];
|
|
4771
|
+
var RoleSchema = z.enum(ROLE_VALUES);
|
|
4772
|
+
var HOST_VALUES = ["hook", "ci", "cli", "loop"];
|
|
4773
|
+
var HostSchema = z.enum(HOST_VALUES);
|
|
4774
|
+
var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
4775
|
+
var HeaderMetaSchema = z.object({
|
|
4776
|
+
schema: z.literal(1),
|
|
4777
|
+
ts: z.string(),
|
|
4778
|
+
run_id: z.string().regex(RUN_ID_PATTERN),
|
|
4779
|
+
seq: z.number().int().nonnegative(),
|
|
4780
|
+
repo: z.string().nullable(),
|
|
4781
|
+
vinaya: z.string(),
|
|
4782
|
+
doctrine: z.string(),
|
|
4783
|
+
host: HostSchema,
|
|
4784
|
+
machine: z.string()
|
|
4785
|
+
}).strict();
|
|
4786
|
+
var SubjectSchema = z.object({
|
|
4787
|
+
issue: z.number().int().nullable(),
|
|
4788
|
+
pr: z.number().int().optional(),
|
|
4789
|
+
sha: z.string().optional(),
|
|
4790
|
+
role: z.union([RoleSchema, z.literal("unattributed")]),
|
|
4791
|
+
round: z.number().int().optional(),
|
|
4792
|
+
objectives_version: z.string().optional()
|
|
4793
|
+
}).strict();
|
|
4794
|
+
var HeaderSchema = z.object({
|
|
4795
|
+
meta: HeaderMetaSchema,
|
|
4796
|
+
subject: SubjectSchema
|
|
4797
|
+
}).strict();
|
|
4798
|
+
var envelopeTail = {
|
|
4799
|
+
duration_ms: z.number().nonnegative().optional(),
|
|
4800
|
+
payload: z.object({}).strict()
|
|
4801
|
+
};
|
|
4802
|
+
var DispatchOutcomeSchema = z.discriminatedUnion("type", [
|
|
4803
|
+
z.object({ type: z.literal("pr_opened"), pr: z.number().int(), head: z.string() }).strict(),
|
|
4804
|
+
z.object({
|
|
4805
|
+
type: z.literal("round_pushed"),
|
|
4806
|
+
pr: z.number().int(),
|
|
4807
|
+
head: z.string(),
|
|
4808
|
+
comment_id: z.number().int()
|
|
4809
|
+
}).strict(),
|
|
4810
|
+
z.object({
|
|
4811
|
+
type: z.literal("verdict"),
|
|
4812
|
+
verdict: z.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
|
|
4813
|
+
head: z.string(),
|
|
4814
|
+
comment_id: z.number().int(),
|
|
4815
|
+
objectives: z.array(z.object({ id: z.string(), met: z.boolean() }).strict()),
|
|
4816
|
+
findings: z.array(z.object({ id: z.string(), severity: z.string(), state: z.string().optional() }).strict())
|
|
4817
|
+
}).strict(),
|
|
4818
|
+
z.object({
|
|
4819
|
+
type: z.literal("escalation"),
|
|
4820
|
+
class: z.enum(["authority", "strategy", "product"]),
|
|
4821
|
+
comment_id: z.number().int()
|
|
4822
|
+
}).strict(),
|
|
4823
|
+
z.object({ type: z.literal("brief"), comment_id: z.number().int(), hash: z.string() }).strict(),
|
|
4824
|
+
z.object({ type: z.literal("plan"), issues: z.array(z.number().int()) }).strict(),
|
|
4825
|
+
z.object({ type: z.literal("archive"), provenance_comment_id: z.number().int() }).strict()
|
|
4826
|
+
]);
|
|
4827
|
+
var dispatchShared = {
|
|
4828
|
+
meta: HeaderMetaSchema,
|
|
4829
|
+
subject: SubjectSchema,
|
|
4830
|
+
kind: z.literal("dispatch"),
|
|
4831
|
+
...envelopeTail,
|
|
4832
|
+
target_role: RoleSchema,
|
|
4833
|
+
model: z.string(),
|
|
4834
|
+
round: z.number().int().optional(),
|
|
4835
|
+
effect_id: z.string()
|
|
4836
|
+
};
|
|
4837
|
+
var dispatchUsageField = z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable();
|
|
4838
|
+
var DispatchEventSchema = z.discriminatedUnion("event", [
|
|
4839
|
+
z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
|
|
4840
|
+
z.object({
|
|
4841
|
+
...dispatchShared,
|
|
4842
|
+
event: z.literal("outcome_received"),
|
|
4843
|
+
outcome: DispatchOutcomeSchema,
|
|
4844
|
+
usage: dispatchUsageField
|
|
4845
|
+
}).strict(),
|
|
4846
|
+
z.object({
|
|
4847
|
+
...dispatchShared,
|
|
4848
|
+
event: z.literal("dispatch_failed"),
|
|
4849
|
+
reason: z.enum(["timeout", "crash", "refused", "unattributed_write"]),
|
|
4850
|
+
usage: dispatchUsageField
|
|
4851
|
+
}).strict()
|
|
4852
|
+
]);
|
|
4853
|
+
var loopShared = {
|
|
4854
|
+
meta: HeaderMetaSchema,
|
|
4855
|
+
subject: SubjectSchema,
|
|
4856
|
+
kind: z.literal("dev_review_loop"),
|
|
4857
|
+
...envelopeTail,
|
|
4858
|
+
loop_id: z.string()
|
|
4859
|
+
};
|
|
4860
|
+
var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
|
|
4861
|
+
z.object({
|
|
4862
|
+
...loopShared,
|
|
4863
|
+
event: z.literal("loop_started"),
|
|
4864
|
+
task: z.number().int(),
|
|
4865
|
+
policy: z.object({
|
|
4866
|
+
max_rounds: z.number().int().nonnegative(),
|
|
4867
|
+
reviewers: z.array(RoleSchema),
|
|
4868
|
+
models: z.record(RoleSchema, z.string())
|
|
4869
|
+
}).strict()
|
|
4870
|
+
}).strict(),
|
|
4871
|
+
z.object({ ...loopShared, event: z.literal("round_started"), round: z.number().int(), base_head: z.string() }).strict(),
|
|
4872
|
+
z.object({
|
|
4873
|
+
...loopShared,
|
|
4874
|
+
event: z.literal("gate_result_read"),
|
|
4875
|
+
round: z.number().int(),
|
|
4876
|
+
head: z.string(),
|
|
4877
|
+
green: z.boolean()
|
|
4878
|
+
}).strict(),
|
|
4879
|
+
z.object({
|
|
4880
|
+
...loopShared,
|
|
4881
|
+
event: z.literal("verdicts_read"),
|
|
4882
|
+
round: z.number().int(),
|
|
4883
|
+
head: z.string(),
|
|
4884
|
+
all_approve: z.boolean(),
|
|
4885
|
+
blockers: z.number().int().nonnegative()
|
|
4886
|
+
}).strict(),
|
|
4887
|
+
z.object({
|
|
4888
|
+
...loopShared,
|
|
4889
|
+
event: z.literal("findings_compared"),
|
|
4890
|
+
round: z.number().int(),
|
|
4891
|
+
open: z.array(z.string()),
|
|
4892
|
+
resolved: z.array(z.string()),
|
|
4893
|
+
new: z.array(z.string()),
|
|
4894
|
+
recurring: z.array(z.string())
|
|
4895
|
+
}).strict(),
|
|
4896
|
+
z.object({
|
|
4897
|
+
...loopShared,
|
|
4898
|
+
event: z.literal("stop_condition_met"),
|
|
4899
|
+
round: z.number().int(),
|
|
4900
|
+
condition: z.enum([
|
|
4901
|
+
"green",
|
|
4902
|
+
"max_rounds",
|
|
4903
|
+
"no_progress",
|
|
4904
|
+
"escalated",
|
|
4905
|
+
"principal_stop",
|
|
4906
|
+
"confidence",
|
|
4907
|
+
"reappearance"
|
|
4908
|
+
])
|
|
4909
|
+
}).strict(),
|
|
4910
|
+
z.object({
|
|
4911
|
+
...loopShared,
|
|
4912
|
+
event: z.literal("paused"),
|
|
4913
|
+
round: z.number().int(),
|
|
4914
|
+
reason: z.enum(["escalation", "principal_item", "refreeze_needed"])
|
|
4915
|
+
}).strict(),
|
|
4916
|
+
z.object({
|
|
4917
|
+
...loopShared,
|
|
4918
|
+
event: z.literal("resumed"),
|
|
4919
|
+
round: z.number().int(),
|
|
4920
|
+
by: z.literal("principal")
|
|
4921
|
+
}).strict(),
|
|
4922
|
+
z.object({
|
|
4923
|
+
...loopShared,
|
|
4924
|
+
event: z.literal("round_ended"),
|
|
4925
|
+
round: z.number().int(),
|
|
4926
|
+
base_head: z.string(),
|
|
4927
|
+
head: z.string(),
|
|
4928
|
+
files_changed: z.number().int().nonnegative(),
|
|
4929
|
+
insertions: z.number().int().nonnegative(),
|
|
4930
|
+
deletions: z.number().int().nonnegative(),
|
|
4931
|
+
wall_ms: z.number().nonnegative(),
|
|
4932
|
+
outcome: z.enum(["green", "changes_requested", "escalated"])
|
|
4933
|
+
}).strict(),
|
|
4934
|
+
z.object({
|
|
4935
|
+
...loopShared,
|
|
4936
|
+
event: z.literal("journal_finalized"),
|
|
4937
|
+
rounds: z.number().int().nonnegative(),
|
|
4938
|
+
total_wall_ms: z.number().nonnegative(),
|
|
4939
|
+
time_to_green_ms: z.number().nonnegative().nullable(),
|
|
4940
|
+
files_changed_total: z.number().int().nonnegative(),
|
|
4941
|
+
final_head: z.string(),
|
|
4942
|
+
result: z.enum(["merged_ready", "stopped"])
|
|
4943
|
+
}).strict()
|
|
4944
|
+
]);
|
|
4945
|
+
var ForgeOpSchema = z.enum([
|
|
4946
|
+
"pr.create",
|
|
4947
|
+
"pr.comment",
|
|
4948
|
+
"pr.body.replace",
|
|
4949
|
+
"pr.refreeze",
|
|
4950
|
+
"issue.create",
|
|
4951
|
+
"issue.edit",
|
|
4952
|
+
"issue.comment",
|
|
4953
|
+
"milestone.create",
|
|
4954
|
+
"milestone.edit",
|
|
4955
|
+
"milestone.close",
|
|
4956
|
+
"label.add",
|
|
4957
|
+
"label.remove"
|
|
4958
|
+
]);
|
|
4959
|
+
var ForgeWriteTargetSchema = z.object({
|
|
4960
|
+
issue: z.number().int().optional(),
|
|
4961
|
+
pr: z.number().int().optional()
|
|
4962
|
+
}).strict();
|
|
4963
|
+
var forgeWriteShared = {
|
|
4964
|
+
meta: HeaderMetaSchema,
|
|
4965
|
+
subject: SubjectSchema,
|
|
4966
|
+
kind: z.literal("forge_write"),
|
|
4967
|
+
...envelopeTail,
|
|
4968
|
+
op: ForgeOpSchema,
|
|
4969
|
+
target: ForgeWriteTargetSchema
|
|
4970
|
+
};
|
|
4971
|
+
var ForgeWriteEventSchema = z.discriminatedUnion("event", [
|
|
4972
|
+
z.object({ ...forgeWriteShared, event: z.literal("validated") }).strict(),
|
|
4973
|
+
z.object({ ...forgeWriteShared, event: z.literal("refused"), reason: z.string() }).strict(),
|
|
4974
|
+
z.object({ ...forgeWriteShared, event: z.literal("written"), comment_ids: z.array(z.string()) }).strict()
|
|
4975
|
+
]);
|
|
4976
|
+
var LogEventSchema = z.union([DispatchEventSchema, DevReviewLoopEventSchema, ForgeWriteEventSchema]);
|
|
4441
4977
|
// src/checks/contract.ts
|
|
4442
4978
|
var CHECK_SCHEMA_VERSION = 1;
|
|
4443
4979
|
function emitCheckError(error) {
|
|
@@ -4451,8 +4987,8 @@ function missingSectionError(checkName) {
|
|
|
4451
4987
|
schema: CHECK_SCHEMA_VERSION,
|
|
4452
4988
|
check: checkName,
|
|
4453
4989
|
severity: "error",
|
|
4454
|
-
message: 'token-report:
|
|
4455
|
-
agent_recovery_prompt:
|
|
4990
|
+
message: 'token-report: the PR body carries no "Token report" row at all — presence is checked here, ' + "never whether any figures reported are correct. An operator-metered host that genuinely " + "cannot meter this turn still states that in a row (`tranche-model.md` §12) rather than by " + "omitting the section: a merged task must never leave the ledger with a silent hole where its " + "spend should be.",
|
|
4991
|
+
agent_recovery_prompt: 'Paste a "## Token report" section into the PR body with at least one row (see ' + "roles/developer.md's token-reporting obligation) — real figures if this host exposes them, " + "or `—` cells if it genuinely does not, then re-run `vinaya check token-report`."
|
|
4456
4992
|
};
|
|
4457
4993
|
}
|
|
4458
4994
|
function blankCellError(checkName, entry2) {
|
|
@@ -4464,14 +5000,17 @@ function blankCellError(checkName, entry2) {
|
|
|
4464
5000
|
agent_recovery_prompt: "Replace the blank/`—`/non-numeric Tokens in/out cell with the real figure this host reports, " + "then re-run `vinaya check token-report`. Never write `—` for a reason other than the host " + "genuinely exposing no usage figure at all — see roles/developer.md's token-reporting section."
|
|
4465
5001
|
};
|
|
4466
5002
|
}
|
|
4467
|
-
function evaluateTokenReportEnforcement(checkName, capability, prBody) {
|
|
5003
|
+
function evaluateTokenReportEnforcement(checkName, capability, prBody, isTaskPr) {
|
|
4468
5004
|
if (!prBody)
|
|
4469
5005
|
return { pass: true };
|
|
4470
|
-
if (!capability.capable)
|
|
4471
|
-
return { pass: true };
|
|
4472
5006
|
const entries = parseTokenReportEntries(prBody);
|
|
4473
|
-
if (entries.length === 0)
|
|
5007
|
+
if (entries.length === 0) {
|
|
5008
|
+
if (!isTaskPr)
|
|
5009
|
+
return { pass: true };
|
|
4474
5010
|
return { pass: false, error: missingSectionError(checkName) };
|
|
5011
|
+
}
|
|
5012
|
+
if (!capability.capable)
|
|
5013
|
+
return { pass: true };
|
|
4475
5014
|
const badEntry = entries.find((e) => e.tokensIn === null || e.tokensOut === null);
|
|
4476
5015
|
if (badEntry)
|
|
4477
5016
|
return { pass: false, error: blankCellError(checkName, badEntry) };
|
|
@@ -4505,7 +5044,9 @@ function main() {
|
|
|
4505
5044
|
};
|
|
4506
5045
|
const capability = resolveMeteringCapability(deps);
|
|
4507
5046
|
const prBody = process.env.PR_BODY ?? "";
|
|
4508
|
-
const
|
|
5047
|
+
const branch = process.env.BRANCH ?? "";
|
|
5048
|
+
const isTaskPr = isTaskBranch(branch);
|
|
5049
|
+
const result = evaluateTokenReportEnforcement(CHECK_NAME, capability, prBody, isTaskPr);
|
|
4509
5050
|
if (!result.pass) {
|
|
4510
5051
|
emitCheckError(result.error);
|
|
4511
5052
|
process.exit(1);
|