@attalabs/vinaya 0.12.0 → 0.17.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.
Files changed (128) hide show
  1. package/README.md +33 -3
  2. package/aeg-root/contracts/planner-brief.md +1 -1
  3. package/aeg-root/enforcement.md +5 -5
  4. package/aeg-root/roles/developer.md +1 -0
  5. package/aeg-root/roles/planner.md +2 -2
  6. package/aeg-root/roles/reviewer.md +17 -4
  7. package/aeg-root/roles/security.md +16 -5
  8. package/aeg-root/skills/aeg/SKILL.md +1 -1
  9. package/aeg-root/state-machine.md +2 -2
  10. package/aeg-root/templates/pr-report-template.md +2 -0
  11. package/aeg-root/tranche-model.md +1 -1
  12. package/dist/checks/bin/check-body-bare-digits.js +3545 -0
  13. package/dist/checks/bin/check-branch-topology.js +99 -10
  14. package/dist/checks/bin/check-brief-shape.js +99 -10
  15. package/dist/checks/bin/check-closes-n.js +99 -10
  16. package/dist/checks/bin/check-coherence.js +106 -12
  17. package/dist/checks/bin/check-dead-branch-push.js +99 -10
  18. package/dist/checks/bin/check-dispatch-readiness.js +106 -12
  19. package/dist/checks/bin/check-doc-coverage-push.js +106 -12
  20. package/dist/checks/bin/check-doc-coverage.js +109 -16
  21. package/dist/checks/bin/check-evidence-fresh.js +99 -10
  22. package/dist/checks/bin/check-first-push-dispatch.js +106 -12
  23. package/dist/checks/bin/check-issue-assignment.js +99 -10
  24. package/dist/checks/bin/check-no-disk-state.js +99 -10
  25. package/dist/checks/bin/check-reader-resolvable-prose.js +99 -10
  26. package/dist/checks/bin/check-registry-gates.js +99 -10
  27. package/dist/checks/bin/check-review-gate.js +109 -25
  28. package/dist/checks/bin/check-single-plan-pr.js +99 -10
  29. package/dist/checks/bin/check-test-plan.js +99 -10
  30. package/dist/index.js +1325 -236
  31. package/package.json +1 -1
  32. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  33. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  34. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  35. package/studio-standalone/apps/vinaya-studio/web/.next/required-server-files.json +1 -1
  36. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  37. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  38. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  39. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  40. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  41. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  42. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  43. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  44. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
  45. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  46. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  47. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  48. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  49. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js +1 -1
  50. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  51. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  52. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  53. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js +1 -1
  54. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  55. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  56. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  57. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  58. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  59. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  60. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js +1 -1
  61. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  62. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  63. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  64. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js +1 -1
  65. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  66. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  67. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  68. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js +1 -1
  69. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  70. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  71. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  72. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  73. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  74. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/[root-of-the-server]__0e95dv5._.js +1 -1
  75. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/1q96_modules_@clerk_nextjs_dist_esm_app-router_client_keyless-creator-reader_0lom2js.js +1 -1
  76. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__029-1ch._.js +1 -1
  77. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0gw1_g2._.js +1 -1
  78. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0q56ohl._.js +1 -1
  79. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0rr_lq7._.js +1 -1
  80. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0zfjc2o._.js +1 -1
  81. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__19jyp3m._.js +1 -1
  82. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1_ojzp1._.js +1 -1
  83. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__1wsl6ro._.js → [root-of-the-server]__1ccgndq._.js} +1 -1
  84. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1d_8nm4._.js +1 -1
  85. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1dfzjrd._.js +1 -1
  86. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1zkwtx8._.js +1 -1
  87. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__210ehg9._.js +1 -1
  88. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0-n77dx._.js +1 -1
  89. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_00yjcv1._.js +1 -1
  90. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_01605g7._.js +1 -1
  91. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0o54suy._.js +1 -1
  92. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0rix02i._.js +1 -1
  93. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_1u3abhy._.js → _0x999pb._.js} +2 -2
  94. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_0ndy563._.js → _1-6rzip._.js} +2 -2
  95. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_18-d2be._.js +1 -1
  96. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_087xa25._.js → _1_talrs._.js} +2 -2
  97. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_11vnfnm._.js → _1ar5uqe._.js} +2 -2
  98. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_02drwa_._.js → _1c9b9j-._.js} +2 -2
  99. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1fqw88f._.js +1 -1
  100. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1gw6vc9._.js +1 -1
  101. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/apps_vinaya-studio_web_src_app_studio_18kqbvs._.js +1 -1
  102. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/apps_vinaya-studio_web_src_app_studio_projects_[name]_tranches_[slug]_20yap_f._.js +1 -1
  103. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  104. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/packages_ui_libraries_animate_components_index_ts_186fje1._.js +1 -1
  105. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/packages_ui_libraries_basic_components_index_ts_0xk1vda._.js +1 -1
  106. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  107. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  108. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  109. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  110. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1mexqnauhtbtp.js → 0_jnwbarv-cqk.js} +1 -1
  111. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{2ieu4oi-pp_bz.js → 0l_vubs1ephe5.js} +1 -1
  112. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/0ojfbn3yvy-8_.js +1 -0
  113. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1jx5_q1g9l-cm.js → 0phqfqwbnt_ey.js} +1 -1
  114. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0qcu17x3h94a9.js → 17r0szvqeeyhx.js} +1 -1
  115. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0ve1-nz-mu_lr.js → 18x3rbqzeutpz.js} +1 -1
  116. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{22y4d5yrovcv-.js → 1bqpfvromtwn1.js} +1 -1
  117. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{39w6jbw3rleez.js → 24ojyzmt_fkg3.js} +1 -1
  118. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/27sqffqm2wbxx.css +1 -0
  119. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0tmcnl-z8yt2_.js → 30lyvn0yzwdiz.js} +1 -1
  120. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1khy4oilrab6g.js → 3m0_gk59s-rbx.js} +1 -1
  121. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1eo32v1tbjy7c.js → 40x7f5hm7a5tk.js} +1 -1
  122. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0-08g_jjalnd9.js → 453zhecht_m1q.js} +1 -1
  123. package/studio-standalone/apps/vinaya-studio/web/server.js +1 -1
  124. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/0qu0xgvifbacu.css +0 -1
  125. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1t7yy_s34022p.js +0 -1
  126. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{ZHVE9Y9G8XKR91g27SgVX → p5sWGp4ANwORiaX4Kk6U2}/_buildManifest.js +0 -0
  127. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{ZHVE9Y9G8XKR91g27SgVX → p5sWGp4ANwORiaX4Kk6U2}/_clientMiddlewareManifest.js +0 -0
  128. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{ZHVE9Y9G8XKR91g27SgVX → p5sWGp4ANwORiaX4Kk6U2}/_ssgManifest.js +0 -0
