@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
@@ -5,7 +5,27 @@ import { execFileSync as execFileSync2 } 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(/\|$/, "");
@@ -2,7 +2,27 @@
2
2
  // ../../packages/aeg-forge-state/src/strip-code.ts
3
3
  function maskCode(body) {
4
4
  const fill = (line) => " ".repeat(line.length);
5
- return maskIndentedCode(maskFencedCode(body, fill), fill).replace(/(`+)[^\n]*?\1/g, (m) => " ".repeat(m.length));
5
+ const blocksMasked = maskIndentedCode(maskFencedCode(body, fill), fill);
6
+ return blocksMasked.split(`
7
+ `).map((line) => replaceInlineSpans(line, (span) => " ".repeat(span.length))).join(`
8
+ `);
9
+ }
10
+ var DETAILS_OPEN = /<details\b[^>]*>/gi;
11
+ var DETAILS_CLOSE = /<\/details\s*>/gi;
12
+ function maskDetailsBlocks(body) {
13
+ const fill = (line) => " ".repeat(line.length);
14
+ const lines = body.split(`
15
+ `);
16
+ let depth = 0;
17
+ const out = lines.map((line) => {
18
+ const opens = line.match(DETAILS_OPEN)?.length ?? 0;
19
+ const closes = line.match(DETAILS_CLOSE)?.length ?? 0;
20
+ const maskThisLine = depth > 0 || opens > 0;
21
+ depth = Math.max(0, depth + opens - closes);
22
+ return maskThisLine ? fill(line) : line;
23
+ });
24
+ return out.join(`
25
+ `);
6
26
  }
7
27
  function stripCode(body, options = {}) {
8
28
  const normalised = body.replace(/\r\n?/g, `
@@ -11,7 +31,50 @@ function stripCode(body, options = {}) {
11
31
  const blocksStripped = maskIndentedCode(maskFencedCode(normalised, blank), blank);
12
32
  if (options.inlineSpans === "keep")
13
33
  return blocksStripped;
14
- return blocksStripped.replace(/(`+)[^\n]*?\1/g, "");
34
+ return blocksStripped.split(`
35
+ `).map((line) => replaceInlineSpans(line, () => "")).join(`
36
+ `);
37
+ }
38
+ function replaceInlineSpans(line, replace) {
39
+ let result = "";
40
+ let i = 0;
41
+ const runLengthAt = (at) => {
42
+ let j = at;
43
+ while (j < line.length && line[j] === "`")
44
+ j++;
45
+ return j - at;
46
+ };
47
+ while (i < line.length) {
48
+ if (line[i] !== "`") {
49
+ result += line[i];
50
+ i++;
51
+ continue;
52
+ }
53
+ const openLen = runLengthAt(i);
54
+ const openEnd = i + openLen;
55
+ let j = openEnd;
56
+ let closeEnd = -1;
57
+ while (j < line.length) {
58
+ if (line[j] !== "`") {
59
+ j++;
60
+ continue;
61
+ }
62
+ const runLen = runLengthAt(j);
63
+ if (runLen === openLen) {
64
+ closeEnd = j + runLen;
65
+ break;
66
+ }
67
+ j += runLen;
68
+ }
69
+ if (closeEnd === -1) {
70
+ result += line.slice(i, openEnd);
71
+ i = openEnd;
72
+ } else {
73
+ result += replace(line.slice(i, closeEnd));
74
+ i = closeEnd;
75
+ }
76
+ }
77
+ return result;
15
78
  }
16
79
  var FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})([^\r\n]*)\r?$/;
17
80
  var FENCE_CLOSE = /^ {0,3}(`+|~+)[ \t]*\r?$/;
@@ -1750,9 +1813,10 @@ function evaluateC5(changed, docOwnersContent, prBody, fileExists, waiverActive,
1750
1813
  return out;
1751
1814
  }
1752
1815
  // ../../packages/aeg-core/src/pr-tier.ts
1816
+ var TIER_FIELD = /(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i;
1753
1817
  function readTierFromPrBody(prBody) {
1754
1818
  const searchIn = anchoredRegion(prBody, "TIER") ?? prBody;
1755
- const m = searchIn.match(/(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i);
1819
+ const m = searchIn.match(TIER_FIELD);
1756
1820
  if (!m)
1757
1821
  return null;
1758
1822
  const t = Number(m[4]);
@@ -2365,15 +2429,25 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
2365
2429
  return { ok: true, expectedIssue };
2366
2430
  }
2367
2431
  // ../../packages/aeg-core/src/verdict-extraction.ts
2432
+ var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
2433
+ function extractHeadSha(comment) {
2434
+ const m = comment.match(HEAD_SHA_PATTERN);
2435
+ return m ? m[1].toLowerCase() : null;
2436
+ }
2368
2437
  function extractVerdict(comments, valuePattern, missingLabel) {
2369
2438
  const clearHits = comments.filter((c) => valuePattern.test(c));
2370
2439
  if (clearHits.length > 0) {
2371
2440
  const latest = clearHits[clearHits.length - 1];
2372
2441
  const m = latest.match(valuePattern);
2373
- return { value: m[1].toUpperCase().replace(/[_-]/g, " "), danglingNote: null };
2442
+ return {
2443
+ value: m[1].toUpperCase().replace(/[_-]/g, " "),
2444
+ headSha: extractHeadSha(latest),
2445
+ danglingNote: null
2446
+ };
2374
2447
  }
2375
2448
  return {
2376
2449
  value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
2450
+ headSha: null,
2377
2451
  danglingNote: `no ${missingLabel} verdict comment found on this PR`
2378
2452
  };
2379
2453
  }
@@ -2384,8 +2458,10 @@ function extractSecurityReviewVerdict(comments) {
2384
2458
  return extractVerdict(comments, /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im, "security-review");
2385
2459
  }
2386
2460
  // ../../packages/aeg-core/src/review-gate.ts
2387
- function isReviewGateExemptBranch(branch) {
2388
- return branch.startsWith("plan/");
2461
+ function isBoundToHead(extraction, headSha) {
2462
+ if (!extraction.headSha)
2463
+ return false;
2464
+ return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
2389
2465
  }
2390
2466
  function checkReviewGate(input) {
2391
2467
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
@@ -2409,18 +2485,26 @@ function checkReviewGate(input) {
2409
2485
  const security = extractSecurityReviewVerdict(verifiedBodies);
2410
2486
  const codeReviewClean = codeReview.value === "APPROVE";
2411
2487
  const securityClean = security.value === "PASS";
2412
- if (codeReviewClean && securityClean) {
2488
+ const codeReviewBound = isBoundToHead(codeReview, input.headSha);
2489
+ const securityBound = isBoundToHead(security, input.headSha);
2490
+ if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
2413
2491
  return {
2414
2492
  verdict: "pass",
2415
- reason: "code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS.",
2493
+ reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
2416
2494
  waived: false
2417
2495
  };
2418
2496
  }
2419
2497
  const problems = [];
2420
- if (!codeReviewClean)
2498
+ if (!codeReviewClean) {
2421
2499
  problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
2422
- if (!securityClean)
2500
+ } else if (!codeReviewBound) {
2501
+ problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2502
+ }
2503
+ if (!securityClean) {
2423
2504
  problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
2505
+ } else if (!securityBound) {
2506
+ problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2507
+ }
2424
2508
  const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
2425
2509
  return {
2426
2510
  verdict: "fail",
@@ -2428,6 +2512,11 @@ function checkReviewGate(input) {
2428
2512
  waived: false
2429
2513
  };
2430
2514
  }
2515
+ var CHANGESET_RELEASE_BRANCH = "changeset-release/main";
2516
+ var DEFAULT_RELEASE_ACTOR = "github-actions[bot]";
2517
+ function isChangesetsReleasePr(branch, author, expectedAuthor) {
2518
+ return branch === CHANGESET_RELEASE_BRANCH && author === expectedAuthor;
2519
+ }
2431
2520
  // ../../packages/aeg-core/src/markdown-table.ts
2432
2521
  function splitRow(line) {
2433
2522
  const trimmed = line.trim().replace(/^\|/, "").replace(/\|$/, "");
@@ -5,7 +5,27 @@ import { execFileSync as execFileSync2 } 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(/\|$/, "");