@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
|
@@ -300,7 +300,7 @@ function issueListByLabelArgs(owner, repo, label) {
|
|
|
300
300
|
"--state",
|
|
301
301
|
"all",
|
|
302
302
|
"--json",
|
|
303
|
-
"number,title,body,state,labels,milestone",
|
|
303
|
+
"number,title,body,state,labels,milestone,stateReason",
|
|
304
304
|
"--limit",
|
|
305
305
|
"200"
|
|
306
306
|
];
|
|
@@ -646,14 +646,19 @@ function resolveTaskIssueRef(title, labels) {
|
|
|
646
646
|
var INTENTS_HEADING = /^#{1,6}\s*Tranche intents\s*$/im;
|
|
647
647
|
var NEXT_HEADING = /^#{1,6}\s+\S/m;
|
|
648
648
|
var INTENT_BULLET = /^-\s+([a-z0-9][a-z0-9-]*)\s*:\s*(.+)$/i;
|
|
649
|
-
function
|
|
650
|
-
const text = stripCode(description, { inlineSpans: "keep" });
|
|
649
|
+
function intentsSection(text) {
|
|
651
650
|
const start = text.match(INTENTS_HEADING);
|
|
652
651
|
if (!start || start.index === undefined)
|
|
653
|
-
return
|
|
652
|
+
return null;
|
|
654
653
|
const rest = text.slice(start.index + start[0].length);
|
|
655
654
|
const next = rest.match(NEXT_HEADING);
|
|
656
|
-
|
|
655
|
+
return rest.slice(0, next && next.index !== undefined ? next.index : rest.length);
|
|
656
|
+
}
|
|
657
|
+
function intentGoalForSlug(description, slug) {
|
|
658
|
+
const text = stripCode(description, { inlineSpans: "keep" });
|
|
659
|
+
const section = intentsSection(text);
|
|
660
|
+
if (section === null)
|
|
661
|
+
return "";
|
|
657
662
|
for (const line of section.split(`
|
|
658
663
|
`)) {
|
|
659
664
|
const trimmed = line.trim();
|
|
@@ -1945,6 +1950,7 @@ function parseInlineFieldList(section) {
|
|
|
1945
1950
|
// ../../packages/aeg-core/src/verdict-extraction.ts
|
|
1946
1951
|
var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
|
|
1947
1952
|
var OBJECTIVES_VERSION_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Objectives version:\s*([0-9a-f]{64})(?![A-Za-z0-9])/im;
|
|
1953
|
+
var RULING_ORDINAL_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Ruling ordinal:\s*(\d+)(?!\d)/im;
|
|
1948
1954
|
function firstFiveLines(comment) {
|
|
1949
1955
|
return comment.split(`
|
|
1950
1956
|
`).slice(0, 5).join(`
|
|
@@ -1958,6 +1964,15 @@ function extractObjectivesVersion(comment) {
|
|
|
1958
1964
|
const m = firstFiveLines(comment).match(OBJECTIVES_VERSION_PATTERN);
|
|
1959
1965
|
return m ? m[1].toLowerCase() : null;
|
|
1960
1966
|
}
|
|
1967
|
+
function firstSevenLines(comment) {
|
|
1968
|
+
return comment.split(`
|
|
1969
|
+
`).slice(0, 7).join(`
|
|
1970
|
+
`);
|
|
1971
|
+
}
|
|
1972
|
+
function extractRulingOrdinal(comment) {
|
|
1973
|
+
const m = firstSevenLines(comment).match(RULING_ORDINAL_PATTERN);
|
|
1974
|
+
return m ? Number.parseInt(m[1], 10) : null;
|
|
1975
|
+
}
|
|
1961
1976
|
function extractVerdict(comments, valuePattern, missingLabel) {
|
|
1962
1977
|
const candidates = comments.filter((c) => valuePattern.test(c));
|
|
1963
1978
|
if (candidates.length === 0) {
|
|
@@ -1965,6 +1980,7 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1965
1980
|
value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
|
|
1966
1981
|
headSha: null,
|
|
1967
1982
|
objectivesVersion: null,
|
|
1983
|
+
rulingOrdinal: null,
|
|
1968
1984
|
danglingNote: `no ${missingLabel} verdict comment found on this PR`
|
|
1969
1985
|
};
|
|
1970
1986
|
}
|
|
@@ -1975,6 +1991,7 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1975
1991
|
value: `the most recent ${missingLabel} comment's VERDICT line is not within its first five lines — DANGLING, see below`,
|
|
1976
1992
|
headSha: null,
|
|
1977
1993
|
objectivesVersion: null,
|
|
1994
|
+
rulingOrdinal: null,
|
|
1978
1995
|
danglingNote: `the most recent ${missingLabel} verdict comment carries a VERDICT-shaped line outside the first-five-line read window`
|
|
1979
1996
|
};
|
|
1980
1997
|
}
|
|
@@ -1982,6 +1999,7 @@ function extractVerdict(comments, valuePattern, missingLabel) {
|
|
|
1982
1999
|
value: m[1].toUpperCase().replace(/[_-]/g, " "),
|
|
1983
2000
|
headSha: extractHeadSha(latest),
|
|
1984
2001
|
objectivesVersion: extractObjectivesVersion(latest),
|
|
2002
|
+
rulingOrdinal: extractRulingOrdinal(latest),
|
|
1985
2003
|
danglingNote: null
|
|
1986
2004
|
};
|
|
1987
2005
|
}
|
|
@@ -2343,6 +2361,9 @@ import { createHash as createHash2 } from "node:crypto";
|
|
|
2343
2361
|
var HEADING_RE = /^##[ \t]*Objectives[ \t]*$/im;
|
|
2344
2362
|
var NEXT_HEADING_RE = /^##[ \t]/m;
|
|
2345
2363
|
var OBJECTIVE_LINE_RE = /^O(\d+)\.[ \t]*(.*)$/;
|
|
2364
|
+
function maskedForHeadingSearch(body) {
|
|
2365
|
+
return maskDetailsBlocks(maskCode(body));
|
|
2366
|
+
}
|
|
2346
2367
|
function hasBacktickedPath(text) {
|
|
2347
2368
|
let i = 0;
|
|
2348
2369
|
while (i < text.length) {
|
|
@@ -2365,16 +2386,22 @@ function stripObjectiveBackticks(text) {
|
|
|
2365
2386
|
function wordCount(text) {
|
|
2366
2387
|
return text.split(/\s+/).filter((w) => /[a-z]/i.test(w)).length;
|
|
2367
2388
|
}
|
|
2368
|
-
function
|
|
2369
|
-
const
|
|
2389
|
+
function objectivesSectionBounds(body) {
|
|
2390
|
+
const masked = maskedForHeadingSearch(body);
|
|
2391
|
+
const heading = HEADING_RE.exec(masked);
|
|
2370
2392
|
if (!heading)
|
|
2371
2393
|
return null;
|
|
2372
|
-
const
|
|
2394
|
+
const start = heading.index + heading[0].length;
|
|
2395
|
+
const afterHeading = masked.slice(start);
|
|
2373
2396
|
const next = NEXT_HEADING_RE.exec(afterHeading);
|
|
2374
|
-
return next ?
|
|
2397
|
+
return { start, end: next ? start + next.index : body.length };
|
|
2398
|
+
}
|
|
2399
|
+
function objectivesSectionText(body) {
|
|
2400
|
+
const bounds = objectivesSectionBounds(body);
|
|
2401
|
+
return bounds === null ? null : body.slice(bounds.start, bounds.end);
|
|
2375
2402
|
}
|
|
2376
2403
|
function hasObjectivesHeading(body) {
|
|
2377
|
-
return HEADING_RE.test(body);
|
|
2404
|
+
return HEADING_RE.test(maskedForHeadingSearch(body));
|
|
2378
2405
|
}
|
|
2379
2406
|
function objectivesOf(body) {
|
|
2380
2407
|
const section = objectivesSectionText(body);
|
|
@@ -2434,6 +2461,15 @@ function isIssueNotFoundError(err) {
|
|
|
2434
2461
|
`);
|
|
2435
2462
|
return /could not resolve to an (?:issue|pull request)|\b404\b|not found/i.test(haystack);
|
|
2436
2463
|
}
|
|
2464
|
+
function resolveObjectivesSource(prBody, issue, cutoverIssue) {
|
|
2465
|
+
if (issue !== null && issue < cutoverIssue)
|
|
2466
|
+
return { kind: "none" };
|
|
2467
|
+
if (issue !== null)
|
|
2468
|
+
return { kind: "issue", issue };
|
|
2469
|
+
if (hasObjectivesHeading(prBody))
|
|
2470
|
+
return { kind: "body" };
|
|
2471
|
+
return { kind: "none" };
|
|
2472
|
+
}
|
|
2437
2473
|
|
|
2438
2474
|
// ../../packages/aeg-core/src/premise-check.ts
|
|
2439
2475
|
import { createHash as createHash3 } from "node:crypto";
|
|
@@ -2935,17 +2971,40 @@ function checkBriefSections(prBody, readTier, options = {}) {
|
|
|
2935
2971
|
];
|
|
2936
2972
|
return { errors: results.flatMap((r) => r.errors) };
|
|
2937
2973
|
}
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
`
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2974
|
+
function contentAfterNLines(body, n) {
|
|
2975
|
+
let idx = -1;
|
|
2976
|
+
for (let i = 0;i < n; i++) {
|
|
2977
|
+
idx = body.indexOf(`
|
|
2978
|
+
`, idx + 1);
|
|
2979
|
+
if (idx === -1)
|
|
2980
|
+
return "";
|
|
2981
|
+
}
|
|
2982
|
+
return body.slice(idx + 1);
|
|
2983
|
+
}
|
|
2984
|
+
var BRIEF_MARKER_LINE_RE = /^<!-- aeg:brief:v(\d+) -->$/;
|
|
2985
|
+
function parseBriefMarkerVersion(firstLine) {
|
|
2986
|
+
const m = BRIEF_MARKER_LINE_RE.exec(firstLine.trim());
|
|
2987
|
+
if (!m)
|
|
2988
|
+
return null;
|
|
2989
|
+
const version = Number.parseInt(m[1], 10);
|
|
2990
|
+
return Number.isInteger(version) && version >= 1 ? version : null;
|
|
2991
|
+
}
|
|
2992
|
+
function frozenBriefContent(body, version) {
|
|
2993
|
+
return contentAfterNLines(body, version === 1 ? 2 : 3);
|
|
2994
|
+
}
|
|
2995
|
+
function resolveNewestFrozenBrief(comments, allowlist) {
|
|
2996
|
+
let best = null;
|
|
2997
|
+
for (const c of comments) {
|
|
2998
|
+
if (!isPrincipal(c.author, allowlist))
|
|
2999
|
+
continue;
|
|
3000
|
+
const version = parseBriefMarkerVersion(c.body.split(`
|
|
3001
|
+
`)[0] ?? "");
|
|
3002
|
+
if (version === null)
|
|
3003
|
+
continue;
|
|
3004
|
+
if (best === null || version > best.version)
|
|
3005
|
+
best = { ...c, version };
|
|
3006
|
+
}
|
|
3007
|
+
return best === null ? null : { ...best, content: frozenBriefContent(best.body, best.version) };
|
|
2949
3008
|
}
|
|
2950
3009
|
// ../../packages/aeg-core/src/doctrine-portability.ts
|
|
2951
3010
|
var DEFAULT_SHIPS_PREFIX = "aeg-root/";
|
|
@@ -3672,6 +3731,11 @@ function isBoundToObjectives(extraction, currentVersion) {
|
|
|
3672
3731
|
return true;
|
|
3673
3732
|
return extraction.objectivesVersion === currentVersion;
|
|
3674
3733
|
}
|
|
3734
|
+
function isBoundToRulings(extraction, currentOrdinal) {
|
|
3735
|
+
if (extraction.rulingOrdinal === null)
|
|
3736
|
+
return currentOrdinal === 0;
|
|
3737
|
+
return extraction.rulingOrdinal === currentOrdinal;
|
|
3738
|
+
}
|
|
3675
3739
|
function checkReviewGate(input) {
|
|
3676
3740
|
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
3677
3741
|
const waived = isWaiverLabelActorVerified({
|
|
@@ -3700,7 +3764,9 @@ function checkReviewGate(input) {
|
|
|
3700
3764
|
const securityBound = isBoundToPatch(security, input.headSha, input.patchIdOf);
|
|
3701
3765
|
const codeReviewObjectivesBound = isBoundToObjectives(codeReview, input.objectivesVersion);
|
|
3702
3766
|
const securityObjectivesBound = isBoundToObjectives(security, input.objectivesVersion);
|
|
3703
|
-
|
|
3767
|
+
const codeReviewRulingsBound = isBoundToRulings(codeReview, input.rulingOrdinal);
|
|
3768
|
+
const securityRulingsBound = isBoundToRulings(security, input.rulingOrdinal);
|
|
3769
|
+
if (codeReviewClean && codeReviewBound && codeReviewObjectivesBound && codeReviewRulingsBound && securityClean && securityBound && securityObjectivesBound && securityRulingsBound && mechanicalChecksClean) {
|
|
3704
3770
|
return {
|
|
3705
3771
|
verdict: "pass",
|
|
3706
3772
|
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.`,
|
|
@@ -3714,6 +3780,8 @@ function checkReviewGate(input) {
|
|
|
3714
3780
|
problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3715
3781
|
} else if (!codeReviewObjectivesBound) {
|
|
3716
3782
|
problems.push(`the newest code-review verdict was cast against objectives version ${codeReview.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3783
|
+
} else if (!codeReviewRulingsBound) {
|
|
3784
|
+
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`);
|
|
3717
3785
|
}
|
|
3718
3786
|
if (!securityClean) {
|
|
3719
3787
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
@@ -3721,6 +3789,8 @@ function checkReviewGate(input) {
|
|
|
3721
3789
|
problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
|
|
3722
3790
|
} else if (!securityObjectivesBound) {
|
|
3723
3791
|
problems.push(`the newest security-review verdict was cast against objectives version ${security.objectivesVersion ?? "none"}, the Issue's list is now ${input.objectivesVersion}`);
|
|
3792
|
+
} else if (!securityRulingsBound) {
|
|
3793
|
+
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`);
|
|
3724
3794
|
}
|
|
3725
3795
|
if (!mechanicalChecksClean) {
|
|
3726
3796
|
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(", ")}`);
|
|
@@ -3737,6 +3807,24 @@ var DEFAULT_RELEASE_ACTOR = "github-actions[bot]";
|
|
|
3737
3807
|
function isChangesetsReleasePr(branch, author, expectedAuthor) {
|
|
3738
3808
|
return branch === CHANGESET_RELEASE_BRANCH && author === expectedAuthor;
|
|
3739
3809
|
}
|
|
3810
|
+
// ../../packages/aeg-core/src/ruling-ordinal.ts
|
|
3811
|
+
var RULING_MARKER_ORDINAL = /^<!-- aeg:principal:ruling:\d+-(\d+) -->$/;
|
|
3812
|
+
function newestPrincipalRulingOrdinal(comments, allowlist) {
|
|
3813
|
+
let best = 0;
|
|
3814
|
+
for (const c of comments) {
|
|
3815
|
+
if (!isPrincipal(c.author, allowlist))
|
|
3816
|
+
continue;
|
|
3817
|
+
const firstLine = (c.body.split(`
|
|
3818
|
+
`)[0] ?? "").trim();
|
|
3819
|
+
const m = RULING_MARKER_ORDINAL.exec(firstLine);
|
|
3820
|
+
if (!m)
|
|
3821
|
+
continue;
|
|
3822
|
+
const k = Number.parseInt(m[1], 10);
|
|
3823
|
+
if (k > best)
|
|
3824
|
+
best = k;
|
|
3825
|
+
}
|
|
3826
|
+
return best;
|
|
3827
|
+
}
|
|
3740
3828
|
// ../../packages/aeg-core/src/markdown-table.ts
|
|
3741
3829
|
function splitRow(line) {
|
|
3742
3830
|
const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
|
|
@@ -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(/\|$/, "");
|
|
@@ -5468,14 +5556,41 @@ var COMMANDS = [
|
|
|
5468
5556
|
},
|
|
5469
5557
|
{
|
|
5470
5558
|
name: "task dispatch",
|
|
5471
|
-
description: "
|
|
5559
|
+
description: "Deprecated — render, pin, and post the brief on the Issue as the frozen original; start the developer",
|
|
5472
5560
|
flags: [
|
|
5473
5561
|
{ flag: "--agent <claude|codex|gemini>", description: "Start the developer through dispatchRole once posted" }
|
|
5474
5562
|
],
|
|
5475
5563
|
details: [
|
|
5476
5564
|
"Renders the brief from the Issue and the tree (the same assembly `brief render` uses) and posts it once as an Issue comment whose first line is `<!-- aeg:brief:v1 -->` and whose second line is `Brief hash: <sha256>` — the hash covers only the body below those two lines, so any reader recomputes it. Refuses outright, naming the existing comment's URL, when a `v1` comment already exists on the Issue — the brief is frozen by design, never overwritten or silently reissued.",
|
|
5477
5565
|
"With `--agent`, starts the Developer through `dispatchRole` when `apps/cli/src/lib/dispatch.ts` exports it; otherwise prints the rendered brief and the manual dispatch instruction and exits `0` — a soft dependency, never a hard block.",
|
|
5478
|
-
"Principal-only, with or without `--agent`: refuses before any render, forge read, or post when the authenticated `gh` identity is not on the Principal allowlist (or cannot be resolved at all) — dispatching is the `todo → in-flight` transition, not a general-purpose comment poster."
|
|
5566
|
+
"Principal-only, with or without `--agent`: refuses before any render, forge read, or post when the authenticated `gh` identity is not on the Principal allowlist (or cannot be resolved at all) — dispatching is the `todo → in-flight` transition, not a general-purpose comment poster.",
|
|
5567
|
+
"Deprecated in favor of `task brief` (preparation only) and `task run` (the full unattended loop) — kept for a documented compatibility window while callers migrate."
|
|
5568
|
+
],
|
|
5569
|
+
status: "shipped"
|
|
5570
|
+
},
|
|
5571
|
+
{
|
|
5572
|
+
name: "task brief",
|
|
5573
|
+
description: "Render and freeze the brief as the Issue's original comment — preparation only, starts nobody",
|
|
5574
|
+
details: [
|
|
5575
|
+
"The preparation half of `task dispatch`, extracted so it is callable on its own: resolves the task's Issue, renders the brief, refuses on any gap, refuses when a frozen brief already exists, and posts it once as the same `aeg:brief:v1` Issue comment `task dispatch` posts. Starts no agent under any circumstances — there is no `--agent` flag here at all.",
|
|
5576
|
+
"Successor to `task dispatch` for the preparation step; the full unattended run (preparation, then the developer, then the review loop) is `task run`."
|
|
5577
|
+
],
|
|
5578
|
+
status: "shipped"
|
|
5579
|
+
},
|
|
5580
|
+
{
|
|
5581
|
+
name: "task run",
|
|
5582
|
+
description: "One command from a planned Issue to a reviewed pull request — exactly one developer started",
|
|
5583
|
+
flags: [
|
|
5584
|
+
{
|
|
5585
|
+
flag: "--agent <claude|codex|gemini>",
|
|
5586
|
+
description: "Vendor for the developer and both reviewers this run dispatches"
|
|
5587
|
+
}
|
|
5588
|
+
],
|
|
5589
|
+
details: [
|
|
5590
|
+
"Composes `task brief`'s own preparation (`prepareTask`) with `dev-review-loop` (`devReviewLoop`) — nothing else. Preparation starts no agent; the loop's own round 1 reads the frozen brief off the Issue and is the only place a developer is ever dispatched from a fresh task, so exactly one developer is started by construction.",
|
|
5591
|
+
"A brief already frozen on the Issue is reused, never re-posted — the second `task dispatch`/`task brief` call this composes around does not fail the whole run, it just skips straight to running the loop. A task whose Issue refuses preparation (a missing brief section, an unmet dispatch gate) is refused before any agent starts, with nothing posted.",
|
|
5592
|
+
"Refuses when the frozen brief's developer branch already has an open pull request — the old `task dispatch` followed by `task run` cannot start two developers this way.",
|
|
5593
|
+
"Exit and printed summary distinguish a published, reviewed pull request (exit `0`, the PR URL) from a pause (exit `1`, with the exact `vinaya dev-review-loop --resume <pr>` command to continue), a usage/argv error (exit `2`), and any other failure (exit `3`) — never sharing `1` with a pause, so an unattended host tells them apart from the exit code alone. A run that pauses is resumed with the loop's own existing `--resume <pr>` flag, never a flag on this command."
|
|
5479
5594
|
],
|
|
5480
5595
|
status: "shipped"
|
|
5481
5596
|
},
|
|
@@ -5683,6 +5798,15 @@ var COMMANDS = [
|
|
|
5683
5798
|
],
|
|
5684
5799
|
status: "shipped"
|
|
5685
5800
|
},
|
|
5801
|
+
{
|
|
5802
|
+
name: "milestone status",
|
|
5803
|
+
description: "Print each of a Milestone's declared tranche intents with its derived lifecycle and issue counts",
|
|
5804
|
+
flags: [{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }],
|
|
5805
|
+
details: [
|
|
5806
|
+
"Read-only — writes nothing. For each `- <slug>: …` line in the Milestone's `### Tranche intents` section, prints the tranche's lifecycle (`planned`/`active`/`complete`) and its labeled Issues' counts (merged, open, not planned), all derived from the forge. Refuses if `<n>` isn't a real Milestone in this repo, or if the forge is unreachable."
|
|
5807
|
+
],
|
|
5808
|
+
status: "shipped"
|
|
5809
|
+
},
|
|
5686
5810
|
{
|
|
5687
5811
|
name: "review status",
|
|
5688
5812
|
description: "Print the review loop's own state for a PR, and its branch's distance from the base",
|