@attalabs/vinaya 0.24.0 → 0.25.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 +25 -2
- package/aeg-root/contracts/developer-reviewer.md +3 -0
- package/aeg-root/contracts/planner-brief.md +13 -0
- package/aeg-root/enforcement.md +11 -7
- package/aeg-root/process.md +4 -4
- package/aeg-root/roles/brief-author.md +4 -2
- package/aeg-root/roles/developer.md +20 -14
- package/aeg-root/roles/planner.md +14 -0
- package/aeg-root/roles/principal.md +3 -0
- package/aeg-root/roles/reviewer.md +23 -7
- package/aeg-root/roles/security.md +21 -6
- package/aeg-root/skills/brief-authoring/SKILL.md +2 -2
- package/aeg-root/state-machine.md +2 -2
- package/aeg-root/templates/brief-template.md +20 -4
- package/aeg-root/templates/issue-rationale-template.md +34 -1
- package/aeg-root/templates/pr-report-template.md +5 -13
- package/dist/checks/bin/check-body-bare-digits.js +572 -127
- package/dist/checks/bin/check-branch-topology.js +629 -73
- package/dist/checks/bin/check-brief-shape.js +1675 -70
- package/dist/checks/bin/check-changeset-coverage.js +584 -127
- package/dist/checks/bin/check-closes-n.js +629 -73
- package/dist/checks/bin/check-coherence.js +696 -134
- package/dist/checks/bin/check-dead-branch-push.js +505 -66
- package/dist/checks/bin/check-dispatch-readiness.js +696 -134
- package/dist/checks/bin/check-doc-coverage-push.js +572 -127
- package/dist/checks/bin/check-doc-coverage.js +572 -127
- package/dist/checks/bin/check-doctrine-no-procedures.js +572 -127
- package/dist/checks/bin/check-doctrine-portability.js +572 -127
- package/dist/checks/bin/check-evidence-fresh.js +572 -127
- package/dist/checks/bin/check-exec-bits.js +572 -127
- package/dist/checks/bin/check-first-push-dispatch.js +696 -134
- package/dist/checks/bin/check-issue-assignment.js +629 -73
- package/dist/checks/bin/check-main-branch-refusal.js +505 -66
- package/dist/checks/bin/check-no-disk-state.js +505 -66
- package/dist/checks/bin/check-pr-report-density.js +505 -66
- package/dist/checks/bin/check-quoted-command.js +572 -127
- package/dist/checks/bin/check-reader-resolvable-prose.js +610 -133
- package/dist/checks/bin/check-registry-gates.js +517 -69
- package/dist/checks/bin/check-retired-vocabulary.js +572 -127
- package/dist/checks/bin/check-review-gate.js +646 -130
- package/dist/checks/bin/check-single-plan-pr.js +505 -66
- package/dist/checks/bin/check-test-plan.js +505 -66
- package/dist/checks/bin/check-token-collection-wired.js +505 -66
- package/dist/checks/bin/check-token-report.js +505 -66
- package/dist/checks/bin/check-workspace-escape.js +582 -129
- package/dist/index.js +7740 -4283
- package/package.json +2 -2
- 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/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
|
@@ -4,7 +4,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
4
4
|
|
|
5
5
|
// src/checks/bin/check-reader-resolvable-prose.ts
|
|
6
6
|
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
7
|
-
import { join as join5 } from "node:path";
|
|
7
|
+
import { extname, join as join5 } from "node:path";
|
|
8
8
|
// ../../packages/aeg-core/src/gate-audience.ts
|
|
9
9
|
var GATE_AUDIENCE = {
|
|
10
10
|
"check-branch-topology": { shippedAs: "branch-topology", ring: 0 },
|
|
@@ -1939,13 +1939,18 @@ function parseInlineFieldList(section) {
|
|
|
1939
1939
|
}
|
|
1940
1940
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
1941
1941
|
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
1942
|
-
|
|
1942
|
+
var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1943
|
+
function firstFiveLines(comment) {
|
|
1943
1944
|
return comment.split(`
|
|
1944
|
-
`).slice(0,
|
|
1945
|
+
`).slice(0, 5).join(`
|
|
1945
1946
|
`);
|
|
1946
1947
|
}
|
|
1947
1948
|
function extractHeadSha(comment) {
|
|
1948
|
-
const m =
|
|
1949
|
+
const m = firstFiveLines(comment).match(HEAD_SHA_PATTERN);
|
|
1950
|
+
return m ? m[1].toLowerCase() : null;
|
|
1951
|
+
}
|
|
1952
|
+
function extractObjectivesVersion(comment) {
|
|
1953
|
+
const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
|
|
1949
1954
|
return m ? m[1].toLowerCase() : null;
|
|
1950
1955
|
}
|
|
1951
1956
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
@@ -1954,21 +1959,24 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1954
1959
|
return {
|
|
1955
1960
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
1956
1961
|
headSha: null,
|
|
1962
|
+
objectivesVersion: null,
|
|
1957
1963
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1958
1964
|
};
|
|
1959
1965
|
}
|
|
1960
1966
|
const latest = candidates[candidates.length - 1];
|
|
1961
|
-
const m =
|
|
1967
|
+
const m = firstFiveLines(latest).match(valuePattern);
|
|
1962
1968
|
if (!m) {
|
|
1963
1969
|
return {
|
|
1964
|
-
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first
|
|
1970
|
+
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
|
|
1965
1971
|
headSha: null,
|
|
1966
|
-
|
|
1972
|
+
objectivesVersion: null,
|
|
1973
|
+
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
|
|
1967
1974
|
};
|
|
1968
1975
|
}
|
|
1969
1976
|
return {
|
|
1970
1977
|
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
1971
1978
|
headSha: extractHeadSha(latest),
|
|
1979
|
+
objectivesVersion: extractObjectivesVersion(latest),
|
|
1972
1980
|
danglingNote: null
|
|
1973
1981
|
};
|
|
1974
1982
|
}
|
|
@@ -2325,8 +2333,105 @@ function overrideActive(opts) {
|
|
|
2325
2333
|
return true;
|
|
2326
2334
|
return false;
|
|
2327
2335
|
}
|
|
2328
|
-
// ../../packages/aeg-core/src/
|
|
2336
|
+
// ../../packages/aeg-core/src/objectives.ts
|
|
2329
2337
|
import { createHash as createHash2 } from "node:crypto";
|
|
2338
|
+
var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
|
|
2339
|
+
var NEXT_HEADING_RE = /^##[ \t]/m;
|
|
2340
|
+
var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
|
|
2341
|
+
function hasBacktickedPath(text) {
|
|
2342
|
+
let i = 0;
|
|
2343
|
+
while (i < text.length) {
|
|
2344
|
+
const start = text.indexOf("`", i);
|
|
2345
|
+
if (start === -1)
|
|
2346
|
+
return false;
|
|
2347
|
+
const end = text.indexOf("`", start + 1);
|
|
2348
|
+
if (end === -1)
|
|
2349
|
+
return false;
|
|
2350
|
+
if (text.slice(start + 1, end).includes("/"))
|
|
2351
|
+
return true;
|
|
2352
|
+
i = end + 1;
|
|
2353
|
+
}
|
|
2354
|
+
return false;
|
|
2355
|
+
}
|
|
2356
|
+
var MIN_WORDS_OUTSIDE_BACKTICKS = 3;
|
|
2357
|
+
function stripObjectiveBackticks(text) {
|
|
2358
|
+
return text.replace(/`[^`\n]*`/g, " ");
|
|
2359
|
+
}
|
|
2360
|
+
function wordCount(text) {
|
|
2361
|
+
return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
|
|
2362
|
+
}
|
|
2363
|
+
function objectivesSectionText(body) {
|
|
2364
|
+
const heading = HEADING_RE.exec(body);
|
|
2365
|
+
if (!heading)
|
|
2366
|
+
return null;
|
|
2367
|
+
const afterHeading = body.slice(heading.index + heading[0].length);
|
|
2368
|
+
const next = NEXT_HEADING_RE.exec(afterHeading);
|
|
2369
|
+
return next ? afterHeading.slice(0, next.index) : afterHeading;
|
|
2370
|
+
}
|
|
2371
|
+
function hasObjectivesHeading(body) {
|
|
2372
|
+
return HEADING_RE.test(body);
|
|
2373
|
+
}
|
|
2374
|
+
function objectivesOf(body) {
|
|
2375
|
+
const section = objectivesSectionText(body);
|
|
2376
|
+
if (section === null) {
|
|
2377
|
+
return { ok: false, errors: ["no `## Objectives` heading found in the body."] };
|
|
2378
|
+
}
|
|
2379
|
+
const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
2380
|
+
if (lines.length === 0) {
|
|
2381
|
+
return { ok: false, errors: ["the `## Objectives` section has no `O<n>.` lines."] };
|
|
2382
|
+
}
|
|
2383
|
+
const objectives = [];
|
|
2384
|
+
const errors = [];
|
|
2385
|
+
for (const line of lines) {
|
|
2386
|
+
const m = OBJECTIVE_LINE_RE.exec(line);
|
|
2387
|
+
if (!m) {
|
|
2388
|
+
errors.push(`"${line}" is not a well-formed objective line — expected \`O<n>. <sentence>\`, e.g. \`O1. …\`.`);
|
|
2389
|
+
continue;
|
|
2390
|
+
}
|
|
2391
|
+
const n = m[1];
|
|
2392
|
+
const text = m[2].trim();
|
|
2393
|
+
if (text.length === 0) {
|
|
2394
|
+
errors.push(`O${n} has no sentence — every objective is one observable sentence.`);
|
|
2395
|
+
continue;
|
|
2396
|
+
}
|
|
2397
|
+
if (hasBacktickedPath(text) && wordCount(stripObjectiveBackticks(text)) < MIN_WORDS_OUTSIDE_BACKTICKS) {
|
|
2398
|
+
errors.push(`O${n} is little more than a file path — an objective states an observable outcome, never a bare path (the Brief Author maps it to files).`);
|
|
2399
|
+
continue;
|
|
2400
|
+
}
|
|
2401
|
+
objectives.push({ id: `O${n}`, text });
|
|
2402
|
+
}
|
|
2403
|
+
for (let i = 0;i < objectives.length; i++) {
|
|
2404
|
+
const expected = i + 1;
|
|
2405
|
+
const actual = Number.parseInt(objectives[i].id.slice(1), 10);
|
|
2406
|
+
if (actual !== expected) {
|
|
2407
|
+
errors.push(`numbering is not contiguous from O1 — expected O${expected}, found ${objectives[i].id}.`);
|
|
2408
|
+
break;
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
if (errors.length > 0)
|
|
2412
|
+
return { ok: false, errors };
|
|
2413
|
+
return { ok: true, objectives };
|
|
2414
|
+
}
|
|
2415
|
+
function normalizeText(text) {
|
|
2416
|
+
return text.trim().replace(/\s+/g, " ");
|
|
2417
|
+
}
|
|
2418
|
+
function objectivesVersion(objectives) {
|
|
2419
|
+
const normalized = objectives.map((o) => `${o.id} ${normalizeText(o.text)}`).join(`
|
|
2420
|
+
`);
|
|
2421
|
+
return createHash2("sha256").update(normalized).digest("hex");
|
|
2422
|
+
}
|
|
2423
|
+
function isIssueNotFoundError(err) {
|
|
2424
|
+
const stderr = err?.stderr;
|
|
2425
|
+
const haystack = [
|
|
2426
|
+
err?.message ?? "",
|
|
2427
|
+
typeof stderr === "string" ? stderr : stderr?.toString() ?? ""
|
|
2428
|
+
].join(`
|
|
2429
|
+
`);
|
|
2430
|
+
return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
// ../../packages/aeg-core/src/premise-check.ts
|
|
2434
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2330
2435
|
var ASSERTION_KINDS = new Set(["contains", "absent", "sha256"]);
|
|
2331
2436
|
function isPremiseHeader(line) {
|
|
2332
2437
|
const stripped = line.replace(/[*#]/g, "").trim();
|
|
@@ -2397,7 +2502,7 @@ function checkPremises(assertions, fileReader) {
|
|
|
2397
2502
|
} else if (a.kind === "absent" && content.includes(a.value)) {
|
|
2398
2503
|
failures.push(`premise-check: ${a.path} now contains "${a.value}", but the brief pinned it absent — the premise has moved; re-dig before proceeding.`);
|
|
2399
2504
|
} else if (a.kind === "sha256") {
|
|
2400
|
-
const actual =
|
|
2505
|
+
const actual = createHash3("sha256").update(content).digest("hex");
|
|
2401
2506
|
if (actual !== a.value.toLowerCase()) {
|
|
2402
2507
|
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.`);
|
|
2403
2508
|
}
|
|
@@ -2758,8 +2863,55 @@ function checkDefeatCases(prBody) {
|
|
|
2758
2863
|
]
|
|
2759
2864
|
};
|
|
2760
2865
|
}
|
|
2866
|
+
var PART_CITATION_RE = /Part\s+\d+\s*\(([^)]*)\)/gi;
|
|
2867
|
+
var OBJECTIVE_REF_RE = /O(\d+)/g;
|
|
2868
|
+
function citedObjectiveIds(section6) {
|
|
2869
|
+
const ids = new Set;
|
|
2870
|
+
for (const m of section6.matchAll(PART_CITATION_RE)) {
|
|
2871
|
+
for (const r of m[1].matchAll(OBJECTIVE_REF_RE))
|
|
2872
|
+
ids.add(Number.parseInt(r[1], 10));
|
|
2873
|
+
}
|
|
2874
|
+
return ids;
|
|
2875
|
+
}
|
|
2876
|
+
function checkObjectivesCopy(prBody, issueObjectives) {
|
|
2877
|
+
const parsed = objectivesOf(prBody);
|
|
2878
|
+
if (!parsed.ok) {
|
|
2879
|
+
return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives copy: ${e}`) };
|
|
2880
|
+
}
|
|
2881
|
+
if (objectivesVersion(parsed.objectives) !== objectivesVersion(issueObjectives)) {
|
|
2882
|
+
return {
|
|
2883
|
+
status: "fail",
|
|
2884
|
+
errors: [
|
|
2885
|
+
"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)."
|
|
2886
|
+
]
|
|
2887
|
+
};
|
|
2888
|
+
}
|
|
2889
|
+
return { status: "pass", errors: [] };
|
|
2890
|
+
}
|
|
2891
|
+
function checkObjectivesCoverage(prBody) {
|
|
2892
|
+
const parsed = objectivesOf(prBody);
|
|
2893
|
+
if (!parsed.ok) {
|
|
2894
|
+
return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives coverage: ${e}`) };
|
|
2895
|
+
}
|
|
2896
|
+
const declaredIds = new Set(parsed.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
|
|
2897
|
+
const section6 = extractNumberedSection(prBody, 6) ?? "";
|
|
2898
|
+
const cited = citedObjectiveIds(section6);
|
|
2899
|
+
const maxDeclared = Math.max(...declaredIds, 0);
|
|
2900
|
+
const errors = [];
|
|
2901
|
+
for (const id of declaredIds) {
|
|
2902
|
+
if (!cited.has(id)) {
|
|
2903
|
+
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.`);
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
for (const id of cited) {
|
|
2907
|
+
if (!declaredIds.has(id)) {
|
|
2908
|
+
errors.push(`brief-validation objectives coverage: a Part in §6 cites O${id}, but the Objectives section ends at O${maxDeclared}.`);
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
|
|
2912
|
+
}
|
|
2761
2913
|
function checkBriefSections(prBody, readTier, options = {}) {
|
|
2762
|
-
const { requireClosesN = true, consumersOf = () => [] } = options;
|
|
2914
|
+
const { requireClosesN = true, consumersOf = () => [], issueObjectives } = options;
|
|
2763
2915
|
const results = [
|
|
2764
2916
|
checkTierField(prBody, readTier),
|
|
2765
2917
|
checkTestPlan(prBody),
|
|
@@ -2777,6 +2929,7 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2777
2929
|
checkCommandsCarryOutput(prBody),
|
|
2778
2930
|
checkConsumerTests(prBody, consumersOf),
|
|
2779
2931
|
checkDefeatCases(prBody),
|
|
2932
|
+
...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
|
|
2780
2933
|
...requireClosesN ? [checkClosesN(prBody)] : []
|
|
2781
2934
|
];
|
|
2782
2935
|
return { errors: results.flatMap((r) => r.errors) };
|
|
@@ -2975,14 +3128,14 @@ function checkSectionDensity(prBody, heading) {
|
|
|
2975
3128
|
]
|
|
2976
3129
|
};
|
|
2977
3130
|
}
|
|
2978
|
-
function
|
|
2979
|
-
return checkSectionDensity(prBody, "
|
|
3131
|
+
function checkDecisionsDensity(prBody) {
|
|
3132
|
+
return checkSectionDensity(prBody, "Decisions");
|
|
2980
3133
|
}
|
|
2981
3134
|
function checkScopeDensity(prBody) {
|
|
2982
3135
|
return checkSectionDensity(prBody, "Scope");
|
|
2983
3136
|
}
|
|
2984
3137
|
function checkPrReportDensity(prBody) {
|
|
2985
|
-
const results = [
|
|
3138
|
+
const results = [checkDecisionsDensity(prBody), checkScopeDensity(prBody)];
|
|
2986
3139
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2987
3140
|
}
|
|
2988
3141
|
// ../../packages/aeg-core/src/issue-validation.ts
|
|
@@ -3014,6 +3167,15 @@ function checkIssueRationale(body) {
|
|
|
3014
3167
|
}
|
|
3015
3168
|
return { status: errors.length > 0 ? "fail" : "pass", errors };
|
|
3016
3169
|
}
|
|
3170
|
+
var OBJECTIVES_SINCE_ISSUE = 404;
|
|
3171
|
+
function checkIssueObjectives(body, issueNumber) {
|
|
3172
|
+
if (issueNumber !== null && issueNumber < OBJECTIVES_SINCE_ISSUE)
|
|
3173
|
+
return { status: "pass", errors: [] };
|
|
3174
|
+
const result = objectivesOf(body);
|
|
3175
|
+
if (result.ok)
|
|
3176
|
+
return { status: "pass", errors: [] };
|
|
3177
|
+
return { status: "fail", errors: result.errors.map((e) => `issue-validation objectives: ${e}`) };
|
|
3178
|
+
}
|
|
3017
3179
|
function isTaskIssueLabelSet(labels) {
|
|
3018
3180
|
return hasLabel("tranche", labels);
|
|
3019
3181
|
}
|
|
@@ -3275,7 +3437,8 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
|
|
|
3275
3437
|
continue;
|
|
3276
3438
|
const errors = [
|
|
3277
3439
|
...checkIssueRationale(issue.body).errors,
|
|
3278
|
-
...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors
|
|
3440
|
+
...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
|
|
3441
|
+
...checkIssueObjectives(issue.body, issue.number).errors
|
|
3279
3442
|
];
|
|
3280
3443
|
if (errors.length === 0)
|
|
3281
3444
|
continue;
|
|
@@ -3390,6 +3553,27 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
3390
3553
|
}
|
|
3391
3554
|
return { ok: true, expectedIssue };
|
|
3392
3555
|
}
|
|
3556
|
+
// ../../packages/aeg-core/src/archive-task.ts
|
|
3557
|
+
function closesRefs(text) {
|
|
3558
|
+
return [...new Set([...text.matchAll(/Closes #(\d+)/gi)].map((m) => Number(m[1])))];
|
|
3559
|
+
}
|
|
3560
|
+
function extractIssue(body) {
|
|
3561
|
+
const stripped = stripCode(body);
|
|
3562
|
+
const bodyNums = closesRefs(stripped);
|
|
3563
|
+
const anchored = anchoredRegion(stripped, "CLOSES");
|
|
3564
|
+
if (anchored !== null) {
|
|
3565
|
+
const anchorNums = closesRefs(anchored);
|
|
3566
|
+
if (anchorNums.length === 0)
|
|
3567
|
+
return { issue: null, extraIssues: bodyNums, outsideHeader: false };
|
|
3568
|
+
const issue2 = anchorNums[0];
|
|
3569
|
+
return { issue: issue2, extraIssues: bodyNums.filter((n) => n !== issue2), outsideHeader: false };
|
|
3570
|
+
}
|
|
3571
|
+
const headerNums = closesRefs(headerRegion(stripped));
|
|
3572
|
+
if (bodyNums.length === 0)
|
|
3573
|
+
return { issue: null, extraIssues: [], outsideHeader: false };
|
|
3574
|
+
const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
|
|
3575
|
+
return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
|
|
3576
|
+
}
|
|
3393
3577
|
// ../../packages/aeg-core/src/review-gate.ts
|
|
3394
3578
|
function isBoundToHead(extraction, headSha) {
|
|
3395
3579
|
if (!extraction.headSha)
|
|
@@ -3408,6 +3592,11 @@ function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
|
|
|
3408
3592
|
function isBoundToPatch(extraction, headSha, patchIdOf) {
|
|
3409
3593
|
return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
|
|
3410
3594
|
}
|
|
3595
|
+
function isBoundToObjectives(extraction, currentVersion) {
|
|
3596
|
+
if (currentVersion === null)
|
|
3597
|
+
return true;
|
|
3598
|
+
return extraction.objectivesVersion === currentVersion;
|
|
3599
|
+
}
|
|
3411
3600
|
function checkReviewGate(input) {
|
|
3412
3601
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
3413
3602
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -3434,7 +3623,9 @@ function checkReviewGate(input) {
|
|
|
3434
3623
|
const securityClean = security.value === "PASS";
|
|
3435
3624
|
const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
|
|
3436
3625
|
const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
|
|
3437
|
-
|
|
3626
|
+
const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
|
|
3627
|
+
const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
|
|
3628
|
+
if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && securityClean && securityBound && securityObjectivesBound && mechanicalChecksClean) {
|
|
3438
3629
|
return {
|
|
3439
3630
|
verdict: "pass",
|
|
3440
3631
|
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.`,
|
|
@@ -3446,11 +3637,15 @@ function checkReviewGate(input) {
|
|
|
3446
3637
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
3447
3638
|
} else if (!codeReviewBound) {
|
|
3448
3639
|
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3640
|
+
} else if (!codeReviewObjectivesBound) {
|
|
3641
|
+
problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3449
3642
|
}
|
|
3450
3643
|
if (!securityClean) {
|
|
3451
3644
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
3452
3645
|
} else if (!securityBound) {
|
|
3453
3646
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3647
|
+
} else if (!securityObjectivesBound) {
|
|
3648
|
+
problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3454
3649
|
}
|
|
3455
3650
|
if (!mechanicalChecksClean) {
|
|
3456
3651
|
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(", ")}`);
|
|
@@ -3686,55 +3881,20 @@ function checkG6(rows, registeredCheckNames) {
|
|
|
3686
3881
|
}
|
|
3687
3882
|
return { check: "G6", status: findings.length > 0 ? "fail" : "pass", findings };
|
|
3688
3883
|
}
|
|
3689
|
-
// ../../packages/aeg-core/src/
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
return {
|
|
3700
|
-
verdict: "refuse",
|
|
3701
|
-
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.`
|
|
3702
|
-
};
|
|
3703
|
-
}
|
|
3704
|
-
if (prState === "OPEN") {
|
|
3705
|
-
return {
|
|
3706
|
-
verdict: "allow",
|
|
3707
|
-
reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
|
|
3708
|
-
};
|
|
3709
|
-
}
|
|
3710
|
-
if (prState === "NONE") {
|
|
3711
|
-
return {
|
|
3712
|
-
verdict: "allow",
|
|
3713
|
-
reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
|
|
3714
|
-
};
|
|
3715
|
-
}
|
|
3716
|
-
return {
|
|
3717
|
-
verdict: "allow",
|
|
3718
|
-
reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
|
|
3719
|
-
};
|
|
3720
|
-
}
|
|
3721
|
-
// ../../packages/aeg-core/src/baseline-capture.ts
|
|
3722
|
-
function captureBaseline(counts, capturedAt) {
|
|
3723
|
-
return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
|
|
3884
|
+
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
3885
|
+
var PRODUCT_SLUG_SCOPE = [
|
|
3886
|
+
"apps/cli/src",
|
|
3887
|
+
".github/workflows",
|
|
3888
|
+
".vinaya",
|
|
3889
|
+
"apps/cli/README.md",
|
|
3890
|
+
"packages/sources/README.md"
|
|
3891
|
+
];
|
|
3892
|
+
function isUnderOrEqual(path, prefix) {
|
|
3893
|
+
return path === prefix || path.startsWith(`${prefix}/`);
|
|
3724
3894
|
}
|
|
3725
|
-
function
|
|
3726
|
-
|
|
3727
|
-
const baselineEntry = baseline.find((b) => b.tool === c.tool);
|
|
3728
|
-
const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
|
|
3729
|
-
return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
|
|
3730
|
-
});
|
|
3731
|
-
return {
|
|
3732
|
-
withinBudget: perTool.every((t) => t.delta <= 0),
|
|
3733
|
-
delta: perTool.reduce((sum, t) => sum + t.delta, 0),
|
|
3734
|
-
perTool
|
|
3735
|
-
};
|
|
3895
|
+
function isProductScopeFile(path) {
|
|
3896
|
+
return PRODUCT_SLUG_SCOPE.some((prefix) => isUnderOrEqual(path, prefix));
|
|
3736
3897
|
}
|
|
3737
|
-
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
3738
3898
|
var SHIPS_PREFIX = "aeg-root/";
|
|
3739
3899
|
function shipsArchivePrefix(shipsPrefix) {
|
|
3740
3900
|
return `${shipsPrefix}tranches/completed/`;
|
|
@@ -3754,6 +3914,8 @@ function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix, shipsPr
|
|
|
3754
3914
|
}
|
|
3755
3915
|
if (isSpecFile2(path) || isClaudeMdFile(path))
|
|
3756
3916
|
return "internal";
|
|
3917
|
+
if (isProductScopeFile(path))
|
|
3918
|
+
return "product";
|
|
3757
3919
|
return null;
|
|
3758
3920
|
}
|
|
3759
3921
|
var SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
|
|
@@ -3766,6 +3928,9 @@ function stripNonProse(path, content) {
|
|
|
3766
3928
|
}
|
|
3767
3929
|
return content;
|
|
3768
3930
|
}
|
|
3931
|
+
function stripNonProseForProduct(path, content) {
|
|
3932
|
+
return path.endsWith(".md") ? stripNonProse(path, content) : content;
|
|
3933
|
+
}
|
|
3769
3934
|
function lineAt(content, index) {
|
|
3770
3935
|
let line = 1;
|
|
3771
3936
|
for (let i = 0;i < index; i++) {
|
|
@@ -3790,12 +3955,19 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
|
|
|
3790
3955
|
{ pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug" },
|
|
3791
3956
|
...legacyPattern ? [{ pattern: legacyPattern, what: "an internal tranche slug", group: 2 }] : []
|
|
3792
3957
|
];
|
|
3958
|
+
const productPatterns = [
|
|
3959
|
+
{ pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug in product code" }
|
|
3960
|
+
];
|
|
3793
3961
|
for (const file of files) {
|
|
3794
3962
|
const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
|
|
3795
|
-
if (!cls ||
|
|
3963
|
+
if (!cls || cls === "internal")
|
|
3796
3964
|
continue;
|
|
3797
|
-
|
|
3798
|
-
|
|
3965
|
+
if (cls !== "product" && !SWEPT_CLASSES.has(cls))
|
|
3966
|
+
continue;
|
|
3967
|
+
const blocking = cls === "product";
|
|
3968
|
+
const scrubbed = blocking ? stripNonProseForProduct(file.path, file.content) : stripNonProse(file.path, file.content);
|
|
3969
|
+
const patternsToRun = blocking ? productPatterns : patterns;
|
|
3970
|
+
for (const { pattern, what, group } of patternsToRun) {
|
|
3799
3971
|
pattern.lastIndex = 0;
|
|
3800
3972
|
let match = pattern.exec(scrubbed);
|
|
3801
3973
|
while (match !== null) {
|
|
@@ -3803,7 +3975,8 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
|
|
|
3803
3975
|
findings.push({
|
|
3804
3976
|
file: file.path,
|
|
3805
3977
|
line: lineAt(scrubbed, match.index),
|
|
3806
|
-
message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve
|
|
3978
|
+
message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve`,
|
|
3979
|
+
blocking
|
|
3807
3980
|
});
|
|
3808
3981
|
match = pattern.exec(scrubbed);
|
|
3809
3982
|
}
|
|
@@ -3853,7 +4026,8 @@ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, read
|
|
|
3853
4026
|
findings.push({
|
|
3854
4027
|
file: file.path,
|
|
3855
4028
|
line,
|
|
3856
|
-
message: `uses coined term "${term}" without defining it inline or linking the glossary
|
|
4029
|
+
message: `uses coined term "${term}" without defining it inline or linking the glossary`,
|
|
4030
|
+
blocking: false
|
|
3857
4031
|
});
|
|
3858
4032
|
}
|
|
3859
4033
|
}
|
|
@@ -3876,6 +4050,7 @@ function parseGlossaryTerms(glossaryContent) {
|
|
|
3876
4050
|
}
|
|
3877
4051
|
return terms;
|
|
3878
4052
|
}
|
|
4053
|
+
|
|
3879
4054
|
// ../../packages/aeg-core/src/quoted-command.ts
|
|
3880
4055
|
var QUOTED_COMMAND_SHIPS_PREFIX = "aeg-root/";
|
|
3881
4056
|
var QUOTED_COMMAND_SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
|
|
@@ -3970,6 +4145,54 @@ function evaluateCitedQuotes(citedQuotes, citedFileContents) {
|
|
|
3970
4145
|
}
|
|
3971
4146
|
return findings;
|
|
3972
4147
|
}
|
|
4148
|
+
// ../../packages/aeg-core/src/dead-branch-push-guard.ts
|
|
4149
|
+
function checkDeadBranchPush(input) {
|
|
4150
|
+
const { branch, prState, prNumber } = input;
|
|
4151
|
+
if (prState === "MERGED") {
|
|
4152
|
+
return {
|
|
4153
|
+
verdict: "refuse",
|
|
4154
|
+
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.`
|
|
4155
|
+
};
|
|
4156
|
+
}
|
|
4157
|
+
if (prState === "CLOSED") {
|
|
4158
|
+
return {
|
|
4159
|
+
verdict: "refuse",
|
|
4160
|
+
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.`
|
|
4161
|
+
};
|
|
4162
|
+
}
|
|
4163
|
+
if (prState === "OPEN") {
|
|
4164
|
+
return {
|
|
4165
|
+
verdict: "allow",
|
|
4166
|
+
reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
|
|
4167
|
+
};
|
|
4168
|
+
}
|
|
4169
|
+
if (prState === "NONE") {
|
|
4170
|
+
return {
|
|
4171
|
+
verdict: "allow",
|
|
4172
|
+
reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
|
|
4173
|
+
};
|
|
4174
|
+
}
|
|
4175
|
+
return {
|
|
4176
|
+
verdict: "allow",
|
|
4177
|
+
reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
|
|
4178
|
+
};
|
|
4179
|
+
}
|
|
4180
|
+
// ../../packages/aeg-core/src/baseline-capture.ts
|
|
4181
|
+
function captureBaseline(counts, capturedAt) {
|
|
4182
|
+
return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
|
|
4183
|
+
}
|
|
4184
|
+
function compareToBaseline(current, baseline) {
|
|
4185
|
+
const perTool = current.map((c) => {
|
|
4186
|
+
const baselineEntry = baseline.find((b) => b.tool === c.tool);
|
|
4187
|
+
const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
|
|
4188
|
+
return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
|
|
4189
|
+
});
|
|
4190
|
+
return {
|
|
4191
|
+
withinBudget: perTool.every((t) => t.delta <= 0),
|
|
4192
|
+
delta: perTool.reduce((sum, t) => sum + t.delta, 0),
|
|
4193
|
+
perTool
|
|
4194
|
+
};
|
|
4195
|
+
}
|
|
3973
4196
|
// ../../packages/aeg-core/src/retired-vocabulary.ts
|
|
3974
4197
|
var RETIRED_PATTERNS = [
|
|
3975
4198
|
String.raw`\bD-[0-9]{3}\b`,
|
|
@@ -4439,6 +4662,222 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
|
|
|
4439
4662
|
}
|
|
4440
4663
|
return findings;
|
|
4441
4664
|
}
|
|
4665
|
+
// ../../packages/aeg-core/src/log/schema.ts
|
|
4666
|
+
import { z } from "zod";
|
|
4667
|
+
var ROLE_VALUES = [
|
|
4668
|
+
"planner",
|
|
4669
|
+
"brief-author",
|
|
4670
|
+
"developer",
|
|
4671
|
+
"code-reviewer",
|
|
4672
|
+
"security",
|
|
4673
|
+
"principal",
|
|
4674
|
+
"archivist",
|
|
4675
|
+
"architect"
|
|
4676
|
+
];
|
|
4677
|
+
var RoleSchema = z.enum(ROLE_VALUES);
|
|
4678
|
+
var HOST_VALUES = ["hook", "ci", "cli", "loop"];
|
|
4679
|
+
var HostSchema = z.enum(HOST_VALUES);
|
|
4680
|
+
var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
4681
|
+
var HeaderMetaSchema = z.object({
|
|
4682
|
+
schema: z.literal(1),
|
|
4683
|
+
ts: z.string(),
|
|
4684
|
+
run_id: z.string().regex(RUN_ID_PATTERN),
|
|
4685
|
+
seq: z.number().int().nonnegative(),
|
|
4686
|
+
repo: z.string().nullable(),
|
|
4687
|
+
vinaya: z.string(),
|
|
4688
|
+
doctrine: z.string(),
|
|
4689
|
+
host: HostSchema,
|
|
4690
|
+
machine: z.string()
|
|
4691
|
+
}).strict();
|
|
4692
|
+
var SubjectSchema = z.object({
|
|
4693
|
+
issue: z.number().int().nullable(),
|
|
4694
|
+
pr: z.number().int().optional(),
|
|
4695
|
+
sha: z.string().optional(),
|
|
4696
|
+
role: z.union([RoleSchema, z.literal("unattributed")]),
|
|
4697
|
+
round: z.number().int().optional(),
|
|
4698
|
+
objectives_version: z.string().optional()
|
|
4699
|
+
}).strict();
|
|
4700
|
+
var HeaderSchema = z.object({
|
|
4701
|
+
meta: HeaderMetaSchema,
|
|
4702
|
+
subject: SubjectSchema
|
|
4703
|
+
}).strict();
|
|
4704
|
+
var envelopeTail = {
|
|
4705
|
+
duration_ms: z.number().nonnegative().optional(),
|
|
4706
|
+
payload: z.object({}).strict()
|
|
4707
|
+
};
|
|
4708
|
+
var DispatchOutcomeSchema = z.discriminatedUnion("type", [
|
|
4709
|
+
z.object({ type: z.literal("pr_opened"), pr: z.number().int(), head: z.string() }).strict(),
|
|
4710
|
+
z.object({
|
|
4711
|
+
type: z.literal("round_pushed"),
|
|
4712
|
+
pr: z.number().int(),
|
|
4713
|
+
head: z.string(),
|
|
4714
|
+
comment_id: z.number().int()
|
|
4715
|
+
}).strict(),
|
|
4716
|
+
z.object({
|
|
4717
|
+
type: z.literal("verdict"),
|
|
4718
|
+
verdict: z.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
|
|
4719
|
+
head: z.string(),
|
|
4720
|
+
comment_id: z.number().int(),
|
|
4721
|
+
objectives: z.array(z.object({ id: z.string(), met: z.boolean() }).strict()),
|
|
4722
|
+
findings: z.array(z.object({ id: z.string(), severity: z.string(), state: z.string().optional() }).strict())
|
|
4723
|
+
}).strict(),
|
|
4724
|
+
z.object({
|
|
4725
|
+
type: z.literal("escalation"),
|
|
4726
|
+
class: z.enum(["authority", "strategy", "product"]),
|
|
4727
|
+
comment_id: z.number().int()
|
|
4728
|
+
}).strict(),
|
|
4729
|
+
z.object({ type: z.literal("brief"), comment_id: z.number().int(), hash: z.string() }).strict(),
|
|
4730
|
+
z.object({ type: z.literal("plan"), issues: z.array(z.number().int()) }).strict(),
|
|
4731
|
+
z.object({ type: z.literal("archive"), provenance_comment_id: z.number().int() }).strict()
|
|
4732
|
+
]);
|
|
4733
|
+
var dispatchShared = {
|
|
4734
|
+
meta: HeaderMetaSchema,
|
|
4735
|
+
subject: SubjectSchema,
|
|
4736
|
+
kind: z.literal("dispatch"),
|
|
4737
|
+
...envelopeTail,
|
|
4738
|
+
target_role: RoleSchema,
|
|
4739
|
+
model: z.string(),
|
|
4740
|
+
round: z.number().int().optional(),
|
|
4741
|
+
effect_id: z.string()
|
|
4742
|
+
};
|
|
4743
|
+
var DispatchEventSchema = z.discriminatedUnion("event", [
|
|
4744
|
+
z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
|
|
4745
|
+
z.object({
|
|
4746
|
+
...dispatchShared,
|
|
4747
|
+
event: z.literal("outcome_received"),
|
|
4748
|
+
outcome: DispatchOutcomeSchema,
|
|
4749
|
+
usage: z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable()
|
|
4750
|
+
}).strict(),
|
|
4751
|
+
z.object({
|
|
4752
|
+
...dispatchShared,
|
|
4753
|
+
event: z.literal("dispatch_failed"),
|
|
4754
|
+
reason: z.enum(["timeout", "crash", "refused", "unattributed_write"])
|
|
4755
|
+
}).strict()
|
|
4756
|
+
]);
|
|
4757
|
+
var loopShared = {
|
|
4758
|
+
meta: HeaderMetaSchema,
|
|
4759
|
+
subject: SubjectSchema,
|
|
4760
|
+
kind: z.literal("dev_review_loop"),
|
|
4761
|
+
...envelopeTail,
|
|
4762
|
+
loop_id: z.string()
|
|
4763
|
+
};
|
|
4764
|
+
var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
|
|
4765
|
+
z.object({
|
|
4766
|
+
...loopShared,
|
|
4767
|
+
event: z.literal("loop_started"),
|
|
4768
|
+
task: z.number().int(),
|
|
4769
|
+
policy: z.object({
|
|
4770
|
+
max_rounds: z.number().int().nonnegative(),
|
|
4771
|
+
reviewers: z.array(RoleSchema),
|
|
4772
|
+
models: z.record(RoleSchema, z.string())
|
|
4773
|
+
}).strict()
|
|
4774
|
+
}).strict(),
|
|
4775
|
+
z.object({ ...loopShared, event: z.literal("round_started"), round: z.number().int(), base_head: z.string() }).strict(),
|
|
4776
|
+
z.object({
|
|
4777
|
+
...loopShared,
|
|
4778
|
+
event: z.literal("gate_result_read"),
|
|
4779
|
+
round: z.number().int(),
|
|
4780
|
+
head: z.string(),
|
|
4781
|
+
green: z.boolean()
|
|
4782
|
+
}).strict(),
|
|
4783
|
+
z.object({
|
|
4784
|
+
...loopShared,
|
|
4785
|
+
event: z.literal("verdicts_read"),
|
|
4786
|
+
round: z.number().int(),
|
|
4787
|
+
head: z.string(),
|
|
4788
|
+
all_approve: z.boolean(),
|
|
4789
|
+
blockers: z.number().int().nonnegative()
|
|
4790
|
+
}).strict(),
|
|
4791
|
+
z.object({
|
|
4792
|
+
...loopShared,
|
|
4793
|
+
event: z.literal("findings_compared"),
|
|
4794
|
+
round: z.number().int(),
|
|
4795
|
+
open: z.array(z.string()),
|
|
4796
|
+
resolved: z.array(z.string()),
|
|
4797
|
+
new: z.array(z.string()),
|
|
4798
|
+
recurring: z.array(z.string())
|
|
4799
|
+
}).strict(),
|
|
4800
|
+
z.object({
|
|
4801
|
+
...loopShared,
|
|
4802
|
+
event: z.literal("stop_condition_met"),
|
|
4803
|
+
round: z.number().int(),
|
|
4804
|
+
condition: z.enum([
|
|
4805
|
+
"green",
|
|
4806
|
+
"max_rounds",
|
|
4807
|
+
"no_progress",
|
|
4808
|
+
"escalated",
|
|
4809
|
+
"principal_stop",
|
|
4810
|
+
"confidence",
|
|
4811
|
+
"reappearance"
|
|
4812
|
+
])
|
|
4813
|
+
}).strict(),
|
|
4814
|
+
z.object({
|
|
4815
|
+
...loopShared,
|
|
4816
|
+
event: z.literal("paused"),
|
|
4817
|
+
round: z.number().int(),
|
|
4818
|
+
reason: z.enum(["escalation", "principal_item", "refreeze_needed"])
|
|
4819
|
+
}).strict(),
|
|
4820
|
+
z.object({
|
|
4821
|
+
...loopShared,
|
|
4822
|
+
event: z.literal("resumed"),
|
|
4823
|
+
round: z.number().int(),
|
|
4824
|
+
by: z.literal("principal")
|
|
4825
|
+
}).strict(),
|
|
4826
|
+
z.object({
|
|
4827
|
+
...loopShared,
|
|
4828
|
+
event: z.literal("round_ended"),
|
|
4829
|
+
round: z.number().int(),
|
|
4830
|
+
base_head: z.string(),
|
|
4831
|
+
head: z.string(),
|
|
4832
|
+
files_changed: z.number().int().nonnegative(),
|
|
4833
|
+
insertions: z.number().int().nonnegative(),
|
|
4834
|
+
deletions: z.number().int().nonnegative(),
|
|
4835
|
+
wall_ms: z.number().nonnegative(),
|
|
4836
|
+
outcome: z.enum(["green", "changes_requested", "escalated"])
|
|
4837
|
+
}).strict(),
|
|
4838
|
+
z.object({
|
|
4839
|
+
...loopShared,
|
|
4840
|
+
event: z.literal("journal_finalized"),
|
|
4841
|
+
rounds: z.number().int().nonnegative(),
|
|
4842
|
+
total_wall_ms: z.number().nonnegative(),
|
|
4843
|
+
time_to_green_ms: z.number().nonnegative().nullable(),
|
|
4844
|
+
files_changed_total: z.number().int().nonnegative(),
|
|
4845
|
+
final_head: z.string(),
|
|
4846
|
+
result: z.enum(["merged_ready", "stopped"])
|
|
4847
|
+
}).strict()
|
|
4848
|
+
]);
|
|
4849
|
+
var ForgeOpSchema = z.enum([
|
|
4850
|
+
"pr.create",
|
|
4851
|
+
"pr.comment",
|
|
4852
|
+
"pr.body.replace",
|
|
4853
|
+
"pr.refreeze",
|
|
4854
|
+
"issue.create",
|
|
4855
|
+
"issue.edit",
|
|
4856
|
+
"issue.comment",
|
|
4857
|
+
"milestone.create",
|
|
4858
|
+
"milestone.edit",
|
|
4859
|
+
"milestone.close",
|
|
4860
|
+
"label.add",
|
|
4861
|
+
"label.remove"
|
|
4862
|
+
]);
|
|
4863
|
+
var ForgeWriteTargetSchema = z.object({
|
|
4864
|
+
issue: z.number().int().optional(),
|
|
4865
|
+
pr: z.number().int().optional()
|
|
4866
|
+
}).strict();
|
|
4867
|
+
var forgeWriteShared = {
|
|
4868
|
+
meta: HeaderMetaSchema,
|
|
4869
|
+
subject: SubjectSchema,
|
|
4870
|
+
kind: z.literal("forge_write"),
|
|
4871
|
+
...envelopeTail,
|
|
4872
|
+
op: ForgeOpSchema,
|
|
4873
|
+
target: ForgeWriteTargetSchema
|
|
4874
|
+
};
|
|
4875
|
+
var ForgeWriteEventSchema = z.discriminatedUnion("event", [
|
|
4876
|
+
z.object({ ...forgeWriteShared, event: z.literal("validated") }).strict(),
|
|
4877
|
+
z.object({ ...forgeWriteShared, event: z.literal("refused"), reason: z.string() }).strict(),
|
|
4878
|
+
z.object({ ...forgeWriteShared, event: z.literal("written"), comment_ids: z.array(z.string()) }).strict()
|
|
4879
|
+
]);
|
|
4880
|
+
var LogEventSchema = z.union([DispatchEventSchema, DevReviewLoopEventSchema, ForgeWriteEventSchema]);
|
|
4442
4881
|
// src/commands/doctrine.ts
|
|
4443
4882
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
4444
4883
|
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
@@ -4497,27 +4936,27 @@ import { execFileSync as execFileSync3 } from "node:child_process";
|
|
|
4497
4936
|
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, realpathSync, writeFileSync } from "node:fs";
|
|
4498
4937
|
import { homedir } from "node:os";
|
|
4499
4938
|
import { dirname as dirname3, join as join3, resolve } from "node:path";
|
|
4500
|
-
import { z } from "zod";
|
|
4939
|
+
import { z as z2 } from "zod";
|
|
4501
4940
|
|
|
4502
4941
|
// src/lib/agent-vendors.ts
|
|
4503
4942
|
var AGENT_VENDORS = ["skills", "claude", "gemini"];
|
|
4504
4943
|
|
|
4505
4944
|
// src/lib/config.ts
|
|
4506
|
-
var EnvEntrySchema =
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4945
|
+
var EnvEntrySchema = z2.union([
|
|
4946
|
+
z2.literal(true),
|
|
4947
|
+
z2.object({ optional: z2.literal(true) }),
|
|
4948
|
+
z2.object({ anyOf: z2.array(z2.string()).min(2) }),
|
|
4949
|
+
z2.string()
|
|
4511
4950
|
]);
|
|
4512
|
-
var CheckEntrySchema =
|
|
4513
|
-
run:
|
|
4514
|
-
scope:
|
|
4515
|
-
include:
|
|
4516
|
-
args:
|
|
4517
|
-
timeoutMs:
|
|
4518
|
-
env:
|
|
4519
|
-
requiresOpenPr:
|
|
4520
|
-
ownWorkflow:
|
|
4951
|
+
var CheckEntrySchema = z2.object({
|
|
4952
|
+
run: z2.string(),
|
|
4953
|
+
scope: z2.enum(["diff", "full"]),
|
|
4954
|
+
include: z2.array(z2.string()).optional(),
|
|
4955
|
+
args: z2.array(z2.string()).optional(),
|
|
4956
|
+
timeoutMs: z2.number().optional(),
|
|
4957
|
+
env: z2.record(z2.string(), EnvEntrySchema).optional(),
|
|
4958
|
+
requiresOpenPr: z2.boolean().optional(),
|
|
4959
|
+
ownWorkflow: z2.boolean().optional()
|
|
4521
4960
|
}).superRefine((entry2, ctx) => {
|
|
4522
4961
|
if (!entry2.env)
|
|
4523
4962
|
return;
|
|
@@ -4527,22 +4966,22 @@ var CheckEntrySchema = z.object({
|
|
|
4527
4966
|
const members = value.anyOf;
|
|
4528
4967
|
if (new Set(members).size !== members.length) {
|
|
4529
4968
|
ctx.addIssue({
|
|
4530
|
-
code:
|
|
4969
|
+
code: z2.ZodIssueCode.custom,
|
|
4531
4970
|
message: `env.${key}.anyOf has duplicate members`,
|
|
4532
4971
|
path: ["env", key, "anyOf"]
|
|
4533
4972
|
});
|
|
4534
4973
|
}
|
|
4535
4974
|
if (!members.includes(key)) {
|
|
4536
4975
|
ctx.addIssue({
|
|
4537
|
-
code:
|
|
4976
|
+
code: z2.ZodIssueCode.custom,
|
|
4538
4977
|
message: `env.${key}.anyOf must include "${key}" itself as a member`,
|
|
4539
4978
|
path: ["env", key, "anyOf"]
|
|
4540
4979
|
});
|
|
4541
4980
|
}
|
|
4542
4981
|
}
|
|
4543
4982
|
});
|
|
4544
|
-
var RoleEntrySchema =
|
|
4545
|
-
contract:
|
|
4983
|
+
var RoleEntrySchema = z2.object({
|
|
4984
|
+
contract: z2.string().refine((p) => p.includes("/"), {
|
|
4546
4985
|
message: 'must be a path (contain at least one "/"), not a bare filename'
|
|
4547
4986
|
})
|
|
4548
4987
|
});
|
|
@@ -4561,19 +5000,21 @@ var BRIEF_BUILTINS = [
|
|
|
4561
5000
|
"closesN",
|
|
4562
5001
|
"premiseCoverage",
|
|
4563
5002
|
"issueRationale",
|
|
5003
|
+
"objectives",
|
|
5004
|
+
"briefSections",
|
|
4564
5005
|
"milestoneShape"
|
|
4565
5006
|
];
|
|
4566
|
-
var BriefSectionSchema =
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
5007
|
+
var BriefSectionSchema = z2.union([
|
|
5008
|
+
z2.object({ builtin: z2.enum(BRIEF_BUILTINS) }),
|
|
5009
|
+
z2.object({ heading: z2.string().min(1), name: z2.string().optional() }),
|
|
5010
|
+
z2.object({ field: z2.string().min(1), name: z2.string().optional() }),
|
|
5011
|
+
z2.object({ phrase: z2.string().min(1), name: z2.string().optional() })
|
|
4571
5012
|
]);
|
|
4572
|
-
var BriefSchemaSchema =
|
|
4573
|
-
pr:
|
|
4574
|
-
issue:
|
|
4575
|
-
milestone:
|
|
4576
|
-
ack:
|
|
5013
|
+
var BriefSchemaSchema = z2.object({
|
|
5014
|
+
pr: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
|
|
5015
|
+
issue: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
|
|
5016
|
+
milestone: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
|
|
5017
|
+
ack: z2.array(z2.enum(BRIEF_BUILTINS)).optional()
|
|
4577
5018
|
});
|
|
4578
5019
|
function isSafeRepoRelPath(p) {
|
|
4579
5020
|
if (p.length === 0)
|
|
@@ -4582,7 +5023,7 @@ function isSafeRepoRelPath(p) {
|
|
|
4582
5023
|
return false;
|
|
4583
5024
|
return p.split(/[\\/]/).every((seg) => seg !== ".." && seg !== "");
|
|
4584
5025
|
}
|
|
4585
|
-
var SafeRepoRelPath =
|
|
5026
|
+
var SafeRepoRelPath = z2.string().refine(isSafeRepoRelPath, {
|
|
4586
5027
|
message: "must be a repo-root-relative path with no `..` segment or absolute root"
|
|
4587
5028
|
});
|
|
4588
5029
|
var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
|
|
@@ -4592,22 +5033,22 @@ function isCanonicalHookBlockPath(p) {
|
|
|
4592
5033
|
var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
|
|
4593
5034
|
message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
|
|
4594
5035
|
});
|
|
4595
|
-
var ManagedBlockRecordSchema =
|
|
5036
|
+
var ManagedBlockRecordSchema = z2.object({
|
|
4596
5037
|
path: ManagedHookBlockPath,
|
|
4597
|
-
marker:
|
|
4598
|
-
comment:
|
|
5038
|
+
marker: z2.string(),
|
|
5039
|
+
comment: z2.enum(["hash", "html"])
|
|
4599
5040
|
});
|
|
4600
|
-
var ManagedManifestSchema =
|
|
4601
|
-
version:
|
|
4602
|
-
files:
|
|
4603
|
-
blocks:
|
|
4604
|
-
labels:
|
|
4605
|
-
agents:
|
|
5041
|
+
var ManagedManifestSchema = z2.object({
|
|
5042
|
+
version: z2.number().int().positive(),
|
|
5043
|
+
files: z2.array(SafeRepoRelPath),
|
|
5044
|
+
blocks: z2.array(ManagedBlockRecordSchema),
|
|
5045
|
+
labels: z2.array(z2.string()),
|
|
5046
|
+
agents: z2.array(z2.enum(AGENT_VENDORS)).optional()
|
|
4606
5047
|
});
|
|
4607
|
-
var ProjectEntrySchema =
|
|
4608
|
-
name:
|
|
4609
|
-
description:
|
|
4610
|
-
path:
|
|
5048
|
+
var ProjectEntrySchema = z2.object({
|
|
5049
|
+
name: z2.string().min(1),
|
|
5050
|
+
description: z2.string().min(1).optional(),
|
|
5051
|
+
path: z2.string().min(1).optional()
|
|
4611
5052
|
});
|
|
4612
5053
|
function parseTokensCollectDeclaration(value) {
|
|
4613
5054
|
const m = value.trim().match(/^(\S+)\s+(\S+)$/);
|
|
@@ -4618,31 +5059,35 @@ function parseTokensCollectDeclaration(value) {
|
|
|
4618
5059
|
return null;
|
|
4619
5060
|
return { interpreter, script };
|
|
4620
5061
|
}
|
|
4621
|
-
var VinayaConfigSchema =
|
|
4622
|
-
rings:
|
|
4623
|
-
ring1_forgeWriteInterception:
|
|
4624
|
-
ring2_asyncAudits:
|
|
5062
|
+
var VinayaConfigSchema = z2.object({
|
|
5063
|
+
rings: z2.object({
|
|
5064
|
+
ring1_forgeWriteInterception: z2.boolean(),
|
|
5065
|
+
ring2_asyncAudits: z2.boolean()
|
|
4625
5066
|
}).optional(),
|
|
4626
|
-
checks:
|
|
4627
|
-
roles:
|
|
5067
|
+
checks: z2.record(z2.string(), CheckEntrySchema).optional(),
|
|
5068
|
+
roles: z2.record(z2.string(), RoleEntrySchema).optional(),
|
|
4628
5069
|
briefSchema: BriefSchemaSchema.optional(),
|
|
4629
5070
|
managed: ManagedManifestSchema.optional(),
|
|
4630
|
-
principals:
|
|
4631
|
-
releaseActor:
|
|
4632
|
-
ci:
|
|
4633
|
-
tokens:
|
|
4634
|
-
collect:
|
|
5071
|
+
principals: z2.array(z2.string()).min(1).optional(),
|
|
5072
|
+
releaseActor: z2.string().min(1).optional(),
|
|
5073
|
+
ci: z2.object({ setup: z2.string().min(1) }).optional(),
|
|
5074
|
+
tokens: z2.object({
|
|
5075
|
+
collect: z2.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
|
|
4635
5076
|
message: 'tokens.collect must be exactly "<interpreter> <repo-relative-script-path>" — two whitespace-separated tokens, no flags, no shell syntax, no quoting'
|
|
4636
5077
|
})
|
|
4637
5078
|
}).optional(),
|
|
4638
|
-
blastRadius:
|
|
4639
|
-
proseGates:
|
|
4640
|
-
doctrineRoot:
|
|
4641
|
-
readerFacingPrefix:
|
|
4642
|
-
readerFacingSuffix:
|
|
4643
|
-
legacySlugDir:
|
|
5079
|
+
blastRadius: z2.object({ extraDomains: z2.array(z2.string()).optional() }).optional(),
|
|
5080
|
+
proseGates: z2.object({
|
|
5081
|
+
doctrineRoot: z2.string().min(1).optional(),
|
|
5082
|
+
readerFacingPrefix: z2.string().min(1).optional(),
|
|
5083
|
+
readerFacingSuffix: z2.string().min(1).optional(),
|
|
5084
|
+
legacySlugDir: z2.string().min(1).optional()
|
|
4644
5085
|
}).optional(),
|
|
4645
|
-
projects:
|
|
5086
|
+
projects: z2.array(ProjectEntrySchema).optional(),
|
|
5087
|
+
dispatch: z2.object({
|
|
5088
|
+
timeoutMs: z2.number().int().positive().optional(),
|
|
5089
|
+
agent: z2.enum(["claude", "codex", "gemini"]).optional()
|
|
5090
|
+
}).optional()
|
|
4646
5091
|
});
|
|
4647
5092
|
var GLOBAL_VINAYA_HOME = join3(homedir(), ".vinaya");
|
|
4648
5093
|
var GLOBAL_CONFIG_PATH = join3(GLOBAL_VINAYA_HOME, "config.json");
|
|
@@ -4972,6 +5417,32 @@ function collect(dir, out = []) {
|
|
|
4972
5417
|
function readAll(paths) {
|
|
4973
5418
|
return paths.map((p) => ({ path: p, content: readFileSync4(p, "utf8") }));
|
|
4974
5419
|
}
|
|
5420
|
+
var PRODUCT_SCOPE_EXTENSIONS = new Set([".ts", ".tsx", ".yml", ".md"]);
|
|
5421
|
+
function collectProductScopeFiles(root) {
|
|
5422
|
+
const out = [];
|
|
5423
|
+
for (const prefix of PRODUCT_SLUG_SCOPE) {
|
|
5424
|
+
const abs = join5(root, prefix);
|
|
5425
|
+
let isDir;
|
|
5426
|
+
try {
|
|
5427
|
+
isDir = statSync(abs).isDirectory();
|
|
5428
|
+
} catch {
|
|
5429
|
+
continue;
|
|
5430
|
+
}
|
|
5431
|
+
if (isDir) {
|
|
5432
|
+
for (const f of collect(abs)) {
|
|
5433
|
+
const rel = f.slice(root.length + 1);
|
|
5434
|
+
if (PRODUCT_SCOPE_EXTENSIONS.has(extname(rel)))
|
|
5435
|
+
out.push(rel);
|
|
5436
|
+
}
|
|
5437
|
+
} else if (PRODUCT_SCOPE_EXTENSIONS.has(extname(prefix))) {
|
|
5438
|
+
out.push(prefix);
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5441
|
+
return out;
|
|
5442
|
+
}
|
|
5443
|
+
function readProductFiles(root, relPaths) {
|
|
5444
|
+
return relPaths.map((rel) => ({ path: rel, content: readFileSync4(join5(root, rel), "utf8") }));
|
|
5445
|
+
}
|
|
4975
5446
|
function legacySlugs(legacySlugDir) {
|
|
4976
5447
|
if (!existsSync4(legacySlugDir))
|
|
4977
5448
|
return { slugs: [], dormant: true };
|
|
@@ -4998,7 +5469,10 @@ function main() {
|
|
|
4998
5469
|
const shipsPrefix = `${DOCTRINE_ROOT}/`;
|
|
4999
5470
|
const shipsPaths = collect(DOCTRINE_ROOT).filter((p) => p.endsWith(".md"));
|
|
5000
5471
|
const readerFacingPaths = READER_FACING_ACTIVE && READER_FACING_SUFFIX !== null ? collect(READER_FACING_PREFIX).filter((p) => p.endsWith(READER_FACING_SUFFIX)) : [];
|
|
5001
|
-
const
|
|
5472
|
+
const productRoot = repoRoot() ?? process.cwd();
|
|
5473
|
+
const productRelPaths = collectProductScopeFiles(productRoot);
|
|
5474
|
+
const productFiles = readProductFiles(productRoot, productRelPaths);
|
|
5475
|
+
const files = [...readAll([...shipsPaths, ...readerFacingPaths]), ...productFiles];
|
|
5002
5476
|
const glossaryPath = join5(DOCTRINE_ROOT, "glossary.md");
|
|
5003
5477
|
const glossaryTerms = existsSync4(glossaryPath) ? parseGlossaryTerms(readFileSync4(glossaryPath, "utf8")) : [];
|
|
5004
5478
|
const legacySlugDir = LEGACY_SLUG_DIR ?? `${DOCTRINE_ROOT}/tranches/completed`;
|
|
@@ -5007,18 +5481,21 @@ function main() {
|
|
|
5007
5481
|
const readerFacingSuffix = READER_FACING_ACTIVE && READER_FACING_SUFFIX !== null ? READER_FACING_SUFFIX : "/page.tsx";
|
|
5008
5482
|
const findings = checkReaderResolvableProse(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, slugs, shipsPrefix);
|
|
5009
5483
|
const reportable = findingsInThisDiff(findings);
|
|
5010
|
-
console.log(`${CHECK_NAME}: doctrine root "${DOCTRINE_ROOT}"; reader-facing class ${READER_FACING_ACTIVE ? "ran" : "dormant — proseGates.readerFacingPrefix/readerFacingSuffix not both set"}; ` + `legacy-slug class ${legacySlugsDormant ? `dormant — ${legacySlugDir} is absent` : `ran (${slugs.length} slug(s))`}; ` + `${findings.length} finding(s) swept, ${reportable.length} in this diff`);
|
|
5484
|
+
console.log(`${CHECK_NAME}: doctrine root "${DOCTRINE_ROOT}"; reader-facing class ${READER_FACING_ACTIVE ? "ran" : "dormant — proseGates.readerFacingPrefix/readerFacingSuffix not both set"}; ` + `legacy-slug class ${legacySlugsDormant ? `dormant — ${legacySlugDir} is absent` : `ran (${slugs.length} slug(s))`}; ` + `product class ran (${productRelPaths.length} file(s) swept); ` + `${findings.length} finding(s) swept, ${reportable.length} in this diff`);
|
|
5485
|
+
let hasBlocking = false;
|
|
5011
5486
|
for (const finding of reportable) {
|
|
5487
|
+
if (finding.blocking)
|
|
5488
|
+
hasBlocking = true;
|
|
5012
5489
|
emitCheckError({
|
|
5013
5490
|
schema: CHECK_SCHEMA_VERSION,
|
|
5014
5491
|
check: CHECK_NAME,
|
|
5015
|
-
severity: "warning",
|
|
5492
|
+
severity: finding.blocking ? "error" : "warning",
|
|
5016
5493
|
message: `${finding.file}:${finding.line}: ${finding.message}`,
|
|
5017
5494
|
file: finding.file,
|
|
5018
5495
|
line: finding.line,
|
|
5019
|
-
agent_recovery_prompt: finding.message.includes("coined term") ? "This page uses AEG/Vinaya-internal vocabulary a first-time reader cannot resolve. Either define the term " + 'inline (the same "Term — one-sentence definition" shape the glossary uses) at its first use on this page, ' + "or link to the glossary. Do not simply delete the word if the sentence needs it." : "This doctrine or page cites a forge number or an internal tranche slug the reader has no tracker to " + "resolve. Rewrite the sentence to state the fact plainly instead of pointing at the citation — say what " + "was learned/decided, not where it was logged."
|
|
5496
|
+
agent_recovery_prompt: finding.message.includes("coined term") ? "This page uses AEG/Vinaya-internal vocabulary a first-time reader cannot resolve. Either define the term " + 'inline (the same "Term — one-sentence definition" shape the glossary uses) at its first use on this page, ' + "or link to the glossary. Do not simply delete the word if the sentence needs it." : finding.blocking ? "This product-code file cites an internal tranche slug a reader outside this repo cannot resolve " + "(Issue #435 — reader-resolvable-prose.ts PRODUCT_SLUG_SCOPE). Remove the citation or rewrite the " + "comment/doc to state the fact plainly instead of pointing at the tranche that did it. This finding " + "blocks the push and CI." : "This doctrine or page cites a forge number or an internal tranche slug the reader has no tracker to " + "resolve. Rewrite the sentence to state the fact plainly instead of pointing at the citation — say what " + "was learned/decided, not where it was logged."
|
|
5020
5497
|
});
|
|
5021
5498
|
}
|
|
5022
|
-
process.exit(0);
|
|
5499
|
+
process.exit(hasBlocking ? 1 : 0);
|
|
5023
5500
|
}
|
|
5024
5501
|
main();
|