@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
|
@@ -24,6 +24,15 @@ var GATE_AUDIENCE = {
|
|
|
24
24
|
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."
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
|
+
var NON_GATE_BINS = [
|
|
28
|
+
"archive-task",
|
|
29
|
+
"assign-task-issue",
|
|
30
|
+
"dead-branch-audit",
|
|
31
|
+
"eval-agent-compliance",
|
|
32
|
+
"open-issue",
|
|
33
|
+
"open-pr",
|
|
34
|
+
"report-tokens"
|
|
35
|
+
];
|
|
27
36
|
function isShipped(a) {
|
|
28
37
|
return "shippedAs" in a;
|
|
29
38
|
}
|
|
@@ -384,7 +393,7 @@ var LABELS = [
|
|
|
384
393
|
id: "vinaya/needs:brief-correction",
|
|
385
394
|
category: "needs",
|
|
386
395
|
form: "literal",
|
|
387
|
-
carries: "Waiting on the
|
|
396
|
+
carries: "Waiting on the Planner — the brief contradicts the surface it describes."
|
|
388
397
|
},
|
|
389
398
|
{
|
|
390
399
|
key: "waiver-docs",
|
|
@@ -1935,13 +1944,18 @@ function parseInlineFieldList(section) {
|
|
|
1935
1944
|
}
|
|
1936
1945
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
1937
1946
|
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
1938
|
-
|
|
1947
|
+
var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1948
|
+
function firstFiveLines(comment) {
|
|
1939
1949
|
return comment.split(`
|
|
1940
|
-
`).slice(0,
|
|
1950
|
+
`).slice(0, 5).join(`
|
|
1941
1951
|
`);
|
|
1942
1952
|
}
|
|
1943
1953
|
function extractHeadSha(comment) {
|
|
1944
|
-
const m =
|
|
1954
|
+
const m = firstFiveLines(comment).match(HEAD_SHA_PATTERN);
|
|
1955
|
+
return m ? m[1].toLowerCase() : null;
|
|
1956
|
+
}
|
|
1957
|
+
function extractObjectivesVersion(comment) {
|
|
1958
|
+
const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
|
|
1945
1959
|
return m ? m[1].toLowerCase() : null;
|
|
1946
1960
|
}
|
|
1947
1961
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
@@ -1950,21 +1964,24 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1950
1964
|
return {
|
|
1951
1965
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
1952
1966
|
headSha: null,
|
|
1967
|
+
objectivesVersion: null,
|
|
1953
1968
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1954
1969
|
};
|
|
1955
1970
|
}
|
|
1956
1971
|
const latest = candidates[candidates.length - 1];
|
|
1957
|
-
const m =
|
|
1972
|
+
const m = firstFiveLines(latest).match(valuePattern);
|
|
1958
1973
|
if (!m) {
|
|
1959
1974
|
return {
|
|
1960
|
-
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first
|
|
1975
|
+
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
|
|
1961
1976
|
headSha: null,
|
|
1962
|
-
|
|
1977
|
+
objectivesVersion: null,
|
|
1978
|
+
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
|
|
1963
1979
|
};
|
|
1964
1980
|
}
|
|
1965
1981
|
return {
|
|
1966
1982
|
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
1967
1983
|
headSha: extractHeadSha(latest),
|
|
1984
|
+
objectivesVersion: extractObjectivesVersion(latest),
|
|
1968
1985
|
danglingNote: null
|
|
1969
1986
|
};
|
|
1970
1987
|
}
|
|
@@ -2321,8 +2338,105 @@ function overrideActive(opts) {
|
|
|
2321
2338
|
return true;
|
|
2322
2339
|
return false;
|
|
2323
2340
|
}
|
|
2324
|
-
// ../../packages/aeg-core/src/
|
|
2341
|
+
// ../../packages/aeg-core/src/objectives.ts
|
|
2325
2342
|
import { createHash as createHash2 } from "node:crypto";
|
|
2343
|
+
var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
|
|
2344
|
+
var NEXT_HEADING_RE = /^##[ \t]/m;
|
|
2345
|
+
var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
|
|
2346
|
+
function hasBacktickedPath(text) {
|
|
2347
|
+
let i = 0;
|
|
2348
|
+
while (i < text.length) {
|
|
2349
|
+
const start = text.indexOf("`", i);
|
|
2350
|
+
if (start === -1)
|
|
2351
|
+
return false;
|
|
2352
|
+
const end = text.indexOf("`", start + 1);
|
|
2353
|
+
if (end === -1)
|
|
2354
|
+
return false;
|
|
2355
|
+
if (text.slice(start + 1, end).includes("/"))
|
|
2356
|
+
return true;
|
|
2357
|
+
i = end + 1;
|
|
2358
|
+
}
|
|
2359
|
+
return false;
|
|
2360
|
+
}
|
|
2361
|
+
var MIN_WORDS_OUTSIDE_BACKTICKS = 3;
|
|
2362
|
+
function stripObjectiveBackticks(text) {
|
|
2363
|
+
return text.replace(/`[^`\n]*`/g, " ");
|
|
2364
|
+
}
|
|
2365
|
+
function wordCount(text) {
|
|
2366
|
+
return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
|
|
2367
|
+
}
|
|
2368
|
+
function objectivesSectionText(body) {
|
|
2369
|
+
const heading = HEADING_RE.exec(body);
|
|
2370
|
+
if (!heading)
|
|
2371
|
+
return null;
|
|
2372
|
+
const afterHeading = body.slice(heading.index + heading[0].length);
|
|
2373
|
+
const next = NEXT_HEADING_RE.exec(afterHeading);
|
|
2374
|
+
return next ? afterHeading.slice(0, next.index) : afterHeading;
|
|
2375
|
+
}
|
|
2376
|
+
function hasObjectivesHeading(body) {
|
|
2377
|
+
return HEADING_RE.test(body);
|
|
2378
|
+
}
|
|
2379
|
+
function objectivesOf(body) {
|
|
2380
|
+
const section = objectivesSectionText(body);
|
|
2381
|
+
if (section === null) {
|
|
2382
|
+
return { ok: false, errors: ["no `## Objectives` heading found in the body."] };
|
|
2383
|
+
}
|
|
2384
|
+
const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
2385
|
+
if (lines.length === 0) {
|
|
2386
|
+
return { ok: false, errors: ["the `## Objectives` section has no `O<n>.` lines."] };
|
|
2387
|
+
}
|
|
2388
|
+
const objectives = [];
|
|
2389
|
+
const errors = [];
|
|
2390
|
+
for (const line of lines) {
|
|
2391
|
+
const m = OBJECTIVE_LINE_RE.exec(line);
|
|
2392
|
+
if (!m) {
|
|
2393
|
+
errors.push(`"${line}" is not a well-formed objective line — expected \`O<n>. <sentence>\`, e.g. \`O1. …\`.`);
|
|
2394
|
+
continue;
|
|
2395
|
+
}
|
|
2396
|
+
const n = m[1];
|
|
2397
|
+
const text = m[2].trim();
|
|
2398
|
+
if (text.length === 0) {
|
|
2399
|
+
errors.push(`O${n} has no sentence — every objective is one observable sentence.`);
|
|
2400
|
+
continue;
|
|
2401
|
+
}
|
|
2402
|
+
if (hasBacktickedPath(text) && wordCount(stripObjectiveBackticks(text)) < MIN_WORDS_OUTSIDE_BACKTICKS) {
|
|
2403
|
+
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).`);
|
|
2404
|
+
continue;
|
|
2405
|
+
}
|
|
2406
|
+
objectives.push({ id: `O${n}`, text });
|
|
2407
|
+
}
|
|
2408
|
+
for (let i = 0;i < objectives.length; i++) {
|
|
2409
|
+
const expected = i + 1;
|
|
2410
|
+
const actual = Number.parseInt(objectives[i].id.slice(1), 10);
|
|
2411
|
+
if (actual !== expected) {
|
|
2412
|
+
errors.push(`numbering is not contiguous from O1 — expected O${expected}, found ${objectives[i].id}.`);
|
|
2413
|
+
break;
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
if (errors.length > 0)
|
|
2417
|
+
return { ok: false, errors };
|
|
2418
|
+
return { ok: true, objectives };
|
|
2419
|
+
}
|
|
2420
|
+
function normalizeText(text) {
|
|
2421
|
+
return text.trim().replace(/\s+/g, " ");
|
|
2422
|
+
}
|
|
2423
|
+
function objectivesVersion(objectives) {
|
|
2424
|
+
const normalized = objectives.map((o) => `${o.id} ${normalizeText(o.text)}`).join(`
|
|
2425
|
+
`);
|
|
2426
|
+
return createHash2("sha256").update(normalized).digest("hex");
|
|
2427
|
+
}
|
|
2428
|
+
function isIssueNotFoundError(err) {
|
|
2429
|
+
const stderr = err?.stderr;
|
|
2430
|
+
const haystack = [
|
|
2431
|
+
err?.message ?? "",
|
|
2432
|
+
typeof stderr === "string" ? stderr : stderr?.toString() ?? ""
|
|
2433
|
+
].join(`
|
|
2434
|
+
`);
|
|
2435
|
+
return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
// ../../packages/aeg-core/src/premise-check.ts
|
|
2439
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2326
2440
|
var ASSERTION_KINDS = new Set(["contains", "absent", "sha256"]);
|
|
2327
2441
|
function isPremiseHeader(line) {
|
|
2328
2442
|
const stripped = line.replace(/[*#]/g, "").trim();
|
|
@@ -2393,7 +2507,7 @@ function checkPremises(assertions, fileReader) {
|
|
|
2393
2507
|
} else if (a.kind === "absent" && content.includes(a.value)) {
|
|
2394
2508
|
failures.push(`premise-check: ${a.path} now contains "${a.value}", but the brief pinned it absent — the premise has moved; re-dig before proceeding.`);
|
|
2395
2509
|
} else if (a.kind === "sha256") {
|
|
2396
|
-
const actual =
|
|
2510
|
+
const actual = createHash3("sha256").update(content).digest("hex");
|
|
2397
2511
|
if (actual !== a.value.toLowerCase()) {
|
|
2398
2512
|
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.`);
|
|
2399
2513
|
}
|
|
@@ -2498,18 +2612,14 @@ function checkTestPlanExclusivity(prBody) {
|
|
|
2498
2612
|
function checkPrincipalPlaceholder(prBody) {
|
|
2499
2613
|
const region = testPlanRegion(prBody);
|
|
2500
2614
|
const lineRe = /^-\s*\[[ xX]\]\s*\*{2}\[principal\]\*{2}(.*)$/gim;
|
|
2615
|
+
const errors = [];
|
|
2501
2616
|
for (const m of region.matchAll(lineRe)) {
|
|
2502
2617
|
const content = m[1] ?? "";
|
|
2503
2618
|
if (/^\s*None\b/i.test(content)) {
|
|
2504
|
-
|
|
2505
|
-
status: "fail",
|
|
2506
|
-
errors: [
|
|
2507
|
-
'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.'
|
|
2508
|
-
]
|
|
2509
|
-
};
|
|
2619
|
+
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.`);
|
|
2510
2620
|
}
|
|
2511
2621
|
}
|
|
2512
|
-
return { status: "pass", errors: [] };
|
|
2622
|
+
return errors.length > 0 ? { status: "fail", errors } : { status: "pass", errors: [] };
|
|
2513
2623
|
}
|
|
2514
2624
|
function checkSurfaceMap(prBody) {
|
|
2515
2625
|
return headingCheck(prBody, "(?:technical\\s+)?surface map", "Technical surface map");
|
|
@@ -2754,8 +2864,55 @@ function checkDefeatCases(prBody) {
|
|
|
2754
2864
|
]
|
|
2755
2865
|
};
|
|
2756
2866
|
}
|
|
2867
|
+
var PART_CITATION_RE = /Part\s+\d+\s*\(([^)]*)\)/gi;
|
|
2868
|
+
var OBJECTIVE_REF_RE = /O(\d+)/g;
|
|
2869
|
+
function citedObjectiveIds(section6) {
|
|
2870
|
+
const ids = new Set;
|
|
2871
|
+
for (const m of section6.matchAll(PART_CITATION_RE)) {
|
|
2872
|
+
for (const r of m[1].matchAll(OBJECTIVE_REF_RE))
|
|
2873
|
+
ids.add(Number.parseInt(r[1], 10));
|
|
2874
|
+
}
|
|
2875
|
+
return ids;
|
|
2876
|
+
}
|
|
2877
|
+
function checkObjectivesCopy(prBody, issueObjectives) {
|
|
2878
|
+
const parsed = objectivesOf(prBody);
|
|
2879
|
+
if (!parsed.ok) {
|
|
2880
|
+
return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives copy: ${e}`) };
|
|
2881
|
+
}
|
|
2882
|
+
if (objectivesVersion(parsed.objectives) !== objectivesVersion(issueObjectives)) {
|
|
2883
|
+
return {
|
|
2884
|
+
status: "fail",
|
|
2885
|
+
errors: [
|
|
2886
|
+
"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)."
|
|
2887
|
+
]
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2890
|
+
return { status: "pass", errors: [] };
|
|
2891
|
+
}
|
|
2892
|
+
function checkObjectivesCoverage(prBody) {
|
|
2893
|
+
const parsed = objectivesOf(prBody);
|
|
2894
|
+
if (!parsed.ok) {
|
|
2895
|
+
return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives coverage: ${e}`) };
|
|
2896
|
+
}
|
|
2897
|
+
const declaredIds = new Set(parsed.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
|
|
2898
|
+
const section6 = extractNumberedSection(prBody, 6) ?? "";
|
|
2899
|
+
const cited = citedObjectiveIds(section6);
|
|
2900
|
+
const maxDeclared = Math.max(...declaredIds, 0);
|
|
2901
|
+
const errors = [];
|
|
2902
|
+
for (const id of declaredIds) {
|
|
2903
|
+
if (!cited.has(id)) {
|
|
2904
|
+
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.`);
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
for (const id of cited) {
|
|
2908
|
+
if (!declaredIds.has(id)) {
|
|
2909
|
+
errors.push(`brief-validation objectives coverage: a Part in §6 cites O${id}, but the Objectives section ends at O${maxDeclared}.`);
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
|
|
2913
|
+
}
|
|
2757
2914
|
function checkBriefSections(prBody, readTier, options = {}) {
|
|
2758
|
-
const { requireClosesN = true, consumersOf = () => [] } = options;
|
|
2915
|
+
const { requireClosesN = true, consumersOf = () => [], issueObjectives } = options;
|
|
2759
2916
|
const results = [
|
|
2760
2917
|
checkTierField(prBody, readTier),
|
|
2761
2918
|
checkTestPlan(prBody),
|
|
@@ -2773,10 +2930,23 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2773
2930
|
checkCommandsCarryOutput(prBody),
|
|
2774
2931
|
checkConsumerTests(prBody, consumersOf),
|
|
2775
2932
|
checkDefeatCases(prBody),
|
|
2933
|
+
...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
|
|
2776
2934
|
...requireClosesN ? [checkClosesN(prBody)] : []
|
|
2777
2935
|
];
|
|
2778
2936
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2779
2937
|
}
|
|
2938
|
+
var AEG_BRIEF_V1_MARKER = "<!-- aeg:brief:v1 -->";
|
|
2939
|
+
function contentAfterTwoLines(body) {
|
|
2940
|
+
const first = body.indexOf(`
|
|
2941
|
+
`);
|
|
2942
|
+
if (first === -1)
|
|
2943
|
+
return "";
|
|
2944
|
+
const second = body.indexOf(`
|
|
2945
|
+
`, first + 1);
|
|
2946
|
+
if (second === -1)
|
|
2947
|
+
return "";
|
|
2948
|
+
return body.slice(second + 1);
|
|
2949
|
+
}
|
|
2780
2950
|
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
2781
2951
|
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
2782
2952
|
var STATIC_PORTABLE_PREFIXES = [
|
|
@@ -2971,14 +3141,14 @@ function checkSectionDensity(prBody, heading) {
|
|
|
2971
3141
|
]
|
|
2972
3142
|
};
|
|
2973
3143
|
}
|
|
2974
|
-
function
|
|
2975
|
-
return checkSectionDensity(prBody, "
|
|
3144
|
+
function checkDecisionsDensity(prBody) {
|
|
3145
|
+
return checkSectionDensity(prBody, "Decisions");
|
|
2976
3146
|
}
|
|
2977
3147
|
function checkScopeDensity(prBody) {
|
|
2978
3148
|
return checkSectionDensity(prBody, "Scope");
|
|
2979
3149
|
}
|
|
2980
3150
|
function checkPrReportDensity(prBody) {
|
|
2981
|
-
const results = [
|
|
3151
|
+
const results = [checkDecisionsDensity(prBody), checkScopeDensity(prBody)];
|
|
2982
3152
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2983
3153
|
}
|
|
2984
3154
|
// ../../packages/aeg-core/src/issue-validation.ts
|
|
@@ -3010,6 +3180,75 @@ function checkIssueRationale(body) {
|
|
|
3010
3180
|
}
|
|
3011
3181
|
return { status: errors.length > 0 ? "fail" : "pass", errors };
|
|
3012
3182
|
}
|
|
3183
|
+
var OBJECTIVES_SINCE_ISSUE = 404;
|
|
3184
|
+
function checkIssueObjectives(body, issueNumber) {
|
|
3185
|
+
if (issueNumber !== null && issueNumber < OBJECTIVES_SINCE_ISSUE)
|
|
3186
|
+
return { status: "pass", errors: [] };
|
|
3187
|
+
const result = objectivesOf(body);
|
|
3188
|
+
if (result.ok)
|
|
3189
|
+
return { status: "pass", errors: [] };
|
|
3190
|
+
return { status: "fail", errors: result.errors.map((e) => `issue-validation objectives: ${e}`) };
|
|
3191
|
+
}
|
|
3192
|
+
function topLevelSectionText(body, headingName) {
|
|
3193
|
+
const headingRe = new RegExp(`^##[ \\t]*${headingName}[ \\t]*$`, "im");
|
|
3194
|
+
const heading = headingRe.exec(body);
|
|
3195
|
+
if (!heading)
|
|
3196
|
+
return null;
|
|
3197
|
+
const afterHeading = body.slice(heading.index + heading[0].length);
|
|
3198
|
+
const next = /^##[ \t]/m.exec(afterHeading);
|
|
3199
|
+
return next ? afterHeading.slice(0, next.index) : afterHeading;
|
|
3200
|
+
}
|
|
3201
|
+
function looksLikeFilePath(entry2) {
|
|
3202
|
+
const stripped = entry2.replace(/\/\*\*?$/, "");
|
|
3203
|
+
const lastSegment = stripped.split("/").pop() ?? stripped;
|
|
3204
|
+
const extMatch = /\.[A-Za-z0-9]{1,6}$/.exec(lastSegment);
|
|
3205
|
+
return extMatch !== null && extMatch.index > 0;
|
|
3206
|
+
}
|
|
3207
|
+
function splitGlobList(raw) {
|
|
3208
|
+
return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
3209
|
+
}
|
|
3210
|
+
function parseIssueSurface(body) {
|
|
3211
|
+
const section = topLevelSectionText(body, "Surface");
|
|
3212
|
+
if (section === null)
|
|
3213
|
+
return { ok: false, errors: ["no `## Surface` heading found in the body."] };
|
|
3214
|
+
const inLine = /^in:\s*(.+)$/im.exec(section);
|
|
3215
|
+
const outLine = /^out:\s*(.+)$/im.exec(section);
|
|
3216
|
+
const errors = [];
|
|
3217
|
+
if (!inLine)
|
|
3218
|
+
errors.push("`## Surface` has no `in:` line.");
|
|
3219
|
+
if (!outLine)
|
|
3220
|
+
errors.push("`## Surface` has no `out:` line.");
|
|
3221
|
+
if (errors.length > 0)
|
|
3222
|
+
return { ok: false, errors };
|
|
3223
|
+
const inGlobs = splitGlobList(inLine[1]);
|
|
3224
|
+
const outGlobs = splitGlobList(outLine[1]);
|
|
3225
|
+
for (const g of [...inGlobs, ...outGlobs]) {
|
|
3226
|
+
if (looksLikeFilePath(g)) {
|
|
3227
|
+
errors.push(`\`${g}\` in \`## Surface\` looks like a file path — Surface entries are directory-level globs, never file paths.`);
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
if (inGlobs.length === 0)
|
|
3231
|
+
errors.push("`## Surface`'s `in:` line resolved to zero globs.");
|
|
3232
|
+
if (errors.length > 0)
|
|
3233
|
+
return { ok: false, errors };
|
|
3234
|
+
return { ok: true, value: { in: inGlobs, out: outGlobs } };
|
|
3235
|
+
}
|
|
3236
|
+
function globCoversPath(glob, path) {
|
|
3237
|
+
const g = glob.replace(/\/\*\*?$/, "").replace(/\/+$/, "");
|
|
3238
|
+
const p = path.replace(/\/+$/, "");
|
|
3239
|
+
return g === p || g.startsWith(`${p}/`) || p.startsWith(`${g}/`);
|
|
3240
|
+
}
|
|
3241
|
+
function checkSurfaceScope(changedFiles, outGlobs) {
|
|
3242
|
+
if (outGlobs.length === 0)
|
|
3243
|
+
return { ok: true };
|
|
3244
|
+
const violations = [];
|
|
3245
|
+
for (const file of changedFiles) {
|
|
3246
|
+
const glob = outGlobs.find((g) => globCoversPath(g, file));
|
|
3247
|
+
if (glob)
|
|
3248
|
+
violations.push({ file, glob });
|
|
3249
|
+
}
|
|
3250
|
+
return violations.length > 0 ? { ok: false, violations } : { ok: true };
|
|
3251
|
+
}
|
|
3013
3252
|
function isTaskIssueLabelSet(labels) {
|
|
3014
3253
|
return hasLabel("tranche", labels);
|
|
3015
3254
|
}
|
|
@@ -3074,6 +3313,8 @@ function checkProjectsRegistered(body, _labels, registeredNames) {
|
|
|
3074
3313
|
}
|
|
3075
3314
|
return errors.length > 0 ? { status: "fail", errors } : { status: "pass", errors: [] };
|
|
3076
3315
|
}
|
|
3316
|
+
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;
|
|
3317
|
+
var DOC_PATH_RE_GLOBAL = new RegExp(DOC_PATH_RE.source, "gi");
|
|
3077
3318
|
|
|
3078
3319
|
// ../../packages/aeg-core/src/coherence-checks.ts
|
|
3079
3320
|
var COHERENCE_ENFORCED_FROM = "2026-07-01";
|
|
@@ -3271,7 +3512,8 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
|
|
|
3271
3512
|
continue;
|
|
3272
3513
|
const errors = [
|
|
3273
3514
|
...checkIssueRationale(issue.body).errors,
|
|
3274
|
-
...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors
|
|
3515
|
+
...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
|
|
3516
|
+
...checkIssueObjectives(issue.body, issue.number).errors
|
|
3275
3517
|
];
|
|
3276
3518
|
if (errors.length === 0)
|
|
3277
3519
|
continue;
|
|
@@ -3386,6 +3628,27 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
3386
3628
|
}
|
|
3387
3629
|
return { ok: true, expectedIssue };
|
|
3388
3630
|
}
|
|
3631
|
+
// ../../packages/aeg-core/src/archive-task.ts
|
|
3632
|
+
function closesRefs(text) {
|
|
3633
|
+
return [...new Set([...text.matchAll(/Closes #(\d+)/gi)].map((m) => Number(m[1])))];
|
|
3634
|
+
}
|
|
3635
|
+
function extractIssue(body) {
|
|
3636
|
+
const stripped = stripCode(body);
|
|
3637
|
+
const bodyNums = closesRefs(stripped);
|
|
3638
|
+
const anchored = anchoredRegion(stripped, "CLOSES");
|
|
3639
|
+
if (anchored !== null) {
|
|
3640
|
+
const anchorNums = closesRefs(anchored);
|
|
3641
|
+
if (anchorNums.length === 0)
|
|
3642
|
+
return { issue: null, extraIssues: bodyNums, outsideHeader: false };
|
|
3643
|
+
const issue2 = anchorNums[0];
|
|
3644
|
+
return { issue: issue2, extraIssues: bodyNums.filter((n) => n !== issue2), outsideHeader: false };
|
|
3645
|
+
}
|
|
3646
|
+
const headerNums = closesRefs(headerRegion(stripped));
|
|
3647
|
+
if (bodyNums.length === 0)
|
|
3648
|
+
return { issue: null, extraIssues: [], outsideHeader: false };
|
|
3649
|
+
const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
|
|
3650
|
+
return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
|
|
3651
|
+
}
|
|
3389
3652
|
// ../../packages/aeg-core/src/review-gate.ts
|
|
3390
3653
|
function isBoundToHead(extraction, headSha) {
|
|
3391
3654
|
if (!extraction.headSha)
|
|
@@ -3404,6 +3667,11 @@ function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
|
|
|
3404
3667
|
function isBoundToPatch(extraction, headSha, patchIdOf) {
|
|
3405
3668
|
return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
|
|
3406
3669
|
}
|
|
3670
|
+
function isBoundToObjectives(extraction, currentVersion) {
|
|
3671
|
+
if (currentVersion === null)
|
|
3672
|
+
return true;
|
|
3673
|
+
return extraction.objectivesVersion === currentVersion;
|
|
3674
|
+
}
|
|
3407
3675
|
function checkReviewGate(input) {
|
|
3408
3676
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
3409
3677
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -3430,7 +3698,9 @@ function checkReviewGate(input) {
|
|
|
3430
3698
|
const securityClean = security.value === "PASS";
|
|
3431
3699
|
const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
|
|
3432
3700
|
const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
|
|
3433
|
-
|
|
3701
|
+
const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
|
|
3702
|
+
const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
|
|
3703
|
+
if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && securityClean && securityBound && securityObjectivesBound && mechanicalChecksClean) {
|
|
3434
3704
|
return {
|
|
3435
3705
|
verdict: "pass",
|
|
3436
3706
|
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.`,
|
|
@@ -3442,11 +3712,15 @@ function checkReviewGate(input) {
|
|
|
3442
3712
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
3443
3713
|
} else if (!codeReviewBound) {
|
|
3444
3714
|
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3715
|
+
} else if (!codeReviewObjectivesBound) {
|
|
3716
|
+
problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3445
3717
|
}
|
|
3446
3718
|
if (!securityClean) {
|
|
3447
3719
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
3448
3720
|
} else if (!securityBound) {
|
|
3449
3721
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3722
|
+
} else if (!securityObjectivesBound) {
|
|
3723
|
+
problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3450
3724
|
}
|
|
3451
3725
|
if (!mechanicalChecksClean) {
|
|
3452
3726
|
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(", ")}`);
|
|
@@ -3570,16 +3844,33 @@ function checkG1(rows, existsFn) {
|
|
|
3570
3844
|
return { check: "G1", status: findings.length > 0 ? "fail" : "pass", findings };
|
|
3571
3845
|
}
|
|
3572
3846
|
var SCAFFOLD_PLACEHOLDER = "[undocumented — fill in why]";
|
|
3847
|
+
var NON_GATE_BIN_NAMES = NON_GATE_BINS;
|
|
3848
|
+
var TWIN_CANDIDATE_EXCEPTIONS = {
|
|
3849
|
+
"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)',
|
|
3850
|
+
"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)`,
|
|
3851
|
+
"apps/cli/src/checks/bin/check-closes-n.ts": 'Ring 1 "Closes linkage" (implementation: verify-coherence.ts)'
|
|
3852
|
+
};
|
|
3853
|
+
var NON_GATE_HOOK_SCRIPTS = [".claude/hooks/track-transcript.sh"];
|
|
3573
3854
|
function checkG2(rows, candidateFiles) {
|
|
3574
3855
|
const implementations = new Set(rows.map((r) => r.implementation).filter((p) => p !== ""));
|
|
3856
|
+
const claimedNames = new Set(rows.flatMap((r) => claimedCheckNames(r.implementation)));
|
|
3575
3857
|
const findings = [];
|
|
3576
3858
|
for (const path of candidateFiles) {
|
|
3577
|
-
if (
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3859
|
+
if (implementations.has(path))
|
|
3860
|
+
continue;
|
|
3861
|
+
if (path.startsWith(AEG_CORE_BIN_PATH_PREFIX) && NON_GATE_BIN_NAMES.includes(basenameNoExt(path)))
|
|
3862
|
+
continue;
|
|
3863
|
+
if (NON_GATE_HOOK_SCRIPTS.includes(path))
|
|
3864
|
+
continue;
|
|
3865
|
+
if (path in TWIN_CANDIDATE_EXCEPTIONS)
|
|
3866
|
+
continue;
|
|
3867
|
+
const candidateNames = claimedCheckNames(path);
|
|
3868
|
+
if (candidateNames.length > 0 && candidateNames.some((n) => claimedNames.has(n)))
|
|
3869
|
+
continue;
|
|
3870
|
+
findings.push({
|
|
3871
|
+
path,
|
|
3872
|
+
reason: `"${path}" is not named as the implementation of any row in enforcement.md's ring tables`
|
|
3873
|
+
});
|
|
3583
3874
|
}
|
|
3584
3875
|
for (const row of rows) {
|
|
3585
3876
|
const carriesPlaceholder = [row.summary, row.description, row.spec].some((cell) => cell === SCAFFOLD_PLACEHOLDER);
|
|
@@ -3591,7 +3882,7 @@ function checkG2(rows, candidateFiles) {
|
|
|
3591
3882
|
});
|
|
3592
3883
|
}
|
|
3593
3884
|
}
|
|
3594
|
-
return { check: "G2", status: findings.length > 0 ? "
|
|
3885
|
+
return { check: "G2", status: findings.length > 0 ? "fail" : "pass", findings };
|
|
3595
3886
|
}
|
|
3596
3887
|
function checkG3(ring0Rows, crossingFiles) {
|
|
3597
3888
|
const ring0Implementations = new Set(ring0Rows.map((r) => r.implementation));
|
|
@@ -3682,55 +3973,20 @@ function checkG6(rows, registeredCheckNames) {
|
|
|
3682
3973
|
}
|
|
3683
3974
|
return { check: "G6", status: findings.length > 0 ? "fail" : "pass", findings };
|
|
3684
3975
|
}
|
|
3685
|
-
// ../../packages/aeg-core/src/
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
return {
|
|
3696
|
-
verdict: "refuse",
|
|
3697
|
-
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.`
|
|
3698
|
-
};
|
|
3699
|
-
}
|
|
3700
|
-
if (prState === "OPEN") {
|
|
3701
|
-
return {
|
|
3702
|
-
verdict: "allow",
|
|
3703
|
-
reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
|
|
3704
|
-
};
|
|
3705
|
-
}
|
|
3706
|
-
if (prState === "NONE") {
|
|
3707
|
-
return {
|
|
3708
|
-
verdict: "allow",
|
|
3709
|
-
reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
|
|
3710
|
-
};
|
|
3711
|
-
}
|
|
3712
|
-
return {
|
|
3713
|
-
verdict: "allow",
|
|
3714
|
-
reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
|
|
3715
|
-
};
|
|
3716
|
-
}
|
|
3717
|
-
// ../../packages/aeg-core/src/baseline-capture.ts
|
|
3718
|
-
function captureBaseline(counts, capturedAt) {
|
|
3719
|
-
return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
|
|
3976
|
+
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
3977
|
+
var PRODUCT_SLUG_SCOPE = [
|
|
3978
|
+
"apps/cli/src",
|
|
3979
|
+
".github/workflows",
|
|
3980
|
+
".vinaya",
|
|
3981
|
+
"apps/cli/README.md",
|
|
3982
|
+
"packages/sources/README.md"
|
|
3983
|
+
];
|
|
3984
|
+
function isUnderOrEqual(path, prefix) {
|
|
3985
|
+
return path === prefix || path.startsWith(`${prefix}/`);
|
|
3720
3986
|
}
|
|
3721
|
-
function
|
|
3722
|
-
|
|
3723
|
-
const baselineEntry = baseline.find((b) => b.tool === c.tool);
|
|
3724
|
-
const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
|
|
3725
|
-
return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
|
|
3726
|
-
});
|
|
3727
|
-
return {
|
|
3728
|
-
withinBudget: perTool.every((t) => t.delta <= 0),
|
|
3729
|
-
delta: perTool.reduce((sum, t) => sum + t.delta, 0),
|
|
3730
|
-
perTool
|
|
3731
|
-
};
|
|
3987
|
+
function isProductScopeFile(path) {
|
|
3988
|
+
return PRODUCT_SLUG_SCOPE.some((prefix) => isUnderOrEqual(path, prefix));
|
|
3732
3989
|
}
|
|
3733
|
-
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
3734
3990
|
var SHIPS_PREFIX = "aeg-root/";
|
|
3735
3991
|
function shipsArchivePrefix(shipsPrefix) {
|
|
3736
3992
|
return `${shipsPrefix}tranches/completed/`;
|
|
@@ -3750,6 +4006,8 @@ function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix, shipsPr
|
|
|
3750
4006
|
}
|
|
3751
4007
|
if (isSpecFile2(path) || isClaudeMdFile(path))
|
|
3752
4008
|
return "internal";
|
|
4009
|
+
if (isProductScopeFile(path))
|
|
4010
|
+
return "product";
|
|
3753
4011
|
return null;
|
|
3754
4012
|
}
|
|
3755
4013
|
var SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
|
|
@@ -3762,6 +4020,9 @@ function stripNonProse(path, content) {
|
|
|
3762
4020
|
}
|
|
3763
4021
|
return content;
|
|
3764
4022
|
}
|
|
4023
|
+
function stripNonProseForProduct(path, content) {
|
|
4024
|
+
return path.endsWith(".md") ? stripNonProse(path, content) : content;
|
|
4025
|
+
}
|
|
3765
4026
|
function lineAt(content, index) {
|
|
3766
4027
|
let line = 1;
|
|
3767
4028
|
for (let i = 0;i < index; i++) {
|
|
@@ -3786,12 +4047,19 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
|
|
|
3786
4047
|
{ pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug" },
|
|
3787
4048
|
...legacyPattern ? [{ pattern: legacyPattern, what: "an internal tranche slug", group: 2 }] : []
|
|
3788
4049
|
];
|
|
4050
|
+
const productPatterns = [
|
|
4051
|
+
{ pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug in product code" }
|
|
4052
|
+
];
|
|
3789
4053
|
for (const file of files) {
|
|
3790
4054
|
const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
|
|
3791
|
-
if (!cls ||
|
|
4055
|
+
if (!cls || cls === "internal")
|
|
3792
4056
|
continue;
|
|
3793
|
-
|
|
3794
|
-
|
|
4057
|
+
if (cls !== "product" && !SWEPT_CLASSES.has(cls))
|
|
4058
|
+
continue;
|
|
4059
|
+
const blocking = cls === "product";
|
|
4060
|
+
const scrubbed = blocking ? stripNonProseForProduct(file.path, file.content) : stripNonProse(file.path, file.content);
|
|
4061
|
+
const patternsToRun = blocking ? productPatterns : patterns;
|
|
4062
|
+
for (const { pattern, what, group } of patternsToRun) {
|
|
3795
4063
|
pattern.lastIndex = 0;
|
|
3796
4064
|
let match = pattern.exec(scrubbed);
|
|
3797
4065
|
while (match !== null) {
|
|
@@ -3799,7 +4067,8 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
|
|
|
3799
4067
|
findings.push({
|
|
3800
4068
|
file: file.path,
|
|
3801
4069
|
line: lineAt(scrubbed, match.index),
|
|
3802
|
-
message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve
|
|
4070
|
+
message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve`,
|
|
4071
|
+
blocking
|
|
3803
4072
|
});
|
|
3804
4073
|
match = pattern.exec(scrubbed);
|
|
3805
4074
|
}
|
|
@@ -3849,7 +4118,8 @@ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, read
|
|
|
3849
4118
|
findings.push({
|
|
3850
4119
|
file: file.path,
|
|
3851
4120
|
line,
|
|
3852
|
-
message: `uses coined term "${term}" without defining it inline or linking the glossary
|
|
4121
|
+
message: `uses coined term "${term}" without defining it inline or linking the glossary`,
|
|
4122
|
+
blocking: false
|
|
3853
4123
|
});
|
|
3854
4124
|
}
|
|
3855
4125
|
}
|
|
@@ -3872,6 +4142,7 @@ function parseGlossaryTerms(glossaryContent) {
|
|
|
3872
4142
|
}
|
|
3873
4143
|
return terms;
|
|
3874
4144
|
}
|
|
4145
|
+
|
|
3875
4146
|
// ../../packages/aeg-core/src/quoted-command.ts
|
|
3876
4147
|
var QUOTED_COMMAND_SHIPS_PREFIX = "aeg-root/";
|
|
3877
4148
|
var QUOTED_COMMAND_SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
|
|
@@ -3966,6 +4237,54 @@ function evaluateCitedQuotes(citedQuotes, citedFileContents) {
|
|
|
3966
4237
|
}
|
|
3967
4238
|
return findings;
|
|
3968
4239
|
}
|
|
4240
|
+
// ../../packages/aeg-core/src/dead-branch-push-guard.ts
|
|
4241
|
+
function checkDeadBranchPush(input) {
|
|
4242
|
+
const { branch, prState, prNumber } = input;
|
|
4243
|
+
if (prState === "MERGED") {
|
|
4244
|
+
return {
|
|
4245
|
+
verdict: "refuse",
|
|
4246
|
+
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.`
|
|
4247
|
+
};
|
|
4248
|
+
}
|
|
4249
|
+
if (prState === "CLOSED") {
|
|
4250
|
+
return {
|
|
4251
|
+
verdict: "refuse",
|
|
4252
|
+
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.`
|
|
4253
|
+
};
|
|
4254
|
+
}
|
|
4255
|
+
if (prState === "OPEN") {
|
|
4256
|
+
return {
|
|
4257
|
+
verdict: "allow",
|
|
4258
|
+
reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
|
|
4259
|
+
};
|
|
4260
|
+
}
|
|
4261
|
+
if (prState === "NONE") {
|
|
4262
|
+
return {
|
|
4263
|
+
verdict: "allow",
|
|
4264
|
+
reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
|
|
4265
|
+
};
|
|
4266
|
+
}
|
|
4267
|
+
return {
|
|
4268
|
+
verdict: "allow",
|
|
4269
|
+
reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
|
|
4270
|
+
};
|
|
4271
|
+
}
|
|
4272
|
+
// ../../packages/aeg-core/src/baseline-capture.ts
|
|
4273
|
+
function captureBaseline(counts, capturedAt) {
|
|
4274
|
+
return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
|
|
4275
|
+
}
|
|
4276
|
+
function compareToBaseline(current, baseline) {
|
|
4277
|
+
const perTool = current.map((c) => {
|
|
4278
|
+
const baselineEntry = baseline.find((b) => b.tool === c.tool);
|
|
4279
|
+
const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
|
|
4280
|
+
return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
|
|
4281
|
+
});
|
|
4282
|
+
return {
|
|
4283
|
+
withinBudget: perTool.every((t) => t.delta <= 0),
|
|
4284
|
+
delta: perTool.reduce((sum, t) => sum + t.delta, 0),
|
|
4285
|
+
perTool
|
|
4286
|
+
};
|
|
4287
|
+
}
|
|
3969
4288
|
// ../../packages/aeg-core/src/retired-vocabulary.ts
|
|
3970
4289
|
var RETIRED_PATTERNS = [
|
|
3971
4290
|
String.raw`\bD-[0-9]{3}\b`,
|
|
@@ -4435,6 +4754,223 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
|
|
|
4435
4754
|
}
|
|
4436
4755
|
return findings;
|
|
4437
4756
|
}
|
|
4757
|
+
// ../../packages/aeg-core/src/log/schema.ts
|
|
4758
|
+
import { z } from "zod";
|
|
4759
|
+
var ROLE_VALUES = [
|
|
4760
|
+
"planner",
|
|
4761
|
+
"developer",
|
|
4762
|
+
"code-reviewer",
|
|
4763
|
+
"security",
|
|
4764
|
+
"principal",
|
|
4765
|
+
"archivist",
|
|
4766
|
+
"architect"
|
|
4767
|
+
];
|
|
4768
|
+
var RoleSchema = z.enum(ROLE_VALUES);
|
|
4769
|
+
var HOST_VALUES = ["hook", "ci", "cli", "loop"];
|
|
4770
|
+
var HostSchema = z.enum(HOST_VALUES);
|
|
4771
|
+
var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
4772
|
+
var HeaderMetaSchema = z.object({
|
|
4773
|
+
schema: z.literal(1),
|
|
4774
|
+
ts: z.string(),
|
|
4775
|
+
run_id: z.string().regex(RUN_ID_PATTERN),
|
|
4776
|
+
seq: z.number().int().nonnegative(),
|
|
4777
|
+
repo: z.string().nullable(),
|
|
4778
|
+
vinaya: z.string(),
|
|
4779
|
+
doctrine: z.string(),
|
|
4780
|
+
host: HostSchema,
|
|
4781
|
+
machine: z.string()
|
|
4782
|
+
}).strict();
|
|
4783
|
+
var SubjectSchema = z.object({
|
|
4784
|
+
issue: z.number().int().nullable(),
|
|
4785
|
+
pr: z.number().int().optional(),
|
|
4786
|
+
sha: z.string().optional(),
|
|
4787
|
+
role: z.union([RoleSchema, z.literal("unattributed")]),
|
|
4788
|
+
round: z.number().int().optional(),
|
|
4789
|
+
objectives_version: z.string().optional()
|
|
4790
|
+
}).strict();
|
|
4791
|
+
var HeaderSchema = z.object({
|
|
4792
|
+
meta: HeaderMetaSchema,
|
|
4793
|
+
subject: SubjectSchema
|
|
4794
|
+
}).strict();
|
|
4795
|
+
var envelopeTail = {
|
|
4796
|
+
duration_ms: z.number().nonnegative().optional(),
|
|
4797
|
+
payload: z.object({}).strict()
|
|
4798
|
+
};
|
|
4799
|
+
var DispatchOutcomeSchema = z.discriminatedUnion("type", [
|
|
4800
|
+
z.object({ type: z.literal("pr_opened"), pr: z.number().int(), head: z.string() }).strict(),
|
|
4801
|
+
z.object({
|
|
4802
|
+
type: z.literal("round_pushed"),
|
|
4803
|
+
pr: z.number().int(),
|
|
4804
|
+
head: z.string(),
|
|
4805
|
+
comment_id: z.number().int()
|
|
4806
|
+
}).strict(),
|
|
4807
|
+
z.object({
|
|
4808
|
+
type: z.literal("verdict"),
|
|
4809
|
+
verdict: z.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
|
|
4810
|
+
head: z.string(),
|
|
4811
|
+
comment_id: z.number().int(),
|
|
4812
|
+
objectives: z.array(z.object({ id: z.string(), met: z.boolean() }).strict()),
|
|
4813
|
+
findings: z.array(z.object({ id: z.string(), severity: z.string(), state: z.string().optional() }).strict())
|
|
4814
|
+
}).strict(),
|
|
4815
|
+
z.object({
|
|
4816
|
+
type: z.literal("escalation"),
|
|
4817
|
+
class: z.enum(["authority", "strategy", "product"]),
|
|
4818
|
+
comment_id: z.number().int()
|
|
4819
|
+
}).strict(),
|
|
4820
|
+
z.object({ type: z.literal("brief"), comment_id: z.number().int(), hash: z.string() }).strict(),
|
|
4821
|
+
z.object({ type: z.literal("plan"), issues: z.array(z.number().int()) }).strict(),
|
|
4822
|
+
z.object({ type: z.literal("archive"), provenance_comment_id: z.number().int() }).strict()
|
|
4823
|
+
]);
|
|
4824
|
+
var dispatchShared = {
|
|
4825
|
+
meta: HeaderMetaSchema,
|
|
4826
|
+
subject: SubjectSchema,
|
|
4827
|
+
kind: z.literal("dispatch"),
|
|
4828
|
+
...envelopeTail,
|
|
4829
|
+
target_role: RoleSchema,
|
|
4830
|
+
model: z.string(),
|
|
4831
|
+
round: z.number().int().optional(),
|
|
4832
|
+
effect_id: z.string()
|
|
4833
|
+
};
|
|
4834
|
+
var dispatchUsageField = z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable();
|
|
4835
|
+
var DispatchEventSchema = z.discriminatedUnion("event", [
|
|
4836
|
+
z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
|
|
4837
|
+
z.object({
|
|
4838
|
+
...dispatchShared,
|
|
4839
|
+
event: z.literal("outcome_received"),
|
|
4840
|
+
outcome: DispatchOutcomeSchema,
|
|
4841
|
+
usage: dispatchUsageField
|
|
4842
|
+
}).strict(),
|
|
4843
|
+
z.object({
|
|
4844
|
+
...dispatchShared,
|
|
4845
|
+
event: z.literal("dispatch_failed"),
|
|
4846
|
+
reason: z.enum(["timeout", "crash", "refused", "unattributed_write"]),
|
|
4847
|
+
usage: dispatchUsageField
|
|
4848
|
+
}).strict()
|
|
4849
|
+
]);
|
|
4850
|
+
var loopShared = {
|
|
4851
|
+
meta: HeaderMetaSchema,
|
|
4852
|
+
subject: SubjectSchema,
|
|
4853
|
+
kind: z.literal("dev_review_loop"),
|
|
4854
|
+
...envelopeTail,
|
|
4855
|
+
loop_id: z.string()
|
|
4856
|
+
};
|
|
4857
|
+
var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
|
|
4858
|
+
z.object({
|
|
4859
|
+
...loopShared,
|
|
4860
|
+
event: z.literal("loop_started"),
|
|
4861
|
+
task: z.number().int(),
|
|
4862
|
+
policy: z.object({
|
|
4863
|
+
max_rounds: z.number().int().nonnegative(),
|
|
4864
|
+
reviewers: z.array(RoleSchema),
|
|
4865
|
+
models: z.record(RoleSchema, z.string())
|
|
4866
|
+
}).strict()
|
|
4867
|
+
}).strict(),
|
|
4868
|
+
z.object({ ...loopShared, event: z.literal("round_started"), round: z.number().int(), base_head: z.string() }).strict(),
|
|
4869
|
+
z.object({
|
|
4870
|
+
...loopShared,
|
|
4871
|
+
event: z.literal("gate_result_read"),
|
|
4872
|
+
round: z.number().int(),
|
|
4873
|
+
head: z.string(),
|
|
4874
|
+
green: z.boolean()
|
|
4875
|
+
}).strict(),
|
|
4876
|
+
z.object({
|
|
4877
|
+
...loopShared,
|
|
4878
|
+
event: z.literal("verdicts_read"),
|
|
4879
|
+
round: z.number().int(),
|
|
4880
|
+
head: z.string(),
|
|
4881
|
+
all_approve: z.boolean(),
|
|
4882
|
+
blockers: z.number().int().nonnegative()
|
|
4883
|
+
}).strict(),
|
|
4884
|
+
z.object({
|
|
4885
|
+
...loopShared,
|
|
4886
|
+
event: z.literal("findings_compared"),
|
|
4887
|
+
round: z.number().int(),
|
|
4888
|
+
open: z.array(z.string()),
|
|
4889
|
+
resolved: z.array(z.string()),
|
|
4890
|
+
new: z.array(z.string()),
|
|
4891
|
+
recurring: z.array(z.string())
|
|
4892
|
+
}).strict(),
|
|
4893
|
+
z.object({
|
|
4894
|
+
...loopShared,
|
|
4895
|
+
event: z.literal("stop_condition_met"),
|
|
4896
|
+
round: z.number().int(),
|
|
4897
|
+
condition: z.enum([
|
|
4898
|
+
"green",
|
|
4899
|
+
"max_rounds",
|
|
4900
|
+
"no_progress",
|
|
4901
|
+
"escalated",
|
|
4902
|
+
"principal_stop",
|
|
4903
|
+
"confidence",
|
|
4904
|
+
"reappearance"
|
|
4905
|
+
])
|
|
4906
|
+
}).strict(),
|
|
4907
|
+
z.object({
|
|
4908
|
+
...loopShared,
|
|
4909
|
+
event: z.literal("paused"),
|
|
4910
|
+
round: z.number().int(),
|
|
4911
|
+
reason: z.enum(["escalation", "principal_item", "refreeze_needed"])
|
|
4912
|
+
}).strict(),
|
|
4913
|
+
z.object({
|
|
4914
|
+
...loopShared,
|
|
4915
|
+
event: z.literal("resumed"),
|
|
4916
|
+
round: z.number().int(),
|
|
4917
|
+
by: z.literal("principal")
|
|
4918
|
+
}).strict(),
|
|
4919
|
+
z.object({
|
|
4920
|
+
...loopShared,
|
|
4921
|
+
event: z.literal("round_ended"),
|
|
4922
|
+
round: z.number().int(),
|
|
4923
|
+
base_head: z.string(),
|
|
4924
|
+
head: z.string(),
|
|
4925
|
+
files_changed: z.number().int().nonnegative(),
|
|
4926
|
+
insertions: z.number().int().nonnegative(),
|
|
4927
|
+
deletions: z.number().int().nonnegative(),
|
|
4928
|
+
wall_ms: z.number().nonnegative(),
|
|
4929
|
+
outcome: z.enum(["green", "changes_requested", "escalated"])
|
|
4930
|
+
}).strict(),
|
|
4931
|
+
z.object({
|
|
4932
|
+
...loopShared,
|
|
4933
|
+
event: z.literal("journal_finalized"),
|
|
4934
|
+
rounds: z.number().int().nonnegative(),
|
|
4935
|
+
total_wall_ms: z.number().nonnegative(),
|
|
4936
|
+
time_to_green_ms: z.number().nonnegative().nullable(),
|
|
4937
|
+
files_changed_total: z.number().int().nonnegative(),
|
|
4938
|
+
final_head: z.string(),
|
|
4939
|
+
result: z.enum(["merged_ready", "stopped"])
|
|
4940
|
+
}).strict()
|
|
4941
|
+
]);
|
|
4942
|
+
var ForgeOpSchema = z.enum([
|
|
4943
|
+
"pr.create",
|
|
4944
|
+
"pr.comment",
|
|
4945
|
+
"pr.body.replace",
|
|
4946
|
+
"pr.refreeze",
|
|
4947
|
+
"issue.create",
|
|
4948
|
+
"issue.edit",
|
|
4949
|
+
"issue.comment",
|
|
4950
|
+
"milestone.create",
|
|
4951
|
+
"milestone.edit",
|
|
4952
|
+
"milestone.close",
|
|
4953
|
+
"label.add",
|
|
4954
|
+
"label.remove"
|
|
4955
|
+
]);
|
|
4956
|
+
var ForgeWriteTargetSchema = z.object({
|
|
4957
|
+
issue: z.number().int().optional(),
|
|
4958
|
+
pr: z.number().int().optional()
|
|
4959
|
+
}).strict();
|
|
4960
|
+
var forgeWriteShared = {
|
|
4961
|
+
meta: HeaderMetaSchema,
|
|
4962
|
+
subject: SubjectSchema,
|
|
4963
|
+
kind: z.literal("forge_write"),
|
|
4964
|
+
...envelopeTail,
|
|
4965
|
+
op: ForgeOpSchema,
|
|
4966
|
+
target: ForgeWriteTargetSchema
|
|
4967
|
+
};
|
|
4968
|
+
var ForgeWriteEventSchema = z.discriminatedUnion("event", [
|
|
4969
|
+
z.object({ ...forgeWriteShared, event: z.literal("validated") }).strict(),
|
|
4970
|
+
z.object({ ...forgeWriteShared, event: z.literal("refused"), reason: z.string() }).strict(),
|
|
4971
|
+
z.object({ ...forgeWriteShared, event: z.literal("written"), comment_ids: z.array(z.string()) }).strict()
|
|
4972
|
+
]);
|
|
4973
|
+
var LogEventSchema = z.union([DispatchEventSchema, DevReviewLoopEventSchema, ForgeWriteEventSchema]);
|
|
4438
4974
|
// src/checks/contract.ts
|
|
4439
4975
|
var CHECK_SCHEMA_VERSION = 1;
|
|
4440
4976
|
function emitCheckError(error) {
|