@@ -7,7 +7,27 @@ import { fileURLToPath } from "node:url";
7
7
  // ../../packages/aeg-forge-state/src/strip-code.ts
8
8
  function maskCode(body) {
9
9
  const fill = (line) => " ".repeat(line.length);
10
- return maskIndentedCode(maskFencedCode(body, fill), fill).replace(/(`+)[^\n]*?\1/g, (m) => " ".repeat(m.length));
10
+ const blocksMasked = maskIndentedCode(maskFencedCode(body, fill), fill);
11
+ return blocksMasked.split(`
12
+ `).map((line) => replaceInlineSpans(line, (span) => " ".repeat(span.length))).join(`
13
+ `);
14
+ }
15
+ var DETAILS_OPEN = /<details\b[^>]*>/gi;
16
+ var DETAILS_CLOSE = /<\/details\s*>/gi;
17
+ function maskDetailsBlocks(body) {
18
+ const fill = (line) => " ".repeat(line.length);
19
+ const lines = body.split(`
20
+ `);
21
+ let depth = 0;
22
+ const out = lines.map((line) => {
23
+ const opens = line.match(DETAILS_OPEN)?.length ?? 0;
24
+ const closes = line.match(DETAILS_CLOSE)?.length ?? 0;
25
+ const maskThisLine = depth > 0 || opens > 0;
26
+ depth = Math.max(0, depth + opens - closes);
27
+ return maskThisLine ? fill(line) : line;
28
+ });
29
+ return out.join(`
30
+ `);
11
31
  }
12
32
  function stripCode(body, options = {}) {
13
33
  const normalised = body.replace(/\r\n?/g, `
@@ -16,7 +36,50 @@ function stripCode(body, options = {}) {
16
36
  const blocksStripped = maskIndentedCode(maskFencedCode(normalised, blank), blank);
17
37
  if (options.inlineSpans === "keep")
18
38
  return blocksStripped;
19
- return blocksStripped.replace(/(`+)[^\n]*?\1/g, "");
39
+ return blocksStripped.split(`
40
+ `).map((line) => replaceInlineSpans(line, () => "")).join(`
41
+ `);
42
+ }
43
+ function replaceInlineSpans(line, replace) {
44
+ let result = "";
45
+ let i = 0;
46
+ const runLengthAt = (at) => {
47
+ let j = at;
48
+ while (j < line.length && line[j] === "`")
49
+ j++;
50
+ return j - at;
51
+ };
52
+ while (i < line.length) {
53
+ if (line[i] !== "`") {
54
+ result += line[i];
55
+ i++;
56
+ continue;
57
+ }
58
+ const openLen = runLengthAt(i);
59
+ const openEnd = i + openLen;
60
+ let j = openEnd;
61
+ let closeEnd = -1;
62
+ while (j < line.length) {
63
+ if (line[j] !== "`") {
64
+ j++;
65
+ continue;
66
+ }
67
+ const runLen = runLengthAt(j);
68
+ if (runLen === openLen) {
69
+ closeEnd = j + runLen;
70
+ break;
71
+ }
72
+ j += runLen;
73
+ }
74
+ if (closeEnd === -1) {
75
+ result += line.slice(i, openEnd);
76
+ i = openEnd;
77
+ } else {
78
+ result += replace(line.slice(i, closeEnd));
79
+ i = closeEnd;
80
+ }
81
+ }
82
+ return result;
20
83
  }
21
84
  var FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})([^\r\n]*)\r?$/;
22
85
  var FENCE_CLOSE = /^ {0,3}(`+|~+)[ \t]*\r?$/;
@@ -1755,9 +1818,10 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive,
1755
1818
  return out;
1756
1819
  }
1757
1820
  // ../../packages/aeg-core/src/pr-tier.ts
1821
+ var TIER_FIELD = /(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i;
1758
1822
  function readTierFromPrBody(prBody) {
1759
1823
  const searchIn = anchoredRegion(prBody, "TIER") ?? prBody;
1760
- const m = searchIn.match(/(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i);
1824
+ const m = searchIn.match(TIER_FIELD);
1761
1825
  if (!m)
1762
1826
  return null;
1763
1827
  const t = Number(m[4]);
@@ -2370,15 +2434,25 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
2370
2434
  return { ok: true, expectedIssue };
2371
2435
  }
2372
2436
  // ../../packages/aeg-core/src/verdict-extraction.ts
2437
+ var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
2438
+ function extractHeadSha(comment) {
2439
+ const m = comment.match(HEAD_SHA_PATTERN);
2440
+ return m ? m[1].toLowerCase() : null;
2441
+ }
2373
2442
  function extractVerdict(comments, valuePattern, missingLabel) {
2374
2443
  const clearHits = comments.filter((c) => valuePattern.test(c));
2375
2444
  if (clearHits.length > 0) {
2376
2445
  const latest = clearHits[clearHits.length - 1];
2377
2446
  const m = latest.match(valuePattern);
2378
- return { value: m[1].toUpperCase().replace(/[_-]/g, " "), danglingNote: null };
2447
+ return {
2448
+ value: m[1].toUpperCase().replace(/[_-]/g, " "),
2449
+ headSha: extractHeadSha(latest),
2450
+ danglingNote: null
2451
+ };
2379
2452
  }
2380
2453
  return {
2381
2454
  value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
2455
+ headSha: null,
2382
2456
  danglingNote: `no ${missingLabel} verdict comment found on this PR`
2383
2457
  };
2384
2458
  }
@@ -2389,8 +2463,10 @@ function extractSecurityReviewVerdict(comments) {
2389
2463
  return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
2390
2464
  }
2391
2465
  // ../../packages/aeg-core/src/review-gate.ts
2392
- function isReviewGateExemptBranch(branch) {
2393
- return branch.startsWith("plan/");
2466
+ function isBoundToHead(extraction, headSha) {
2467
+ if (!extraction.headSha)
2468
+ return false;
2469
+ return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
2394
2470
  }
2395
2471
  function checkReviewGate(input) {
2396
2472
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
@@ -2414,18 +2490,26 @@ function checkReviewGate(input) {
2414
2490
  const security = extractSecurityReviewVerdict(verifiedBodies);
2415
2491
  const codeReviewClean = codeReview.value === "APPROVE";
2416
2492
  const securityClean = security.value === "PASS";
2417
- if (codeReviewClean && securityClean) {
2493
+ const codeReviewBound = isBoundToHead(codeReview, input.headSha);
2494
+ const securityBound = isBoundToHead(security, input.headSha);
2495
+ if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
2418
2496
  return {
2419
2497
  verdict: "pass",
2420
- reason: "code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS.",
2498
+ reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
2421
2499
  waived: false
2422
2500
  };
2423
2501
  }
2424
2502
  const problems = [];
2425
- if (!codeReviewClean)
2503
+ if (!codeReviewClean) {
2426
2504
  problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
2427
- if (!securityClean)
2505
+ } else if (!codeReviewBound) {
2506
+ problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2507
+ }
2508
+ if (!securityClean) {
2428
2509
  problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
2510
+ } else if (!securityBound) {
2511
+ problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2512
+ }
2429
2513
  const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
2430
2514
  return {
2431
2515
  verdict: "fail",
@@ -2433,6 +2517,11 @@ function checkReviewGate(input) {
2433
2517
  waived: false
2434
2518
  };
2435
2519
  }
2520
+ var CHANGESET_RELEASE_BRANCH = "changeset-release/main";
2521
+ var DEFAULT_RELEASE_ACTOR = "github-actions[bot]";
2522
+ function isChangesetsReleasePr(branch, author, expectedAuthor) {
2523
+ return branch === CHANGESET_RELEASE_BRANCH && author === expectedAuthor;
2524
+ }
2436
2525
  // ../../packages/aeg-core/src/markdown-table.ts
2437
2526
  function splitRow(line) {
2438
2527
  const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
@@ -7,7 +7,27 @@ import matter2 from "gray-matter";
7
7
  // ../../packages/aeg-forge-state/src/strip-code.ts
8
8
  function maskCode(body) {
9
9
  const fill = (line) => " ".repeat(line.length);
10
- return maskIndentedCode(maskFencedCode(body, fill), fill).replace(/(`+)[^\n]*?\1/g, (m) => " ".repeat(m.length));
10
+ const blocksMasked = maskIndentedCode(maskFencedCode(body, fill), fill);
11
+ return blocksMasked.split(`
12
+ `).map((line) => replaceInlineSpans(line, (span) => " ".repeat(span.length))).join(`
13
+ `);
14
+ }
15
+ var DETAILS_OPEN = /<details\b[^>]*>/gi;
16
+ var DETAILS_CLOSE = /<\/details\s*>/gi;
17
+ function maskDetailsBlocks(body) {
18
+ const fill = (line) => " ".repeat(line.length);
19
+ const lines = body.split(`
20
+ `);
21
+ let depth = 0;
22
+ const out = lines.map((line) => {
23
+ const opens = line.match(DETAILS_OPEN)?.length ?? 0;
24
+ const closes = line.match(DETAILS_CLOSE)?.length ?? 0;
25
+ const maskThisLine = depth > 0 || opens > 0;
26
+ depth = Math.max(0, depth + opens - closes);
27
+ return maskThisLine ? fill(line) : line;
28
+ });
29
+ return out.join(`
30
+ `);
11
31
  }
