@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
|
@@ -1938,13 +1938,18 @@ function parseInlineFieldList(section) {
|
|
|
1938
1938
|
}
|
|
1939
1939
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
1940
1940
|
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
1941
|
-
|
|
1941
|
+
var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1942
|
+
function firstFiveLines(comment) {
|
|
1942
1943
|
return comment.split(`
|
|
1943
|
-
`).slice(0,
|
|
1944
|
+
`).slice(0, 5).join(`
|
|
1944
1945
|
`);
|
|
1945
1946
|
}
|
|
1946
1947
|
function extractHeadSha(comment) {
|
|
1947
|
-
const m =
|
|
1948
|
+
const m = firstFiveLines(comment).match(HEAD_SHA_PATTERN);
|
|
1949
|
+
return m ? m[1].toLowerCase() : null;
|
|
1950
|
+
}
|
|
1951
|
+
function extractObjectivesVersion(comment) {
|
|
1952
|
+
const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
|
|
1948
1953
|
return m ? m[1].toLowerCase() : null;
|
|
1949
1954
|
}
|
|
1950
1955
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
@@ -1953,21 +1958,24 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1953
1958
|
return {
|
|
1954
1959
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
1955
1960
|
headSha: null,
|
|
1961
|
+
objectivesVersion: null,
|
|
1956
1962
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1957
1963
|
};
|
|
1958
1964
|
}
|
|
1959
1965
|
const latest = candidates[candidates.length - 1];
|
|
1960
|
-
const m =
|
|
1966
|
+
const m = firstFiveLines(latest).match(valuePattern);
|
|
1961
1967
|
if (!m) {
|
|
1962
1968
|
return {
|
|
1963
|
-
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first
|
|
1969
|
+
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
|
|
1964
1970
|
headSha: null,
|
|
1965
|
-
|
|
1971
|
+
objectivesVersion: null,
|
|
1972
|
+
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
|
|
1966
1973
|
};
|
|
1967
1974
|
}
|
|
1968
1975
|
return {
|
|
1969
1976
|
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
1970
1977
|
headSha: extractHeadSha(latest),
|
|
1978
|
+
objectivesVersion: extractObjectivesVersion(latest),
|
|
1971
1979
|
danglingNote: null
|
|
1972
1980
|
};
|
|
1973
1981
|
}
|
|
@@ -2324,8 +2332,105 @@ function overrideActive(opts) {
|
|
|
2324
2332
|
return true;
|
|
2325
2333
|
return false;
|
|
2326
2334
|
}
|
|
2327
|
-
// ../../packages/aeg-core/src/
|
|
2335
|
+
// ../../packages/aeg-core/src/objectives.ts
|
|
2328
2336
|
import { createHash as createHash2 } from "node:crypto";
|
|
2337
|
+
var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
|
|
2338
|
+
var NEXT_HEADING_RE = /^##[ \t]/m;
|
|
2339
|
+
var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
|
|
2340
|
+
function hasBacktickedPath(text) {
|
|
2341
|
+
let i = 0;
|
|
2342
|
+
while (i < text.length) {
|
|
2343
|
+
const start = text.indexOf("`", i);
|
|
2344
|
+
if (start === -1)
|
|
2345
|
+
return false;
|
|
2346
|
+
const end = text.indexOf("`", start + 1);
|
|
2347
|
+
if (end === -1)
|
|
2348
|
+
return false;
|
|
2349
|
+
if (text.slice(start + 1, end).includes("/"))
|
|
2350
|
+
return true;
|
|
2351
|
+
i = end + 1;
|
|
2352
|
+
}
|
|
2353
|
+
return false;
|
|
2354
|
+
}
|
|
2355
|
+
var MIN_WORDS_OUTSIDE_BACKTICKS = 3;
|
|
2356
|
+
function stripObjectiveBackticks(text) {
|
|
2357
|
+
return text.replace(/`[^`\n]*`/g, " ");
|
|
2358
|
+
}
|
|
2359
|
+
function wordCount(text) {
|
|
2360
|
+
return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
|
|
2361
|
+
}
|
|
2362
|
+
function objectivesSectionText(body) {
|
|
2363
|
+
const heading = HEADING_RE.exec(body);
|
|
2364
|
+
if (!heading)
|
|
2365
|
+
return null;
|
|
2366
|
+
const afterHeading = body.slice(heading.index + heading[0].length);
|
|
2367
|
+
const next = NEXT_HEADING_RE.exec(afterHeading);
|
|
2368
|
+
return next ? afterHeading.slice(0, next.index) : afterHeading;
|
|
2369
|
+
}
|
|
2370
|
+
function hasObjectivesHeading(body) {
|
|
2371
|
+
return HEADING_RE.test(body);
|
|
2372
|
+
}
|
|
2373
|
+
function objectivesOf(body) {
|
|
2374
|
+
const section = objectivesSectionText(body);
|
|
2375
|
+
if (section === null) {
|
|
2376
|
+
return { ok: false, errors: ["no `## Objectives` heading found in the body."] };
|
|
2377
|
+
}
|
|
2378
|
+
const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
2379
|
+
if (lines.length === 0) {
|
|
2380
|
+
return { ok: false, errors: ["the `## Objectives` section has no `O<n>.` lines."] };
|
|
2381
|
+
}
|
|
2382
|
+
const objectives = [];
|
|
2383
|
+
const errors = [];
|
|
2384
|
+
for (const line of lines) {
|
|
2385
|
+
const m = OBJECTIVE_LINE_RE.exec(line);
|
|
2386
|
+
if (!m) {
|
|
2387
|
+
errors.push(`"${line}" is not a well-formed objective line — expected \`O<n>. <sentence>\`, e.g. \`O1. …\`.`);
|
|
2388
|
+
continue;
|
|
2389
|
+
}
|
|
2390
|
+
const n = m[1];
|
|
2391
|
+
const text = m[2].trim();
|
|
2392
|
+
if (text.length === 0) {
|
|
2393
|
+
errors.push(`O${n} has no sentence — every objective is one observable sentence.`);
|
|
2394
|
+
continue;
|
|
2395
|
+
}
|
|
2396
|
+
if (hasBacktickedPath(text) && wordCount(stripObjectiveBackticks(text)) < MIN_WORDS_OUTSIDE_BACKTICKS) {
|
|
2397
|
+
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).`);
|
|
2398
|
+
continue;
|
|
2399
|
+
}
|
|
2400
|
+
objectives.push({ id: `O${n}`, text });
|
|
2401
|
+
}
|
|
2402
|
+
for (let i = 0;i < objectives.length; i++) {
|
|
2403
|
+
const expected = i + 1;
|
|
2404
|
+
const actual = Number.parseInt(objectives[i].id.slice(1), 10);
|
|
2405
|
+
if (actual !== expected) {
|
|
2406
|
+
errors.push(`numbering is not contiguous from O1 — expected O${expected}, found ${objectives[i].id}.`);
|
|
2407
|
+
break;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
if (errors.length > 0)
|
|
2411
|
+
return { ok: false, errors };
|
|
2412
|
+
return { ok: true, objectives };
|
|
2413
|
+
}
|
|
2414
|
+
function normalizeText(text) {
|
|
2415
|
+
return text.trim().replace(/\s+/g, " ");
|
|
2416
|
+
}
|
|
2417
|
+
function objectivesVersion(objectives) {
|
|
2418
|
+
const normalized = objectives.map((o) => `${o.id} ${normalizeText(o.text)}`).join(`
|
|
2419
|
+
`);
|
|
2420
|
+
return createHash2("sha256").update(normalized).digest("hex");
|
|
2421
|
+
}
|
|
2422
|
+
function isIssueNotFoundError(err) {
|
|
2423
|
+
const stderr = err?.stderr;
|
|
2424
|
+
const haystack = [
|
|
2425
|
+
err?.message ?? "",
|
|
2426
|
+
typeof stderr === "string" ? stderr : stderr?.toString() ?? ""
|
|
2427
|
+
].join(`
|
|
2428
|
+
`);
|
|
2429
|
+
return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
// ../../packages/aeg-core/src/premise-check.ts
|
|
2433
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2329
2434
|
var ASSERTION_KINDS = new Set(["contains", "absent", "sha256"]);
|
|
2330
2435
|
function isPremiseHeader(line) {
|
|
2331
2436
|
const stripped = line.replace(/[*#]/g, "").trim();
|
|
@@ -2396,7 +2501,7 @@ function checkPremises(assertions, fileReader) {
|
|
|
2396
2501
|
} else if (a.kind === "absent" && content.includes(a.value)) {
|
|
2397
2502
|
failures.push(`premise-check: ${a.path} now contains "${a.value}", but the brief pinned it absent — the premise has moved; re-dig before proceeding.`);
|
|
2398
2503
|
} else if (a.kind === "sha256") {
|
|
2399
|
-
const actual =
|
|
2504
|
+
const actual = createHash3("sha256").update(content).digest("hex");
|
|
2400
2505
|
if (actual !== a.value.toLowerCase()) {
|
|
2401
2506
|
failures.push(`premise-check: ${a.path} sha256 mismatch (brief pinned ${a.value}, file is now ${actual}) — the file changed since this brief was authored; re-dig before proceeding.`);
|
|
2402
2507
|
}
|
|
@@ -2757,8 +2862,55 @@ function checkDefeatCases(prBody) {
|
|
|
2757
2862
|
]
|
|
2758
2863
|
};
|
|
2759
2864
|
}
|
|
2865
|
+
var PART_CITATION_RE = /Part\s+\d+\s*\(([^)]*)\)/gi;
|
|
2866
|
+
var OBJECTIVE_REF_RE = /O(\d+)/g;
|
|
2867
|
+
function citedObjectiveIds(section6) {
|
|
2868
|
+
const ids = new Set;
|
|
2869
|
+
for (const m of section6.matchAll(PART_CITATION_RE)) {
|
|
2870
|
+
for (const r of m[1].matchAll(OBJECTIVE_REF_RE))
|
|
2871
|
+
ids.add(Number.parseInt(r[1], 10));
|
|
2872
|
+
}
|
|
2873
|
+
return ids;
|
|
2874
|
+
}
|
|
2875
|
+
function checkObjectivesCopy(prBody, issueObjectives) {
|
|
2876
|
+
const parsed = objectivesOf(prBody);
|
|
2877
|
+
if (!parsed.ok) {
|
|
2878
|
+
return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives copy: ${e}`) };
|
|
2879
|
+
}
|
|
2880
|
+
if (objectivesVersion(parsed.objectives) !== objectivesVersion(issueObjectives)) {
|
|
2881
|
+
return {
|
|
2882
|
+
status: "fail",
|
|
2883
|
+
errors: [
|
|
2884
|
+
"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)."
|
|
2885
|
+
]
|
|
2886
|
+
};
|
|
2887
|
+
}
|
|
2888
|
+
return { status: "pass", errors: [] };
|
|
2889
|
+
}
|
|
2890
|
+
function checkObjectivesCoverage(prBody) {
|
|
2891
|
+
const parsed = objectivesOf(prBody);
|
|
2892
|
+
if (!parsed.ok) {
|
|
2893
|
+
return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives coverage: ${e}`) };
|
|
2894
|
+
}
|
|
2895
|
+
const declaredIds = new Set(parsed.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
|
|
2896
|
+
const section6 = extractNumberedSection(prBody, 6) ?? "";
|
|
2897
|
+
const cited = citedObjectiveIds(section6);
|
|
2898
|
+
const maxDeclared = Math.max(...declaredIds, 0);
|
|
2899
|
+
const errors = [];
|
|
2900
|
+
for (const id of declaredIds) {
|
|
2901
|
+
if (!cited.has(id)) {
|
|
2902
|
+
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.`);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
for (const id of cited) {
|
|
2906
|
+
if (!declaredIds.has(id)) {
|
|
2907
|
+
errors.push(`brief-validation objectives coverage: a Part in §6 cites O${id}, but the Objectives section ends at O${maxDeclared}.`);
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
|
|
2911
|
+
}
|
|
2760
2912
|
function checkBriefSections(prBody, readTier, options = {}) {
|
|
2761
|
-
const { requireClosesN = true, consumersOf = () => [] } = options;
|
|
2913
|
+
const { requireClosesN = true, consumersOf = () => [], issueObjectives } = options;
|
|
2762
2914
|
const results = [
|
|
2763
2915
|
checkTierField(prBody, readTier),
|
|
2764
2916
|
checkTestPlan(prBody),
|
|
@@ -2776,6 +2928,7 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2776
2928
|
checkCommandsCarryOutput(prBody),
|
|
2777
2929
|
checkConsumerTests(prBody, consumersOf),
|
|
2778
2930
|
checkDefeatCases(prBody),
|
|
2931
|
+
...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
|
|
2779
2932
|
...requireClosesN ? [checkClosesN(prBody)] : []
|
|
2780
2933
|
];
|
|
2781
2934
|
return { errors: results.flatMap((r) => r.errors) };
|
|
@@ -2974,14 +3127,14 @@ function checkSectionDensity(prBody, heading) {
|
|
|
2974
3127
|
]
|
|
2975
3128
|
};
|
|
2976
3129
|
}
|
|
2977
|
-
function
|
|
2978
|
-
return checkSectionDensity(prBody, "
|
|
3130
|
+
function checkDecisionsDensity(prBody) {
|
|
3131
|
+
return checkSectionDensity(prBody, "Decisions");
|
|
2979
3132
|
}
|
|
2980
3133
|
function checkScopeDensity(prBody) {
|
|
2981
3134
|
return checkSectionDensity(prBody, "Scope");
|
|
2982
3135
|
}
|
|
2983
3136
|
function checkPrReportDensity(prBody) {
|
|
2984
|
-
const results = [
|
|
3137
|
+
const results = [checkDecisionsDensity(prBody), checkScopeDensity(prBody)];
|
|
2985
3138
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2986
3139
|
}
|
|
2987
3140
|
// ../../packages/aeg-core/src/issue-validation.ts
|
|
@@ -3013,6 +3166,15 @@ function checkIssueRationale(body) {
|
|
|
3013
3166
|
}
|
|
3014
3167
|
return { status: errors.length > 0 ? "fail" : "pass", errors };
|
|
3015
3168
|
}
|
|
3169
|
+
var OBJECTIVES_SINCE_ISSUE = 404;
|
|
3170
|
+
function checkIssueObjectives(body, issueNumber) {
|
|
3171
|
+
if (issueNumber !== null && issueNumber < OBJECTIVES_SINCE_ISSUE)
|
|
3172
|
+
return { status: "pass", errors: [] };
|
|
3173
|
+
const result = objectivesOf(body);
|
|
3174
|
+
if (result.ok)
|
|
3175
|
+
return { status: "pass", errors: [] };
|
|
3176
|
+
return { status: "fail", errors: result.errors.map((e) => `issue-validation objectives: ${e}`) };
|
|
3177
|
+
}
|
|
3016
3178
|
function isTaskIssueLabelSet(labels) {
|
|
3017
3179
|
return hasLabel("tranche", labels);
|
|
3018
3180
|
}
|
|
@@ -3274,7 +3436,8 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
|
|
|
3274
3436
|
continue;
|
|
3275
3437
|
const errors = [
|
|
3276
3438
|
...checkIssueRationale(issue.body).errors,
|
|
3277
|
-
...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors
|
|
3439
|
+
...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
|
|
3440
|
+
...checkIssueObjectives(issue.body, issue.number).errors
|
|
3278
3441
|
];
|
|
3279
3442
|
if (errors.length === 0)
|
|
3280
3443
|
continue;
|
|
@@ -3389,6 +3552,27 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
3389
3552
|
}
|
|
3390
3553
|
return { ok: true, expectedIssue };
|
|
3391
3554
|
}
|
|
3555
|
+
// ../../packages/aeg-core/src/archive-task.ts
|
|
3556
|
+
function closesRefs(text) {
|
|
3557
|
+
return [...new Set([...text.matchAll(/Closes #(\d+)/gi)].map((m) => Number(m[1])))];
|
|
3558
|
+
}
|
|
3559
|
+
function extractIssue(body) {
|
|
3560
|
+
const stripped = stripCode(body);
|
|
3561
|
+
const bodyNums = closesRefs(stripped);
|
|
3562
|
+
const anchored = anchoredRegion(stripped, "CLOSES");
|
|
3563
|
+
if (anchored !== null) {
|
|
3564
|
+
const anchorNums = closesRefs(anchored);
|
|
3565
|
+
if (anchorNums.length === 0)
|
|
3566
|
+
return { issue: null, extraIssues: bodyNums, outsideHeader: false };
|
|
3567
|
+
const issue2 = anchorNums[0];
|
|
3568
|
+
return { issue: issue2, extraIssues: bodyNums.filter((n) => n !== issue2), outsideHeader: false };
|
|
3569
|
+
}
|
|
3570
|
+
const headerNums = closesRefs(headerRegion(stripped));
|
|
3571
|
+
if (bodyNums.length === 0)
|
|
3572
|
+
return { issue: null, extraIssues: [], outsideHeader: false };
|
|
3573
|
+
const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
|
|
3574
|
+
return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
|
|
3575
|
+
}
|
|
3392
3576
|
// ../../packages/aeg-core/src/review-gate.ts
|
|
3393
3577
|
function isBoundToHead(extraction, headSha) {
|
|
3394
3578
|
if (!extraction.headSha)
|
|
@@ -3407,6 +3591,11 @@ function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
|
|
|
3407
3591
|
function isBoundToPatch(extraction, headSha, patchIdOf) {
|
|
3408
3592
|
return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
|
|
3409
3593
|
}
|
|
3594
|
+
function isBoundToObjectives(extraction, currentVersion) {
|
|
3595
|
+
if (currentVersion === null)
|
|
3596
|
+
return true;
|
|
3597
|
+
return extraction.objectivesVersion === currentVersion;
|
|
3598
|
+
}
|
|
3410
3599
|
function checkReviewGate(input) {
|
|
3411
3600
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
3412
3601
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -3433,7 +3622,9 @@ function checkReviewGate(input) {
|
|
|
3433
3622
|
const securityClean = security.value === "PASS";
|
|
3434
3623
|
const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
|
|
3435
3624
|
const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
|
|
3436
|
-
|
|
3625
|
+
const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
|
|
3626
|
+
const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
|
|
3627
|
+
if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && securityClean && securityBound && securityObjectivesBound && mechanicalChecksClean) {
|
|
3437
3628
|
return {
|
|
3438
3629
|
verdict: "pass",
|
|
3439
3630
|
reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}, and every reported mechanical check is green.`,
|
|
@@ -3445,11 +3636,15 @@ function checkReviewGate(input) {
|
|
|
3445
3636
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
3446
3637
|
} else if (!codeReviewBound) {
|
|
3447
3638
|
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3639
|
+
} else if (!codeReviewObjectivesBound) {
|
|
3640
|
+
problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3448
3641
|
}
|
|
3449
3642
|
if (!securityClean) {
|
|
3450
3643
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
3451
3644
|
} else if (!securityBound) {
|
|
3452
3645
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3646
|
+
} else if (!securityObjectivesBound) {
|
|
3647
|
+
problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3453
3648
|
}
|
|
3454
3649
|
if (!mechanicalChecksClean) {
|
|
3455
3650
|
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(", ")}`);
|
|
@@ -3685,55 +3880,20 @@ function checkG6(rows, registeredCheckNames) {
|
|
|
3685
3880
|
}
|
|
3686
3881
|
return { check: "G6", status: findings.length > 0 ? "fail" : "pass", findings };
|
|
3687
3882
|
}
|
|
3688
|
-
// ../../packages/aeg-core/src/
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
return {
|
|
3699
|
-
verdict: "refuse",
|
|
3700
|
-
reason: `Branch \`${branch}\` has a CLOSED PR (#${prNumber}) that was never merged — pushing here would resurrect abandoned/rejected work. Open a new task branch instead.`
|
|
3701
|
-
};
|
|
3702
|
-
}
|
|
3703
|
-
if (prState === "OPEN") {
|
|
3704
|
-
return {
|
|
3705
|
-
verdict: "allow",
|
|
3706
|
-
reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
|
|
3707
|
-
};
|
|
3708
|
-
}
|
|
3709
|
-
if (prState === "NONE") {
|
|
3710
|
-
return {
|
|
3711
|
-
verdict: "allow",
|
|
3712
|
-
reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
|
|
3713
|
-
};
|
|
3714
|
-
}
|
|
3715
|
-
return {
|
|
3716
|
-
verdict: "allow",
|
|
3717
|
-
reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
|
|
3718
|
-
};
|
|
3719
|
-
}
|
|
3720
|
-
// ../../packages/aeg-core/src/baseline-capture.ts
|
|
3721
|
-
function captureBaseline(counts, capturedAt) {
|
|
3722
|
-
return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
|
|
3883
|
+
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
3884
|
+
var PRODUCT_SLUG_SCOPE = [
|
|
3885
|
+
"apps/cli/src",
|
|
3886
|
+
".github/workflows",
|
|
3887
|
+
".vinaya",
|
|
3888
|
+
"apps/cli/README.md",
|
|
3889
|
+
"packages/sources/README.md"
|
|
3890
|
+
];
|
|
3891
|
+
function isUnderOrEqual(path, prefix) {
|
|
3892
|
+
return path === prefix || path.startsWith(`${prefix}/`);
|
|
3723
3893
|
}
|
|
3724
|
-
function
|
|
3725
|
-
|
|
3726
|
-
const baselineEntry = baseline.find((b) => b.tool === c.tool);
|
|
3727
|
-
const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
|
|
3728
|
-
return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
|
|
3729
|
-
});
|
|
3730
|
-
return {
|
|
3731
|
-
withinBudget: perTool.every((t) => t.delta <= 0),
|
|
3732
|
-
delta: perTool.reduce((sum, t) => sum + t.delta, 0),
|
|
3733
|
-
perTool
|
|
3734
|
-
};
|
|
3894
|
+
function isProductScopeFile(path) {
|
|
3895
|
+
return PRODUCT_SLUG_SCOPE.some((prefix) => isUnderOrEqual(path, prefix));
|
|
3735
3896
|
}
|
|
3736
|
-
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
3737
3897
|
var SHIPS_PREFIX = "aeg-root/";
|
|
3738
3898
|
function shipsArchivePrefix(shipsPrefix) {
|
|
3739
3899
|
return `${shipsPrefix}tranches/completed/`;
|
|
@@ -3753,6 +3913,8 @@ function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix, shipsPr
|
|
|
3753
3913
|
}
|
|
3754
3914
|
if (isSpecFile2(path) || isClaudeMdFile(path))
|
|
3755
3915
|
return "internal";
|
|
3916
|
+
if (isProductScopeFile(path))
|
|
3917
|
+
return "product";
|
|
3756
3918
|
return null;
|
|
3757
3919
|
}
|
|
3758
3920
|
var SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
|
|
@@ -3765,6 +3927,9 @@ function stripNonProse(path, content) {
|
|
|
3765
3927
|
}
|
|
3766
3928
|
return content;
|
|
3767
3929
|
}
|
|
3930
|
+
function stripNonProseForProduct(path, content) {
|
|
3931
|
+
return path.endsWith(".md") ? stripNonProse(path, content) : content;
|
|
3932
|
+
}
|
|
3768
3933
|
function lineAt(content, index) {
|
|
3769
3934
|
let line = 1;
|
|
3770
3935
|
for (let i = 0;i < index; i++) {
|
|
@@ -3789,12 +3954,19 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
|
|
|
3789
3954
|
{ pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug" },
|
|
3790
3955
|
...legacyPattern ? [{ pattern: legacyPattern, what: "an internal tranche slug", group: 2 }] : []
|
|
3791
3956
|
];
|
|
3957
|
+
const productPatterns = [
|
|
3958
|
+
{ pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug in product code" }
|
|
3959
|
+
];
|
|
3792
3960
|
for (const file of files) {
|
|
3793
3961
|
const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
|
|
3794
|
-
if (!cls ||
|
|
3962
|
+
if (!cls || cls === "internal")
|
|
3795
3963
|
continue;
|
|
3796
|
-
|
|
3797
|
-
|
|
3964
|
+
if (cls !== "product" && !SWEPT_CLASSES.has(cls))
|
|
3965
|
+
continue;
|
|
3966
|
+
const blocking = cls === "product";
|
|
3967
|
+
const scrubbed = blocking ? stripNonProseForProduct(file.path, file.content) : stripNonProse(file.path, file.content);
|
|
3968
|
+
const patternsToRun = blocking ? productPatterns : patterns;
|
|
3969
|
+
for (const { pattern, what, group } of patternsToRun) {
|
|
3798
3970
|
pattern.lastIndex = 0;
|
|
3799
3971
|
let match = pattern.exec(scrubbed);
|
|
3800
3972
|
while (match !== null) {
|
|
@@ -3802,7 +3974,8 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
|
|
|
3802
3974
|
findings.push({
|
|
3803
3975
|
file: file.path,
|
|
3804
3976
|
line: lineAt(scrubbed, match.index),
|
|
3805
|
-
message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve
|
|
3977
|
+
message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve`,
|
|
3978
|
+
blocking
|
|
3806
3979
|
});
|
|
3807
3980
|
match = pattern.exec(scrubbed);
|
|
3808
3981
|
}
|
|
@@ -3852,7 +4025,8 @@ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, read
|
|
|
3852
4025
|
findings.push({
|
|
3853
4026
|
file: file.path,
|
|
3854
4027
|
line,
|
|
3855
|
-
message: `uses coined term "${term}" without defining it inline or linking the glossary
|
|
4028
|
+
message: `uses coined term "${term}" without defining it inline or linking the glossary`,
|
|
4029
|
+
blocking: false
|
|
3856
4030
|
});
|
|
3857
4031
|
}
|
|
3858
4032
|
}
|
|
@@ -3875,6 +4049,7 @@ function parseGlossaryTerms(glossaryContent) {
|
|
|
3875
4049
|
}
|
|
3876
4050
|
return terms;
|
|
3877
4051
|
}
|
|
4052
|
+
|
|
3878
4053
|
// ../../packages/aeg-core/src/quoted-command.ts
|
|
3879
4054
|
var QUOTED_COMMAND_SHIPS_PREFIX = "aeg-root/";
|
|
3880
4055
|
var QUOTED_COMMAND_SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
|
|
@@ -3969,6 +4144,54 @@ function evaluateCitedQuotes(citedQuotes, citedFileContents) {
|
|
|
3969
4144
|
}
|
|
3970
4145
|
return findings;
|
|
3971
4146
|
}
|
|
4147
|
+
// ../../packages/aeg-core/src/dead-branch-push-guard.ts
|
|
4148
|
+
function checkDeadBranchPush(input) {
|
|
4149
|
+
const { branch, prState, prNumber } = input;
|
|
4150
|
+
if (prState === "MERGED") {
|
|
4151
|
+
return {
|
|
4152
|
+
verdict: "refuse",
|
|
4153
|
+
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.`
|
|
4154
|
+
};
|
|
4155
|
+
}
|
|
4156
|
+
if (prState === "CLOSED") {
|
|
4157
|
+
return {
|
|
4158
|
+
verdict: "refuse",
|
|
4159
|
+
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.`
|
|
4160
|
+
};
|
|
4161
|
+
}
|
|
4162
|
+
if (prState === "OPEN") {
|
|
4163
|
+
return {
|
|
4164
|
+
verdict: "allow",
|
|
4165
|
+
reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
|
|
4166
|
+
};
|
|
4167
|
+
}
|
|
4168
|
+
if (prState === "NONE") {
|
|
4169
|
+
return {
|
|
4170
|
+
verdict: "allow",
|
|
4171
|
+
reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
|
|
4172
|
+
};
|
|
4173
|
+
}
|
|
4174
|
+
return {
|
|
4175
|
+
verdict: "allow",
|
|
4176
|
+
reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
|
|
4177
|
+
};
|
|
4178
|
+
}
|
|
4179
|
+
// ../../packages/aeg-core/src/baseline-capture.ts
|
|
4180
|
+
function captureBaseline(counts, capturedAt) {
|
|
4181
|
+
return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
|
|
4182
|
+
}
|
|
4183
|
+
function compareToBaseline(current, baseline) {
|
|
4184
|
+
const perTool = current.map((c) => {
|
|
4185
|
+
const baselineEntry = baseline.find((b) => b.tool === c.tool);
|
|
4186
|
+
const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
|
|
4187
|
+
return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
|
|
4188
|
+
});
|
|
4189
|
+
return {
|
|
4190
|
+
withinBudget: perTool.every((t) => t.delta <= 0),
|
|
4191
|
+
delta: perTool.reduce((sum, t) => sum + t.delta, 0),
|
|
4192
|
+
perTool
|
|
4193
|
+
};
|
|
4194
|
+
}
|
|
3972
4195
|
// ../../packages/aeg-core/src/retired-vocabulary.ts
|
|
3973
4196
|
var RETIRED_PATTERNS = [
|
|
3974
4197
|
String.raw`\bD-[0-9]{3}\b`,
|
|
@@ -4438,32 +4661,248 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
|
|
|
4438
4661
|
}
|
|
4439
4662
|
return findings;
|
|
4440
4663
|
}
|
|
4664
|
+
// ../../packages/aeg-core/src/log/schema.ts
|
|
4665
|
+
import { z } from "zod";
|
|
4666
|
+
var ROLE_VALUES = [
|
|
4667
|
+
"planner",
|
|
4668
|
+
"brief-author",
|
|
4669
|
+
"developer",
|
|
4670
|
+
"code-reviewer",
|
|
4671
|
+
"security",
|
|
4672
|
+
"principal",
|
|
4673
|
+
"archivist",
|
|
4674
|
+
"architect"
|
|
4675
|
+
];
|
|
4676
|
+
var RoleSchema = z.enum(ROLE_VALUES);
|
|
4677
|
+
var HOST_VALUES = ["hook", "ci", "cli", "loop"];
|
|
4678
|
+
var HostSchema = z.enum(HOST_VALUES);
|
|
4679
|
+
var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
4680
|
+
var HeaderMetaSchema = z.object({
|
|
4681
|
+
schema: z.literal(1),
|
|
4682
|
+
ts: z.string(),
|
|
4683
|
+
run_id: z.string().regex(RUN_ID_PATTERN),
|
|
4684
|
+
seq: z.number().int().nonnegative(),
|
|
4685
|
+
repo: z.string().nullable(),
|
|
4686
|
+
vinaya: z.string(),
|
|
4687
|
+
doctrine: z.string(),
|
|
4688
|
+
host: HostSchema,
|
|
4689
|
+
machine: z.string()
|
|
4690
|
+
}).strict();
|
|
4691
|
+
var SubjectSchema = z.object({
|
|
4692
|
+
issue: z.number().int().nullable(),
|
|
4693
|
+
pr: z.number().int().optional(),
|
|
4694
|
+
sha: z.string().optional(),
|
|
4695
|
+
role: z.union([RoleSchema, z.literal("unattributed")]),
|
|
4696
|
+
round: z.number().int().optional(),
|
|
4697
|
+
objectives_version: z.string().optional()
|
|
4698
|
+
}).strict();
|
|
4699
|
+
var HeaderSchema = z.object({
|
|
4700
|
+
meta: HeaderMetaSchema,
|
|
4701
|
+
subject: SubjectSchema
|
|
4702
|
+
}).strict();
|
|
4703
|
+
var envelopeTail = {
|
|
4704
|
+
duration_ms: z.number().nonnegative().optional(),
|
|
4705
|
+
payload: z.object({}).strict()
|
|
4706
|
+
};
|
|
4707
|
+
var DispatchOutcomeSchema = z.discriminatedUnion("type", [
|
|
4708
|
+
z.object({ type: z.literal("pr_opened"), pr: z.number().int(), head: z.string() }).strict(),
|
|
4709
|
+
z.object({
|
|
4710
|
+
type: z.literal("round_pushed"),
|
|
4711
|
+
pr: z.number().int(),
|
|
4712
|
+
head: z.string(),
|
|
4713
|
+
comment_id: z.number().int()
|
|
4714
|
+
}).strict(),
|
|
4715
|
+
z.object({
|
|
4716
|
+
type: z.literal("verdict"),
|
|
4717
|
+
verdict: z.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
|
|
4718
|
+
head: z.string(),
|
|
4719
|
+
comment_id: z.number().int(),
|
|
4720
|
+
objectives: z.array(z.object({ id: z.string(), met: z.boolean() }).strict()),
|
|
4721
|
+
findings: z.array(z.object({ id: z.string(), severity: z.string(), state: z.string().optional() }).strict())
|
|
4722
|
+
}).strict(),
|
|
4723
|
+
z.object({
|
|
4724
|
+
type: z.literal("escalation"),
|
|
4725
|
+
class: z.enum(["authority", "strategy", "product"]),
|
|
4726
|
+
comment_id: z.number().int()
|
|
4727
|
+
}).strict(),
|
|
4728
|
+
z.object({ type: z.literal("brief"), comment_id: z.number().int(), hash: z.string() }).strict(),
|
|
4729
|
+
z.object({ type: z.literal("plan"), issues: z.array(z.number().int()) }).strict(),
|
|
4730
|
+
z.object({ type: z.literal("archive"), provenance_comment_id: z.number().int() }).strict()
|
|
4731
|
+
]);
|
|
4732
|
+
var dispatchShared = {
|
|
4733
|
+
meta: HeaderMetaSchema,
|
|
4734
|
+
subject: SubjectSchema,
|
|
4735
|
+
kind: z.literal("dispatch"),
|
|
4736
|
+
...envelopeTail,
|
|
4737
|
+
target_role: RoleSchema,
|
|
4738
|
+
model: z.string(),
|
|
4739
|
+
round: z.number().int().optional(),
|
|
4740
|
+
effect_id: z.string()
|
|
4741
|
+
};
|
|
4742
|
+
var DispatchEventSchema = z.discriminatedUnion("event", [
|
|
4743
|
+
z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
|
|
4744
|
+
z.object({
|
|
4745
|
+
...dispatchShared,
|
|
4746
|
+
event: z.literal("outcome_received"),
|
|
4747
|
+
outcome: DispatchOutcomeSchema,
|
|
4748
|
+
usage: z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable()
|
|
4749
|
+
}).strict(),
|
|
4750
|
+
z.object({
|
|
4751
|
+
...dispatchShared,
|
|
4752
|
+
event: z.literal("dispatch_failed"),
|
|
4753
|
+
reason: z.enum(["timeout", "crash", "refused", "unattributed_write"])
|
|
4754
|
+
}).strict()
|
|
4755
|
+
]);
|
|
4756
|
+
var loopShared = {
|
|
4757
|
+
meta: HeaderMetaSchema,
|
|
4758
|
+
subject: SubjectSchema,
|
|
4759
|
+
kind: z.literal("dev_review_loop"),
|
|
4760
|
+
...envelopeTail,
|
|
4761
|
+
loop_id: z.string()
|
|
4762
|
+
};
|
|
4763
|
+
var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
|
|
4764
|
+
z.object({
|
|
4765
|
+
...loopShared,
|
|
4766
|
+
event: z.literal("loop_started"),
|
|
4767
|
+
task: z.number().int(),
|
|
4768
|
+
policy: z.object({
|
|
4769
|
+
max_rounds: z.number().int().nonnegative(),
|
|
4770
|
+
reviewers: z.array(RoleSchema),
|
|
4771
|
+
models: z.record(RoleSchema, z.string())
|
|
4772
|
+
}).strict()
|
|
4773
|
+
}).strict(),
|
|
4774
|
+
z.object({ ...loopShared, event: z.literal("round_started"), round: z.number().int(), base_head: z.string() }).strict(),
|
|
4775
|
+
z.object({
|
|
4776
|
+
...loopShared,
|
|
4777
|
+
event: z.literal("gate_result_read"),
|
|
4778
|
+
round: z.number().int(),
|
|
4779
|
+
head: z.string(),
|
|
4780
|
+
green: z.boolean()
|
|
4781
|
+
}).strict(),
|
|
4782
|
+
z.object({
|
|
4783
|
+
...loopShared,
|
|
4784
|
+
event: z.literal("verdicts_read"),
|
|
4785
|
+
round: z.number().int(),
|
|
4786
|
+
head: z.string(),
|
|
4787
|
+
all_approve: z.boolean(),
|
|
4788
|
+
blockers: z.number().int().nonnegative()
|
|
4789
|
+
}).strict(),
|
|
4790
|
+
z.object({
|
|
4791
|
+
...loopShared,
|
|
4792
|
+
event: z.literal("findings_compared"),
|
|
4793
|
+
round: z.number().int(),
|
|
4794
|
+
open: z.array(z.string()),
|
|
4795
|
+
resolved: z.array(z.string()),
|
|
4796
|
+
new: z.array(z.string()),
|
|
4797
|
+
recurring: z.array(z.string())
|
|
4798
|
+
}).strict(),
|
|
4799
|
+
z.object({
|
|
4800
|
+
...loopShared,
|
|
4801
|
+
event: z.literal("stop_condition_met"),
|
|
4802
|
+
round: z.number().int(),
|
|
4803
|
+
condition: z.enum([
|
|
4804
|
+
"green",
|
|
4805
|
+
"max_rounds",
|
|
4806
|
+
"no_progress",
|
|
4807
|
+
"escalated",
|
|
4808
|
+
"principal_stop",
|
|
4809
|
+
"confidence",
|
|
4810
|
+
"reappearance"
|
|
4811
|
+
])
|
|
4812
|
+
}).strict(),
|
|
4813
|
+
z.object({
|
|
4814
|
+
...loopShared,
|
|
4815
|
+
event: z.literal("paused"),
|
|
4816
|
+
round: z.number().int(),
|
|
4817
|
+
reason: z.enum(["escalation", "principal_item", "refreeze_needed"])
|
|
4818
|
+
}).strict(),
|
|
4819
|
+
z.object({
|
|
4820
|
+
...loopShared,
|
|
4821
|
+
event: z.literal("resumed"),
|
|
4822
|
+
round: z.number().int(),
|
|
4823
|
+
by: z.literal("principal")
|
|
4824
|
+
}).strict(),
|
|
4825
|
+
z.object({
|
|
4826
|
+
...loopShared,
|
|
4827
|
+
event: z.literal("round_ended"),
|
|
4828
|
+
round: z.number().int(),
|
|
4829
|
+
base_head: z.string(),
|
|
4830
|
+
head: z.string(),
|
|
4831
|
+
files_changed: z.number().int().nonnegative(),
|
|
4832
|
+
insertions: z.number().int().nonnegative(),
|
|
4833
|
+
deletions: z.number().int().nonnegative(),
|
|
4834
|
+
wall_ms: z.number().nonnegative(),
|
|
4835
|
+
outcome: z.enum(["green", "changes_requested", "escalated"])
|
|
4836
|
+
}).strict(),
|
|
4837
|
+
z.object({
|
|
4838
|
+
...loopShared,
|
|
4839
|
+
event: z.literal("journal_finalized"),
|
|
4840
|
+
rounds: z.number().int().nonnegative(),
|
|
4841
|
+
total_wall_ms: z.number().nonnegative(),
|
|
4842
|
+
time_to_green_ms: z.number().nonnegative().nullable(),
|
|
4843
|
+
files_changed_total: z.number().int().nonnegative(),
|
|
4844
|
+
final_head: z.string(),
|
|
4845
|
+
result: z.enum(["merged_ready", "stopped"])
|
|
4846
|
+
}).strict()
|
|
4847
|
+
]);
|
|
4848
|
+
var ForgeOpSchema = z.enum([
|
|
4849
|
+
"pr.create",
|
|
4850
|
+
"pr.comment",
|
|
4851
|
+
"pr.body.replace",
|
|
4852
|
+
"pr.refreeze",
|
|
4853
|
+
"issue.create",
|
|
4854
|
+
"issue.edit",
|
|
4855
|
+
"issue.comment",
|
|
4856
|
+
"milestone.create",
|
|
4857
|
+
"milestone.edit",
|
|
4858
|
+
"milestone.close",
|
|
4859
|
+
"label.add",
|
|
4860
|
+
"label.remove"
|
|
4861
|
+
]);
|
|
4862
|
+
var ForgeWriteTargetSchema = z.object({
|
|
4863
|
+
issue: z.number().int().optional(),
|
|
4864
|
+
pr: z.number().int().optional()
|
|
4865
|
+
}).strict();
|
|
4866
|
+
var forgeWriteShared = {
|
|
4867
|
+
meta: HeaderMetaSchema,
|
|
4868
|
+
subject: SubjectSchema,
|
|
4869
|
+
kind: z.literal("forge_write"),
|
|
4870
|
+
...envelopeTail,
|
|
4871
|
+
op: ForgeOpSchema,
|
|
4872
|
+
target: ForgeWriteTargetSchema
|
|
4873
|
+
};
|
|
4874
|
+
var ForgeWriteEventSchema = z.discriminatedUnion("event", [
|
|
4875
|
+
z.object({ ...forgeWriteShared, event: z.literal("validated") }).strict(),
|
|
4876
|
+
z.object({ ...forgeWriteShared, event: z.literal("refused"), reason: z.string() }).strict(),
|
|
4877
|
+
z.object({ ...forgeWriteShared, event: z.literal("written"), comment_ids: z.array(z.string()) }).strict()
|
|
4878
|
+
]);
|
|
4879
|
+
var LogEventSchema = z.union([DispatchEventSchema, DevReviewLoopEventSchema, ForgeWriteEventSchema]);
|
|
4441
4880
|
// src/lib/config.ts
|
|
4442
4881
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
4443
4882
|
import { existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "node:fs";
|
|
4444
4883
|
import { homedir } from "node:os";
|
|
4445
4884
|
import { dirname, join, resolve } from "node:path";
|
|
4446
|
-
import { z } from "zod";
|
|
4885
|
+
import { z as z2 } from "zod";
|
|
4447
4886
|
|
|
4448
4887
|
// src/lib/agent-vendors.ts
|
|
4449
4888
|
var AGENT_VENDORS = ["skills", "claude", "gemini"];
|
|
4450
4889
|
|
|
4451
4890
|
// src/lib/config.ts
|
|
4452
|
-
var EnvEntrySchema =
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4891
|
+
var EnvEntrySchema = z2.union([
|
|
4892
|
+
z2.literal(true),
|
|
4893
|
+
z2.object({ optional: z2.literal(true) }),
|
|
4894
|
+
z2.object({ anyOf: z2.array(z2.string()).min(2) }),
|
|
4895
|
+
z2.string()
|
|
4457
4896
|
]);
|
|
4458
|
-
var CheckEntrySchema =
|
|
4459
|
-
run:
|
|
4460
|
-
scope:
|
|
4461
|
-
include:
|
|
4462
|
-
args:
|
|
4463
|
-
timeoutMs:
|
|
4464
|
-
env:
|
|
4465
|
-
requiresOpenPr:
|
|
4466
|
-
ownWorkflow:
|
|
4897
|
+
var CheckEntrySchema = z2.object({
|
|
4898
|
+
run: z2.string(),
|
|
4899
|
+
scope: z2.enum(["diff", "full"]),
|
|
4900
|
+
include: z2.array(z2.string()).optional(),
|
|
4901
|
+
args: z2.array(z2.string()).optional(),
|
|
4902
|
+
timeoutMs: z2.number().optional(),
|
|
4903
|
+
env: z2.record(z2.string(), EnvEntrySchema).optional(),
|
|
4904
|
+
requiresOpenPr: z2.boolean().optional(),
|
|
4905
|
+
ownWorkflow: z2.boolean().optional()
|
|
4467
4906
|
}).superRefine((entry2, ctx) => {
|
|
4468
4907
|
if (!entry2.env)
|
|
4469
4908
|
return;
|
|
@@ -4473,22 +4912,22 @@ var CheckEntrySchema = z.object({
|
|
|
4473
4912
|
const members = value.anyOf;
|
|
4474
4913
|
if (new Set(members).size !== members.length) {
|
|
4475
4914
|
ctx.addIssue({
|
|
4476
|
-
code:
|
|
4915
|
+
code: z2.ZodIssueCode.custom,
|
|
4477
4916
|
message: `env.${key}.anyOf has duplicate members`,
|
|
4478
4917
|
path: ["env", key, "anyOf"]
|
|
4479
4918
|
});
|
|
4480
4919
|
}
|
|
4481
4920
|
if (!members.includes(key)) {
|
|
4482
4921
|
ctx.addIssue({
|
|
4483
|
-
code:
|
|
4922
|
+
code: z2.ZodIssueCode.custom,
|
|
4484
4923
|
message: `env.${key}.anyOf must include "${key}" itself as a member`,
|
|
4485
4924
|
path: ["env", key, "anyOf"]
|
|
4486
4925
|
});
|
|
4487
4926
|
}
|
|
4488
4927
|
}
|
|
4489
4928
|
});
|
|
4490
|
-
var RoleEntrySchema =
|
|
4491
|
-
contract:
|
|
4929
|
+
var RoleEntrySchema = z2.object({
|
|
4930
|
+
contract: z2.string().refine((p) => p.includes("/"), {
|
|
4492
4931
|
message: 'must be a path (contain at least one "/"), not a bare filename'
|
|
4493
4932
|
})
|
|
4494
4933
|
});
|
|
@@ -4507,19 +4946,21 @@ var BRIEF_BUILTINS = [
|
|
|
4507
4946
|
"closesN",
|
|
4508
4947
|
"premiseCoverage",
|
|
4509
4948
|
"issueRationale",
|
|
4949
|
+
"objectives",
|
|
4950
|
+
"briefSections",
|
|
4510
4951
|
"milestoneShape"
|
|
4511
4952
|
];
|
|
4512
|
-
var BriefSectionSchema =
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4953
|
+
var BriefSectionSchema = z2.union([
|
|
4954
|
+
z2.object({ builtin: z2.enum(BRIEF_BUILTINS) }),
|
|
4955
|
+
z2.object({ heading: z2.string().min(1), name: z2.string().optional() }),
|
|
4956
|
+
z2.object({ field: z2.string().min(1), name: z2.string().optional() }),
|
|
4957
|
+
z2.object({ phrase: z2.string().min(1), name: z2.string().optional() })
|
|
4517
4958
|
]);
|
|
4518
|
-
var BriefSchemaSchema =
|
|
4519
|
-
pr:
|
|
4520
|
-
issue:
|
|
4521
|
-
milestone:
|
|
4522
|
-
ack:
|
|
4959
|
+
var BriefSchemaSchema = z2.object({
|
|
4960
|
+
pr: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
|
|
4961
|
+
issue: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
|
|
4962
|
+
milestone: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
|
|
4963
|
+
ack: z2.array(z2.enum(BRIEF_BUILTINS)).optional()
|
|
4523
4964
|
});
|
|
4524
4965
|
function isSafeRepoRelPath(p) {
|
|
4525
4966
|
if (p.length === 0)
|
|
@@ -4528,7 +4969,7 @@ function isSafeRepoRelPath(p) {
|
|
|
4528
4969
|
return false;
|
|
4529
4970
|
return p.split(/[\\/]/).every((seg) => seg !== ".." && seg !== "");
|
|
4530
4971
|
}
|
|
4531
|
-
var SafeRepoRelPath =
|
|
4972
|
+
var SafeRepoRelPath = z2.string().refine(isSafeRepoRelPath, {
|
|
4532
4973
|
message: "must be a repo-root-relative path with no `..` segment or absolute root"
|
|
4533
4974
|
});
|
|
4534
4975
|
var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
|
|
@@ -4538,22 +4979,22 @@ function isCanonicalHookBlockPath(p) {
|
|
|
4538
4979
|
var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
|
|
4539
4980
|
message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
|
|
4540
4981
|
});
|
|
4541
|
-
var ManagedBlockRecordSchema =
|
|
4982
|
+
var ManagedBlockRecordSchema = z2.object({
|
|
4542
4983
|
path: ManagedHookBlockPath,
|
|
4543
|
-
marker:
|
|
4544
|
-
comment:
|
|
4984
|
+
marker: z2.string(),
|
|
4985
|
+
comment: z2.enum(["hash", "html"])
|
|
4545
4986
|
});
|
|
4546
|
-
var ManagedManifestSchema =
|
|
4547
|
-
version:
|
|
4548
|
-
files:
|
|
4549
|
-
blocks:
|
|
4550
|
-
labels:
|
|
4551
|
-
agents:
|
|
4987
|
+
var ManagedManifestSchema = z2.object({
|
|
4988
|
+
version: z2.number().int().positive(),
|
|
4989
|
+
files: z2.array(SafeRepoRelPath),
|
|
4990
|
+
blocks: z2.array(ManagedBlockRecordSchema),
|
|
4991
|
+
labels: z2.array(z2.string()),
|
|
4992
|
+
agents: z2.array(z2.enum(AGENT_VENDORS)).optional()
|
|
4552
4993
|
});
|
|
4553
|
-
var ProjectEntrySchema =
|
|
4554
|
-
name:
|
|
4555
|
-
description:
|
|
4556
|
-
path:
|
|
4994
|
+
var ProjectEntrySchema = z2.object({
|
|
4995
|
+
name: z2.string().min(1),
|
|
4996
|
+
description: z2.string().min(1).optional(),
|
|
4997
|
+
path: z2.string().min(1).optional()
|
|
4557
4998
|
});
|
|
4558
4999
|
function parseTokensCollectDeclaration(value) {
|
|
4559
5000
|
const m = value.trim().match(/^(\S+)\s+(\S+)$/);
|
|
@@ -4564,31 +5005,35 @@ function parseTokensCollectDeclaration(value) {
|
|
|
4564
5005
|
return null;
|
|
4565
5006
|
return { interpreter, script };
|
|
4566
5007
|
}
|
|
4567
|
-
var VinayaConfigSchema =
|
|
4568
|
-
rings:
|
|
4569
|
-
ring1_forgeWriteInterception:
|
|
4570
|
-
ring2_asyncAudits:
|
|
5008
|
+
var VinayaConfigSchema = z2.object({
|
|
5009
|
+
rings: z2.object({
|
|
5010
|
+
ring1_forgeWriteInterception: z2.boolean(),
|
|
5011
|
+
ring2_asyncAudits: z2.boolean()
|
|
4571
5012
|
}).optional(),
|
|
4572
|
-
checks:
|
|
4573
|
-
roles:
|
|
5013
|
+
checks: z2.record(z2.string(), CheckEntrySchema).optional(),
|
|
5014
|
+
roles: z2.record(z2.string(), RoleEntrySchema).optional(),
|
|
4574
5015
|
briefSchema: BriefSchemaSchema.optional(),
|
|
4575
5016
|
managed: ManagedManifestSchema.optional(),
|
|
4576
|
-
principals:
|
|
4577
|
-
releaseActor:
|
|
4578
|
-
ci:
|
|
4579
|
-
tokens:
|
|
4580
|
-
collect:
|
|
5017
|
+
principals: z2.array(z2.string()).min(1).optional(),
|
|
5018
|
+
releaseActor: z2.string().min(1).optional(),
|
|
5019
|
+
ci: z2.object({ setup: z2.string().min(1) }).optional(),
|
|
5020
|
+
tokens: z2.object({
|
|
5021
|
+
collect: z2.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
|
|
4581
5022
|
message: 'tokens.collect must be exactly "<interpreter> <repo-relative-script-path>" — two whitespace-separated tokens, no flags, no shell syntax, no quoting'
|
|
4582
5023
|
})
|
|
4583
5024
|
}).optional(),
|
|
4584
|
-
blastRadius:
|
|
4585
|
-
proseGates:
|
|
4586
|
-
doctrineRoot:
|
|
4587
|
-
readerFacingPrefix:
|
|
4588
|
-
readerFacingSuffix:
|
|
4589
|
-
legacySlugDir:
|
|
5025
|
+
blastRadius: z2.object({ extraDomains: z2.array(z2.string()).optional() }).optional(),
|
|
5026
|
+
proseGates: z2.object({
|
|
5027
|
+
doctrineRoot: z2.string().min(1).optional(),
|
|
5028
|
+
readerFacingPrefix: z2.string().min(1).optional(),
|
|
5029
|
+
readerFacingSuffix: z2.string().min(1).optional(),
|
|
5030
|
+
legacySlugDir: z2.string().min(1).optional()
|
|
4590
5031
|
}).optional(),
|
|
4591
|
-
projects:
|
|
5032
|
+
projects: z2.array(ProjectEntrySchema).optional(),
|
|
5033
|
+
dispatch: z2.object({
|
|
5034
|
+
timeoutMs: z2.number().int().positive().optional(),
|
|
5035
|
+
agent: z2.enum(["claude", "codex", "gemini"]).optional()
|
|
5036
|
+
}).optional()
|
|
4592
5037
|
});
|
|
4593
5038
|
var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
|
|
4594
5039
|
var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");
|