@attalabs/vinaya 0.9.0 → 0.16.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 (117) hide show
  1. package/README.md +33 -7
  2. package/aeg-root/contracts/planner-brief.md +1 -1
  3. package/aeg-root/enforcement.md +7 -7
  4. package/aeg-root/glossary.md +4 -0
  5. package/aeg-root/roles/archivist.md +2 -2
  6. package/aeg-root/roles/developer.md +29 -5
  7. package/aeg-root/roles/planner.md +3 -3
  8. package/aeg-root/roles/reviewer.md +17 -4
  9. package/aeg-root/roles/security.md +16 -5
  10. package/aeg-root/roles/tranche-archivist.md +1 -1
  11. package/aeg-root/skills/aeg/SKILL.md +1 -1
  12. package/aeg-root/state-machine.md +8 -8
  13. package/aeg-root/templates/pr-report-template.md +8 -4
  14. package/aeg-root/tranche-model.md +34 -12
  15. package/dist/checks/bin/check-branch-topology.js +232 -120
  16. package/dist/checks/bin/check-brief-shape.js +232 -120
  17. package/dist/checks/bin/check-closes-n.js +232 -120
  18. package/dist/checks/bin/check-coherence.js +234 -121
  19. package/dist/checks/bin/check-dead-branch-push.js +232 -120
  20. package/dist/checks/bin/check-dispatch-readiness.js +234 -121
  21. package/dist/checks/bin/check-doc-coverage-push.js +261 -130
  22. package/dist/checks/bin/check-doc-coverage.js +263 -132
  23. package/dist/checks/bin/check-evidence-fresh.js +3196 -0
  24. package/dist/checks/bin/check-first-push-dispatch.js +234 -121
  25. package/dist/checks/bin/check-issue-assignment.js +232 -120
  26. package/dist/checks/bin/check-no-disk-state.js +232 -120
  27. package/dist/checks/bin/check-reader-resolvable-prose.js +232 -120
  28. package/dist/checks/bin/check-registry-gates.js +232 -120
  29. package/dist/checks/bin/check-review-gate.js +237 -123
  30. package/dist/checks/bin/check-single-plan-pr.js +232 -120
  31. package/dist/checks/bin/check-test-plan.js +232 -120
  32. package/dist/index.js +1232 -262
  33. package/package.json +1 -1
  34. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  35. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  36. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  37. package/studio-standalone/apps/vinaya-studio/web/.next/required-server-files.json +3 -1
  38. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  39. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  40. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  41. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  42. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  43. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  44. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  45. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  46. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
  47. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  48. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  49. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  50. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  51. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  52. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  53. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  54. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js +3 -3
  55. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  56. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  57. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  58. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  59. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  60. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  61. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js +2 -3
  62. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  63. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  64. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  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.nft.json +1 -1
  69. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  70. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  71. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  72. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  73. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/[root-of-the-server]__0e95dv5._.js +1 -1
  74. 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
  75. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__029-1ch._.js +1 -1
  76. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__05t6482._.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]__0mdkphe._.js +3 -0
  79. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0plfbyn._.js +45 -0
  80. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0q56ohl._.js +1 -1
  81. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0rr_lq7._.js +51 -0
  82. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0zfjc2o._.js +1 -1
  83. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__19jyp3m._.js +1 -1
  84. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1_ojzp1._.js +1 -1
  85. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1d_8nm4._.js +1 -1
  86. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__1p19382._.js → [root-of-the-server]__1qpl4m_._.js} +1 -1
  87. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1zkwtx8._.js +1 -1
  88. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__210ehg9._.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/_0o54suy._.js +1 -1
  91. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0rix02i._.js +1 -1
  92. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_18-d2be._.js +1 -1
  93. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1fqw88f._.js +5 -4
  94. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1gw6vc9._.js +1 -1
  95. 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
  96. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  97. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  98. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  99. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  100. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  101. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3antrj5z0uf_c.js → 0ek6qlqhokys0.js} +1 -1
  102. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1khy4oilrab6g.js → 0fm4p9os2lsom.js} +1 -1
  103. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3zyedaek9nvr_.js → 0ywkq74falx-x.js} +1 -1
  104. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1s9ixp5nhnevp.js +1 -0
  105. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/27sqffqm2wbxx.css +1 -0
  106. package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
  107. package/studio-standalone/apps/vinaya-studio/web/server.js +1 -1
  108. package/templates/custom-check.template.ts +5 -0
  109. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1s7kj3s._.js +0 -3
  110. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1tswrm4._.js +0 -9
  111. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_01jl99k._.js +0 -45
  112. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/apps_vinaya-studio_web_src_20c6-3m._.js +0 -45
  113. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/0qu0xgvifbacu.css +0 -1
  114. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/2e68sbtdhi5dg.js +0 -1
  115. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{pY8RKWuVAUZOjj2-bAYCY → R7xvB2iuNFp_jxENpL7xv}/_buildManifest.js +0 -0
  116. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{pY8RKWuVAUZOjj2-bAYCY → R7xvB2iuNFp_jxENpL7xv}/_clientMiddlewareManifest.js +0 -0
  117. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{pY8RKWuVAUZOjj2-bAYCY → R7xvB2iuNFp_jxENpL7xv}/_ssgManifest.js +0 -0
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/checks/bin/check-doc-coverage-push.ts
4
- import { execFileSync as execFileSync3 } from "node:child_process";
4
+ import { execFileSync as execFileSync4 } from "node:child_process";
5
5
  import { existsSync, readFileSync } from "node:fs";