12
32
  function stripCode(body, options = {}) {
13
33
  const normalised = body.replace(/\r\n?/g, `
@@ -16,7 +36,50 @@ function stripCode(body, options = {}) {
16
36
  const blocksStripped = maskIndentedCode(maskFencedCode(normalised, blank), blank);
17
37
  if (options.inlineSpans === "keep")
18
38
  return blocksStripped;
19
- return blocksStripped.replace(/(`+)[^\n]*?\1/g, "");
39
+ return blocksStripped.split(`
40
+ `).map((line) => replaceInlineSpans(line, () => "")).join(`
41
+ `);
42
+ }
43
+ function replaceInlineSpans(line, replace) {
44
+ let result = "";
45
+ let i = 0;
46
+ const runLengthAt = (at) => {
47
+ let j = at;
48
+ while (j < line.length && line[j] === "`")
49
+ j++;
50
+ return j - at;
51
+ };
52
+ while (i < line.length) {
53
+ if (line[i] !== "`") {
54
+ result += line[i];
55
+ i++;
56
+ continue;
57
+ }
58
+ const openLen = runLengthAt(i);
59
+ const openEnd = i + openLen;
60
+ let j = openEnd;
61
+ let closeEnd = -1;
62
+ while (j < line.length) {
63
+ if (line[j] !== "`") {
64
+ j++;
65
+ continue;
66
+ }
67
+ const runLen = runLengthAt(j);
68
+ if (runLen === openLen) {
69
+ closeEnd = j + runLen;
70
+ break;
71
+ }
72
+ j += runLen;
73
+ }
74
+ if (closeEnd === -1) {
75
+ result += line.slice(i, openEnd);
76
+ i = openEnd;
77
+ } else {
78
+ result += replace(line.slice(i, closeEnd));
79
+ i = closeEnd;
80
+ }
81
+ }
82
+ return result;
20
83
  }
