@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
|
@@ -1950,13 +1950,18 @@ function parseInlineFieldList(section) {
|
|
|
1950
1950
|
}
|
|
1951
1951
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
1952
1952
|
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
1953
|
-
|
|
1953
|
+
var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1954
|
+
function firstFiveLines(comment) {
|
|
1954
1955
|
return comment.split(`
|
|
1955
|
-
`).slice(0,
|
|
1956
|
+
`).slice(0, 5).join(`
|
|
1956
1957
|
`);
|
|
1957
1958
|
}
|
|
1958
1959
|
function extractHeadSha(comment) {
|
|
1959
|
-
const m =
|
|
1960
|
+
const m = firstFiveLines(comment).match(HEAD_SHA_PATTERN);
|
|
1961
|
+
return m ? m[1].toLowerCase() : null;
|
|
1962
|
+
}
|
|
1963
|
+
function extractObjectivesVersion(comment) {
|
|
1964
|
+
const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
|
|
1960
1965
|
return m ? m[1].toLowerCase() : null;
|
|
1961
1966
|
}
|
|
1962
1967
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
@@ -1965,21 +1970,24 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1965
1970
|
return {
|
|
1966
1971
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
1967
1972
|
headSha: null,
|
|
1973
|
+
objectivesVersion: null,
|
|
1968
1974
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1969
1975
|
};
|
|
1970
1976
|
}
|
|
1971
1977
|
const latest = candidates[candidates.length - 1];
|
|
1972
|
-
const m =
|
|
1978
|
+
const m = firstFiveLines(latest).match(valuePattern);
|
|
1973
1979
|
if (!m) {
|
|
1974
1980
|
return {
|
|
1975
|
-
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first
|
|
1981
|
+
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
|
|
1976
1982
|
headSha: null,
|
|
1977
|
-
|
|
1983
|
+
objectivesVersion: null,
|
|
1984
|
+
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
|
|
1978
1985
|
};
|
|
1979
1986
|
}
|
|
1980
1987
|
return {
|
|
1981
1988
|
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
1982
1989
|
headSha: extractHeadSha(latest),
|
|
1990
|
+
objectivesVersion: extractObjectivesVersion(latest),
|
|
1983
1991
|
danglingNote: null
|
|
1984
1992
|
};
|
|
1985
1993
|
}
|
|
@@ -2336,8 +2344,105 @@ function overrideActive(opts) {
|
|
|
2336
2344
|
return true;
|
|
2337
2345
|
return false;
|
|
2338
2346
|
}
|
|
2339
|
-
// ../../packages/aeg-core/src/
|
|
2347
|
+
// ../../packages/aeg-core/src/objectives.ts
|
|
2340
2348
|
import { createHash as createHash2 } from "node:crypto";
|
|
2349
|
+
var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
|
|
2350
|
+
var NEXT_HEADING_RE = /^##[ \t]/m;
|
|
2351
|
+
var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
|
|
2352
|
+
function hasBacktickedPath(text) {
|
|
2353
|
+
let i = 0;
|
|
2354
|
+
while (i < text.length) {
|
|
2355
|
+
const start = text.indexOf("`", i);
|
|
2356
|
+
if (start === -1)
|
|
2357
|
+
return false;
|
|
2358
|
+
const end = text.indexOf("`", start + 1);
|
|
2359
|
+
if (end === -1)
|
|
2360
|
+
return false;
|
|
2361
|
+
if (text.slice(start + 1, end).includes("/"))
|
|
2362
|
+
return true;
|
|
2363
|
+
i = end + 1;
|
|
2364
|
+
}
|
|
2365
|
+
return false;
|
|
2366
|
+
}
|
|
2367
|
+
var MIN_WORDS_OUTSIDE_BACKTICKS = 3;
|
|
2368
|
+
function stripObjectiveBackticks(text) {
|
|
2369
|
+
return text.replace(/`[^`\n]*`/g, " ");
|
|
2370
|
+
}
|
|
2371
|
+
function wordCount(text) {
|
|
2372
|
+
return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
|
|
2373
|
+
}
|
|
2374
|
+
function objectivesSectionText(body) {
|
|
2375
|
+
const heading = HEADING_RE.exec(body);
|
|
2376
|
+
if (!heading)
|
|
2377
|
+
return null;
|
|
2378
|
+
const afterHeading = body.slice(heading.index + heading[0].length);
|
|
2379
|
+
const next = NEXT_HEADING_RE.exec(afterHeading);
|
|
2380
|
+
return next ? afterHeading.slice(0, next.index) : afterHeading;
|
|
2381
|
+
}
|
|
2382
|
+
function hasObjectivesHeading(body) {
|
|
2383
|
+
return HEADING_RE.test(body);
|
|
2384
|
+
}
|
|
2385
|
+
function objectivesOf(body) {
|
|
2386
|
+
const section = objectivesSectionText(body);
|
|
2387
|
+
if (section === null) {
|
|
2388
|
+
return { ok: false, errors: ["no `## Objectives` heading found in the body."] };
|
|
2389
|
+
}
|
|
2390
|
+
const lines = section.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
2391
|
+
if (lines.length === 0) {
|
|
2392
|
+
return { ok: false, errors: ["the `## Objectives` section has no `O<n>.` lines."] };
|
|
2393
|
+
}
|
|
2394
|
+
const objectives = [];
|
|
2395
|
+
const errors = [];
|
|
2396
|
+
for (const line of lines) {
|
|
2397
|
+
const m = OBJECTIVE_LINE_RE.exec(line);
|
|
2398
|
+
if (!m) {
|
|
2399
|
+
errors.push(`"${line}" is not a well-formed objective line — expected \`O<n>. <sentence>\`, e.g. \`O1. …\`.`);
|
|
2400
|
+
continue;
|
|
2401
|
+
}
|
|
2402
|
+
const n = m[1];
|
|
2403
|
+
const text = m[2].trim();
|
|
2404
|
+
if (text.length === 0) {
|
|
2405
|
+
errors.push(`O${n} has no sentence — every objective is one observable sentence.`);
|
|
2406
|
+
continue;
|
|
2407
|
+
}
|
|
2408
|
+
if (hasBacktickedPath(text) && wordCount(stripObjectiveBackticks(text)) < MIN_WORDS_OUTSIDE_BACKTICKS) {
|
|
2409
|
+
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).`);
|
|
2410
|
+
continue;
|
|
2411
|
+
}
|
|
2412
|
+
objectives.push({ id: `O${n}`, text });
|
|
2413
|
+
}
|
|
2414
|
+
for (let i = 0;i < objectives.length; i++) {
|
|
2415
|
+
const expected = i + 1;
|
|
2416
|
+
const actual = Number.parseInt(objectives[i].id.slice(1), 10);
|
|
2417
|
+
if (actual !== expected) {
|
|
2418
|
+
errors.push(`numbering is not contiguous from O1 — expected O${expected}, found ${objectives[i].id}.`);
|
|
2419
|
+
break;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
if (errors.length > 0)
|
|
2423
|
+
return { ok: false, errors };
|
|
2424
|
+
return { ok: true, objectives };
|
|
2425
|
+
}
|
|
2426
|
+
function normalizeText(text) {
|
|
2427
|
+
return text.trim().replace(/\s+/g, " ");
|
|
2428
|
+
}
|
|
2429
|
+
function objectivesVersion(objectives) {
|
|
2430
|
+
const normalized = objectives.map((o) => `${o.id} ${normalizeText(o.text)}`).join(`
|
|
2431
|
+
`);
|
|
2432
|
+
return createHash2("sha256").update(normalized).digest("hex");
|
|
2433
|
+
}
|
|
2434
|
+
function isIssueNotFoundError(err) {
|
|
2435
|
+
const stderr = err?.stderr;
|
|
2436
|
+
const haystack = [
|
|
2437
|
+
err?.message ?? "",
|
|
2438
|
+
typeof stderr === "string" ? stderr : stderr?.toString() ?? ""
|
|
2439
|
+
].join(`
|
|
2440
|
+
`);
|
|
2441
|
+
return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
// ../../packages/aeg-core/src/premise-check.ts
|
|
2445
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2341
2446
|
var ASSERTION_KINDS = new Set(["contains", "absent", "sha256"]);
|
|
2342
2447
|
function isPremiseHeader(line) {
|
|
2343
2448
|
const stripped = line.replace(/[*#]/g, "").trim();
|
|
@@ -2408,7 +2513,7 @@ function checkPremises(assertions, fileReader) {
|
|
|
2408
2513
|
} else if (a.kind === "absent" && content.includes(a.value)) {
|
|
2409
2514
|
failures.push(`premise-check: ${a.path} now contains "${a.value}", but the brief pinned it absent — the premise has moved; re-dig before proceeding.`);
|
|
2410
2515
|
} else if (a.kind === "sha256") {
|
|
2411
|
-
const actual =
|
|
2516
|
+
const actual = createHash3("sha256").update(content).digest("hex");
|
|
2412
2517
|
if (actual !== a.value.toLowerCase()) {
|
|
2413
2518
|
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.`);
|
|
2414
2519
|
}
|
|
@@ -2769,8 +2874,55 @@ function checkDefeatCases(prBody) {
|
|
|
2769
2874
|
]
|
|
2770
2875
|
};
|
|
2771
2876
|
}
|
|
2877
|
+
var PART_CITATION_RE = /Part\s+\d+\s*\(([^)]*)\)/gi;
|
|
2878
|
+
var OBJECTIVE_REF_RE = /O(\d+)/g;
|
|
2879
|
+
function citedObjectiveIds(section6) {
|
|
2880
|
+
const ids = new Set;
|
|
2881
|
+
for (const m of section6.matchAll(PART_CITATION_RE)) {
|
|
2882
|
+
for (const r of m[1].matchAll(OBJECTIVE_REF_RE))
|
|
2883
|
+
ids.add(Number.parseInt(r[1], 10));
|
|
2884
|
+
}
|
|
2885
|
+
return ids;
|
|
2886
|
+
}
|
|
2887
|
+
function checkObjectivesCopy(prBody, issueObjectives) {
|
|
2888
|
+
const parsed = objectivesOf(prBody);
|
|
2889
|
+
if (!parsed.ok) {
|
|
2890
|
+
return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives copy: ${e}`) };
|
|
2891
|
+
}
|
|
2892
|
+
if (objectivesVersion(parsed.objectives) !== objectivesVersion(issueObjectives)) {
|
|
2893
|
+
return {
|
|
2894
|
+
status: "fail",
|
|
2895
|
+
errors: [
|
|
2896
|
+
"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)."
|
|
2897
|
+
]
|
|
2898
|
+
};
|
|
2899
|
+
}
|
|
2900
|
+
return { status: "pass", errors: [] };
|
|
2901
|
+
}
|
|
2902
|
+
function checkObjectivesCoverage(prBody) {
|
|
2903
|
+
const parsed = objectivesOf(prBody);
|
|
2904
|
+
if (!parsed.ok) {
|
|
2905
|
+
return { status: "fail", errors: parsed.errors.map((e) => `brief-validation objectives coverage: ${e}`) };
|
|
2906
|
+
}
|
|
2907
|
+
const declaredIds = new Set(parsed.objectives.map((o) => Number.parseInt(o.id.slice(1), 10)));
|
|
2908
|
+
const section6 = extractNumberedSection(prBody, 6) ?? "";
|
|
2909
|
+
const cited = citedObjectiveIds(section6);
|
|
2910
|
+
const maxDeclared = Math.max(...declaredIds, 0);
|
|
2911
|
+
const errors = [];
|
|
2912
|
+
for (const id of declaredIds) {
|
|
2913
|
+
if (!cited.has(id)) {
|
|
2914
|
+
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.`);
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
for (const id of cited) {
|
|
2918
|
+
if (!declaredIds.has(id)) {
|
|
2919
|
+
errors.push(`brief-validation objectives coverage: a Part in §6 cites O${id}, but the Objectives section ends at O${maxDeclared}.`);
|
|
2920
|
+
}
|
|
2921
|
+
}
|
|
2922
|
+
return errors.length === 0 ? { status: "pass", errors: [] } : { status: "fail", errors };
|
|
2923
|
+
}
|
|
2772
2924
|
function checkBriefSections(prBody, readTier, options = {}) {
|
|
2773
|
-
const { requireClosesN = true, consumersOf = () => [] } = options;
|
|
2925
|
+
const { requireClosesN = true, consumersOf = () => [], issueObjectives } = options;
|
|
2774
2926
|
const results = [
|
|
2775
2927
|
checkTierField(prBody, readTier),
|
|
2776
2928
|
checkTestPlan(prBody),
|
|
@@ -2788,6 +2940,7 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2788
2940
|
checkCommandsCarryOutput(prBody),
|
|
2789
2941
|
checkConsumerTests(prBody, consumersOf),
|
|
2790
2942
|
checkDefeatCases(prBody),
|
|
2943
|
+
...issueObjectives !== undefined ? [checkObjectivesCopy(prBody, issueObjectives), checkObjectivesCoverage(prBody)] : [],
|
|
2791
2944
|
...requireClosesN ? [checkClosesN(prBody)] : []
|
|
2792
2945
|
];
|
|
2793
2946
|
return { errors: results.flatMap((r) => r.errors) };
|
|
@@ -2986,14 +3139,14 @@ function checkSectionDensity(prBody, heading) {
|
|
|
2986
3139
|
]
|
|
2987
3140
|
};
|
|
2988
3141
|
}
|
|
2989
|
-
function
|
|
2990
|
-
return checkSectionDensity(prBody, "
|
|
3142
|
+
function checkDecisionsDensity(prBody) {
|
|
3143
|
+
return checkSectionDensity(prBody, "Decisions");
|
|
2991
3144
|
}
|
|
2992
3145
|
function checkScopeDensity(prBody) {
|
|
2993
3146
|
return checkSectionDensity(prBody, "Scope");
|
|
2994
3147
|
}
|
|
2995
3148
|
function checkPrReportDensity(prBody) {
|
|
2996
|
-
const results = [
|
|
3149
|
+
const results = [checkDecisionsDensity(prBody), checkScopeDensity(prBody)];
|
|
2997
3150
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2998
3151
|
}
|
|
2999
3152
|
// ../../packages/aeg-core/src/issue-validation.ts
|
|
@@ -3025,6 +3178,15 @@ function checkIssueRationale(body) {
|
|
|
3025
3178
|
}
|
|
3026
3179
|
return { status: errors.length > 0 ? "fail" : "pass", errors };
|
|
3027
3180
|
}
|
|
3181
|
+
var OBJECTIVES_SINCE_ISSUE = 404;
|
|
3182
|
+
function checkIssueObjectives(body, issueNumber) {
|
|
3183
|
+
if (issueNumber !== null && issueNumber < OBJECTIVES_SINCE_ISSUE)
|
|
3184
|
+
return { status: "pass", errors: [] };
|
|
3185
|
+
const result = objectivesOf(body);
|
|
3186
|
+
if (result.ok)
|
|
3187
|
+
return { status: "pass", errors: [] };
|
|
3188
|
+
return { status: "fail", errors: result.errors.map((e) => `issue-validation objectives: ${e}`) };
|
|
3189
|
+
}
|
|
3028
3190
|
function isTaskIssueLabelSet(labels) {
|
|
3029
3191
|
return hasLabel("tranche", labels);
|
|
3030
3192
|
}
|
|
@@ -3286,7 +3448,8 @@ function checkR1(issuesBySlug, grandfatheredIssues, registeredNames = []) {
|
|
|
3286
3448
|
continue;
|
|
3287
3449
|
const errors = [
|
|
3288
3450
|
...checkIssueRationale(issue.body).errors,
|
|
3289
|
-
...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors
|
|
3451
|
+
...checkProjectsRegistered(issue.body, issue.labels, registeredNames).errors,
|
|
3452
|
+
...checkIssueObjectives(issue.body, issue.number).errors
|
|
3290
3453
|
];
|
|
3291
3454
|
if (errors.length === 0)
|
|
3292
3455
|
continue;
|
|
@@ -3401,6 +3564,27 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
|
|
|
3401
3564
|
}
|
|
3402
3565
|
return { ok: true, expectedIssue };
|
|
3403
3566
|
}
|
|
3567
|
+
// ../../packages/aeg-core/src/archive-task.ts
|
|
3568
|
+
function closesRefs(text) {
|
|
3569
|
+
return [...new Set([...text.matchAll(/Closes #(\d+)/gi)].map((m) => Number(m[1])))];
|
|
3570
|
+
}
|
|
3571
|
+
function extractIssue(body) {
|
|
3572
|
+
const stripped = stripCode(body);
|
|
3573
|
+
const bodyNums = closesRefs(stripped);
|
|
3574
|
+
const anchored = anchoredRegion(stripped, "CLOSES");
|
|
3575
|
+
if (anchored !== null) {
|
|
3576
|
+
const anchorNums = closesRefs(anchored);
|
|
3577
|
+
if (anchorNums.length === 0)
|
|
3578
|
+
return { issue: null, extraIssues: bodyNums, outsideHeader: false };
|
|
3579
|
+
const issue2 = anchorNums[0];
|
|
3580
|
+
return { issue: issue2, extraIssues: bodyNums.filter((n) => n !== issue2), outsideHeader: false };
|
|
3581
|
+
}
|
|
3582
|
+
const headerNums = closesRefs(headerRegion(stripped));
|
|
3583
|
+
if (bodyNums.length === 0)
|
|
3584
|
+
return { issue: null, extraIssues: [], outsideHeader: false };
|
|
3585
|
+
const issue = headerNums.length > 0 ? headerNums[0] : bodyNums[0];
|
|
3586
|
+
return { issue, extraIssues: bodyNums.filter((n) => n !== issue), outsideHeader: headerNums.length === 0 };
|
|
3587
|
+
}
|
|
3404
3588
|
// ../../packages/aeg-core/src/review-gate.ts
|
|
3405
3589
|
function isBoundToHead(extraction, headSha) {
|
|
3406
3590
|
if (!extraction.headSha)
|
|
@@ -3419,6 +3603,11 @@ function isBoundByPatchIdentity(extraction, headSha, patchIdOf) {
|
|
|
3419
3603
|
function isBoundToPatch(extraction, headSha, patchIdOf) {
|
|
3420
3604
|
return isBoundToHead(extraction, headSha) || isBoundByPatchIdentity(extraction, headSha, patchIdOf);
|
|
3421
3605
|
}
|
|
3606
|
+
function isBoundToObjectives(extraction, currentVersion) {
|
|
3607
|
+
if (currentVersion === null)
|
|
3608
|
+
return true;
|
|
3609
|
+
return extraction.objectivesVersion === currentVersion;
|
|
3610
|
+
}
|
|
3422
3611
|
function checkReviewGate(input) {
|
|
3423
3612
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
3424
3613
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -3445,7 +3634,9 @@ function checkReviewGate(input) {
|
|
|
3445
3634
|
const securityClean = security.value === "PASS";
|
|
3446
3635
|
const codeReviewBound = isBoundToPatch(codeReview, input.headSha, input.patchIdOf);
|
|
3447
3636
|
const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
|
|
3448
|
-
|
|
3637
|
+
const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
|
|
3638
|
+
const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
|
|
3639
|
+
if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && securityClean && securityBound && securityObjectivesBound && mechanicalChecksClean) {
|
|
3449
3640
|
return {
|
|
3450
3641
|
verdict: "pass",
|
|
3451
3642
|
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.`,
|
|
@@ -3457,11 +3648,15 @@ function checkReviewGate(input) {
|
|
|
3457
3648
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
3458
3649
|
} else if (!codeReviewBound) {
|
|
3459
3650
|
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3651
|
+
} else if (!codeReviewObjectivesBound) {
|
|
3652
|
+
problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3460
3653
|
}
|
|
3461
3654
|
if (!securityClean) {
|
|
3462
3655
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
3463
3656
|
} else if (!securityBound) {
|
|
3464
3657
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3658
|
+
} else if (!securityObjectivesBound) {
|
|
3659
|
+
problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3465
3660
|
}
|
|
3466
3661
|
if (!mechanicalChecksClean) {
|
|
3467
3662
|
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(", ")}`);
|
|
@@ -3697,55 +3892,20 @@ function checkG6(rows, registeredCheckNames) {
|
|
|
3697
3892
|
}
|
|
3698
3893
|
return { check: "G6", status: findings.length > 0 ? "fail" : "pass", findings };
|
|
3699
3894
|
}
|
|
3700
|
-
// ../../packages/aeg-core/src/
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
return {
|
|
3711
|
-
verdict: "refuse",
|
|
3712
|
-
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.`
|
|
3713
|
-
};
|
|
3714
|
-
}
|
|
3715
|
-
if (prState === "OPEN") {
|
|
3716
|
-
return {
|
|
3717
|
-
verdict: "allow",
|
|
3718
|
-
reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
|
|
3719
|
-
};
|
|
3720
|
-
}
|
|
3721
|
-
if (prState === "NONE") {
|
|
3722
|
-
return {
|
|
3723
|
-
verdict: "allow",
|
|
3724
|
-
reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
|
|
3725
|
-
};
|
|
3726
|
-
}
|
|
3727
|
-
return {
|
|
3728
|
-
verdict: "allow",
|
|
3729
|
-
reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
|
|
3730
|
-
};
|
|
3731
|
-
}
|
|
3732
|
-
// ../../packages/aeg-core/src/baseline-capture.ts
|
|
3733
|
-
function captureBaseline(counts, capturedAt) {
|
|
3734
|
-
return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
|
|
3895
|
+
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
3896
|
+
var PRODUCT_SLUG_SCOPE = [
|
|
3897
|
+
"apps/cli/src",
|
|
3898
|
+
".github/workflows",
|
|
3899
|
+
".vinaya",
|
|
3900
|
+
"apps/cli/README.md",
|
|
3901
|
+
"packages/sources/README.md"
|
|
3902
|
+
];
|
|
3903
|
+
function isUnderOrEqual(path, prefix) {
|
|
3904
|
+
return path === prefix || path.startsWith(`${prefix}/`);
|
|
3735
3905
|
}
|
|
3736
|
-
function
|
|
3737
|
-
|
|
3738
|
-
const baselineEntry = baseline.find((b) => b.tool === c.tool);
|
|
3739
|
-
const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
|
|
3740
|
-
return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
|
|
3741
|
-
});
|
|
3742
|
-
return {
|
|
3743
|
-
withinBudget: perTool.every((t) => t.delta <= 0),
|
|
3744
|
-
delta: perTool.reduce((sum, t) => sum + t.delta, 0),
|
|
3745
|
-
perTool
|
|
3746
|
-
};
|
|
3906
|
+
function isProductScopeFile(path) {
|
|
3907
|
+
return PRODUCT_SLUG_SCOPE.some((prefix) => isUnderOrEqual(path, prefix));
|
|
3747
3908
|
}
|
|
3748
|
-
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
3749
3909
|
var SHIPS_PREFIX = "aeg-root/";
|
|
3750
3910
|
function shipsArchivePrefix(shipsPrefix) {
|
|
3751
3911
|
return `${shipsPrefix}tranches/completed/`;
|
|
@@ -3765,6 +3925,8 @@ function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix, shipsPr
|
|
|
3765
3925
|
}
|
|
3766
3926
|
if (isSpecFile2(path) || isClaudeMdFile(path))
|
|
3767
3927
|
return "internal";
|
|
3928
|
+
if (isProductScopeFile(path))
|
|
3929
|
+
return "product";
|
|
3768
3930
|
return null;
|
|
3769
3931
|
}
|
|
3770
3932
|
var SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
|
|
@@ -3777,6 +3939,9 @@ function stripNonProse(path, content) {
|
|
|
3777
3939
|
}
|
|
3778
3940
|
return content;
|
|
3779
3941
|
}
|
|
3942
|
+
function stripNonProseForProduct(path, content) {
|
|
3943
|
+
return path.endsWith(".md") ? stripNonProse(path, content) : content;
|
|
3944
|
+
}
|
|
3780
3945
|
function lineAt(content, index) {
|
|
3781
3946
|
let line = 1;
|
|
3782
3947
|
for (let i = 0;i < index; i++) {
|
|
@@ -3801,12 +3966,19 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
|
|
|
3801
3966
|
{ pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug" },
|
|
3802
3967
|
...legacyPattern ? [{ pattern: legacyPattern, what: "an internal tranche slug", group: 2 }] : []
|
|
3803
3968
|
];
|
|
3969
|
+
const productPatterns = [
|
|
3970
|
+
{ pattern: TRANCHE_SLUG_VN_PATTERN, what: "an internal tranche slug in product code" }
|
|
3971
|
+
];
|
|
3804
3972
|
for (const file of files) {
|
|
3805
3973
|
const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
|
|
3806
|
-
if (!cls ||
|
|
3974
|
+
if (!cls || cls === "internal")
|
|
3807
3975
|
continue;
|
|
3808
|
-
|
|
3809
|
-
|
|
3976
|
+
if (cls !== "product" && !SWEPT_CLASSES.has(cls))
|
|
3977
|
+
continue;
|
|
3978
|
+
const blocking = cls === "product";
|
|
3979
|
+
const scrubbed = blocking ? stripNonProseForProduct(file.path, file.content) : stripNonProse(file.path, file.content);
|
|
3980
|
+
const patternsToRun = blocking ? productPatterns : patterns;
|
|
3981
|
+
for (const { pattern, what, group } of patternsToRun) {
|
|
3810
3982
|
pattern.lastIndex = 0;
|
|
3811
3983
|
let match = pattern.exec(scrubbed);
|
|
3812
3984
|
while (match !== null) {
|
|
@@ -3814,7 +3986,8 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
|
|
|
3814
3986
|
findings.push({
|
|
3815
3987
|
file: file.path,
|
|
3816
3988
|
line: lineAt(scrubbed, match.index),
|
|
3817
|
-
message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve
|
|
3989
|
+
message: `references ${what} ("${cited}") a reader outside this repo's tracker cannot resolve`,
|
|
3990
|
+
blocking
|
|
3818
3991
|
});
|
|
3819
3992
|
match = pattern.exec(scrubbed);
|
|
3820
3993
|
}
|
|
@@ -3864,7 +4037,8 @@ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, read
|
|
|
3864
4037
|
findings.push({
|
|
3865
4038
|
file: file.path,
|
|
3866
4039
|
line,
|
|
3867
|
-
message: `uses coined term "${term}" without defining it inline or linking the glossary
|
|
4040
|
+
message: `uses coined term "${term}" without defining it inline or linking the glossary`,
|
|
4041
|
+
blocking: false
|
|
3868
4042
|
});
|
|
3869
4043
|
}
|
|
3870
4044
|
}
|
|
@@ -3887,6 +4061,7 @@ function parseGlossaryTerms(glossaryContent) {
|
|
|
3887
4061
|
}
|
|
3888
4062
|
return terms;
|
|
3889
4063
|
}
|
|
4064
|
+
|
|
3890
4065
|
// ../../packages/aeg-core/src/quoted-command.ts
|
|
3891
4066
|
var QUOTED_COMMAND_SHIPS_PREFIX = "aeg-root/";
|
|
3892
4067
|
var QUOTED_COMMAND_SWEPT_CLASSES = new Set(["ships", "reader-facing"]);
|
|
@@ -3981,6 +4156,54 @@ function evaluateCitedQuotes(citedQuotes, citedFileContents) {
|
|
|
3981
4156
|
}
|
|
3982
4157
|
return findings;
|
|
3983
4158
|
}
|
|
4159
|
+
// ../../packages/aeg-core/src/dead-branch-push-guard.ts
|
|
4160
|
+
function checkDeadBranchPush(input) {
|
|
4161
|
+
const { branch, prState, prNumber } = input;
|
|
4162
|
+
if (prState === "MERGED") {
|
|
4163
|
+
return {
|
|
4164
|
+
verdict: "refuse",
|
|
4165
|
+
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.`
|
|
4166
|
+
};
|
|
4167
|
+
}
|
|
4168
|
+
if (prState === "CLOSED") {
|
|
4169
|
+
return {
|
|
4170
|
+
verdict: "refuse",
|
|
4171
|
+
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.`
|
|
4172
|
+
};
|
|
4173
|
+
}
|
|
4174
|
+
if (prState === "OPEN") {
|
|
4175
|
+
return {
|
|
4176
|
+
verdict: "allow",
|
|
4177
|
+
reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
|
|
4178
|
+
};
|
|
4179
|
+
}
|
|
4180
|
+
if (prState === "NONE") {
|
|
4181
|
+
return {
|
|
4182
|
+
verdict: "allow",
|
|
4183
|
+
reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
|
|
4184
|
+
};
|
|
4185
|
+
}
|
|
4186
|
+
return {
|
|
4187
|
+
verdict: "allow",
|
|
4188
|
+
reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
|
|
4189
|
+
};
|
|
4190
|
+
}
|
|
4191
|
+
// ../../packages/aeg-core/src/baseline-capture.ts
|
|
4192
|
+
function captureBaseline(counts, capturedAt) {
|
|
4193
|
+
return counts.map((c) => ({ tool: c.tool, findingCount: c.findingCount, capturedAt }));
|
|
4194
|
+
}
|
|
4195
|
+
function compareToBaseline(current, baseline) {
|
|
4196
|
+
const perTool = current.map((c) => {
|
|
4197
|
+
const baselineEntry = baseline.find((b) => b.tool === c.tool);
|
|
4198
|
+
const baselineCount = baselineEntry ? baselineEntry.findingCount : c.findingCount;
|
|
4199
|
+
return { tool: c.tool, baseline: baselineCount, current: c.findingCount, delta: c.findingCount - baselineCount };
|
|
4200
|
+
});
|
|
4201
|
+
return {
|
|
4202
|
+
withinBudget: perTool.every((t) => t.delta <= 0),
|
|
4203
|
+
delta: perTool.reduce((sum, t) => sum + t.delta, 0),
|
|
4204
|
+
perTool
|
|
4205
|
+
};
|
|
4206
|
+
}
|
|
3984
4207
|
// ../../packages/aeg-core/src/retired-vocabulary.ts
|
|
3985
4208
|
var RETIRED_PATTERNS = [
|
|
3986
4209
|
String.raw`\bD-[0-9]{3}\b`,
|
|
@@ -4450,32 +4673,248 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
|
|
|
4450
4673
|
}
|
|
4451
4674
|
return findings;
|
|
4452
4675
|
}
|
|
4676
|
+
// ../../packages/aeg-core/src/log/schema.ts
|
|
4677
|
+
import { z } from "zod";
|
|
4678
|
+
var ROLE_VALUES = [
|
|
4679
|
+
"planner",
|
|
4680
|
+
"brief-author",
|
|
4681
|
+
"developer",
|
|
4682
|
+
"code-reviewer",
|
|
4683
|
+
"security",
|
|
4684
|
+
"principal",
|
|
4685
|
+
"archivist",
|
|
4686
|
+
"architect"
|
|
4687
|
+
];
|
|
4688
|
+
var RoleSchema = z.enum(ROLE_VALUES);
|
|
4689
|
+
var HOST_VALUES = ["hook", "ci", "cli", "loop"];
|
|
4690
|
+
var HostSchema = z.enum(HOST_VALUES);
|
|
4691
|
+
var RUN_ID_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
|
|
4692
|
+
var HeaderMetaSchema = z.object({
|
|
4693
|
+
schema: z.literal(1),
|
|
4694
|
+
ts: z.string(),
|
|
4695
|
+
run_id: z.string().regex(RUN_ID_PATTERN),
|
|
4696
|
+
seq: z.number().int().nonnegative(),
|
|
4697
|
+
repo: z.string().nullable(),
|
|
4698
|
+
vinaya: z.string(),
|
|
4699
|
+
doctrine: z.string(),
|
|
4700
|
+
host: HostSchema,
|
|
4701
|
+
machine: z.string()
|
|
4702
|
+
}).strict();
|
|
4703
|
+
var SubjectSchema = z.object({
|
|
4704
|
+
issue: z.number().int().nullable(),
|
|
4705
|
+
pr: z.number().int().optional(),
|
|
4706
|
+
sha: z.string().optional(),
|
|
4707
|
+
role: z.union([RoleSchema, z.literal("unattributed")]),
|
|
4708
|
+
round: z.number().int().optional(),
|
|
4709
|
+
objectives_version: z.string().optional()
|
|
4710
|
+
}).strict();
|
|
4711
|
+
var HeaderSchema = z.object({
|
|
4712
|
+
meta: HeaderMetaSchema,
|
|
4713
|
+
subject: SubjectSchema
|
|
4714
|
+
}).strict();
|
|
4715
|
+
var envelopeTail = {
|
|
4716
|
+
duration_ms: z.number().nonnegative().optional(),
|
|
4717
|
+
payload: z.object({}).strict()
|
|
4718
|
+
};
|
|
4719
|
+
var DispatchOutcomeSchema = z.discriminatedUnion("type", [
|
|
4720
|
+
z.object({ type: z.literal("pr_opened"), pr: z.number().int(), head: z.string() }).strict(),
|
|
4721
|
+
z.object({
|
|
4722
|
+
type: z.literal("round_pushed"),
|
|
4723
|
+
pr: z.number().int(),
|
|
4724
|
+
head: z.string(),
|
|
4725
|
+
comment_id: z.number().int()
|
|
4726
|
+
}).strict(),
|
|
4727
|
+
z.object({
|
|
4728
|
+
type: z.literal("verdict"),
|
|
4729
|
+
verdict: z.enum(["APPROVE", "REQUEST CHANGES", "PASS", "FAIL"]),
|
|
4730
|
+
head: z.string(),
|
|
4731
|
+
comment_id: z.number().int(),
|
|
4732
|
+
objectives: z.array(z.object({ id: z.string(), met: z.boolean() }).strict()),
|
|
4733
|
+
findings: z.array(z.object({ id: z.string(), severity: z.string(), state: z.string().optional() }).strict())
|
|
4734
|
+
}).strict(),
|
|
4735
|
+
z.object({
|
|
4736
|
+
type: z.literal("escalation"),
|
|
4737
|
+
class: z.enum(["authority", "strategy", "product"]),
|
|
4738
|
+
comment_id: z.number().int()
|
|
4739
|
+
}).strict(),
|
|
4740
|
+
z.object({ type: z.literal("brief"), comment_id: z.number().int(), hash: z.string() }).strict(),
|
|
4741
|
+
z.object({ type: z.literal("plan"), issues: z.array(z.number().int()) }).strict(),
|
|
4742
|
+
z.object({ type: z.literal("archive"), provenance_comment_id: z.number().int() }).strict()
|
|
4743
|
+
]);
|
|
4744
|
+
var dispatchShared = {
|
|
4745
|
+
meta: HeaderMetaSchema,
|
|
4746
|
+
subject: SubjectSchema,
|
|
4747
|
+
kind: z.literal("dispatch"),
|
|
4748
|
+
...envelopeTail,
|
|
4749
|
+
target_role: RoleSchema,
|
|
4750
|
+
model: z.string(),
|
|
4751
|
+
round: z.number().int().optional(),
|
|
4752
|
+
effect_id: z.string()
|
|
4753
|
+
};
|
|
4754
|
+
var DispatchEventSchema = z.discriminatedUnion("event", [
|
|
4755
|
+
z.object({ ...dispatchShared, event: z.literal("dispatched"), prompt_hash: z.string() }).strict(),
|
|
4756
|
+
z.object({
|
|
4757
|
+
...dispatchShared,
|
|
4758
|
+
event: z.literal("outcome_received"),
|
|
4759
|
+
outcome: DispatchOutcomeSchema,
|
|
4760
|
+
usage: z.object({ input: z.number().nonnegative(), output: z.number().nonnegative() }).strict().nullable()
|
|
4761
|
+
}).strict(),
|
|
4762
|
+
z.object({
|
|
4763
|
+
...dispatchShared,
|
|
4764
|
+
event: z.literal("dispatch_failed"),
|
|
4765
|
+
reason: z.enum(["timeout", "crash", "refused", "unattributed_write"])
|
|
4766
|
+
}).strict()
|
|
4767
|
+
]);
|
|
4768
|
+
var loopShared = {
|
|
4769
|
+
meta: HeaderMetaSchema,
|
|
4770
|
+
subject: SubjectSchema,
|
|
4771
|
+
kind: z.literal("dev_review_loop"),
|
|
4772
|
+
...envelopeTail,
|
|
4773
|
+
loop_id: z.string()
|
|
4774
|
+
};
|
|
4775
|
+
var DevReviewLoopEventSchema = z.discriminatedUnion("event", [
|
|
4776
|
+
z.object({
|
|
4777
|
+
...loopShared,
|
|
4778
|
+
event: z.literal("loop_started"),
|
|
4779
|
+
task: z.number().int(),
|
|
4780
|
+
policy: z.object({
|
|
4781
|
+
max_rounds: z.number().int().nonnegative(),
|
|
4782
|
+
reviewers: z.array(RoleSchema),
|
|
4783
|
+
models: z.record(RoleSchema, z.string())
|
|
4784
|
+
}).strict()
|
|
4785
|
+
}).strict(),
|
|
4786
|
+
z.object({ ...loopShared, event: z.literal("round_started"), round: z.number().int(), base_head: z.string() }).strict(),
|
|
4787
|
+
z.object({
|
|
4788
|
+
...loopShared,
|
|
4789
|
+
event: z.literal("gate_result_read"),
|
|
4790
|
+
round: z.number().int(),
|
|
4791
|
+
head: z.string(),
|
|
4792
|
+
green: z.boolean()
|
|
4793
|
+
}).strict(),
|
|
4794
|
+
z.object({
|
|
4795
|
+
...loopShared,
|
|
4796
|
+
event: z.literal("verdicts_read"),
|
|
4797
|
+
round: z.number().int(),
|
|
4798
|
+
head: z.string(),
|
|
4799
|
+
all_approve: z.boolean(),
|
|
4800
|
+
blockers: z.number().int().nonnegative()
|
|
4801
|
+
}).strict(),
|
|
4802
|
+
z.object({
|
|
4803
|
+
...loopShared,
|
|
4804
|
+
event: z.literal("findings_compared"),
|
|
4805
|
+
round: z.number().int(),
|
|
4806
|
+
open: z.array(z.string()),
|
|
4807
|
+
resolved: z.array(z.string()),
|
|
4808
|
+
new: z.array(z.string()),
|
|
4809
|
+
recurring: z.array(z.string())
|
|
4810
|
+
}).strict(),
|
|
4811
|
+
z.object({
|
|
4812
|
+
...loopShared,
|
|
4813
|
+
event: z.literal("stop_condition_met"),
|
|
4814
|
+
round: z.number().int(),
|
|
4815
|
+
condition: z.enum([
|
|
4816
|
+
"green",
|
|
4817
|
+
"max_rounds",
|
|
4818
|
+
"no_progress",
|
|
4819
|
+
"escalated",
|
|
4820
|
+
"principal_stop",
|
|
4821
|
+
"confidence",
|
|
4822
|
+
"reappearance"
|
|
4823
|
+
])
|
|
4824
|
+
}).strict(),
|
|
4825
|
+
z.object({
|
|
4826
|
+
...loopShared,
|
|
4827
|
+
event: z.literal("paused"),
|
|
4828
|
+
round: z.number().int(),
|
|
4829
|
+
reason: z.enum(["escalation", "principal_item", "refreeze_needed"])
|
|
4830
|
+
}).strict(),
|
|
4831
|
+
z.object({
|
|
4832
|
+
...loopShared,
|
|
4833
|
+
event: z.literal("resumed"),
|
|
4834
|
+
round: z.number().int(),
|
|
4835
|
+
by: z.literal("principal")
|
|
4836
|
+
}).strict(),
|
|
4837
|
+
z.object({
|
|
4838
|
+
...loopShared,
|
|
4839
|
+
event: z.literal("round_ended"),
|
|
4840
|
+
round: z.number().int(),
|
|
4841
|
+
base_head: z.string(),
|
|
4842
|
+
head: z.string(),
|
|
4843
|
+
files_changed: z.number().int().nonnegative(),
|
|
4844
|
+
insertions: z.number().int().nonnegative(),
|
|
4845
|
+
deletions: z.number().int().nonnegative(),
|
|
4846
|
+
wall_ms: z.number().nonnegative(),
|
|
4847
|
+
outcome: z.enum(["green", "changes_requested", "escalated"])
|
|
4848
|
+
}).strict(),
|
|
4849
|
+
z.object({
|
|
4850
|
+
...loopShared,
|
|
4851
|
+
event: z.literal("journal_finalized"),
|
|
4852
|
+
rounds: z.number().int().nonnegative(),
|
|
4853
|
+
total_wall_ms: z.number().nonnegative(),
|
|
4854
|
+
time_to_green_ms: z.number().nonnegative().nullable(),
|
|
4855
|
+
files_changed_total: z.number().int().nonnegative(),
|
|
4856
|
+
final_head: z.string(),
|
|
4857
|
+
result: z.enum(["merged_ready", "stopped"])
|
|
4858
|
+
}).strict()
|
|
4859
|
+
]);
|
|
4860
|
+
var ForgeOpSchema = z.enum([
|
|
4861
|
+
"pr.create",
|
|
4862
|
+
"pr.comment",
|
|
4863
|
+
"pr.body.replace",
|
|
4864
|
+
"pr.refreeze",
|
|
4865
|
+
"issue.create",
|
|
4866
|
+
"issue.edit",
|
|
4867
|
+
"issue.comment",
|
|
4868
|
+
"milestone.create",
|
|
4869
|
+
"milestone.edit",
|
|
4870
|
+
"milestone.close",
|
|
4871
|
+
"label.add",
|
|
4872
|
+
"label.remove"
|
|
4873
|
+
]);
|
|
4874
|
+
var ForgeWriteTargetSchema = z.object({
|
|
4875
|
+
issue: z.number().int().optional(),
|
|
4876
|
+
pr: z.number().int().optional()
|
|
4877
|
+
}).strict();
|
|
4878
|
+
var forgeWriteShared = {
|
|
4879
|
+
meta: HeaderMetaSchema,
|
|
4880
|
+
subject: SubjectSchema,
|
|
4881
|
+
kind: z.literal("forge_write"),
|
|
4882
|
+
...envelopeTail,
|
|
4883
|
+
op: ForgeOpSchema,
|
|
4884
|
+
target: ForgeWriteTargetSchema
|
|
4885
|
+
};
|
|
4886
|
+
var ForgeWriteEventSchema = z.discriminatedUnion("event", [
|
|
4887
|
+
z.object({ ...forgeWriteShared, event: z.literal("validated") }).strict(),
|
|
4888
|
+
z.object({ ...forgeWriteShared, event: z.literal("refused"), reason: z.string() }).strict(),
|
|
4889
|
+
z.object({ ...forgeWriteShared, event: z.literal("written"), comment_ids: z.array(z.string()) }).strict()
|
|
4890
|
+
]);
|
|
4891
|
+
var LogEventSchema = z.union([DispatchEventSchema, DevReviewLoopEventSchema, ForgeWriteEventSchema]);
|
|
4453
4892
|
// src/lib/config.ts
|
|
4454
4893
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
4455
4894
|
import { existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "node:fs";
|
|
4456
4895
|
import { homedir } from "node:os";
|
|
4457
4896
|
import { dirname, join, resolve } from "node:path";
|
|
4458
|
-
import { z } from "zod";
|
|
4897
|
+
import { z as z2 } from "zod";
|
|
4459
4898
|
|
|
4460
4899
|
// src/lib/agent-vendors.ts
|
|
4461
4900
|
var AGENT_VENDORS = ["skills", "claude", "gemini"];
|
|
4462
4901
|
|
|
4463
4902
|
// src/lib/config.ts
|
|
4464
|
-
var EnvEntrySchema =
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4903
|
+
var EnvEntrySchema = z2.union([
|
|
4904
|
+
z2.literal(true),
|
|
4905
|
+
z2.object({ optional: z2.literal(true) }),
|
|
4906
|
+
z2.object({ anyOf: z2.array(z2.string()).min(2) }),
|
|
4907
|
+
z2.string()
|
|
4469
4908
|
]);
|
|
4470
|
-
var CheckEntrySchema =
|
|
4471
|
-
run:
|
|
4472
|
-
scope:
|
|
4473
|
-
include:
|
|
4474
|
-
args:
|
|
4475
|
-
timeoutMs:
|
|
4476
|
-
env:
|
|
4477
|
-
requiresOpenPr:
|
|
4478
|
-
ownWorkflow:
|
|
4909
|
+
var CheckEntrySchema = z2.object({
|
|
4910
|
+
run: z2.string(),
|
|
4911
|
+
scope: z2.enum(["diff", "full"]),
|
|
4912
|
+
include: z2.array(z2.string()).optional(),
|
|
4913
|
+
args: z2.array(z2.string()).optional(),
|
|
4914
|
+
timeoutMs: z2.number().optional(),
|
|
4915
|
+
env: z2.record(z2.string(), EnvEntrySchema).optional(),
|
|
4916
|
+
requiresOpenPr: z2.boolean().optional(),
|
|
4917
|
+
ownWorkflow: z2.boolean().optional()
|
|
4479
4918
|
}).superRefine((entry2, ctx) => {
|
|
4480
4919
|
if (!entry2.env)
|
|
4481
4920
|
return;
|
|
@@ -4485,22 +4924,22 @@ var CheckEntrySchema = z.object({
|
|
|
4485
4924
|
const members = value.anyOf;
|
|
4486
4925
|
if (new Set(members).size !== members.length) {
|
|
4487
4926
|
ctx.addIssue({
|
|
4488
|
-
code:
|
|
4927
|
+
code: z2.ZodIssueCode.custom,
|
|
4489
4928
|
message: `env.${key}.anyOf has duplicate members`,
|
|
4490
4929
|
path: ["env", key, "anyOf"]
|
|
4491
4930
|
});
|
|
4492
4931
|
}
|
|
4493
4932
|
if (!members.includes(key)) {
|
|
4494
4933
|
ctx.addIssue({
|
|
4495
|
-
code:
|
|
4934
|
+
code: z2.ZodIssueCode.custom,
|
|
4496
4935
|
message: `env.${key}.anyOf must include "${key}" itself as a member`,
|
|
4497
4936
|
path: ["env", key, "anyOf"]
|
|
4498
4937
|
});
|
|
4499
4938
|
}
|
|
4500
4939
|
}
|
|
4501
4940
|
});
|
|
4502
|
-
var RoleEntrySchema =
|
|
4503
|
-
contract:
|
|
4941
|
+
var RoleEntrySchema = z2.object({
|
|
4942
|
+
contract: z2.string().refine((p) => p.includes("/"), {
|
|
4504
4943
|
message: 'must be a path (contain at least one "/"), not a bare filename'
|
|
4505
4944
|
})
|
|
4506
4945
|
});
|
|
@@ -4519,19 +4958,21 @@ var BRIEF_BUILTINS = [
|
|
|
4519
4958
|
"closesN",
|
|
4520
4959
|
"premiseCoverage",
|
|
4521
4960
|
"issueRationale",
|
|
4961
|
+
"objectives",
|
|
4962
|
+
"briefSections",
|
|
4522
4963
|
"milestoneShape"
|
|
4523
4964
|
];
|
|
4524
|
-
var BriefSectionSchema =
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4965
|
+
var BriefSectionSchema = z2.union([
|
|
4966
|
+
z2.object({ builtin: z2.enum(BRIEF_BUILTINS) }),
|
|
4967
|
+
z2.object({ heading: z2.string().min(1), name: z2.string().optional() }),
|
|
4968
|
+
z2.object({ field: z2.string().min(1), name: z2.string().optional() }),
|
|
4969
|
+
z2.object({ phrase: z2.string().min(1), name: z2.string().optional() })
|
|
4529
4970
|
]);
|
|
4530
|
-
var BriefSchemaSchema =
|
|
4531
|
-
pr:
|
|
4532
|
-
issue:
|
|
4533
|
-
milestone:
|
|
4534
|
-
ack:
|
|
4971
|
+
var BriefSchemaSchema = z2.object({
|
|
4972
|
+
pr: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
|
|
4973
|
+
issue: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
|
|
4974
|
+
milestone: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
|
|
4975
|
+
ack: z2.array(z2.enum(BRIEF_BUILTINS)).optional()
|
|
4535
4976
|
});
|
|
4536
4977
|
function isSafeRepoRelPath(p) {
|
|
4537
4978
|
if (p.length === 0)
|
|
@@ -4540,7 +4981,7 @@ function isSafeRepoRelPath(p) {
|
|
|
4540
4981
|
return false;
|
|
4541
4982
|
return p.split(/[\\/]/).every((seg) => seg !== ".." && seg !== "");
|
|
4542
4983
|
}
|
|
4543
|
-
var SafeRepoRelPath =
|
|
4984
|
+
var SafeRepoRelPath = z2.string().refine(isSafeRepoRelPath, {
|
|
4544
4985
|
message: "must be a repo-root-relative path with no `..` segment or absolute root"
|
|
4545
4986
|
});
|
|
4546
4987
|
var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
|
|
@@ -4550,22 +4991,22 @@ function isCanonicalHookBlockPath(p) {
|
|
|
4550
4991
|
var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
|
|
4551
4992
|
message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
|
|
4552
4993
|
});
|
|
4553
|
-
var ManagedBlockRecordSchema =
|
|
4994
|
+
var ManagedBlockRecordSchema = z2.object({
|
|
4554
4995
|
path: ManagedHookBlockPath,
|
|
4555
|
-
marker:
|
|
4556
|
-
comment:
|
|
4996
|
+
marker: z2.string(),
|
|
4997
|
+
comment: z2.enum(["hash", "html"])
|
|
4557
4998
|
});
|
|
4558
|
-
var ManagedManifestSchema =
|
|
4559
|
-
version:
|
|
4560
|
-
files:
|
|
4561
|
-
blocks:
|
|
4562
|
-
labels:
|
|
4563
|
-
agents:
|
|
4999
|
+
var ManagedManifestSchema = z2.object({
|
|
5000
|
+
version: z2.number().int().positive(),
|
|
5001
|
+
files: z2.array(SafeRepoRelPath),
|
|
5002
|
+
blocks: z2.array(ManagedBlockRecordSchema),
|
|
5003
|
+
labels: z2.array(z2.string()),
|
|
5004
|
+
agents: z2.array(z2.enum(AGENT_VENDORS)).optional()
|
|
4564
5005
|
});
|
|
4565
|
-
var ProjectEntrySchema =
|
|
4566
|
-
name:
|
|
4567
|
-
description:
|
|
4568
|
-
path:
|
|
5006
|
+
var ProjectEntrySchema = z2.object({
|
|
5007
|
+
name: z2.string().min(1),
|
|
5008
|
+
description: z2.string().min(1).optional(),
|
|
5009
|
+
path: z2.string().min(1).optional()
|
|
4569
5010
|
});
|
|
4570
5011
|
function parseTokensCollectDeclaration(value) {
|
|
4571
5012
|
const m = value.trim().match(/^(\S+)\s+(\S+)$/);
|
|
@@ -4576,31 +5017,35 @@ function parseTokensCollectDeclaration(value) {
|
|
|
4576
5017
|
return null;
|
|
4577
5018
|
return { interpreter, script };
|
|
4578
5019
|
}
|
|
4579
|
-
var VinayaConfigSchema =
|
|
4580
|
-
rings:
|
|
4581
|
-
ring1_forgeWriteInterception:
|
|
4582
|
-
ring2_asyncAudits:
|
|
5020
|
+
var VinayaConfigSchema = z2.object({
|
|
5021
|
+
rings: z2.object({
|
|
5022
|
+
ring1_forgeWriteInterception: z2.boolean(),
|
|
5023
|
+
ring2_asyncAudits: z2.boolean()
|
|
4583
5024
|
}).optional(),
|
|
4584
|
-
checks:
|
|
4585
|
-
roles:
|
|
5025
|
+
checks: z2.record(z2.string(), CheckEntrySchema).optional(),
|
|
5026
|
+
roles: z2.record(z2.string(), RoleEntrySchema).optional(),
|
|
4586
5027
|
briefSchema: BriefSchemaSchema.optional(),
|
|
4587
5028
|
managed: ManagedManifestSchema.optional(),
|
|
4588
|
-
principals:
|
|
4589
|
-
releaseActor:
|
|
4590
|
-
ci:
|
|
4591
|
-
tokens:
|
|
4592
|
-
collect:
|
|
5029
|
+
principals: z2.array(z2.string()).min(1).optional(),
|
|
5030
|
+
releaseActor: z2.string().min(1).optional(),
|
|
5031
|
+
ci: z2.object({ setup: z2.string().min(1) }).optional(),
|
|
5032
|
+
tokens: z2.object({
|
|
5033
|
+
collect: z2.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
|
|
4593
5034
|
message: 'tokens.collect must be exactly "<interpreter> <repo-relative-script-path>" — two whitespace-separated tokens, no flags, no shell syntax, no quoting'
|
|
4594
5035
|
})
|
|
4595
5036
|
}).optional(),
|
|
4596
|
-
blastRadius:
|
|
4597
|
-
proseGates:
|
|
4598
|
-
doctrineRoot:
|
|
4599
|
-
readerFacingPrefix:
|
|
4600
|
-
readerFacingSuffix:
|
|
4601
|
-
legacySlugDir:
|
|
5037
|
+
blastRadius: z2.object({ extraDomains: z2.array(z2.string()).optional() }).optional(),
|
|
5038
|
+
proseGates: z2.object({
|
|
5039
|
+
doctrineRoot: z2.string().min(1).optional(),
|
|
5040
|
+
readerFacingPrefix: z2.string().min(1).optional(),
|
|
5041
|
+
readerFacingSuffix: z2.string().min(1).optional(),
|
|
5042
|
+
legacySlugDir: z2.string().min(1).optional()
|
|
4602
5043
|
}).optional(),
|
|
4603
|
-
projects:
|
|
5044
|
+
projects: z2.array(ProjectEntrySchema).optional(),
|
|
5045
|
+
dispatch: z2.object({
|
|
5046
|
+
timeoutMs: z2.number().int().positive().optional(),
|
|
5047
|
+
agent: z2.enum(["claude", "codex", "gemini"]).optional()
|
|
5048
|
+
}).optional()
|
|
4604
5049
|
});
|
|
4605
5050
|
var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
|
|
4606
5051
|
var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");
|