@attalabs/vinaya 0.2.0 → 0.4.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/aeg-root/enforcement.md +5 -4
- package/aeg-root/glossary.md +4 -0
- package/aeg-root/roles/archivist.md +1 -1
- package/aeg-root/roles/developer.md +2 -2
- package/aeg-root/roles/reviewer.md +1 -1
- package/aeg-root/roles/security.md +1 -1
- package/aeg-root/state-machine.md +17 -3
- package/aeg-root/tranche-model.md +5 -3
- package/dist/checks/bin/check-branch-topology.js +76 -14
- package/dist/checks/bin/check-brief-shape.js +76 -14
- package/dist/checks/bin/check-closes-n.js +76 -14
- package/dist/checks/bin/check-coherence.js +76 -14
- package/dist/checks/bin/check-dead-branch-push.js +76 -14
- package/dist/checks/bin/check-dispatch-readiness.js +76 -14
- package/dist/checks/bin/check-doc-coverage-push.js +76 -14
- package/dist/checks/bin/check-doc-coverage.js +76 -14
- package/dist/checks/bin/check-first-push-dispatch.js +76 -14
- package/dist/checks/bin/check-issue-assignment.js +76 -14
- package/dist/checks/bin/check-no-disk-state.js +76 -14
- package/dist/checks/bin/check-reader-resolvable-prose.js +76 -14
- package/dist/checks/bin/check-registry-gates.js +76 -14
- package/dist/checks/bin/check-review-gate.js +81 -15
- package/dist/checks/bin/check-single-plan-pr.js +76 -14
- package/dist/checks/bin/check-test-plan.js +76 -14
- package/dist/index.js +427 -68
- package/package.json +1 -1
|
@@ -1513,15 +1513,58 @@ function pointerToPath(p) {
|
|
|
1513
1513
|
return idx === -1 ? p : p.slice(0, idx);
|
|
1514
1514
|
}
|
|
1515
1515
|
var SEPARATOR = /(?:[ \t]*[—–][ \t]*|[ \t]+-[ \t]+)/.source;
|
|
1516
|
-
function
|
|
1517
|
-
const
|
|
1518
|
-
const re = new RegExp(`^[ \\t]
|
|
1516
|
+
function readPointerNoteField(body, field) {
|
|
1517
|
+
const out = [];
|
|
1518
|
+
const re = new RegExp(`^[ \\t]*${field}[ \\t]*:[ \\t]*(.+?)${SEPARATOR}(.+?)[ \\t]*$`, "gim");
|
|
1519
1519
|
for (const m of body.matchAll(re)) {
|
|
1520
|
-
|
|
1520
|
+
out.push({ surface: (m[1] ?? "").trim(), note: (m[2] ?? "").trim() });
|
|
1521
|
+
}
|
|
1522
|
+
return out;
|
|
1523
|
+
}
|
|
1524
|
+
function readDocAcks(body) {
|
|
1525
|
+
return readPointerNoteField(body, "Doc-ack");
|
|
1526
|
+
}
|
|
1527
|
+
function readDocNeutrals(body) {
|
|
1528
|
+
return readPointerNoteField(body, "Doc-neutral");
|
|
1529
|
+
}
|
|
1530
|
+
var LANGUAGE_COMMENT_PREFIXES = [
|
|
1531
|
+
{ test: /\.(ts|tsx|js|jsx|mjs|cjs)$/, prefixes: ["//"] },
|
|
1532
|
+
{ test: /(^|\/)(pre-push|pre-commit|pre-merge-commit)$/, prefixes: ["#"] },
|
|
1533
|
+
{ test: /\.(sh|bash)$/, prefixes: ["#"] },
|
|
1534
|
+
{ test: /\.ya?ml$/, prefixes: ["#"] },
|
|
1535
|
+
{ test: /\.py$/, prefixes: ["#"] }
|
|
1536
|
+
];
|
|
1537
|
+
function commentPrefixesForPath(path) {
|
|
1538
|
+
for (const { test, prefixes } of LANGUAGE_COMMENT_PREFIXES) {
|
|
1539
|
+
if (test.test(path))
|
|
1540
|
+
return prefixes;
|
|
1521
1541
|
}
|
|
1522
|
-
return
|
|
1542
|
+
return [];
|
|
1523
1543
|
}
|
|
1524
|
-
function
|
|
1544
|
+
function isNeutralDiffContentLine(line, prefixes) {
|
|
1545
|
+
const t = line.trim();
|
|
1546
|
+
if (t === "")
|
|
1547
|
+
return true;
|
|
1548
|
+
if (t.startsWith("#!"))
|
|
1549
|
+
return false;
|
|
1550
|
+
return prefixes.some((p) => t.startsWith(p));
|
|
1551
|
+
}
|
|
1552
|
+
function isMechanicallyNeutralDiff(diffText, path) {
|
|
1553
|
+
const prefixes = commentPrefixesForPath(path);
|
|
1554
|
+
let sawChange = false;
|
|
1555
|
+
for (const raw of diffText.split(`
|
|
1556
|
+
`)) {
|
|
1557
|
+
if (raw.startsWith("+++ ") || raw.startsWith("--- ") || raw.startsWith("@@"))
|
|
1558
|
+
continue;
|
|
1559
|
+
if (raw.startsWith("+") || raw.startsWith("-")) {
|
|
1560
|
+
if (!isNeutralDiffContentLine(raw.slice(1), prefixes))
|
|
1561
|
+
return false;
|
|
1562
|
+
sawChange = true;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
return sawChange;
|
|
1566
|
+
}
|
|
1567
|
+
function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive, getDiff) {
|
|
1525
1568
|
const out = { errors: [], notes: [] };
|
|
1526
1569
|
if (docOwnersContent === null)
|
|
1527
1570
|
return out;
|
|
@@ -1534,13 +1577,15 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive)
|
|
|
1534
1577
|
const fired = [];
|
|
1535
1578
|
for (const b of bindings) {
|
|
1536
1579
|
const re = globToRegex(b.glob);
|
|
1537
|
-
|
|
1538
|
-
|
|
1580
|
+
const matchedFiles = codeFiles.filter((f) => re.test(f));
|
|
1581
|
+
if (matchedFiles.length > 0)
|
|
1582
|
+
fired.push({ binding: b, matchedFiles });
|
|
1539
1583
|
}
|
|
1540
1584
|
if (fired.length === 0)
|
|
1541
1585
|
return out;
|
|
1542
1586
|
const acks = readDocAcks(prBody);
|
|
1543
|
-
|
|
1587
|
+
const neutrals = readDocNeutrals(prBody);
|
|
1588
|
+
for (const { binding: b, matchedFiles } of fired) {
|
|
1544
1589
|
if (waiverActive) {
|
|
1545
1590
|
out.notes.push(`C5 doc-waiver active for ${b.pointer} (binding ${DOC_OWNERS_PATH}:${b.lineNum})`);
|
|
1546
1591
|
continue;
|
|
@@ -1557,9 +1602,22 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive)
|
|
|
1557
1602
|
out.errors.push(`C5 doc-owners-dangling: ${DOC_OWNERS_PATH}:${b.lineNum} points to ${b.pointer}, which does not exist on disk. Fix the binding or add the doc.`);
|
|
1558
1603
|
continue;
|
|
1559
1604
|
}
|
|
1560
|
-
if (
|
|
1561
|
-
|
|
1605
|
+
if (changed.includes(pointerPath))
|
|
1606
|
+
continue;
|
|
1607
|
+
const declared = neutrals.find((n) => n.surface === b.pointer);
|
|
1608
|
+
if (declared) {
|
|
1609
|
+
const evidenced = getDiff !== undefined && matchedFiles.every((f) => {
|
|
1610
|
+
const diff = getDiff(f);
|
|
1611
|
+
return diff !== null && isMechanicallyNeutralDiff(diff, f);
|
|
1612
|
+
});
|
|
1613
|
+
if (evidenced) {
|
|
1614
|
+
out.notes.push(`C5 doc-neutral: ${b.pointer} not required for ${DOC_OWNERS_PATH}:${b.lineNum} (glob \`${b.glob}\`) — declared neutral ("${declared.note}"), diff of ${matchedFiles.join(", ")} confirmed comment/whitespace-only.`);
|
|
1615
|
+
continue;
|
|
1616
|
+
}
|
|
1617
|
+
out.errors.push(`C5 doc-neutral-unverified: ${DOC_OWNERS_PATH}:${b.lineNum} (glob \`${b.glob}\` → ${b.pointer}) declared \`Doc-neutral: ${b.pointer} — ${declared.note}\`, but the diff of ${matchedFiles.join(", ")} contains changes beyond comments/whitespace. Update ${pointerPath}, or have a principal apply the \`${WAIVER_LABEL}\` label.`);
|
|
1618
|
+
continue;
|
|
1562
1619
|
}
|
|
1620
|
+
out.errors.push(`C5 doc-coverage: code change matched ${DOC_OWNERS_PATH}:${b.lineNum} (glob \`${b.glob}\` → ${b.pointer}), but ${pointerPath} is not in the PR diff. Update it, declare \`Doc-neutral: ${b.pointer} — <why>\` if the change is genuinely mechanically-neutral, or have a principal apply the \`${WAIVER_LABEL}\` label.`);
|
|
1563
1621
|
}
|
|
1564
1622
|
return out;
|
|
1565
1623
|
}
|
|
@@ -2112,8 +2170,11 @@ function checkReviewGate(input) {
|
|
|
2112
2170
|
waived: true
|
|
2113
2171
|
};
|
|
2114
2172
|
}
|
|
2115
|
-
const
|
|
2116
|
-
const
|
|
2173
|
+
const verified = input.comments.filter((c) => c.author !== null && PRINCIPAL_ALLOWLIST.includes(c.author));
|
|
2174
|
+
const ignoredCount = input.comments.filter((c) => (c.author === null || !PRINCIPAL_ALLOWLIST.includes(c.author)) && c.body.includes("VERDICT")).length;
|
|
2175
|
+
const verifiedBodies = verified.map((c) => c.body);
|
|
2176
|
+
const codeReview = extractCodeReviewVerdict(verifiedBodies);
|
|
2177
|
+
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
2117
2178
|
const codeReviewClean = codeReview.value === "APPROVE";
|
|
2118
2179
|
const securityClean = security.value === "PASS";
|
|
2119
2180
|
if (codeReviewClean && securityClean) {
|
|
@@ -2128,9 +2189,10 @@ function checkReviewGate(input) {
|
|
|
2128
2189
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
2129
2190
|
if (!securityClean)
|
|
2130
2191
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
2192
|
+
const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
|
|
2131
2193
|
return {
|
|
2132
2194
|
verdict: "fail",
|
|
2133
|
-
reason: `${problems.join("; ")}. A principal can apply an actor-verified \`${WAIVER_LABEL_REVIEW}\` label to skip this requirement, or post the missing/clean verdict comment(s)
|
|
2195
|
+
reason: `${problems.join("; ")}. A principal can apply an actor-verified \`${WAIVER_LABEL_REVIEW}\` label to skip this requirement, or post the missing/clean verdict comment(s).${ignoredNote}`,
|
|
2134
2196
|
waived: false
|
|
2135
2197
|
};
|
|
2136
2198
|
}
|
|
@@ -1514,15 +1514,58 @@ function pointerToPath(p) {
|
|
|
1514
1514
|
return idx === -1 ? p : p.slice(0, idx);
|
|
1515
1515
|
}
|
|
1516
1516
|
var SEPARATOR = /(?:[ \t]*[—–][ \t]*|[ \t]+-[ \t]+)/.source;
|
|
1517
|
-
function
|
|
1518
|
-
const
|
|
1519
|
-
const re = new RegExp(`^[ \\t]
|
|
1517
|
+
function readPointerNoteField(body, field) {
|
|
1518
|
+
const out = [];
|
|
1519
|
+
const re = new RegExp(`^[ \\t]*${field}[ \\t]*:[ \\t]*(.+?)${SEPARATOR}(.+?)[ \\t]*$`, "gim");
|
|
1520
1520
|
for (const m of body.matchAll(re)) {
|
|
1521
|
-
|
|
1521
|
+
out.push({ surface: (m[1] ?? "").trim(), note: (m[2] ?? "").trim() });
|
|
1522
|
+
}
|
|
1523
|
+
return out;
|
|
1524
|
+
}
|
|
1525
|
+
function readDocAcks(body) {
|
|
1526
|
+
return readPointerNoteField(body, "Doc-ack");
|
|
1527
|
+
}
|
|
1528
|
+
function readDocNeutrals(body) {
|
|
1529
|
+
return readPointerNoteField(body, "Doc-neutral");
|
|
1530
|
+
}
|
|
1531
|
+
var LANGUAGE_COMMENT_PREFIXES = [
|
|
1532
|
+
{ test: /\.(ts|tsx|js|jsx|mjs|cjs)$/, prefixes: ["//"] },
|
|
1533
|
+
{ test: /(^|\/)(pre-push|pre-commit|pre-merge-commit)$/, prefixes: ["#"] },
|
|
1534
|
+
{ test: /\.(sh|bash)$/, prefixes: ["#"] },
|
|
1535
|
+
{ test: /\.ya?ml$/, prefixes: ["#"] },
|
|
1536
|
+
{ test: /\.py$/, prefixes: ["#"] }
|
|
1537
|
+
];
|
|
1538
|
+
function commentPrefixesForPath(path) {
|
|
1539
|
+
for (const { test, prefixes } of LANGUAGE_COMMENT_PREFIXES) {
|
|
1540
|
+
if (test.test(path))
|
|
1541
|
+
return prefixes;
|
|
1522
1542
|
}
|
|
1523
|
-
return
|
|
1543
|
+
return [];
|
|
1524
1544
|
}
|
|
1525
|
-
function
|
|
1545
|
+
function isNeutralDiffContentLine(line, prefixes) {
|
|
1546
|
+
const t = line.trim();
|
|
1547
|
+
if (t === "")
|
|
1548
|
+
return true;
|
|
1549
|
+
if (t.startsWith("#!"))
|
|
1550
|
+
return false;
|
|
1551
|
+
return prefixes.some((p) => t.startsWith(p));
|
|
1552
|
+
}
|
|
1553
|
+
function isMechanicallyNeutralDiff(diffText, path) {
|
|
1554
|
+
const prefixes = commentPrefixesForPath(path);
|
|
1555
|
+
let sawChange = false;
|
|
1556
|
+
for (const raw of diffText.split(`
|
|
1557
|
+
`)) {
|
|
1558
|
+
if (raw.startsWith("+++ ") || raw.startsWith("--- ") || raw.startsWith("@@"))
|
|
1559
|
+
continue;
|
|
1560
|
+
if (raw.startsWith("+") || raw.startsWith("-")) {
|
|
1561
|
+
if (!isNeutralDiffContentLine(raw.slice(1), prefixes))
|
|
1562
|
+
return false;
|
|
1563
|
+
sawChange = true;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
return sawChange;
|
|
1567
|
+
}
|
|
1568
|
+
function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive, getDiff) {
|
|
1526
1569
|
const out = { errors: [], notes: [] };
|
|
1527
1570
|
if (docOwnersContent === null)
|
|
1528
1571
|
return out;
|
|
@@ -1535,13 +1578,15 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive)
|
|
|
1535
1578
|
const fired = [];
|
|
1536
1579
|
for (const b of bindings) {
|
|
1537
1580
|
const re = globToRegex(b.glob);
|
|
1538
|
-
|
|
1539
|
-
|
|
1581
|
+
const matchedFiles = codeFiles.filter((f) => re.test(f));
|
|
1582
|
+
if (matchedFiles.length > 0)
|
|
1583
|
+
fired.push({ binding: b, matchedFiles });
|
|
1540
1584
|
}
|
|
1541
1585
|
if (fired.length === 0)
|
|
1542
1586
|
return out;
|
|
1543
1587
|
const acks = readDocAcks(prBody);
|
|
1544
|
-
|
|
1588
|
+
const neutrals = readDocNeutrals(prBody);
|
|
1589
|
+
for (const { binding: b, matchedFiles } of fired) {
|
|
1545
1590
|
if (waiverActive) {
|
|
1546
1591
|
out.notes.push(`C5 doc-waiver active for ${b.pointer} (binding ${DOC_OWNERS_PATH}:${b.lineNum})`);
|
|
1547
1592
|
continue;
|
|
@@ -1558,9 +1603,22 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive)
|
|
|
1558
1603
|
out.errors.push(`C5 doc-owners-dangling: ${DOC_OWNERS_PATH}:${b.lineNum} points to ${b.pointer}, which does not exist on disk. Fix the binding or add the doc.`);
|
|
1559
1604
|
continue;
|
|
1560
1605
|
}
|
|
1561
|
-
if (
|
|
1562
|
-
|
|
1606
|
+
if (changed.includes(pointerPath))
|
|
1607
|
+
continue;
|
|
1608
|
+
const declared = neutrals.find((n) => n.surface === b.pointer);
|
|
1609
|
+
if (declared) {
|
|
1610
|
+
const evidenced = getDiff !== undefined && matchedFiles.every((f) => {
|
|
1611
|
+
const diff = getDiff(f);
|
|
1612
|
+
return diff !== null && isMechanicallyNeutralDiff(diff, f);
|
|
1613
|
+
});
|
|
1614
|
+
if (evidenced) {
|
|
1615
|
+
out.notes.push(`C5 doc-neutral: ${b.pointer} not required for ${DOC_OWNERS_PATH}:${b.lineNum} (glob \`${b.glob}\`) — declared neutral ("${declared.note}"), diff of ${matchedFiles.join(", ")} confirmed comment/whitespace-only.`);
|
|
1616
|
+
continue;
|
|
1617
|
+
}
|
|
1618
|
+
out.errors.push(`C5 doc-neutral-unverified: ${DOC_OWNERS_PATH}:${b.lineNum} (glob \`${b.glob}\` → ${b.pointer}) declared \`Doc-neutral: ${b.pointer} — ${declared.note}\`, but the diff of ${matchedFiles.join(", ")} contains changes beyond comments/whitespace. Update ${pointerPath}, or have a principal apply the \`${WAIVER_LABEL}\` label.`);
|
|
1619
|
+
continue;
|
|
1563
1620
|
}
|
|
1621
|
+
out.errors.push(`C5 doc-coverage: code change matched ${DOC_OWNERS_PATH}:${b.lineNum} (glob \`${b.glob}\` → ${b.pointer}), but ${pointerPath} is not in the PR diff. Update it, declare \`Doc-neutral: ${b.pointer} — <why>\` if the change is genuinely mechanically-neutral, or have a principal apply the \`${WAIVER_LABEL}\` label.`);
|
|
1564
1622
|
}
|
|
1565
1623
|
return out;
|
|
1566
1624
|
}
|
|
@@ -2113,8 +2171,11 @@ function checkReviewGate(input) {
|
|
|
2113
2171
|
waived: true
|
|
2114
2172
|
};
|
|
2115
2173
|
}
|
|
2116
|
-
const
|
|
2117
|
-
const
|
|
2174
|
+
const verified = input.comments.filter((c) => c.author !== null && PRINCIPAL_ALLOWLIST.includes(c.author));
|
|
2175
|
+
const ignoredCount = input.comments.filter((c) => (c.author === null || !PRINCIPAL_ALLOWLIST.includes(c.author)) && c.body.includes("VERDICT")).length;
|
|
2176
|
+
const verifiedBodies = verified.map((c) => c.body);
|
|
2177
|
+
const codeReview = extractCodeReviewVerdict(verifiedBodies);
|
|
2178
|
+
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
2118
2179
|
const codeReviewClean = codeReview.value === "APPROVE";
|
|
2119
2180
|
const securityClean = security.value === "PASS";
|
|
2120
2181
|
if (codeReviewClean && securityClean) {
|
|
@@ -2129,9 +2190,10 @@ function checkReviewGate(input) {
|
|
|
2129
2190
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
2130
2191
|
if (!securityClean)
|
|
2131
2192
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
2193
|
+
const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
|
|
2132
2194
|
return {
|
|
2133
2195
|
verdict: "fail",
|
|
2134
|
-
reason: `${problems.join("; ")}. A principal can apply an actor-verified \`${WAIVER_LABEL_REVIEW}\` label to skip this requirement, or post the missing/clean verdict comment(s)
|
|
2196
|
+
reason: `${problems.join("; ")}. A principal can apply an actor-verified \`${WAIVER_LABEL_REVIEW}\` label to skip this requirement, or post the missing/clean verdict comment(s).${ignoredNote}`,
|
|
2135
2197
|
waived: false
|
|
2136
2198
|
};
|
|
2137
2199
|
}
|
|
@@ -1513,15 +1513,58 @@ function pointerToPath(p) {
|
|
|
1513
1513
|
return idx === -1 ? p : p.slice(0, idx);
|
|
1514
1514
|
}
|
|
1515
1515
|
var SEPARATOR = /(?:[ \t]*[—–][ \t]*|[ \t]+-[ \t]+)/.source;
|
|
1516
|
-
function
|
|
1517
|
-
const
|
|
1518
|
-
const re = new RegExp(`^[ \\t]
|
|
1516
|
+
function readPointerNoteField(body, field) {
|
|
1517
|
+
const out = [];
|
|
1518
|
+
const re = new RegExp(`^[ \\t]*${field}[ \\t]*:[ \\t]*(.+?)${SEPARATOR}(.+?)[ \\t]*$`, "gim");
|
|
1519
1519
|
for (const m of body.matchAll(re)) {
|
|
1520
|
-
|
|
1520
|
+
out.push({ surface: (m[1] ?? "").trim(), note: (m[2] ?? "").trim() });
|
|
1521
|
+
}
|
|
1522
|
+
return out;
|
|
1523
|
+
}
|
|
1524
|
+
function readDocAcks(body) {
|
|
1525
|
+
return readPointerNoteField(body, "Doc-ack");
|
|
1526
|
+
}
|
|
1527
|
+
function readDocNeutrals(body) {
|
|
1528
|
+
return readPointerNoteField(body, "Doc-neutral");
|
|
1529
|
+
}
|
|
1530
|
+
var LANGUAGE_COMMENT_PREFIXES = [
|
|
1531
|
+
{ test: /\.(ts|tsx|js|jsx|mjs|cjs)$/, prefixes: ["//"] },
|
|
1532
|
+
{ test: /(^|\/)(pre-push|pre-commit|pre-merge-commit)$/, prefixes: ["#"] },
|
|
1533
|
+
{ test: /\.(sh|bash)$/, prefixes: ["#"] },
|
|
1534
|
+
{ test: /\.ya?ml$/, prefixes: ["#"] },
|
|
1535
|
+
{ test: /\.py$/, prefixes: ["#"] }
|
|
1536
|
+
];
|
|
1537
|
+
function commentPrefixesForPath(path) {
|
|
1538
|
+
for (const { test, prefixes } of LANGUAGE_COMMENT_PREFIXES) {
|
|
1539
|
+
if (test.test(path))
|
|
1540
|
+
return prefixes;
|
|
1521
1541
|
}
|
|
1522
|
-
return
|
|
1542
|
+
return [];
|
|
1523
1543
|
}
|
|
1524
|
-
function
|
|
1544
|
+
function isNeutralDiffContentLine(line, prefixes) {
|
|
1545
|
+
const t = line.trim();
|
|
1546
|
+
if (t === "")
|
|
1547
|
+
return true;
|
|
1548
|
+
if (t.startsWith("#!"))
|
|
1549
|
+
return false;
|
|
1550
|
+
return prefixes.some((p) => t.startsWith(p));
|
|
1551
|
+
}
|
|
1552
|
+
function isMechanicallyNeutralDiff(diffText, path) {
|
|
1553
|
+
const prefixes = commentPrefixesForPath(path);
|
|
1554
|
+
let sawChange = false;
|
|
1555
|
+
for (const raw of diffText.split(`
|
|
1556
|
+
`)) {
|
|
1557
|
+
if (raw.startsWith("+++ ") || raw.startsWith("--- ") || raw.startsWith("@@"))
|
|
1558
|
+
continue;
|
|
1559
|
+
if (raw.startsWith("+") || raw.startsWith("-")) {
|
|
1560
|
+
if (!isNeutralDiffContentLine(raw.slice(1), prefixes))
|
|
1561
|
+
return false;
|
|
1562
|
+
sawChange = true;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
return sawChange;
|
|
1566
|
+
}
|
|
1567
|
+
function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive, getDiff) {
|
|
1525
1568
|
const out = { errors: [], notes: [] };
|
|
1526
1569
|
if (docOwnersContent === null)
|
|
1527
1570
|
return out;
|
|
@@ -1534,13 +1577,15 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive)
|
|
|
1534
1577
|
const fired = [];
|
|
1535
1578
|
for (const b of bindings) {
|
|
1536
1579
|
const re = globToRegex(b.glob);
|
|
1537
|
-
|
|
1538
|
-
|
|
1580
|
+
const matchedFiles = codeFiles.filter((f) => re.test(f));
|
|
1581
|
+
if (matchedFiles.length > 0)
|
|
1582
|
+
fired.push({ binding: b, matchedFiles });
|
|
1539
1583
|
}
|
|
1540
1584
|
if (fired.length === 0)
|
|
1541
1585
|
return out;
|
|
1542
1586
|
const acks = readDocAcks(prBody);
|
|
1543
|
-
|
|
1587
|
+
const neutrals = readDocNeutrals(prBody);
|
|
1588
|
+
for (const { binding: b, matchedFiles } of fired) {
|
|
1544
1589
|
if (waiverActive) {
|
|
1545
1590
|
out.notes.push(`C5 doc-waiver active for ${b.pointer} (binding ${DOC_OWNERS_PATH}:${b.lineNum})`);
|
|
1546
1591
|
continue;
|
|
@@ -1557,9 +1602,22 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive)
|
|
|
1557
1602
|
out.errors.push(`C5 doc-owners-dangling: ${DOC_OWNERS_PATH}:${b.lineNum} points to ${b.pointer}, which does not exist on disk. Fix the binding or add the doc.`);
|
|
1558
1603
|
continue;
|
|
1559
1604
|
}
|
|
1560
|
-
if (
|
|
1561
|
-
|
|
1605
|
+
if (changed.includes(pointerPath))
|
|
1606
|
+
continue;
|
|
1607
|
+
const declared = neutrals.find((n) => n.surface === b.pointer);
|
|
1608
|
+
if (declared) {
|
|
1609
|
+
const evidenced = getDiff !== undefined && matchedFiles.every((f) => {
|
|
1610
|
+
const diff = getDiff(f);
|
|
1611
|
+
return diff !== null && isMechanicallyNeutralDiff(diff, f);
|
|
1612
|
+
});
|
|
1613
|
+
if (evidenced) {
|
|
1614
|
+
out.notes.push(`C5 doc-neutral: ${b.pointer} not required for ${DOC_OWNERS_PATH}:${b.lineNum} (glob \`${b.glob}\`) — declared neutral ("${declared.note}"), diff of ${matchedFiles.join(", ")} confirmed comment/whitespace-only.`);
|
|
1615
|
+
continue;
|
|
1616
|
+
}
|
|
1617
|
+
out.errors.push(`C5 doc-neutral-unverified: ${DOC_OWNERS_PATH}:${b.lineNum} (glob \`${b.glob}\` → ${b.pointer}) declared \`Doc-neutral: ${b.pointer} — ${declared.note}\`, but the diff of ${matchedFiles.join(", ")} contains changes beyond comments/whitespace. Update ${pointerPath}, or have a principal apply the \`${WAIVER_LABEL}\` label.`);
|
|
1618
|
+
continue;
|
|
1562
1619
|
}
|
|
1620
|
+
out.errors.push(`C5 doc-coverage: code change matched ${DOC_OWNERS_PATH}:${b.lineNum} (glob \`${b.glob}\` → ${b.pointer}), but ${pointerPath} is not in the PR diff. Update it, declare \`Doc-neutral: ${b.pointer} — <why>\` if the change is genuinely mechanically-neutral, or have a principal apply the \`${WAIVER_LABEL}\` label.`);
|
|
1563
1621
|
}
|
|
1564
1622
|
return out;
|
|
1565
1623
|
}
|
|
@@ -2112,8 +2170,11 @@ function checkReviewGate(input) {
|
|
|
2112
2170
|
waived: true
|
|
2113
2171
|
};
|
|
2114
2172
|
}
|
|
2115
|
-
const
|
|
2116
|
-
const
|
|
2173
|
+
const verified = input.comments.filter((c) => c.author !== null && PRINCIPAL_ALLOWLIST.includes(c.author));
|
|
2174
|
+
const ignoredCount = input.comments.filter((c) => (c.author === null || !PRINCIPAL_ALLOWLIST.includes(c.author)) && c.body.includes("VERDICT")).length;
|
|
2175
|
+
const verifiedBodies = verified.map((c) => c.body);
|
|
2176
|
+
const codeReview = extractCodeReviewVerdict(verifiedBodies);
|
|
2177
|
+
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
2117
2178
|
const codeReviewClean = codeReview.value === "APPROVE";
|
|
2118
2179
|
const securityClean = security.value === "PASS";
|
|
2119
2180
|
if (codeReviewClean && securityClean) {
|
|
@@ -2128,9 +2189,10 @@ function checkReviewGate(input) {
|
|
|
2128
2189
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
2129
2190
|
if (!securityClean)
|
|
2130
2191
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
2192
|
+
const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
|
|
2131
2193
|
return {
|
|
2132
2194
|
verdict: "fail",
|
|
2133
|
-
reason: `${problems.join("; ")}. A principal can apply an actor-verified \`${WAIVER_LABEL_REVIEW}\` label to skip this requirement, or post the missing/clean verdict comment(s)
|
|
2195
|
+
reason: `${problems.join("; ")}. A principal can apply an actor-verified \`${WAIVER_LABEL_REVIEW}\` label to skip this requirement, or post the missing/clean verdict comment(s).${ignoredNote}`,
|
|
2134
2196
|
waived: false
|
|
2135
2197
|
};
|
|
2136
2198
|
}
|
|
@@ -1514,15 +1514,58 @@ function pointerToPath(p) {
|
|
|
1514
1514
|
return idx === -1 ? p : p.slice(0, idx);
|
|
1515
1515
|
}
|
|
1516
1516
|
var SEPARATOR = /(?:[ \t]*[—–][ \t]*|[ \t]+-[ \t]+)/.source;
|
|
1517
|
-
function
|
|
1518
|
-
const
|
|
1519
|
-
const re = new RegExp(`^[ \\t]
|
|
1517
|
+
function readPointerNoteField(body, field) {
|
|
1518
|
+
const out = [];
|
|
1519
|
+
const re = new RegExp(`^[ \\t]*${field}[ \\t]*:[ \\t]*(.+?)${SEPARATOR}(.+?)[ \\t]*$`, "gim");
|
|
1520
1520
|
for (const m of body.matchAll(re)) {
|
|
1521
|
-
|
|
1521
|
+
out.push({ surface: (m[1] ?? "").trim(), note: (m[2] ?? "").trim() });
|
|
1522
|
+
}
|
|
1523
|
+
return out;
|
|
1524
|
+
}
|
|
1525
|
+
function readDocAcks(body) {
|
|
1526
|
+
return readPointerNoteField(body, "Doc-ack");
|
|
1527
|
+
}
|
|
1528
|
+
function readDocNeutrals(body) {
|
|
1529
|
+
return readPointerNoteField(body, "Doc-neutral");
|
|
1530
|
+
}
|
|
1531
|
+
var LANGUAGE_COMMENT_PREFIXES = [
|
|
1532
|
+
{ test: /\.(ts|tsx|js|jsx|mjs|cjs)$/, prefixes: ["//"] },
|
|
1533
|
+
{ test: /(^|\/)(pre-push|pre-commit|pre-merge-commit)$/, prefixes: ["#"] },
|
|
1534
|
+
{ test: /\.(sh|bash)$/, prefixes: ["#"] },
|
|
1535
|
+
{ test: /\.ya?ml$/, prefixes: ["#"] },
|
|
1536
|
+
{ test: /\.py$/, prefixes: ["#"] }
|
|
1537
|
+
];
|
|
1538
|
+
function commentPrefixesForPath(path) {
|
|
1539
|
+
for (const { test, prefixes } of LANGUAGE_COMMENT_PREFIXES) {
|
|
1540
|
+
if (test.test(path))
|
|
1541
|
+
return prefixes;
|
|
1522
1542
|
}
|
|
1523
|
-
return
|
|
1543
|
+
return [];
|
|
1524
1544
|
}
|
|
1525
|
-
function
|
|
1545
|
+
function isNeutralDiffContentLine(line, prefixes) {
|
|
1546
|
+
const t = line.trim();
|
|
1547
|
+
if (t === "")
|
|
1548
|
+
return true;
|
|
1549
|
+
if (t.startsWith("#!"))
|
|
1550
|
+
return false;
|
|
1551
|
+
return prefixes.some((p) => t.startsWith(p));
|
|
1552
|
+
}
|
|
1553
|
+
function isMechanicallyNeutralDiff(diffText, path) {
|
|
1554
|
+
const prefixes = commentPrefixesForPath(path);
|
|
1555
|
+
let sawChange = false;
|
|
1556
|
+
for (const raw of diffText.split(`
|
|
1557
|
+
`)) {
|
|
1558
|
+
if (raw.startsWith("+++ ") || raw.startsWith("--- ") || raw.startsWith("@@"))
|
|
1559
|
+
continue;
|
|
1560
|
+
if (raw.startsWith("+") || raw.startsWith("-")) {
|
|
1561
|
+
if (!isNeutralDiffContentLine(raw.slice(1), prefixes))
|
|
1562
|
+
return false;
|
|
1563
|
+
sawChange = true;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
return sawChange;
|
|
1567
|
+
}
|
|
1568
|
+
function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive, getDiff) {
|
|
1526
1569
|
const out = { errors: [], notes: [] };
|
|
1527
1570
|
if (docOwnersContent === null)
|
|
1528
1571
|
return out;
|
|
@@ -1535,13 +1578,15 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive)
|
|
|
1535
1578
|
const fired = [];
|
|
1536
1579
|
for (const b of bindings) {
|
|
1537
1580
|
const re = globToRegex(b.glob);
|
|
1538
|
-
|
|
1539
|
-
|
|
1581
|
+
const matchedFiles = codeFiles.filter((f) => re.test(f));
|
|
1582
|
+
if (matchedFiles.length > 0)
|
|
1583
|
+
fired.push({ binding: b, matchedFiles });
|
|
1540
1584
|
}
|
|
1541
1585
|
if (fired.length === 0)
|
|
1542
1586
|
return out;
|
|
1543
1587
|
const acks = readDocAcks(prBody);
|
|
1544
|
-
|
|
1588
|
+
const neutrals = readDocNeutrals(prBody);
|
|
1589
|
+
for (const { binding: b, matchedFiles } of fired) {
|
|
1545
1590
|
if (waiverActive) {
|
|
1546
1591
|
out.notes.push(`C5 doc-waiver active for ${b.pointer} (binding ${DOC_OWNERS_PATH}:${b.lineNum})`);
|
|
1547
1592
|
continue;
|
|
@@ -1558,9 +1603,22 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive)
|
|
|
1558
1603
|
out.errors.push(`C5 doc-owners-dangling: ${DOC_OWNERS_PATH}:${b.lineNum} points to ${b.pointer}, which does not exist on disk. Fix the binding or add the doc.`);
|
|
1559
1604
|
continue;
|
|
1560
1605
|
}
|
|
1561
|
-
if (
|
|
1562
|
-
|
|
1606
|
+
if (changed.includes(pointerPath))
|
|
1607
|
+
continue;
|
|
1608
|
+
const declared = neutrals.find((n) => n.surface === b.pointer);
|
|
1609
|
+
if (declared) {
|
|
1610
|
+
const evidenced = getDiff !== undefined && matchedFiles.every((f) => {
|
|
1611
|
+
const diff = getDiff(f);
|
|
1612
|
+
return diff !== null && isMechanicallyNeutralDiff(diff, f);
|
|
1613
|
+
});
|
|
1614
|
+
if (evidenced) {
|
|
1615
|
+
out.notes.push(`C5 doc-neutral: ${b.pointer} not required for ${DOC_OWNERS_PATH}:${b.lineNum} (glob \`${b.glob}\`) — declared neutral ("${declared.note}"), diff of ${matchedFiles.join(", ")} confirmed comment/whitespace-only.`);
|
|
1616
|
+
continue;
|
|
1617
|
+
}
|
|
1618
|
+
out.errors.push(`C5 doc-neutral-unverified: ${DOC_OWNERS_PATH}:${b.lineNum} (glob \`${b.glob}\` → ${b.pointer}) declared \`Doc-neutral: ${b.pointer} — ${declared.note}\`, but the diff of ${matchedFiles.join(", ")} contains changes beyond comments/whitespace. Update ${pointerPath}, or have a principal apply the \`${WAIVER_LABEL}\` label.`);
|
|
1619
|
+
continue;
|
|
1563
1620
|
}
|
|
1621
|
+
out.errors.push(`C5 doc-coverage: code change matched ${DOC_OWNERS_PATH}:${b.lineNum} (glob \`${b.glob}\` → ${b.pointer}), but ${pointerPath} is not in the PR diff. Update it, declare \`Doc-neutral: ${b.pointer} — <why>\` if the change is genuinely mechanically-neutral, or have a principal apply the \`${WAIVER_LABEL}\` label.`);
|
|
1564
1622
|
}
|
|
1565
1623
|
return out;
|
|
1566
1624
|
}
|
|
@@ -2113,8 +2171,11 @@ function checkReviewGate(input) {
|
|
|
2113
2171
|
waived: true
|
|
2114
2172
|
};
|
|
2115
2173
|
}
|
|
2116
|
-
const
|
|
2117
|
-
const
|
|
2174
|
+
const verified = input.comments.filter((c) => c.author !== null && PRINCIPAL_ALLOWLIST.includes(c.author));
|
|
2175
|
+
const ignoredCount = input.comments.filter((c) => (c.author === null || !PRINCIPAL_ALLOWLIST.includes(c.author)) && c.body.includes("VERDICT")).length;
|
|
2176
|
+
const verifiedBodies = verified.map((c) => c.body);
|
|
2177
|
+
const codeReview = extractCodeReviewVerdict(verifiedBodies);
|
|
2178
|
+
const security = extractSecurityReviewVerdict(verifiedBodies);
|
|
2118
2179
|
const codeReviewClean = codeReview.value === "APPROVE";
|
|
2119
2180
|
const securityClean = security.value === "PASS";
|
|
2120
2181
|
if (codeReviewClean && securityClean) {
|
|
@@ -2129,9 +2190,10 @@ function checkReviewGate(input) {
|
|
|
2129
2190
|
problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
|
|
2130
2191
|
if (!securityClean)
|
|
2131
2192
|
problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
|
|
2193
|
+
const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
|
|
2132
2194
|
return {
|
|
2133
2195
|
verdict: "fail",
|
|
2134
|
-
reason: `${problems.join("; ")}. A principal can apply an actor-verified \`${WAIVER_LABEL_REVIEW}\` label to skip this requirement, or post the missing/clean verdict comment(s)
|
|
2196
|
+
reason: `${problems.join("; ")}. A principal can apply an actor-verified \`${WAIVER_LABEL_REVIEW}\` label to skip this requirement, or post the missing/clean verdict comment(s).${ignoredNote}`,
|
|
2135
2197
|
waived: false
|
|
2136
2198
|
};
|
|
2137
2199
|
}
|