21
84
  var FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})([^\r\n]*)\r?$/;
22
85
  var FENCE_CLOSE = /^ {0,3}(`+|~+)[ \t]*\r?$/;
@@ -1755,9 +1818,10 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive,
1755
1818
  return out;
1756
1819
  }
1757
1820
  // ../../packages/aeg-core/src/pr-tier.ts
1821
+ var TIER_FIELD = /(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i;
1758
1822
  function readTierFromPrBody(prBody) {
1759
1823
  const searchIn = anchoredRegion(prBody, "TIER") ?? prBody;
1760
- const m = searchIn.match(/(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i);
1824
+ const m = searchIn.match(TIER_FIELD);
1761
1825
  if (!m)
1762
1826
  return null;
1763
1827
  const t = Number(m[4]);
@@ -2370,15 +2434,25 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
2370
2434
  return { ok: true, expectedIssue };
2371
2435
  }
2372
2436
  // ../../packages/aeg-core/src/verdict-extraction.ts
2437
+ var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
2438
+ function extractHeadSha(comment) {
2439
+ const m = comment.match(HEAD_SHA_PATTERN);
2440
+ return m ? m[1].toLowerCase() : null;
2441
+ }
2373
2442
  function extractVerdict(comments, valuePattern, missingLabel) {
2374
2443
  const clearHits = comments.filter((c) => valuePattern.test(c));
2375
2444
  if (clearHits.length > 0) {
2376
2445
  const latest = clearHits[clearHits.length - 1];
2377
2446
  const m = latest.match(valuePattern);
2378
- return { value: m[1].toUpperCase().replace(/[_-]/g, " "), danglingNote: null };
2447
+ return {
2448
+ value: m[1].toUpperCase().replace(/[_-]/g, " "),
2449
+ headSha: extractHeadSha(latest),
2450
+ danglingNote: null
2451
+ };
2379
2452
  }
2380
2453
  return {
2381
2454
  value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
2455
+ headSha: null,
2382
2456
  danglingNote: `no ${missingLabel} verdict comment found on this PR`
2383
2457
  };
2384
2458
  }
@@ -2389,8 +2463,10 @@ function extractSecurityReviewVerdict(comments) {
2389
2463
  return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
2390
2464
  }
2391
2465
  // ../../packages/aeg-core/src/review-gate.ts
2392
- function isReviewGateExemptBranch(branch) {
2393
- return branch.startsWith("plan/");
2466
+ function isBoundToHead(extraction, headSha) {
2467
+ if (!extraction.headSha)
2468
+ return false;
2469
+ return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
2394
2470
  }
2395
2471
  function checkReviewGate(input) {
2396
2472
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
@@ -2414,18 +2490,26 @@ function checkReviewGate(input) {
2414
2490
  const security = extractSecurityReviewVerdict(verifiedBodies);
2415
2491
  const codeReviewClean = codeReview.value === "APPROVE";
2416
2492
  const securityClean = security.value === "PASS";
2417
- if (codeReviewClean && securityClean) {
2493
+ const codeReviewBound = isBoundToHead(codeReview, input.headSha);
2494
+ const securityBound = isBoundToHead(security, input.headSha);
2495
+ if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
2418
2496
  return {
2419
2497
  verdict: "pass",
2420
- reason: "code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS.",
2498
+ reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
2421
2499
  waived: false
2422
2500
  };
2423
2501
  }
2424
2502
  const problems = [];
2425
- if (!codeReviewClean)
2503
+ if (!codeReviewClean) {
2426
2504
  problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
2427
- if (!securityClean)
2505
+ } else if (!codeReviewBound) {
2506
+ problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2507
+ }
2508
+ if (!securityClean) {
2428
2509
  problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
2510
+ } else if (!securityBound) {
2511
+ problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2512
+ }
2429
2513
  const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
2430
2514
  return {
2431
2515
  verdict: "fail",
@@ -2433,6 +2517,11 @@ function checkReviewGate(input) {
2433
2517
  waived: false
2434
2518
  };
2435
2519
  }
2520
+ var CHANGESET_RELEASE_BRANCH = "changeset-release/main";
2521
+ var DEFAULT_RELEASE_ACTOR = "github-actions[bot]";
2522
+ function isChangesetsReleasePr(branch, author, expectedAuthor) {
2523
+ return branch === CHANGESET_RELEASE_BRANCH && author === expectedAuthor;
2524
+ }
2436
2525
  // ../../packages/aeg-core/src/markdown-table.ts
2437
2526
  function splitRow(line) {
2438
2527
  const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
@@ -5,7 +5,27 @@ import { execFileSync as execFileSync3 } from "node:child_process";
5
5
  // ../../packages/aeg-forge-state/src/strip-code.ts
6
6
  function maskCode(body) {
7
7
  const fill = (line) => " ".repeat(line.length);
8
- return maskIndentedCode(maskFencedCode(body, fill), fill).replace(/(`+)[^\n]*?\1/g, (m) => " ".repeat(m.length));
8
+ const blocksMasked = maskIndentedCode(maskFencedCode(body, fill), fill);
9
+ return blocksMasked.split(`
10
+ `).map((line) => replaceInlineSpans(line, (span) => " ".repeat(span.length))).join(`
11
+ `);
12
+ }
13
+ var DETAILS_OPEN = /<details\b[^>]*>/gi;
14
+ var DETAILS_CLOSE = /<\/details\s*>/gi;
15
+ function maskDetailsBlocks(body) {
16
+ const fill = (line) => " ".repeat(line.length);
17
+ const lines = body.split(`
18
+ `);
19
+ let depth = 0;
20
+ const out = lines.map((line) => {
21
+ const opens = line.match(DETAILS_OPEN)?.length ?? 0;
22
+ const closes = line.match(DETAILS_CLOSE)?.length ?? 0;
23
+ const maskThisLine = depth > 0 || opens > 0;
24
+ depth = Math.max(0, depth + opens - closes);
25
+ return maskThisLine ? fill(line) : line;
26
+ });
27
+ return out.join(`
28
+ `);
9
29
  }
