@attalabs/vinaya 0.26.0 → 0.27.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 +4 -2
- package/aeg-root/enforcement.md +2 -2
- package/aeg-root/milestone-model.md +2 -0
- package/aeg-root/process.md +1 -1
- package/aeg-root/roles/planner.md +2 -0
- package/aeg-root/roles/principal.md +4 -2
- package/aeg-root/roles/reviewer.md +6 -2
- package/aeg-root/roles/security.md +6 -2
- package/dist/checks/bin/check-body-bare-digits.js +128 -22
- package/dist/checks/bin/check-branch-topology.js +148 -24
- package/dist/checks/bin/check-brief-shape.js +430 -32
- package/dist/checks/bin/check-changeset-coverage.js +148 -24
- package/dist/checks/bin/check-closes-n.js +148 -24
- package/dist/checks/bin/check-coherence.js +148 -24
- package/dist/checks/bin/check-dead-branch-push.js +110 -22
- package/dist/checks/bin/check-dispatch-readiness.js +148 -24
- package/dist/checks/bin/check-doc-coverage-push.js +148 -24
- package/dist/checks/bin/check-doc-coverage.js +148 -24
- package/dist/checks/bin/check-doctrine-no-procedures.js +110 -22
- package/dist/checks/bin/check-doctrine-portability.js +148 -24
- package/dist/checks/bin/check-evidence-fresh.js +650 -63
- package/dist/checks/bin/check-exec-bits.js +148 -24
- package/dist/checks/bin/check-first-push-dispatch.js +148 -24
- package/dist/checks/bin/check-issue-assignment.js +148 -24
- package/dist/checks/bin/check-main-branch-refusal.js +110 -22
- package/dist/checks/bin/check-no-disk-state.js +110 -22
- package/dist/checks/bin/check-pr-premise-reassert.js +110 -22
- package/dist/checks/bin/check-pr-report-density.js +110 -22
- package/dist/checks/bin/check-quoted-command.js +148 -24
- package/dist/checks/bin/check-reader-resolvable-prose.js +148 -24
- package/dist/checks/bin/check-registry-gates.js +110 -22
- package/dist/checks/bin/check-retired-vocabulary.js +148 -24
- package/dist/checks/bin/check-review-gate.js +169 -77
- package/dist/checks/bin/check-single-plan-pr.js +110 -22
- package/dist/checks/bin/check-surface-scope.js +148 -24
- package/dist/checks/bin/check-test-plan.js +110 -22
- package/dist/checks/bin/check-token-collection-wired.js +110 -22
- package/dist/checks/bin/check-token-report.js +110 -22
- package/dist/checks/bin/check-workspace-escape.js +148 -24
- package/dist/index.js +1584 -572
- package/package.json +1 -1
|
@@ -3,8 +3,8 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
4
|
|
|
5
5
|
// src/checks/bin/check-brief-shape.ts
|
|
6
|
-
import { execFileSync as
|
|
7
|
-
import { readdirSync, readFileSync as
|
|
6
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
7
|
+
import { readdirSync, readFileSync as readFileSync3 } from "node:fs";
|
|
8
8
|
// ../../packages/aeg-core/src/gate-audience.ts
|
|
9
9
|
var GATE_AUDIENCE = {
|
|
10
10
|
"check-branch-topology": { shippedAs: "branch-topology", ring: 0 },
|
|
@@ -304,7 +304,7 @@ function issueListByLabelArgs(owner, repo, label) {
|
|
|
304
304
|
"--state",
|
|
305
305
|
"all",
|
|
306
306
|
"--json",
|
|
307
|
-
"number,title,body,state,labels,milestone",
|
|
307
|
+
"number,title,body,state,labels,milestone,stateReason",
|
|
308
308
|
"--limit",
|
|
309
309
|
"200"
|
|
310
310
|
];
|
|
@@ -650,14 +650,19 @@ function resolveTaskIssueRef(title, labels) {
|
|
|
650
650
|
var INTENTS_HEADING = /^#{1,6}\s*Tranche intents\s*$/im;
|
|
651
651
|
var NEXT_HEADING = /^#{1,6}\s+\S/m;
|
|
652
652
|
var INTENT_BULLET = /^-\s+([a-z0-9][a-z0-9-]*)\s*:\s*(.+)$/i;
|
|
653
|
-
function
|
|
654
|
-
const text = stripCode(description, { inlineSpans: "keep" });
|
|
653
|
+
function intentsSection(text) {
|
|
655
654
|
const start = text.match(INTENTS_HEADING);
|
|
656
655
|
if (!start || start.index === undefined)
|
|
657
|
-
return
|
|
656
|
+
return null;
|
|
658
657
|
const rest = text.slice(start.index + start[0].length);
|
|
659
658
|
const next = rest.match(NEXT_HEADING);
|
|
660
|
-
|
|
659
|
+
return rest.slice(0, next && next.index !== undefined ? next.index : rest.length);
|
|
660
|
+
}
|
|
661
|
+
function intentGoalForSlug(description, slug) {
|
|
662
|
+
const text = stripCode(description, { inlineSpans: "keep" });
|
|
663
|
+
const section = intentsSection(text);
|
|
664
|
+
if (section === null)
|
|
665
|
+
return "";
|
|
661
666
|
for (const line of section.split(`
|
|
662
667
|
`)) {
|
|
663
668
|
const trimmed = line.trim();
|
|
@@ -1949,6 +1954,7 @@ function parseInlineFieldList(section) {
|
|
|
1949
1954
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
1950
1955
|
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
1951
1956
|
var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1957
|
+
var RULING_ORDINAL_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Ruling ordinal:\s*(\d+)(?!\d)/im;
|
|
1952
1958
|
function firstFiveLines(comment) {
|
|
1953
1959
|
return comment.split(`
|
|
1954
1960
|
`).slice(0, 5).join(`
|
|
@@ -1962,6 +1968,15 @@ function extractObjectivesVersion(comment) {
|
|
|
1962
1968
|
const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
|
|
1963
1969
|
return m ? m[1].toLowerCase() : null;
|
|
1964
1970
|
}
|
|
1971
|
+
function firstSevenLines(comment) {
|
|
1972
|
+
return comment.split(`
|
|
1973
|
+
`).slice(0, 7).join(`
|
|
1974
|
+
`);
|
|
1975
|
+
}
|
|
1976
|
+
function extractRulingOrdinal(comment) {
|
|
1977
|
+
const m = firstSevenLines(comment).match(RULING_ORDINAL_PATTERN);
|
|
1978
|
+
return m ? Number.parseInt(m[1], 10) : null;
|
|
1979
|
+
}
|
|
1965
1980
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
1966
1981
|
const candidates = comments.filter((c) => valuePattern.test(c));
|
|
1967
1982
|
if (candidates.length === 0) {
|
|
@@ -1969,6 +1984,7 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1969
1984
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
1970
1985
|
headSha: null,
|
|
1971
1986
|
objectivesVersion: null,
|
|
1987
|
+
rulingOrdinal: null,
|
|
1972
1988
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1973
1989
|
};
|
|
1974
1990
|
}
|
|
@@ -1979,6 +1995,7 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1979
1995
|
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
|
|
1980
1996
|
headSha: null,
|
|
1981
1997
|
objectivesVersion: null,
|
|
1998
|
+
rulingOrdinal: null,
|
|
1982
1999
|
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
|
|
1983
2000
|
};
|
|
1984
2001
|
}
|
|
@@ -1986,6 +2003,7 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1986
2003
|
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
1987
2004
|
headSha: extractHeadSha(latest),
|
|
1988
2005
|
objectivesVersion: extractObjectivesVersion(latest),
|
|
2006
|
+
rulingOrdinal: extractRulingOrdinal(latest),
|
|
1989
2007
|
danglingNote: null
|
|
1990
2008
|
};
|
|
1991
2009
|
}
|
|
@@ -2347,6 +2365,9 @@ import { createHash as createHash2 } from "node:crypto";
|
|
|
2347
2365
|
var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
|
|
2348
2366
|
var NEXT_HEADING_RE = /^##[ \t]/m;
|
|
2349
2367
|
var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
|
|
2368
|
+
function maskedForHeadingSearch(body) {
|
|
2369
|
+
return maskDetailsBlocks(maskCode(body));
|
|
2370
|
+
}
|
|
2350
2371
|
function hasBacktickedPath(text) {
|
|
2351
2372
|
let i = 0;
|
|
2352
2373
|
while (i < text.length) {
|
|
@@ -2369,16 +2390,22 @@ function stripObjectiveBackticks(text) {
|
|
|
2369
2390
|
function wordCount(text) {
|
|
2370
2391
|
return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
|
|
2371
2392
|
}
|
|
2372
|
-
function
|
|
2373
|
-
const
|
|
2393
|
+
function objectivesSectionBounds(body) {
|
|
2394
|
+
const masked = maskedForHeadingSearch(body);
|
|
2395
|
+
const heading = HEADING_RE.exec(masked);
|
|
2374
2396
|
if (!heading)
|
|
2375
2397
|
return null;
|
|
2376
|
-
const
|
|
2398
|
+
const start = heading.index + heading[0].length;
|
|
2399
|
+
const afterHeading = masked.slice(start);
|
|
2377
2400
|
const next = NEXT_HEADING_RE.exec(afterHeading);
|
|
2378
|
-
return next ?
|
|
2401
|
+
return { start, end: next ? start + next.index : body.length };
|
|
2402
|
+
}
|
|
2403
|
+
function objectivesSectionText(body) {
|
|
2404
|
+
const bounds = objectivesSectionBounds(body);
|
|
2405
|
+
return bounds === null ? null : body.slice(bounds.start, bounds.end);
|
|
2379
2406
|
}
|
|
2380
2407
|
function hasObjectivesHeading(body) {
|
|
2381
|
-
return HEADING_RE.test(body);
|
|
2408
|
+
return HEADING_RE.test(maskedForHeadingSearch(body));
|
|
2382
2409
|
}
|
|
2383
2410
|
function objectivesOf(body) {
|
|
2384
2411
|
const section = objectivesSectionText(body);
|
|
@@ -2438,6 +2465,15 @@ function isIssueNotFoundError(err) {
|
|
|
2438
2465
|
`);
|
|
2439
2466
|
return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
|
|
2440
2467
|
}
|
|
2468
|
+
function resolveObjectivesSource(prBody, issue, cutoverIssue) {
|
|
2469
|
+
if (issue !== null && issue < cutoverIssue)
|
|
2470
|
+
return { kind: "none" };
|
|
2471
|
+
if (issue !== null)
|
|
2472
|
+
return { kind: "issue", issue };
|
|
2473
|
+
if (hasObjectivesHeading(prBody))
|
|
2474
|
+
return { kind: "body" };
|
|
2475
|
+
return { kind: "none" };
|
|
2476
|
+
}
|
|
2441
2477
|
|
|
2442
2478
|
// ../../packages/aeg-core/src/premise-check.ts
|
|
2443
2479
|
import { createHash as createHash3 } from "node:crypto";
|
|
@@ -2939,17 +2975,40 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2939
2975
|
];
|
|
2940
2976
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2941
2977
|
}
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
`
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2978
|
+
function contentAfterNLines(body, n) {
|
|
2979
|
+
let idx = -1;
|
|
2980
|
+
for (let i = 0;i < n; i++) {
|
|
2981
|
+
idx = body.indexOf(`
|
|
2982
|
+
`, idx + 1);
|
|
2983
|
+
if (idx === -1)
|
|
2984
|
+
return "";
|
|
2985
|
+
}
|
|
2986
|
+
return body.slice(idx + 1);
|
|
2987
|
+
}
|
|
2988
|
+
var BRIEF_MARKER_LINE_RE = /^<!-- aeg:brief:v(\d+) -->$/;
|
|
2989
|
+
function parseBriefMarkerVersion(firstLine) {
|
|
2990
|
+
const m = BRIEF_MARKER_LINE_RE.exec(firstLine.trim());
|
|
2991
|
+
if (!m)
|
|
2992
|
+
return null;
|
|
2993
|
+
const version = Number.parseInt(m[1], 10);
|
|
2994
|
+
return Number.isInteger(version) && version >= 1 ? version : null;
|
|
2995
|
+
}
|
|
2996
|
+
function frozenBriefContent(body, version) {
|
|
2997
|
+
return contentAfterNLines(body, version === 1 ? 2 : 3);
|
|
2998
|
+
}
|
|
2999
|
+
function resolveNewestFrozenBrief(comments, allowlist) {
|
|
3000
|
+
let best = null;
|
|
3001
|
+
for (const c of comments) {
|
|
3002
|
+
if (!isPrincipal(c.author, allowlist))
|
|
3003
|
+
continue;
|
|
3004
|
+
const version = parseBriefMarkerVersion(c.body.split(`
|
|
3005
|
+
`)[0] ?? "");
|
|
3006
|
+
if (version === null)
|
|
3007
|
+
continue;
|
|
3008
|
+
if (best === null || version > best.version)
|
|
3009
|
+
best = { ...c, version };
|
|
3010
|
+
}
|
|
3011
|
+
return best === null ? null : { ...best, content: frozenBriefContent(best.body, best.version) };
|
|
2953
3012
|
}
|
|
2954
3013
|
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
2955
3014
|
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
@@ -3676,6 +3735,11 @@ function isBoundToObjectives(extraction, currentVersion) {
|
|
|
3676
3735
|
return true;
|
|
3677
3736
|
return extraction.objectivesVersion === currentVersion;
|
|
3678
3737
|
}
|
|
3738
|
+
function isBoundToRulings(extraction, currentOrdinal) {
|
|
3739
|
+
if (extraction.rulingOrdinal === null)
|
|
3740
|
+
return currentOrdinal === 0;
|
|
3741
|
+
return extraction.rulingOrdinal === currentOrdinal;
|
|
3742
|
+
}
|
|
3679
3743
|
function checkReviewGate(input) {
|
|
3680
3744
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
3681
3745
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -3704,7 +3768,9 @@ function checkReviewGate(input) {
|
|
|
3704
3768
|
const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
|
|
3705
3769
|
const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
|
|
3706
3770
|
const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
|
|
3707
|
-
|
|
3771
|
+
const codeReviewRulingsBound = isBoundToRulings(codeReview, input.rulingOrdinal);
|
|
3772
|
+
const securityRulingsBound = isBoundToRulings(security, input.rulingOrdinal);
|
|
3773
|
+
if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && codeReviewRulingsBound && securityClean && securityBound && securityObjectivesBound && securityRulingsBound && mechanicalChecksClean) {
|
|
3708
3774
|
return {
|
|
3709
3775
|
verdict: "pass",
|
|
3710
3776
|
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.`,
|
|
@@ -3718,6 +3784,8 @@ function checkReviewGate(input) {
|
|
|
3718
3784
|
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3719
3785
|
} else if (!codeReviewObjectivesBound) {
|
|
3720
3786
|
problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3787
|
+
} else if (!codeReviewRulingsBound) {
|
|
3788
|
+
problems.push(`the newest code-review verdict was cast against ruling ordinal ${codeReview.rulingOrdinal ?? "none"}, a newer ruling (ruling ${input.rulingOrdinal}) is now posted on this PR`);
|
|
3721
3789
|
}
|
|
3722
3790
|
if (!securityClean) {
|
|
3723
3791
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
@@ -3725,6 +3793,8 @@ function checkReviewGate(input) {
|
|
|
3725
3793
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3726
3794
|
} else if (!securityObjectivesBound) {
|
|
3727
3795
|
problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3796
|
+
} else if (!securityRulingsBound) {
|
|
3797
|
+
problems.push(`the newest security-review verdict was cast against ruling ordinal ${security.rulingOrdinal ?? "none"}, a newer ruling (ruling ${input.rulingOrdinal}) is now posted on this PR`);
|
|
3728
3798
|
}
|
|
3729
3799
|
if (!mechanicalChecksClean) {
|
|
3730
3800
|
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(", ")}`);
|
|
@@ -3741,6 +3811,24 @@ var DEFAULT_RELEASE_ACTOR = "github-actions[bot]";
|
|
|
3741
3811
|
function isChangesetsReleasePr(branch, author, expectedAuthor) {
|
|
3742
3812
|
return branch === CHANGESET_RELEASE_BRANCH && author === expectedAuthor;
|
|
3743
3813
|
}
|
|
3814
|
+
// ../../packages/aeg-core/src/ruling-ordinal.ts
|
|
3815
|
+
var RULING_MARKER_ORDINAL = /^<!-- aeg:principal:ruling:\d+-(\d+) -->$/;
|
|
3816
|
+
function newestPrincipalRulingOrdinal(comments, allowlist) {
|
|
3817
|
+
let best = 0;
|
|
3818
|
+
for (const c of comments) {
|
|
3819
|
+
if (!isPrincipal(c.author, allowlist))
|
|
3820
|
+
continue;
|
|
3821
|
+
const firstLine = (c.body.split(`
|
|
3822
|
+
`)[0] ?? "").trim();
|
|
3823
|
+
const m = RULING_MARKER_ORDINAL.exec(firstLine);
|
|
3824
|
+
if (!m)
|
|
3825
|
+
continue;
|
|
3826
|
+
const k = Number.parseInt(m[1], 10);
|
|
3827
|
+
if (k > best)
|
|
3828
|
+
best = k;
|
|
3829
|
+
}
|
|
3830
|
+
return best;
|
|
3831
|
+
}
|
|
3744
3832
|
// ../../packages/aeg-core/src/markdown-table.ts
|
|
3745
3833
|
function splitRow(line) {
|
|
3746
3834
|
const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
|
|
@@ -4982,10 +5070,319 @@ function emitCheckError(error) {
|
|
|
4982
5070
|
`);
|
|
4983
5071
|
}
|
|
4984
5072
|
|
|
5073
|
+
// src/lib/config.ts
|
|
5074
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
5075
|
+
import { existsSync, mkdirSync, readFileSync as readFileSync2, realpathSync, writeFileSync } from "node:fs";
|
|
5076
|
+
import { homedir } from "node:os";
|
|
5077
|
+
import { dirname, join, resolve } from "node:path";
|
|
5078
|
+
import { z as z2 } from "zod";
|
|
5079
|
+
|
|
5080
|
+
// src/lib/agent-vendors.ts
|
|
5081
|
+
var AGENT_VENDORS = ["skills", "claude", "gemini"];
|
|
5082
|
+
|
|
5083
|
+
// src/lib/config.ts
|
|
5084
|
+
var EnvEntrySchema = z2.union([
|
|
5085
|
+
z2.literal(true),
|
|
5086
|
+
z2.object({ optional: z2.literal(true) }),
|
|
5087
|
+
z2.object({ anyOf: z2.array(z2.string()).min(2) }),
|
|
5088
|
+
z2.string()
|
|
5089
|
+
]);
|
|
5090
|
+
var CheckEntrySchema = z2.object({
|
|
5091
|
+
run: z2.string(),
|
|
5092
|
+
scope: z2.enum(["diff", "full"]),
|
|
5093
|
+
include: z2.array(z2.string()).optional(),
|
|
5094
|
+
args: z2.array(z2.string()).optional(),
|
|
5095
|
+
timeoutMs: z2.number().optional(),
|
|
5096
|
+
env: z2.record(z2.string(), EnvEntrySchema).optional(),
|
|
5097
|
+
requiresOpenPr: z2.boolean().optional(),
|
|
5098
|
+
ownWorkflow: z2.boolean().optional()
|
|
5099
|
+
}).superRefine((entry2, ctx) => {
|
|
5100
|
+
if (!entry2.env)
|
|
5101
|
+
return;
|
|
5102
|
+
for (const [key, value] of Object.entries(entry2.env)) {
|
|
5103
|
+
if (typeof value !== "object" || value === null || !("anyOf" in value))
|
|
5104
|
+
continue;
|
|
5105
|
+
const members = value.anyOf;
|
|
5106
|
+
if (new Set(members).size !== members.length) {
|
|
5107
|
+
ctx.addIssue({
|
|
5108
|
+
code: z2.ZodIssueCode.custom,
|
|
5109
|
+
message: `env.${key}.anyOf has duplicate members`,
|
|
5110
|
+
path: ["env", key, "anyOf"]
|
|
5111
|
+
});
|
|
5112
|
+
}
|
|
5113
|
+
if (!members.includes(key)) {
|
|
5114
|
+
ctx.addIssue({
|
|
5115
|
+
code: z2.ZodIssueCode.custom,
|
|
5116
|
+
message: `env.${key}.anyOf must include "${key}" itself as a member`,
|
|
5117
|
+
path: ["env", key, "anyOf"]
|
|
5118
|
+
});
|
|
5119
|
+
}
|
|
5120
|
+
}
|
|
5121
|
+
});
|
|
5122
|
+
var RoleEntrySchema = z2.object({
|
|
5123
|
+
contract: z2.string().refine((p) => p.includes("/"), {
|
|
5124
|
+
message: 'must be a path (contain at least one "/"), not a bare filename'
|
|
5125
|
+
})
|
|
5126
|
+
});
|
|
5127
|
+
var BRIEF_BUILTINS = [
|
|
5128
|
+
"tier",
|
|
5129
|
+
"testPlan",
|
|
5130
|
+
"testPlanExclusivity",
|
|
5131
|
+
"principalPlaceholder",
|
|
5132
|
+
"surfaceMap",
|
|
5133
|
+
"docUpdateList",
|
|
5134
|
+
"worktreeStep0",
|
|
5135
|
+
"stopConditions",
|
|
5136
|
+
"autonomyClause",
|
|
5137
|
+
"project",
|
|
5138
|
+
"for",
|
|
5139
|
+
"closesN",
|
|
5140
|
+
"premiseCoverage",
|
|
5141
|
+
"issueRationale",
|
|
5142
|
+
"objectives",
|
|
5143
|
+
"briefSections",
|
|
5144
|
+
"milestoneShape"
|
|
5145
|
+
];
|
|
5146
|
+
var BriefSectionSchema = z2.union([
|
|
5147
|
+
z2.object({ builtin: z2.enum(BRIEF_BUILTINS) }),
|
|
5148
|
+
z2.object({ heading: z2.string().min(1), name: z2.string().optional() }),
|
|
5149
|
+
z2.object({ field: z2.string().min(1), name: z2.string().optional() }),
|
|
5150
|
+
z2.object({ phrase: z2.string().min(1), name: z2.string().optional() })
|
|
5151
|
+
]);
|
|
5152
|
+
var BriefSchemaSchema = z2.object({
|
|
5153
|
+
pr: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
|
|
5154
|
+
issue: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
|
|
5155
|
+
milestone: z2.object({ sections: z2.array(BriefSectionSchema) }).optional(),
|
|
5156
|
+
ack: z2.array(z2.enum(BRIEF_BUILTINS)).optional()
|
|
5157
|
+
});
|
|
5158
|
+
function isSafeRepoRelPath(p) {
|
|
5159
|
+
if (p.length === 0)
|
|
5160
|
+
return false;
|
|
5161
|
+
if (p.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(p))
|
|
5162
|
+
return false;
|
|
5163
|
+
return p.split(/[\\/]/).every((seg) => seg !== ".." && seg !== "");
|
|
5164
|
+
}
|
|
5165
|
+
var SafeRepoRelPath = z2.string().refine(isSafeRepoRelPath, {
|
|
5166
|
+
message: "must be a repo-root-relative path with no `..` segment or absolute root"
|
|
5167
|
+
});
|
|
5168
|
+
var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
|
|
5169
|
+
function isCanonicalHookBlockPath(p) {
|
|
5170
|
+
return CANONICAL_HOOK_BLOCK_PREFIXES.some((prefix) => p.startsWith(prefix) && p.length > prefix.length);
|
|
5171
|
+
}
|
|
5172
|
+
var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
|
|
5173
|
+
message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
|
|
5174
|
+
});
|
|
5175
|
+
var ManagedBlockRecordSchema = z2.object({
|
|
5176
|
+
path: ManagedHookBlockPath,
|
|
5177
|
+
marker: z2.string(),
|
|
5178
|
+
comment: z2.enum(["hash", "html"])
|
|
5179
|
+
});
|
|
5180
|
+
var ManagedManifestSchema = z2.object({
|
|
5181
|
+
version: z2.number().int().positive(),
|
|
5182
|
+
files: z2.array(SafeRepoRelPath),
|
|
5183
|
+
blocks: z2.array(ManagedBlockRecordSchema),
|
|
5184
|
+
labels: z2.array(z2.string()),
|
|
5185
|
+
agents: z2.array(z2.enum(AGENT_VENDORS)).optional()
|
|
5186
|
+
});
|
|
5187
|
+
var ProjectEntrySchema = z2.object({
|
|
5188
|
+
name: z2.string().min(1),
|
|
5189
|
+
description: z2.string().min(1).optional(),
|
|
5190
|
+
path: z2.string().min(1).optional()
|
|
5191
|
+
});
|
|
5192
|
+
function parseTokensCollectDeclaration(value) {
|
|
5193
|
+
const m = value.trim().match(/^(\S+)\s+(\S+)$/);
|
|
5194
|
+
if (!m)
|
|
5195
|
+
return null;
|
|
5196
|
+
const [, interpreter, script] = m;
|
|
5197
|
+
if (!isSafeRepoRelPath(script))
|
|
5198
|
+
return null;
|
|
5199
|
+
return { interpreter, script };
|
|
5200
|
+
}
|
|
5201
|
+
var VinayaConfigSchema = z2.object({
|
|
5202
|
+
rings: z2.object({
|
|
5203
|
+
ring1_forgeWriteInterception: z2.boolean(),
|
|
5204
|
+
ring2_asyncAudits: z2.boolean()
|
|
5205
|
+
}).optional(),
|
|
5206
|
+
checks: z2.record(z2.string(), CheckEntrySchema).optional(),
|
|
5207
|
+
roles: z2.record(z2.string(), RoleEntrySchema).optional(),
|
|
5208
|
+
briefSchema: BriefSchemaSchema.optional(),
|
|
5209
|
+
managed: ManagedManifestSchema.optional(),
|
|
5210
|
+
principals: z2.array(z2.string()).min(1).optional(),
|
|
5211
|
+
releaseActor: z2.string().min(1).optional(),
|
|
5212
|
+
ci: z2.object({ setup: z2.string().min(1) }).optional(),
|
|
5213
|
+
tokens: z2.object({
|
|
5214
|
+
collect: z2.string().min(1).refine((v) => parseTokensCollectDeclaration(v) !== null, {
|
|
5215
|
+
message: 'tokens.collect must be exactly "<interpreter> <repo-relative-script-path>" — two whitespace-separated tokens, no flags, no shell syntax, no quoting'
|
|
5216
|
+
})
|
|
5217
|
+
}).optional(),
|
|
5218
|
+
blastRadius: z2.object({ extraDomains: z2.array(z2.string()).optional() }).optional(),
|
|
5219
|
+
proseGates: z2.object({
|
|
5220
|
+
doctrineRoot: z2.string().min(1).optional(),
|
|
5221
|
+
readerFacingPrefix: z2.string().min(1).optional(),
|
|
5222
|
+
readerFacingSuffix: z2.string().min(1).optional(),
|
|
5223
|
+
legacySlugDir: z2.string().min(1).optional()
|
|
5224
|
+
}).optional(),
|
|
5225
|
+
projects: z2.array(ProjectEntrySchema).optional(),
|
|
5226
|
+
dispatch: z2.object({
|
|
5227
|
+
timeoutMs: z2.number().int().positive().optional(),
|
|
5228
|
+
agent: z2.enum(["claude", "codex", "gemini"]).optional()
|
|
5229
|
+
}).optional()
|
|
5230
|
+
});
|
|
5231
|
+
var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
|
|
5232
|
+
var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");
|
|
5233
|
+
var LOCAL_CONFIG_FILENAME = "vinaya.config.json";
|
|
5234
|
+
function findLocalConfig() {
|
|
5235
|
+
let dir = process.cwd();
|
|
5236
|
+
while (true) {
|
|
5237
|
+
const candidate = join(dir, LOCAL_CONFIG_FILENAME);
|
|
5238
|
+
if (existsSync(candidate))
|
|
5239
|
+
return candidate;
|
|
5240
|
+
if (existsSync(join(dir, ".git")))
|
|
5241
|
+
return null;
|
|
5242
|
+
const parent = dirname(dir);
|
|
5243
|
+
if (parent === dir)
|
|
5244
|
+
return null;
|
|
5245
|
+
dir = parent;
|
|
5246
|
+
}
|
|
5247
|
+
}
|
|
5248
|
+
function configPath() {
|
|
5249
|
+
const local = findLocalConfig();
|
|
5250
|
+
if (local)
|
|
5251
|
+
return local;
|
|
5252
|
+
if (existsSync(GLOBAL_CONFIG_PATH))
|
|
5253
|
+
return GLOBAL_CONFIG_PATH;
|
|
5254
|
+
return null;
|
|
5255
|
+
}
|
|
5256
|
+
function globalChecksIgnoredWarning(path) {
|
|
5257
|
+
return `${path}: "checks" registration in the global config is ignored — checks may only be registered from a repo-local vinaya.config.json.`;
|
|
5258
|
+
}
|
|
5259
|
+
function globalPrincipalsIgnoredWarning(path) {
|
|
5260
|
+
return `${path}: "principals" in the global config is ignored — principals may only be declared from a repo-local vinaya.config.json.`;
|
|
5261
|
+
}
|
|
5262
|
+
function globalRolesIgnoredWarning(path) {
|
|
5263
|
+
return `${path}: "roles" registration in the global config is ignored — roles may only be registered from a repo-local vinaya.config.json.`;
|
|
5264
|
+
}
|
|
5265
|
+
function globalReleaseActorIgnoredWarning(path) {
|
|
5266
|
+
return `${path}: "releaseActor" in the global config is ignored — releaseActor may only be declared from a repo-local vinaya.config.json.`;
|
|
5267
|
+
}
|
|
5268
|
+
function globalTokensCollectIgnoredWarning(path) {
|
|
5269
|
+
return `${path}: "tokens" in the global config is ignored — tokens.collect may only be declared from a repo-local vinaya.config.json.`;
|
|
5270
|
+
}
|
|
5271
|
+
function stripGlobalOnlyKeys(config, path) {
|
|
5272
|
+
if (path !== GLOBAL_CONFIG_PATH)
|
|
5273
|
+
return config;
|
|
5274
|
+
let result = config;
|
|
5275
|
+
if (result.checks && Object.keys(result.checks).length > 0) {
|
|
5276
|
+
console.error(`⚠ ${globalChecksIgnoredWarning(path)}`);
|
|
5277
|
+
result = { ...result, checks: undefined };
|
|
5278
|
+
}
|
|
5279
|
+
if (result.roles && Object.keys(result.roles).length > 0) {
|
|
5280
|
+
console.error(`⚠ ${globalRolesIgnoredWarning(path)}`);
|
|
5281
|
+
result = { ...result, roles: undefined };
|
|
5282
|
+
}
|
|
5283
|
+
if (result.principals && result.principals.length > 0) {
|
|
5284
|
+
console.error(`⚠ ${globalPrincipalsIgnoredWarning(path)}`);
|
|
5285
|
+
result = { ...result, principals: undefined };
|
|
5286
|
+
}
|
|
5287
|
+
if (result.releaseActor) {
|
|
5288
|
+
console.error(`⚠ ${globalReleaseActorIgnoredWarning(path)}`);
|
|
5289
|
+
result = { ...result, releaseActor: undefined };
|
|
5290
|
+
}
|
|
5291
|
+
if (result.tokens) {
|
|
5292
|
+
console.error(`⚠ ${globalTokensCollectIgnoredWarning(path)}`);
|
|
5293
|
+
result = { ...result, tokens: undefined };
|
|
5294
|
+
}
|
|
5295
|
+
return result;
|
|
5296
|
+
}
|
|
5297
|
+
function resolvePrincipalAllowlist(config) {
|
|
5298
|
+
return config?.principals ?? PRINCIPAL_ALLOWLIST;
|
|
5299
|
+
}
|
|
5300
|
+
function resolveReleaseActor(config) {
|
|
5301
|
+
return config?.releaseActor ?? DEFAULT_RELEASE_ACTOR;
|
|
5302
|
+
}
|
|
5303
|
+
function trustAnchorRepo() {
|
|
5304
|
+
const wellFormed = (slug) => /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
|
|
5305
|
+
const fromRunner = process.env.GITHUB_REPOSITORY?.trim();
|
|
5306
|
+
if (fromRunner) {
|
|
5307
|
+
const validated = wellFormed(fromRunner);
|
|
5308
|
+
if (validated)
|
|
5309
|
+
return validated;
|
|
5310
|
+
}
|
|
5311
|
+
try {
|
|
5312
|
+
const url = execFileSync2("git", ["remote", "get-url", "origin"], {
|
|
5313
|
+
encoding: "utf-8",
|
|
5314
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
5315
|
+
}).trim();
|
|
5316
|
+
const m = url.match(/github\.com[:/]([^/]+)\/(.+?)(?:\.git)?\/?$/);
|
|
5317
|
+
return m?.[1] && m[2] ? wellFormed(`${m[1]}/${m[2]}`) : null;
|
|
5318
|
+
} catch {
|
|
5319
|
+
return null;
|
|
5320
|
+
}
|
|
5321
|
+
}
|
|
5322
|
+
function ghFetchTrustAnchorConfig() {
|
|
5323
|
+
const repo = trustAnchorRepo();
|
|
5324
|
+
if (!repo)
|
|
5325
|
+
throw new Error("could not resolve repo identity for the trust-anchor read");
|
|
5326
|
+
return execFileSync2("gh", ["api", `repos/${repo}/contents/${LOCAL_CONFIG_FILENAME}`, "--jq", ".content"], {
|
|
5327
|
+
encoding: "utf-8",
|
|
5328
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
5329
|
+
});
|
|
5330
|
+
}
|
|
5331
|
+
function firstLine(err) {
|
|
5332
|
+
return err?.message?.split(`
|
|
5333
|
+
`)[0] ?? "unknown error";
|
|
5334
|
+
}
|
|
5335
|
+
function isMissingFileError(err) {
|
|
5336
|
+
const stderr = err?.stderr;
|
|
5337
|
+
const haystack = [
|
|
5338
|
+
err?.message ?? "",
|
|
5339
|
+
typeof stderr === "string" ? stderr : stderr?.toString() ?? ""
|
|
5340
|
+
].join(`
|
|
5341
|
+
`);
|
|
5342
|
+
return /\b404\b|not found/i.test(haystack);
|
|
5343
|
+
}
|
|
5344
|
+
function loadTrustAnchorConfig(fetcher = ghFetchTrustAnchorConfig) {
|
|
5345
|
+
const warn = (why) => process.stdout.write(`⚠ could not read the trust-anchor config (\`principals\`/\`releaseActor\`) from the default branch (${why}) — falling back to vinaya's built-in defaults.
|
|
5346
|
+
`);
|
|
5347
|
+
let base64;
|
|
5348
|
+
try {
|
|
5349
|
+
base64 = fetcher().trim();
|
|
5350
|
+
} catch (err) {
|
|
5351
|
+
if (!isMissingFileError(err))
|
|
5352
|
+
warn(firstLine(err));
|
|
5353
|
+
return null;
|
|
5354
|
+
}
|
|
5355
|
+
if (!base64)
|
|
5356
|
+
return null;
|
|
5357
|
+
try {
|
|
5358
|
+
const raw = Buffer.from(base64, "base64").toString("utf-8");
|
|
5359
|
+
const parsed = VinayaConfigSchema.safeParse(JSON.parse(raw));
|
|
5360
|
+
if (parsed.success)
|
|
5361
|
+
return parsed.data;
|
|
5362
|
+
warn("it did not satisfy the config schema");
|
|
5363
|
+
return null;
|
|
5364
|
+
} catch {
|
|
5365
|
+
warn("it was not readable as JSON");
|
|
5366
|
+
return null;
|
|
5367
|
+
}
|
|
5368
|
+
}
|
|
5369
|
+
function loadConfig() {
|
|
5370
|
+
const path = configPath();
|
|
5371
|
+
if (!path)
|
|
5372
|
+
return null;
|
|
5373
|
+
try {
|
|
5374
|
+
const raw = JSON.parse(readFileSync2(path, "utf-8"));
|
|
5375
|
+
return stripGlobalOnlyKeys(VinayaConfigSchema.parse(raw), path);
|
|
5376
|
+
} catch {
|
|
5377
|
+
return null;
|
|
5378
|
+
}
|
|
5379
|
+
}
|
|
5380
|
+
var TOKENS_COLLECT_TRUST_PATH = join(GLOBAL_VINAYA_HOME, "tokens-collect-trust.json");
|
|
5381
|
+
|
|
4985
5382
|
// src/checks/bin/check-brief-shape.ts
|
|
4986
5383
|
var CHECK_NAME = "brief-shape";
|
|
4987
5384
|
function fetchIssueComments(issueNumber) {
|
|
4988
|
-
const out =
|
|
5385
|
+
const out = execFileSync3("gh", ["issue", "view", String(issueNumber), "--json", "comments"], {
|
|
4989
5386
|
encoding: "utf8",
|
|
4990
5387
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4991
5388
|
});
|
|
@@ -5010,12 +5407,13 @@ function resolveGradedBody(prBody, taskBranch) {
|
|
|
5010
5407
|
message: `could not fetch Issue #${issue}'s comments (\`gh issue view\`) to grade the dispatched brief: ${err instanceof Error ? err.message : String(err)}`
|
|
5011
5408
|
};
|
|
5012
5409
|
}
|
|
5013
|
-
const
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5410
|
+
const allowlist = resolvePrincipalAllowlist(loadTrustAnchorConfig());
|
|
5411
|
+
const comments = json.comments.map((c) => ({ body: c.body, author: c.author?.login ?? null }));
|
|
5412
|
+
const resolved = resolveNewestFrozenBrief(comments, allowlist);
|
|
5413
|
+
if (!resolved) {
|
|
5414
|
+
return { ok: false, message: `not dispatched — no \`aeg:brief:v<k>\` comment on Issue #${issue}.` };
|
|
5017
5415
|
}
|
|
5018
|
-
return { ok: true, body:
|
|
5416
|
+
return { ok: true, body: resolved.content };
|
|
5019
5417
|
}
|
|
5020
5418
|
function listDirs(dir) {
|
|
5021
5419
|
try {
|
|
@@ -5026,7 +5424,7 @@ function listDirs(dir) {
|
|
|
5026
5424
|
}
|
|
5027
5425
|
function readJson(path) {
|
|
5028
5426
|
try {
|
|
5029
|
-
return JSON.parse(
|
|
5427
|
+
return JSON.parse(readFileSync3(path, "utf8"));
|
|
5030
5428
|
} catch {
|
|
5031
5429
|
return {};
|
|
5032
5430
|
}
|
|
@@ -5041,7 +5439,7 @@ function buildConsumersOf2() {
|
|
|
5041
5439
|
return buildConsumersOf(workspaces, listDirs, readManifest);
|
|
5042
5440
|
}
|
|
5043
5441
|
function fetchIssueBody(issueNumber) {
|
|
5044
|
-
return
|
|
5442
|
+
return execFileSync3("gh", ["issue", "view", String(issueNumber), "--json", "body", "--jq", ".body"], {
|
|
5045
5443
|
encoding: "utf8",
|
|
5046
5444
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5047
5445
|
});
|