6
- // ../../packages/aeg-core/src/anchored-region.ts
6
+ // ../../packages/aeg-forge-state/src/strip-code.ts
7
7
  function maskCode(body) {
8
8
  const fill = (line) => " ".repeat(line.length);
9
9
  return maskIndentedCode(maskFencedCode(body, fill), fill).replace(/(`+)[^\n]*?\1/g, (m) => " ".repeat(m.length));
@@ -20,29 +20,38 @@ function stripCode(body, options = {}) {
20
20
  var FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})([^\r\n]*)\r?$/;
21
21
  var FENCE_CLOSE = /^ {0,3}(`+|~+)[ \t]*\r?$/;
22
22
  function maskFencedCode(body, fill) {
23
+ const { lines } = scanFencedCode(body);
24
+ return lines.map(({ line, inCode }) => inCode ? fill(line) : line).join(`
25
+ `);
26
+ }
27
+ function scanFencedCode(body) {
23
28
  let fenceChar = null;
24
29
  let fenceLen = 0;
25
- return body.split(`
30
+ const lines = body.split(`
26
31
  `).map((line) => {
27
32
  if (fenceChar === null) {
28
33
  const open = line.match(FENCE_OPEN);
29
34
  if (!open)
30
- return line;
35
+ return { line, inCode: false };
31
36
  const marker = open[1];
32
37
  if (marker[0] === "`" && open[2].includes("`"))
33
- return line;
38
+ return { line, inCode: false };
34
39
  fenceChar = marker[0];
35
40
  fenceLen = marker.length;
36
- return fill(line);
41
+ return { line, inCode: true };
37
42
  }
38
43
  const close = line.match(FENCE_CLOSE);
39
44
  if (close && close[1][0] === fenceChar && close[1].length >= fenceLen) {
40
45
  fenceChar = null;
41
46
  fenceLen = 0;
42
47
  }
43
- return fill(line);
44
- }).join(`
45
- `);
48
+ return { line, inCode: true };
49
+ });
50
+ return { lines, unterminated: fenceChar !== null };
51
+ }
52
+ function hasUnterminatedFence(body) {
53
+ return scanFencedCode(body.replace(/\r\n?/g, `
54
+ `)).unterminated;
46
55
  }
47
56
  var LIST_MARKER = /^ {0,3}(?:[-*+]|\d{1,9}[.)])(?:\s|$)/;
48
57
  function indentWidth(line) {
@@ -85,88 +94,7 @@ function maskIndentedCode(body, fill) {
85
94
  return out.join(`
86
95
  `);
87
96
  }
88
- function anchoredRegion(body, field) {
89
- const masked = maskCode(body);
90
- const start = new RegExp(`<!--\\s*AEG:${field}:START\\s*-->`).exec(masked);
91
- if (!start)
92
- return null;
93
- const afterStart = start.index + start[0].length;
94
- const end = new RegExp(`<!--\\s*AEG:${field}:END\\s*-->`).exec(masked.slice(afterStart));
95
- if (!end)
96
- return null;
97
- return body.slice(afterStart, afterStart + end.index);
98
- }
99
- // ../../packages/aeg-core/src/parse-registry.ts
100
- function parseRegistry(md) {
101
- const rows = readMarkdownTable(md, /^##\s+Registry\b/i);
102
- const projects = [];
103
- for (const row of rows) {
104
- if (row.length < 4)
105
- continue;
106
- const name = stripBackticks(row[0] ?? "").trim();
107
- const path = stripBackticks(row[1] ?? "").trim();
108
- const specsPath = stripBackticks(row[2] ?? "").trim();
109
- const stateCell = (row[3] ?? "").trim();
110
- if (!name || !path || !specsPath)
111
- continue;
112
- projects.push({
113
- name,
114
- path,
115
- specsPath,
116
- statePath: parseStateCell(stateCell)
117
- });
118
- }
119
- return projects;
120
- }
121
- function parseStateCell(cell) {
122
- if (!cell)
123
- return null;
124
- const stripped = stripBackticks(cell).trim();
125
- if (/^[\w./@-]+\/?$/.test(stripped) && cell.includes("`")) {
126
- return stripped;
127
- }
128
- return null;
129
- }
130
- function stripBackticks(s) {
131
- return s.replace(/`/g, "");
132
- }
133
- function readMarkdownTable(md, headingMatcher) {
134
- const lines = md.split(/\r?\n/);
135
- let i = 0;
136
- for (;i < lines.length; i++) {
137
- if (headingMatcher.test(lines[i] ?? "")) {
138
- i++;
139
- break;
140
- }
141
- }
142
- if (i >= lines.length)
143
- return [];
144
- for (;i < lines.length; i++) {
145
- if ((lines[i] ?? "").trim().startsWith("|"))
146
- break;
147
- }
148
- if (i + 1 >= lines.length)
149
- return [];
150
- i += 2;
151
- const out = [];
152
- for (;i < lines.length; i++) {
153
- const trimmed = (lines[i] ?? "").trim();
154
- if (!trimmed.startsWith("|"))
155
- break;
156
- out.push(splitTableRow(trimmed));
157
- }
158
- return out;
159
- }
160
- function splitTableRow(row) {
161
- const PLACEHOLDER = "\x00PIPE\x00";
162
- const escaped = row.replace(/\\\|/g, PLACEHOLDER);
163
- const parts = escaped.split("|").map((s) => s.trim());
164
- if (parts.length && parts[0] === "")
165
- parts.shift();
166
- if (parts.length && parts[parts.length - 1] === "")
167
- parts.pop();
168
- return parts.map((p) => p.split(PLACEHOLDER).join("|"));
169
- }
97
+
170
98
  // ../../packages/aeg-forge-state/src/parse-rationale-deps.ts
171
99
  var SECTION_HEADER = /\*\*Dependency rationale\*\*/i;
172
100
  var NEXT_HEADER = /\*\*[A-Z][^*]*\*\*/;
@@ -464,11 +392,37 @@ function findTrancheSlug(names) {
464
392
  var TITLE_PATTERN = /^\[([^\]]+)]\s*(\S+)\s*—\s*(.+)$/;
465
393
  var PROJECT_SLUG = /^[a-z0-9][a-z0-9-]*$/i;
466
394
  var PROJECT_FIELD = /^\s*(?:\*\*)?Project(?:\(s\))?(?:\*\*)?\s*:\s*(?:\*\*)?\s*(.+)$/im;
395
+ var VALUE_EDGE_MARKUP = /^[`.;\s]+|[`.;\s]+$/g;
396
+ function unwrapValue(raw) {
397
+ return raw.replace(/\*\*/g, "").replace(VALUE_EDGE_MARKUP, "");
398
+ }
399
+ function projectFieldFromBody(body) {
400
+ const m = stripCode(body, { inlineSpans: "keep" }).match(PROJECT_FIELD);
401
+ if (m)
402
+ return parseFieldValue(m[1] ?? "");
403
+ if (hasUnterminatedFence(body)) {
404
+ const raw = body.match(PROJECT_FIELD);
405
+ if (raw)
406
+ return { declared: true, names: [], unparsed: [(raw[1] ?? "").trim()], unreadable: true };
407
+ }
408
+ return { declared: false, names: [], unparsed: [], unreadable: false };
409
+ }
410
+ function parseFieldValue(value) {
411
+ const names = [];
412
+ const unparsed = [];
413
+ for (const raw of value.split(",")) {
414
+ const name = unwrapValue(raw);
415
+ if (name.length === 0)
416
+ continue;
417
+ if (PROJECT_SLUG.test(name))
418
+ names.push(name);
419
+ else
420
+ unparsed.push(raw.trim());
421
+ }
422
+ return { declared: true, names: [...new Set(names)], unparsed, unreadable: false };
423
+ }
467
424
  function projectsFromBody(body) {
468
- const m = body.match(PROJECT_FIELD);
469
- if (!m)
470
- return [];
471
- return (m[1] ?? "").split(",").map((s) => s.trim()).filter((s) => PROJECT_SLUG.test(s));
425
+ return projectFieldFromBody(body).names;
472
426
  }
473
427
  function taskFromIssue(issue) {
474
428
  const m = issue.title.match(TITLE_PATTERN);
@@ -510,8 +464,8 @@ function tasksFromIssues(issues) {
510
464
  }
511
465
  return tasks.sort((a, b) => compareTaskIds(a.id, b.id));
512
466
  }
513
- async function listTasksForSlugAsync(owner, repo, slug) {
514
- return tasksFromIssues(await ghIssueListByAnyLabelAsync(owner, repo, [trancheLabel(slug)]));
467
+ async function fetchTrancheIssuesAsync(owner, repo, slug) {
468
+ return ghIssueListByAnyLabelAsync(owner, repo, [trancheLabel(slug)]);
515
469
  }
516
470
  function resolveTaskIssueRef(title, labels) {
517
471
  const m = title.match(TITLE_PATTERN);
@@ -527,17 +481,20 @@ function resolveTaskIssueRef(title, labels) {
527
481
  }
528
482
 
529
483
  // ../../packages/aeg-forge-state/src/derive-from-forge.ts
530
- async function deriveTrancheFromForge(owner, repo, slug, known) {
531
- const milestone = known ?? findMilestoneForSlug(owner, repo, slug);
532
- const tasks = await listTasksForSlugAsync(owner, repo, slug);
484
+ function trancheFromIssues(slug, issues, known) {
533
485
  return {
534
486
  name: slug,
535
- lifecycle: milestone?.lifecycle ?? "active",
536
- goal: milestone?.goal ?? "",
537
- tasks,
487
+ lifecycle: known?.lifecycle ?? "active",
488
+ goal: known?.goal ?? "",
489
+ tasks: tasksFromIssues(issues),
538
490
  backlog: []
539
491
  };
540
492
  }
493
+ async function deriveTrancheFromForge(owner, repo, slug, known) {
494
+ const milestone = known ?? findMilestoneForSlug(owner, repo, slug);
495
+ const issues = await fetchTrancheIssuesAsync(owner, repo, slug);
496
+ return trancheFromIssues(slug, issues, milestone);
497
+ }
541
498
  // ../../node_modules/universal-user-agent/index.js
542
499
  function getUserAgent() {
543
500
  if (typeof navigator === "object" && "userAgent" in navigator) {
@@ -1457,6 +1414,96 @@ var systemEnv3 = {
1457
1414
  ...process.env,
1458
1415
  PATH: [process.env.PATH, "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"].filter(Boolean).join(":")
1459
1416
  };
1417
+ // ../../packages/aeg-core/src/anchored-region.ts
1418
+ function anchoredRegionBounds(body, field) {
1419
+ const masked = maskCode(body);
1420
+ const start = new RegExp(`<!--\\s*AEG:${field}:START\\s*-->`).exec(masked);
1421
+ if (!start)
1422
+ return null;
1423
+ const innerStart = start.index + start[0].length;
1424
+ const end = new RegExp(`<!--\\s*AEG:${field}:END\\s*-->`).exec(masked.slice(innerStart));
1425
+ if (!end)
1426
+ return null;
1427
+ const innerEnd = innerStart + end.index;
1428
+ return { outerStart: start.index, outerEnd: innerEnd + end[0].length, innerStart, innerEnd };
1429
+ }
1430
+ function anchoredRegion(body, field) {
1431
+ const bounds = anchoredRegionBounds(body, field);
1432
+ if (!bounds)
1433
+ return null;
1434
+ return body.slice(bounds.innerStart, bounds.innerEnd);
1435
+ }
1436
+ // ../../packages/aeg-core/src/parse-registry.ts
1437
+ function parseRegistry(md) {
1438
+ const rows = readMarkdownTable(md, /^##\s+Registry\b/i);
1439
+ const projects = [];
1440
+ for (const row of rows) {
1441
+ if (row.length < 4)
1442
+ continue;
1443
+ const name = stripBackticks(row[0] ?? "").trim();
1444
+ const path = stripBackticks(row[1] ?? "").trim();
1445
+ const specsPath = stripBackticks(row[2] ?? "").trim();
1446
+ const stateCell = (row[3] ?? "").trim();
1447
+ if (!name || !path || !specsPath)
1448
+ continue;
1449
+ projects.push({
1450
+ name,
1451
+ path,
1452
+ specsPath,
1453
+ statePath: parseStateCell(stateCell)
1454
+ });
1455
+ }
1456
+ return projects;
1457
+ }
1458
+ function parseStateCell(cell) {
1459
+ if (!cell)
1460
+ return null;
1461
+ const stripped = stripBackticks(cell).trim();
1462
+ if (/^[\w./@-]+\/?$/.test(stripped) && cell.includes("`")) {
1463
+ return stripped;
1464
+ }
1465
+ return null;
1466
+ }
1467
+ function stripBackticks(s) {
1468
+ return s.replace(/`/g, "");
1469
+ }
1470
+ function readMarkdownTable(md, headingMatcher) {
1471
+ const lines = md.split(/\r?\n/);
1472
+ let i = 0;
1473
+ for (;i < lines.length; i++) {
1474
+ if (headingMatcher.test(lines[i] ?? "")) {
1475
+ i++;
1476
+ break;
1477
+ }
1478
+ }
1479
+ if (i >= lines.length)
1480
+ return [];
1481
+ for (;i < lines.length; i++) {
1482
+ if ((lines[i] ?? "").trim().startsWith("|"))
1483
+ break;
1484
+ }
1485
+ if (i + 1 >= lines.length)
1486
+ return [];
1487
+ i += 2;
1488
+ const out = [];
1489
+ for (;i < lines.length; i++) {
1490
+ const trimmed = (lines[i] ?? "").trim();
1491
+ if (!trimmed.startsWith("|"))
1492
+ break;
1493
+ out.push(splitTableRow(trimmed));
1494
+ }
1495
+ return out;
1496
+ }
1497
+ function splitTableRow(row) {
1498
+ const PLACEHOLDER = "\x00PIPE\x00";
1499
+ const escaped = row.replace(/\\\|/g, PLACEHOLDER);
1500
+ const parts = escaped.split("|").map((s) => s.trim());
1501
+ if (parts.length && parts[0] === "")
1502
+ parts.shift();
1503
+ if (parts.length && parts[parts.length - 1] === "")
1504
+ parts.pop();
1505
+ return parts.map((p) => p.split(PLACEHOLDER).join("|"));
1506
+ }
1460
1507
  // ../../packages/aeg-core/src/state-machine-model.ts
1461
1508
  var FORGE_FACT_INPUTS = [
1462
1509
  {
@@ -1960,23 +2007,65 @@ function checkIssueRationale(body) {
1960
2007
  function isTaskIssueLabelSet(labels) {
1961
2008
  return hasLabel("tranche", labels);
1962
2009
  }
2010
+ function isControlCodePoint(codePoint) {
2011
+ return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
2012
+ }
2013
+ function isDisplayControlCodePoint(codePoint) {
2014
+ return codePoint >= 8203 && codePoint <= 8207 || codePoint >= 8234 && codePoint <= 8238 || codePoint >= 8294 && codePoint <= 8297 || codePoint === 65279;
2015
+ }
2016
+ var MESSAGE_VALUE_COUNT_MAX = 5;
2017
+ function residueForMessage(values) {
2018
+ const shown = values.slice(0, MESSAGE_VALUE_COUNT_MAX).map((v) => `\`${forMessage(v)}\``);
2019
+ const rest = values.length - shown.length;
2020
+ return rest > 0 ? `${shown.join(", ")} and ${rest} more` : shown.join(", ");
2021
+ }
2022
+ var MESSAGE_VALUE_MAX = 64;
2023
+ function forMessage(value) {
2024
+ const kept = [];
2025
+ for (const ch of value.replace(/\s+/g, " ")) {
2026
+ const codePoint = ch.codePointAt(0) ?? 0;
2027
+ if (isControlCodePoint(codePoint) || isDisplayControlCodePoint(codePoint))
2028
+ continue;
2029
+ kept.push(ch);
2030
+ }
2031
+ const cleaned = kept.join("").trim();
2032
+ if (cleaned.length === 0)
2033
+ return "(unprintable)";
2034
+ const points = Array.from(cleaned);
2035
+ return points.length > MESSAGE_VALUE_MAX ? `${points.slice(0, MESSAGE_VALUE_MAX).join("")}…` : cleaned;
2036
+ }
1963
2037
  function checkProjectsRegistered(body, _labels, registeredNames) {
1964
2038
  if (registeredNames.length === 0)
1965
2039
  return { status: "pass", errors: [] };
1966
- const known = new Set(registeredNames.map((n) => n.trim()));
1967
- const unregistered = projectsFromBody(body).filter((p) => !known.has(p));
1968
- if (unregistered.length === 0)
2040
+ const field = projectFieldFromBody(body);
2041
+ if (!field.declared)
1969
2042
  return { status: "pass", errors: [] };
1970
- const caseHints = unregistered.map((p) => {
1971
- const row = [...known].find((k) => k.toLowerCase() === p.toLowerCase());
1972
- return row ? `\`${p}\` differs from the registered \`${row}\` only in case` : null;
1973
- }).filter((h) => h !== null);
1974
- return {
1975
- status: "fail",
1976
- errors: [
1977
- `issue-validation project registry: the \`**Project:**\` field declares ${unregistered.join(", ")} no such row in \`.vinaya/projects.md\`, which is the authority for valid project names (registered: ${[...registeredNames].join(", ")}). Fix the name, or register the project with \`vinaya init product <name> --path <folder>\` first; an unregistered project has no specs to read and no per-project state to update.${caseHints.length > 0 ? ` Note: ${caseHints.join("; ")} project names are matched exactly, because every downstream consumer compares them literally.` : ""} This reads the same field \`projectsFromBody\` derives the task's project from, so a name here that is not a row is a task that resolves to a project that does not exist.`
1978
- ]
1979
- };
2043
+ const known = new Set(registeredNames.map((n) => n.trim()));
2044
+ const registeredList = [...registeredNames].map(forMessage).join(", ");
2045
+ const errors = [];
2046
+ if (field.unreadable) {
2047
+ return {
2048
+ status: "fail",
2049
+ errors: [
2050
+ `issue-validation project registry: the body has an unterminated code fence, so its \`**Project:**\` field could not be read reliably the fence runs to end of body (CommonMark, and how the forge renders it), swallowing everything after it including the foot field where the declaration lives by convention. Read from the raw body the field says ${residueForMessage(field.unparsed) || "(nothing)"}, but a line inside a swallowed region may be a quoted example rather than a real declaration, so this gate refuses rather than guessing which. Balance the fences every opening run of backticks or tildes needs a closing run at least as long and the field reads normally.`
2051
+ ]
2052
+ };
2053
+ }
2054
+ const unregistered = field.names.filter((p) => !known.has(p));
2055
+ if (unregistered.length > 0) {
2056
+ const caseHints = unregistered.map((p) => {
2057
+ const row = [...known].find((k) => k.toLowerCase() === p.toLowerCase());
2058
+ return row ? `\`${forMessage(p)}\` differs from the registered \`${forMessage(row)}\` only in case` : null;
2059
+ }).filter((h) => h !== null);
2060
+ errors.push(`issue-validation project registry: the \`**Project:**\` field declares ${unregistered.map(forMessage).join(", ")} — no such row in \`.vinaya/projects.md\`, which is the authority for valid project names (registered: ${registeredList}). Fix the name, or register the project with \`vinaya init product <name> --path <folder>\` first; an unregistered project has no specs to read and no per-project state to update.${caseHints.length > 0 ? ` Note: ${caseHints.join("; ")} — project names are matched exactly, because every downstream consumer compares them literally.` : ""} This reads the same field \`projectsFromBody\` derives the task's project from, so a name here that is not a row is a task that resolves to a project that does not exist.`);
2061
+ }
2062
+ if (field.names.length === 0) {
2063
+ const residue = field.unparsed.length > 0 ? ` — ${residueForMessage(field.unparsed)}` : " — the field is empty";
2064
+ errors.push(`issue-validation project registry: the \`**Project:**\` field is present but resolves to no project name${residue} (registered: ${registeredList}). A project name is a slug (\`[a-z0-9][a-z0-9-]*\`, matched exactly); prose, a parenthetical, or a sentence in this field resolves to no project at all, and this gate cannot check a name it never receives — which is how a declaration like this used to pass. Write the registered name on its own, or register the project with \`vinaya init product <name> --path <folder>\` first. If the task genuinely touches no registered project, omit the field rather than explaining its absence inside it — \`checkIssueRationale\` already requires the \`Project(s) + blast radius\` narrative field for that.`);
2065
+ } else if (field.unparsed.length > 0) {
2066
+ errors.push(`issue-validation project registry: the \`**Project:**\` field declares a value that is not a project name — ${residueForMessage(field.unparsed)} (registered: ${registeredList}). The rest of the field parsed, so this is a name the gate silently could not check rather than a field it could not read at all. A project name is a slug (\`[a-z0-9][a-z0-9-]*\`, matched exactly); write it on its own, or drop it if it names no project.`);
2067
+ }
2068
+ return errors.length > 0 ? { status: "fail", errors } : { status: "pass", errors: [] };
1980
2069
  }
1981
2070
 
1982
2071
  // ../../packages/aeg-core/src/coherence-checks.ts
@@ -2280,15 +2369,25 @@ function checkClosesN2(branch, prBody, trancheFiles, taskIssueRefs) {
2280
2369
  return { ok: true, expectedIssue };
2281
2370
  }
2282
2371
  // ../../packages/aeg-core/src/verdict-extraction.ts
2372
+ var HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im;
2373
+ function extractHeadSha(comment) {
2374
+ const m = comment.match(HEAD_SHA_PATTERN);
2375
+ return m ? m[1].toLowerCase() : null;
2376
+ }
2283
2377
  function extractVerdict(comments, valuePattern, missingLabel) {
2284
2378
  const clearHits = comments.filter((c) => valuePattern.test(c));
2285
2379
  if (clearHits.length > 0) {
2286
2380
  const latest = clearHits[clearHits.length - 1];
2287
2381
  const m = latest.match(valuePattern);
2288
- return { value: m[1].toUpperCase().replace(/[_-]/g, " "), danglingNote: null };
2382
+ return {
2383
+ value: m[1].toUpperCase().replace(/[_-]/g, " "),
2384
+ headSha: extractHeadSha(latest),
2385
+ danglingNote: null
2386
+ };
2289
2387
  }
2290
2388
  return {
2291
2389
  value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
2390
+ headSha: null,
2292
2391
  danglingNote: `no ${missingLabel} verdict comment found on this PR`
2293
2392
  };
2294
2393
  }
@@ -2302,6 +2401,11 @@ function extractSecurityReviewVerdict(comments) {
2302
2401
  function isReviewGateExemptBranch(branch) {
2303
2402
  return branch.startsWith("plan/");
2304
2403
  }
2404
+ function isBoundToHead(extraction, headSha) {
2405
+ if (!extraction.headSha)
2406
+ return false;
2407
+ return headSha.toLowerCase().startsWith(extraction.headSha.toLowerCase());
2408
+ }
2305
2409
  function checkReviewGate(input) {
2306
2410
  const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
2307
2411
  const waived = isWaiverLabelActorVerified({
@@ -2324,18 +2428,26 @@ function checkReviewGate(input) {
2324
2428
  const security = extractSecurityReviewVerdict(verifiedBodies);
2325
2429
  const codeReviewClean = codeReview.value === "APPROVE";
2326
2430
  const securityClean = security.value === "PASS";
2327
- if (codeReviewClean && securityClean) {
2431
+ const codeReviewBound = isBoundToHead(codeReview, input.headSha);
2432
+ const securityBound = isBoundToHead(security, input.headSha);
2433
+ if (codeReviewClean && codeReviewBound && securityClean && securityBound) {
2328
2434
  return {
2329
2435
  verdict: "pass",
2330
- reason: "code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS.",
2436
+ reason: `code-reviewer verdict is a clean APPROVE and security-review verdict is a clean PASS, both covering head ${input.headSha}.`,
2331
2437
  waived: false
2332
2438
  };
2333
2439
  }
2334
2440
  const problems = [];
2335
- if (!codeReviewClean)
2441
+ if (!codeReviewClean) {
2336
2442
  problems.push(`code-reviewer verdict is not a clean APPROVE (found: ${codeReview.value})`);
2337
- if (!securityClean)
2443
+ } else if (!codeReviewBound) {
2444
+ problems.push(`the newest code-review verdict covers ${codeReview.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2445
+ }
2446
+ if (!securityClean) {
2338
2447
  problems.push(`security-review verdict is not a clean PASS (found: ${security.value})`);
2448
+ } else if (!securityBound) {
2449
+ problems.push(`the newest security-review verdict covers ${security.headSha ?? "no recorded commit"}, head is ${input.headSha}`);
2450
+ }
2339
2451
  const ignoredNote = ignoredCount > 0 ? ` ${ignoredCount} verdict-shaped comment(s) from authors outside the principal allowlist were ignored.` : "";
2340
2452
  return {
2341
2453
  verdict: "fail",
@@ -2939,6 +3051,21 @@ function evaluateTestPlanGate(body, branch) {
2939
3051
  messages.push("All Test Plan items ticked — runtime verification complete.", "PASS.");
2940
3052
  return { verdict: "pass", messages };
2941
3053
  }
3054
+ // src/lib/diff-evidence.ts
3055
+ import { execFileSync as execFileSync2 } from "node:child_process";
3056
+ function fileDiffAgainst(ref, path) {
3057
+ let out;
3058
+ try {
3059
+ out = execFileSync2("git", ["diff", `${ref}...HEAD`, "--", path], {
3060
+ encoding: "utf8",
3061
+ stdio: ["ignore", "pipe", "pipe"]
3062
+ }).trim();
3063
+ } catch {
3064
+ return null;
3065
+ }
3066
+ return out === "" ? null : out;
3067
+ }
3068
+
2942
3069
  // src/checks/contract.ts
2943
3070
  var CHECK_SCHEMA_VERSION = 1;
2944
3071
  function emitCheckError(error) {
@@ -2947,7 +3074,7 @@ function emitCheckError(error) {
2947
3074
  }
2948
3075
 
2949
3076
  // src/lib/config.ts
2950
- import { execFileSync as execFileSync2 } from "node:child_process";
3077
+ import { execFileSync as execFileSync3 } from "node:child_process";
2951
3078
  import { homedir } from "node:os";
2952
3079
  import { dirname, join } from "node:path";
2953
3080
  import { z } from "zod";
@@ -3045,7 +3172,8 @@ var VinayaConfigSchema = z.object({
3045
3172
  briefSchema: BriefSchemaSchema.optional(),
3046
3173
  managed: ManagedManifestSchema.optional(),
3047
3174
  principals: z.array(z.string()).min(1).optional(),
3048
- ci: z.object({ setup: z.string().min(1) }).optional()
3175
+ ci: z.object({ setup: z.string().min(1) }).optional(),
3176
+ blastRadius: z.object({ extraDomains: z.array(z.string()).optional() }).optional()
3049
3177
  });
3050
3178
  var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
3051
3179
  var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");
@@ -3062,7 +3190,7 @@ function trustAnchorRepo() {
3062
3190
  return validated;
3063
3191
  }
3064
3192
  try {
3065
- const url = execFileSync2("git", ["remote", "get-url", "origin"], {
3193
+ const url = execFileSync3("git", ["remote", "get-url", "origin"], {
3066
3194
  encoding: "utf-8",
3067
3195
  stdio: ["ignore", "pipe", "pipe"]
3068
3196
  }).trim();
@@ -3076,7 +3204,7 @@ function ghFetchTrustAnchorConfig() {
3076
3204
  const repo = trustAnchorRepo();
3077
3205
  if (!repo)
3078
3206
  throw new Error("could not resolve repo identity for the trust-anchor read");
3079
- return execFileSync2("gh", ["api", `repos/${repo}/contents/${LOCAL_CONFIG_FILENAME}`, "--jq", ".content"], {
3207
+ return execFileSync3("gh", ["api", `repos/${repo}/contents/${LOCAL_CONFIG_FILENAME}`, "--jq", ".content"], {
3080
3208
  encoding: "utf-8",
3081
3209
  stdio: ["ignore", "pipe", "pipe"]
3082
3210
  });
@@ -3124,7 +3252,7 @@ function loadTrustAnchorConfig(fetcher = ghFetchTrustAnchorConfig) {
3124
3252
  var CHECK_NAME = "doc-coverage-push";
3125
3253
  function git(args) {
3126
3254
  try {
3127
- return execFileSync3("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
3255
+ return execFileSync4("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
3128
3256
  } catch {
3129
3257
  return "";
3130
3258
  }
@@ -3165,13 +3293,16 @@ function main() {
3165
3293
  process.exit(0);
3166
3294
  }
3167
3295
  const base = process.env.BASE_SHA || "origin/main";
3168
- let changed = changedFiles(base);
3169
- if (changed.length === 0)
3170
- changed = changedFiles("main");
3296
+ let ref = base;
3297
+ let changed = changedFiles(ref);
3298
+ if (changed.length === 0) {
3299
+ ref = "main";
3300
+ changed = changedFiles(ref);
3301
+ }
3171
3302
  if (changed.length === 0)
3172
3303
  process.exit(0);
3173
3304
  const content = existsSync(DOC_OWNERS_PATH) ? readFileSync(DOC_OWNERS_PATH, "utf8") : null;
3174
- const result = evaluateC5(changed, content, resolvePrBody(), existsSync, waiverActiveFromEnv());
3305
+ const result = evaluateC5(changed, content, resolvePrBody(), existsSync, waiverActiveFromEnv(), (p) => fileDiffAgainst(ref, p));
3175
3306
  for (const message of result.errors) {
3176
3307
  emitCheckError({
3177
3308
  schema: CHECK_SCHEMA_VERSION,