10
30
  function stripCode(body, options = {}) {
11
31
  const normalised = body.replace(/\r\n?/g, `
@@ -14,7 +34,50 @@ function stripCode(body, options = {}) {
14
34
  const blocksStripped = maskIndentedCode(maskFencedCode(normalised, blank), blank);
15
35
  if (options.inlineSpans === "keep")
16
36
  return blocksStripped;
17
- return blocksStripped.replace(/(`+)[^\n]*?\1/g, "");
37
+ return blocksStripped.split(`
38
+ `).map((line) => replaceInlineSpans(line, () => "")).join(`
39
+ `);
40
+ }
41
+ function replaceInlineSpans(line, replace) {
42
+ let result = "";
43
+ let i = 0;
44
+ const runLengthAt = (at) => {
45
+ let j = at;
46
+ while (j < line.length && line[j] === "`")
47
+ j++;
48
+ return j - at;
49
+ };
50
+ while (i < line.length) {
51
+ if (line[i] !== "`") {
52
+ result += line[i];
53
+ i++;
54
+ continue;
55
+ }
56
+ const openLen = runLengthAt(i);
57
+ const openEnd = i + openLen;
58
+ let j = openEnd;
59
+ let closeEnd = -1;
60
+ while (j < line.length) {
61
+ if (line[j] !== "`") {
62
+ j++;
63
+ continue;
64
+ }
65
+ const runLen = runLengthAt(j);
66
+ if (runLen === openLen) {
67
+ closeEnd = j + runLen;
68
+ break;
69
+ }
70
+ j += runLen;
71
+ }
72
+ if (closeEnd === -1) {
73
+ result += line.slice(i, openEnd);
74
+ i = openEnd;
75
+ } else {
76
+ result += replace(line.slice(i, closeEnd));
77
+ i = closeEnd;
78
+ }
79
+ }
80
+ return result;
18
81
  }
19
82
  var FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})([^\r\n]*)\r?$/;
20
83
  var FENCE_CLOSE = /^ {0,3}(`+|~+)[ \t]*\r?$/;
@@ -1753,9 +1816,10 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive,
1753
1816
  return out;
1754
1817
  }
1755
1818
  // ../../packages/aeg-core/src/pr-tier.ts
1819
+ var TIER_FIELD = /(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i;
1756
1820
  function readTierFromPrBody(prBody) {
1757
1821
  const searchIn = anchoredRegion(prBody, "TIER") ?? prBody;
1758
- const m = searchIn.match(/(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i);
1822
+ const m = searchIn.match(TIER_FIELD);
1759
1823
  if (!m)
1760
1824
  return null;
1761
1825
  const t = Number(m[4]);
@@ -2368,15 +2432,25 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
2368
2432
  return { ok: true, expectedIssue };
2369
2433
  }
2370
2434
  // ../../packages/aeg-core/src/verdict-extraction.ts
2435
+ var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
2436
+ function extractHeadSha(comment) {
2437
+ const m = comment.match(HEAD_SHA_PATTERN);
2438
+ return m ? m[1].toLowerCase() : null;
2439
+ }
2371
2440
  function extractVerdict(comments, valuePattern, missingLabel) {
2372
2441
  const clearHits = comments.filter((c) => valuePattern.test(c));
2373
2442
  if (clearHits.length > 0) {
2374
2443
  const latest = clearHits[clearHits.length - 1];
2375
2444
  const m = latest.match(valuePattern);
2376
- return { value: m[1].toUpperCase().replace(/[_-]/g, " "), danglingNote: null };
2445
+ return {
2446
+ value: m[1].toUpperCase().replace(/[_-]/g, " "),
2447
+ headSha: extractHeadSha(latest),
2448
+ danglingNote: null
2449
+ };
2377
2450
  }
2378
2451
  return {
2379
2452
  value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
2453
+ headSha: null,
2380
2454
  danglingNote: `no ${missingLabel} verdict comment found on this PR`
2381
2455
  };
2382
2456
  }
@@ -2387,8 +2461,10 @@ function extractSecurityReviewVerdict(comments) {
2387
2461
  return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
2388
2462
  }
2389
2463
  // ../../packages/aeg-core/src/review-gate.ts
2390
- function isReviewGateExemptBranch(branch) {
2391
- return branch.startsWith("plan/");
2464
+ function isBoundToHead(extraction, headSha) {
2465
+ if (!extraction.headSha)
2466
+ return false;
2467
+ return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
2392
2468
  }
2393
2469
  function checkReviewGate(input) {
2394
2470
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
@@ -2412,18 +2488,26 @@ function checkReviewGate(input) {
2412
2488
  const security = extractSecurityReviewVerdict(verifiedBodies);
2413
2489
  const codeReviewClean = codeReview.value === "APPROVE";
2414
2490
  const securityClean = security.value === "PASS";
2415
- if (codeReviewClean && securityClean) {
2491
+ const codeReviewBound = isBoundToHead(codeReview, input.headSha);
2492
+ const securityBound = isBoundToHead(security, input.headSha);
2493
+ if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
2416
2494
  return {
2417
2495
  verdict: "pass",
2418
- reason: "code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS.",
2496
+ reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
2419
2497
  waived: false
2420
2498
  };
2421
2499
  }
2422
2500
  const problems = [];
2423
- if (!codeReviewClean)
2501
+ if (!codeReviewClean) {
2424
2502
  problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
2425
- if (!securityClean)
2503
+ } else if (!codeReviewBound) {
2504
+ problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2505
+ }
2506
+ if (!securityClean) {
2426
2507
  problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
2508
+ } else if (!securityBound) {
2509
+ problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2510
+ }
2427
2511
  const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
2428
2512
  return {
2429
2513
  verdict: "fail",
@@ -2431,6 +2515,11 @@ function checkReviewGate(input) {
2431
2515
  waived: false
2432
2516
  };
2433
2517
  }
2518
+ var CHANGESET_RELEASE_BRANCH = "changeset-release/main";
2519
+ var DEFAULT_RELEASE_ACTOR = "github-actions[bot]";
2520
+ function isChangesetsReleasePr(branch, author, expectedAuthor) {
2521
+ return branch === CHANGESET_RELEASE_BRANCH && author === expectedAuthor;
2522
+ }
2434
2523
  // ../../packages/aeg-core/src/markdown-table.ts
2435
2524
  function splitRow(line) {
2436
2525
  const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
@@ -3133,7 +3222,9 @@ var VinayaConfigSchema = z.object({
3133
3222
  briefSchema: BriefSchemaSchema.optional(),
3134
3223
  managed: ManagedManifestSchema.optional(),
3135
3224
  principals: z.array(z.string()).min(1).optional(),
3136
- ci: z.object({ setup: z.string().min(1) }).optional()
3225
+ releaseActor: z.string().min(1).optional(),
3226
+ ci: z.object({ setup: z.string().min(1) }).optional(),
3227
+ blastRadius: z.object({ extraDomains: z.array(z.string()).optional() }).optional()
3137
3228
  });
3138
3229
  var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
3139
3230
  var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");
@@ -3141,6 +3232,9 @@ var LOCAL_CONFIG_FILENAME = "vinaya.config.json";
3141
3232
  function resolvePrincipalAllowlist(config) {
3142
3233
  return config?.principals ?? PRINCIPAL_ALLOWLIST;
3143
3234
  }
3235
+ function resolveReleaseActor(config) {
3236
+ return config?.releaseActor ?? DEFAULT_RELEASE_ACTOR;
3237
+ }
3144
3238
  function trustAnchorRepo() {
3145
3239
  const wellFormed = (slug) => /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
3146
3240
  const fromRunner = process.env.GITHUB_REPOSITORY?.trim();
@@ -3183,7 +3277,7 @@ function isMissingFileError(err) {
3183
3277
  return /\b404\b|not found/i.test(haystack);
3184
3278
  }
3185
3279
  function loadTrustAnchorConfig(fetcher = ghFetchTrustAnchorConfig) {
3186
- const warn = (why) => process.stdout.write(`⚠ could not read \`principals\` from the default branch (${why}) — falling back to vinaya's built-in principal allowlist.
3280
+ 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.
3187
3281
  `);
3188
3282
  let base64;
3189
3283
  try {
@@ -3210,16 +3304,9 @@ function loadTrustAnchorConfig(fetcher = ghFetchTrustAnchorConfig) {
3210
3304
 
3211
3305
  // src/checks/bin/check-review-gate.ts
3212
3306
  var CHECK_NAME = "review-gate";
3213
- function git(args) {
3214
- try {
3215
- return execFileSync3("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
3216
- } catch {
3217
- return "";
3218
- }
3219
- }
3220
3307
  function fetchPr(prNumber) {
3221
3308
  try {
3222
- const out = execFileSync3("gh", ["pr", "view", String(prNumber), "--json", "number,comments,labels"], {
3309
+ const out = execFileSync3("gh", ["pr", "view", String(prNumber), "--json", "number,comments,labels,headRefOid"], {
3223
3310
  encoding: "utf8",
3224
3311
  stdio: ["ignore", "pipe", "pipe"]
3225
3312
  });
@@ -3243,10 +3330,6 @@ function fetchWaiverLabelActor(prNumber, label2) {
3243
3330
  }
3244
3331
  }
3245
3332
  function main() {
3246
- const branch = process.env.BRANCH || git(["rev-parse", "--abbrev-ref", "HEAD"]);
3247
- if (isReviewGateExemptBranch(branch)) {
3248
- process.exit(0);
3249
- }
3250
3333
  const prNumberStr = process.env.PR_NUMBER;
3251
3334
  if (!prNumberStr) {
3252
3335
  process.exit(0);
@@ -3269,7 +3352,8 @@ function main() {
3269
3352
  comments: pr.comments.map((c) => ({ body: c.body, author: c.author?.login ?? null })),
3270
3353
  labels,
3271
3354
  waiverLabelActor,
3272
- principalAllowlist: resolvePrincipalAllowlist(loadTrustAnchorConfig())
3355
+ principalAllowlist: resolvePrincipalAllowlist(loadTrustAnchorConfig()),
3356
+ headSha: pr.headRefOid
3273
3357
  });
3274
3358
  if (result.verdict === "fail") {
3275
3359
  emitCheckError({