@attalabs/vinaya 0.9.0 → 0.12.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 (108) hide show
  1. package/README.md +7 -5
  2. package/aeg-root/enforcement.md +3 -3
  3. package/aeg-root/glossary.md +4 -0
  4. package/aeg-root/roles/archivist.md +2 -2
  5. package/aeg-root/roles/developer.md +29 -5
  6. package/aeg-root/roles/planner.md +1 -1
  7. package/aeg-root/roles/reviewer.md +1 -1
  8. package/aeg-root/roles/security.md +1 -1
  9. package/aeg-root/roles/tranche-archivist.md +1 -1
  10. package/aeg-root/state-machine.md +6 -6
  11. package/aeg-root/templates/pr-report-template.md +8 -4
  12. package/aeg-root/tranche-model.md +33 -11
  13. package/dist/checks/bin/check-branch-topology.js +204 -115
  14. package/dist/checks/bin/check-brief-shape.js +204 -115
  15. package/dist/checks/bin/check-closes-n.js +204 -115
  16. package/dist/checks/bin/check-coherence.js +204 -115
  17. package/dist/checks/bin/check-dead-branch-push.js +204 -115
  18. package/dist/checks/bin/check-dispatch-readiness.js +204 -115
  19. package/dist/checks/bin/check-doc-coverage-push.js +231 -124
  20. package/dist/checks/bin/check-doc-coverage.js +233 -126
  21. package/dist/checks/bin/check-evidence-fresh.js +3173 -0
  22. package/dist/checks/bin/check-first-push-dispatch.js +204 -115
  23. package/dist/checks/bin/check-issue-assignment.js +204 -115
  24. package/dist/checks/bin/check-no-disk-state.js +204 -115
  25. package/dist/checks/bin/check-reader-resolvable-prose.js +204 -115
  26. package/dist/checks/bin/check-registry-gates.js +204 -115
  27. package/dist/checks/bin/check-review-gate.js +204 -115
  28. package/dist/checks/bin/check-single-plan-pr.js +204 -115
  29. package/dist/checks/bin/check-test-plan.js +204 -115
  30. package/dist/index.js +487 -221
  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 +3 -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/page/server-reference-manifest.json +2 -2
  45. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  46. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  47. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  48. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  49. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  50. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  51. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js +3 -3
  52. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  53. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  54. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  55. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  56. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  57. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  58. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js +2 -3
  59. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  60. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  61. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  62. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  63. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  64. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  65. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  66. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  67. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  68. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  69. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  70. 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
  71. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__029-1ch._.js +1 -1
  72. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__05t6482._.js +1 -1
  73. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0gw1_g2._.js +1 -1
  74. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0mdkphe._.js +3 -0
  75. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0plfbyn._.js +45 -0
  76. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0q56ohl._.js +1 -1
  77. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0rr_lq7._.js +51 -0
  78. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__19jyp3m._.js +1 -1
  79. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1_ojzp1._.js +1 -1
  80. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1d_8nm4._.js +1 -1
  81. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__1p19382._.js → [root-of-the-server]__1wsl6ro._.js} +1 -1
  82. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1zkwtx8._.js +1 -1
  83. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__210ehg9._.js +1 -1
  84. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_00yjcv1._.js +1 -1
  85. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0rix02i._.js +1 -1
  86. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_18-d2be._.js +1 -1
  87. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1fqw88f._.js +5 -4
  88. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1gw6vc9._.js +1 -1
  89. 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
  90. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  91. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  92. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  93. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  94. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  95. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3zyedaek9nvr_.js → 1eo32v1tbjy7c.js} +1 -1
  96. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1t7yy_s34022p.js +1 -0
  97. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3antrj5z0uf_c.js → 2ieu4oi-pp_bz.js} +1 -1
  98. package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
  99. package/studio-standalone/apps/vinaya-studio/web/server.js +1 -1
  100. package/templates/custom-check.template.ts +5 -0
  101. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1s7kj3s._.js +0 -3
  102. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1tswrm4._.js +0 -9
  103. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_01jl99k._.js +0 -45
  104. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/apps_vinaya-studio_web_src_20c6-3m._.js +0 -45
  105. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/2e68sbtdhi5dg.js +0 -1
  106. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{pY8RKWuVAUZOjj2-bAYCY → ZHVE9Y9G8XKR91g27SgVX}/_buildManifest.js +0 -0
  107. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{pY8RKWuVAUZOjj2-bAYCY → ZHVE9Y9G8XKR91g27SgVX}/_clientMiddlewareManifest.js +0 -0
  108. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{pY8RKWuVAUZOjj2-bAYCY → ZHVE9Y9G8XKR91g27SgVX}/_ssgManifest.js +0 -0
@@ -2,9 +2,9 @@
2
2
  // @bun
3
3
 
4
4
  // src/checks/bin/check-doc-coverage.ts
5
- import { execFileSync as execFileSync3 } from "child_process";
5
+ import { execFileSync as execFileSync4 } from "child_process";
6
6
  import { existsSync, readFileSync } from "fs";
7
- // ../../packages/aeg-core/src/anchored-region.ts
7
+ // ../../packages/aeg-forge-state/src/strip-code.ts
8
8
  function maskCode(body) {
9
9
  const fill = (line) => " ".repeat(line.length);
10
10
  return maskIndentedCode(maskFencedCode(body, fill), fill).replace(/(`+)[^\n]*?\1/g, (m) => " ".repeat(m.length));
@@ -21,29 +21,38 @@ function stripCode(body, options = {}) {
21
21
  var FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})([^\r\n]*)\r?$/;
22
22
  var FENCE_CLOSE = /^ {0,3}(`+|~+)[ \t]*\r?$/;
23
23
  function maskFencedCode(body, fill) {
24
+ const { lines } = scanFencedCode(body);
25
+ return lines.map(({ line, inCode }) => inCode ? fill(line) : line).join(`
26
+ `);
27
+ }
28
+ function scanFencedCode(body) {
24
29
  let fenceChar = null;
25
30
  let fenceLen = 0;
26
- return body.split(`
31
+ const lines = body.split(`
27
32
  `).map((line) => {
28
33
  if (fenceChar === null) {
29
34
  const open = line.match(FENCE_OPEN);
30
35
  if (!open)
31
- return line;
36
+ return { line, inCode: false };
32
37
  const marker = open[1];
33
38
  if (marker[0] === "`" && open[2].includes("`"))
34
- return line;
39
+ return { line, inCode: false };
35
40
  fenceChar = marker[0];
36
41
  fenceLen = marker.length;
37
- return fill(line);
42
+ return { line, inCode: true };
38
43
  }
39
44
  const close = line.match(FENCE_CLOSE);
40
45
  if (close && close[1][0] === fenceChar && close[1].length >= fenceLen) {
41
46
  fenceChar = null;
42
47
  fenceLen = 0;
43
48
  }
44
- return fill(line);
45
- }).join(`
46
- `);
49
+ return { line, inCode: true };
50
+ });
51
+ return { lines, unterminated: fenceChar !== null };
52
+ }
53
+ function hasUnterminatedFence(body) {
54
+ return scanFencedCode(body.replace(/\r\n?/g, `
55
+ `)).unterminated;
47
56
  }
48
57
  var LIST_MARKER = /^ {0,3}(?:[-*+]|\d{1,9}[.)])(?:\s|$)/;
49
58
  function indentWidth(line) {
@@ -86,88 +95,7 @@ function maskIndentedCode(body, fill) {
86
95
  return out.join(`
87
96
  `);
88
97
  }
89
- function anchoredRegion(body, field) {
90
- const masked = maskCode(body);
91
- const start = new RegExp(`<!--\\s*AEG:${field}:START\\s*-->`).exec(masked);
92
- if (!start)
93
- return null;
94
- const afterStart = start.index + start[0].length;
95
- const end = new RegExp(`<!--\\s*AEG:${field}:END\\s*-->`).exec(masked.slice(afterStart));
96
- if (!end)
97
- return null;
98
- return body.slice(afterStart, afterStart + end.index);
99
- }
100
- // ../../packages/aeg-core/src/parse-registry.ts
101
- function parseRegistry(md) {
102
- const rows = readMarkdownTable(md, /^##\s+Registry\b/i);
103
- const projects = [];
104
- for (const row of rows) {
105
- if (row.length < 4)
106
- continue;
107
- const name = stripBackticks(row[0] ?? "").trim();
108
- const path = stripBackticks(row[1] ?? "").trim();
109
- const specsPath = stripBackticks(row[2] ?? "").trim();
110
- const stateCell = (row[3] ?? "").trim();
111
- if (!name || !path || !specsPath)
112
- continue;
113
- projects.push({
114
- name,
115
- path,
116
- specsPath,
117
- statePath: parseStateCell(stateCell)
118
- });
119
- }
120
- return projects;
121
- }
122
- function parseStateCell(cell) {
123
- if (!cell)
124
- return null;
125
- const stripped = stripBackticks(cell).trim();
126
- if (/^[\w./@-]+\/?$/.test(stripped) && cell.includes("`")) {
127
- return stripped;
128
- }
129
- return null;
130
- }
131
- function stripBackticks(s) {
132
- return s.replace(/`/g, "");
133
- }
134
- function readMarkdownTable(md, headingMatcher) {
135
- const lines = md.split(/\r?\n/);
136
- let i = 0;
137
- for (;i < lines.length; i++) {
138
- if (headingMatcher.test(lines[i] ?? "")) {
139
- i++;
140
- break;
141
- }
142
- }
143
- if (i >= lines.length)
144
- return [];
145
- for (;i < lines.length; i++) {
146
- if ((lines[i] ?? "").trim().startsWith("|"))
147
- break;
148
- }
149
- if (i + 1 >= lines.length)
150
- return [];
151
- i += 2;
152
- const out = [];
153
- for (;i < lines.length; i++) {
154
- const trimmed = (lines[i] ?? "").trim();
155
- if (!trimmed.startsWith("|"))
156
- break;
157
- out.push(splitTableRow(trimmed));
158
- }
159
- return out;
160
- }
161
- function splitTableRow(row) {
162
- const PLACEHOLDER = "\x00PIPE\x00";
163
- const escaped = row.replace(/\\\|/g, PLACEHOLDER);
164
- const parts = escaped.split("|").map((s) => s.trim());
165
- if (parts.length && parts[0] === "")
166
- parts.shift();
167
- if (parts.length && parts[parts.length - 1] === "")
168
- parts.pop();
169
- return parts.map((p) => p.split(PLACEHOLDER).join("|"));
170
- }
98
+
171
99
  // ../../packages/aeg-forge-state/src/parse-rationale-deps.ts
172
100
  var SECTION_HEADER = /\*\*Dependency rationale\*\*/i;
173
101
  var NEXT_HEADER = /\*\*[A-Z][^*]*\*\*/;
@@ -465,11 +393,37 @@ function findTrancheSlug(names) {
465
393
  var TITLE_PATTERN = /^\[([^\]]+)]\s*(\S+)\s*—\s*(.+)$/;
466
394
  var PROJECT_SLUG = /^[a-z0-9][a-z0-9-]*$/i;
467
395
  var PROJECT_FIELD = /^\s*(?:\*\*)?Project(?:\(s\))?(?:\*\*)?\s*:\s*(?:\*\*)?\s*(.+)$/im;
396
+ var VALUE_EDGE_MARKUP = /^[`.;\s]+|[`.;\s]+$/g;
397
+ function unwrapValue(raw) {
398
+ return raw.replace(/\*\*/g, "").replace(VALUE_EDGE_MARKUP, "");
399
+ }
400
+ function projectFieldFromBody(body) {
401
+ const m = stripCode(body, { inlineSpans: "keep" }).match(PROJECT_FIELD);
402
+ if (m)
403
+ return parseFieldValue(m[1] ?? "");
404
+ if (hasUnterminatedFence(body)) {
405
+ const raw = body.match(PROJECT_FIELD);
406
+ if (raw)
407
+ return { declared: true, names: [], unparsed: [(raw[1] ?? "").trim()], unreadable: true };
408
+ }
409
+ return { declared: false, names: [], unparsed: [], unreadable: false };
410
+ }
411
+ function parseFieldValue(value) {
412
+ const names = [];
413
+ const unparsed = [];
414
+ for (const raw of value.split(",")) {
415
+ const name = unwrapValue(raw);
416
+ if (name.length === 0)
417
+ continue;
418
+ if (PROJECT_SLUG.test(name))
419
+ names.push(name);
420
+ else
421
+ unparsed.push(raw.trim());
422
+ }
423
+ return { declared: true, names: [...new Set(names)], unparsed, unreadable: false };
424
+ }
468
425
  function projectsFromBody(body) {
469
- const m = body.match(PROJECT_FIELD);
470
- if (!m)
471
- return [];
472
- return (m[1] ?? "").split(",").map((s) => s.trim()).filter((s) => PROJECT_SLUG.test(s));
426
+ return projectFieldFromBody(body).names;
473
427
  }
474
428
  function taskFromIssue(issue) {
475
429
  const m = issue.title.match(TITLE_PATTERN);
@@ -511,8 +465,8 @@ function tasksFromIssues(issues) {
511
465
  }
512
466
  return tasks.sort((a, b) => compareTaskIds(a.id, b.id));
513
467
  }
514
- async function listTasksForSlugAsync(owner, repo, slug) {
515
- return tasksFromIssues(await ghIssueListByAnyLabelAsync(owner, repo, [trancheLabel(slug)]));
468
+ async function fetchTrancheIssuesAsync(owner, repo, slug) {
469
+ return ghIssueListByAnyLabelAsync(owner, repo, [trancheLabel(slug)]);
516
470
  }
517
471
  function resolveTaskIssueRef(title, labels) {
518
472
  const m = title.match(TITLE_PATTERN);
@@ -528,17 +482,20 @@ function resolveTaskIssueRef(title, labels) {
528
482
  }
529
483
 
530
484
  // ../../packages/aeg-forge-state/src/derive-from-forge.ts
531
- async function deriveTrancheFromForge(owner, repo, slug, known) {
532
- const milestone = known ?? findMilestoneForSlug(owner, repo, slug);
533
- const tasks = await listTasksForSlugAsync(owner, repo, slug);
485
+ function trancheFromIssues(slug, issues, known) {
534
486
  return {
535
487
  name: slug,
536
- lifecycle: milestone?.lifecycle ?? "active",
537
- goal: milestone?.goal ?? "",
538
- tasks,
488
+ lifecycle: known?.lifecycle ?? "active",
489
+ goal: known?.goal ?? "",
490
+ tasks: tasksFromIssues(issues),
539
491
  backlog: []
540
492
  };
541
493
  }
494
+ async function deriveTrancheFromForge(owner, repo, slug, known) {
495
+ const milestone = known ?? findMilestoneForSlug(owner, repo, slug);
496
+ const issues = await fetchTrancheIssuesAsync(owner, repo, slug);
497
+ return trancheFromIssues(slug, issues, milestone);
498
+ }
542
499
  // ../../node_modules/universal-user-agent/index.js
543
500
  function getUserAgent() {
544
501
  if (typeof navigator === "object" && "userAgent" in navigator) {
@@ -1458,6 +1415,96 @@ var systemEnv3 = {
1458
1415
  ...process.env,
1459
1416
  PATH: [process.env.PATH, "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"].filter(Boolean).join(":")
1460
1417
  };
1418
+ // ../../packages/aeg-core/src/anchored-region.ts
1419
+ function anchoredRegionBounds(body, field) {
1420
+ const masked = maskCode(body);
1421
+ const start = new RegExp(`<!--\\s*AEG:${field}:START\\s*-->`).exec(masked);
1422
+ if (!start)
1423
+ return null;
1424
+ const innerStart = start.index + start[0].length;
1425
+ const end = new RegExp(`<!--\\s*AEG:${field}:END\\s*-->`).exec(masked.slice(innerStart));
1426
+ if (!end)
1427
+ return null;
1428
+ const innerEnd = innerStart + end.index;
1429
+ return { outerStart: start.index, outerEnd: innerEnd + end[0].length, innerStart, innerEnd };
1430
+ }
1431
+ function anchoredRegion(body, field) {
1432
+ const bounds = anchoredRegionBounds(body, field);
1433
+ if (!bounds)
1434
+ return null;
1435
+ return body.slice(bounds.innerStart, bounds.innerEnd);
1436
+ }
1437
+ // ../../packages/aeg-core/src/parse-registry.ts
1438
+ function parseRegistry(md) {
1439
+ const rows = readMarkdownTable(md, /^##\s+Registry\b/i);
1440
+ const projects = [];
1441
+ for (const row of rows) {
1442
+ if (row.length < 4)
1443
+ continue;
1444
+ const name = stripBackticks(row[0] ?? "").trim();
1445
+ const path = stripBackticks(row[1] ?? "").trim();
1446
+ const specsPath = stripBackticks(row[2] ?? "").trim();
1447
+ const stateCell = (row[3] ?? "").trim();
1448
+ if (!name || !path || !specsPath)
1449
+ continue;
1450
+ projects.push({
1451
+ name,
1452
+ path,
1453
+ specsPath,
1454
+ statePath: parseStateCell(stateCell)
1455
+ });
1456
+ }
1457
+ return projects;
1458
+ }
1459
+ function parseStateCell(cell) {
1460
+ if (!cell)
1461
+ return null;
1462
+ const stripped = stripBackticks(cell).trim();
1463
+ if (/^[\w./@-]+\/?$/.test(stripped) && cell.includes("`")) {
1464
+ return stripped;
1465
+ }
1466
+ return null;
1467
+ }
1468
+ function stripBackticks(s) {
1469
+ return s.replace(/`/g, "");
1470
+ }
1471
+ function readMarkdownTable(md, headingMatcher) {
1472
+ const lines = md.split(/\r?\n/);
1473
+ let i = 0;
1474
+ for (;i < lines.length; i++) {
1475
+ if (headingMatcher.test(lines[i] ?? "")) {
1476
+ i++;
1477
+ break;
1478
+ }
1479
+ }
1480
+ if (i >= lines.length)
1481
+ return [];
1482
+ for (;i < lines.length; i++) {
1483
+ if ((lines[i] ?? "").trim().startsWith("|"))
1484
+ break;
1485
+ }
1486
+ if (i + 1 >= lines.length)
1487
+ return [];
1488
+ i += 2;
1489
+ const out = [];
1490
+ for (;i < lines.length; i++) {
1491
+ const trimmed = (lines[i] ?? "").trim();
1492
+ if (!trimmed.startsWith("|"))
1493
+ break;
1494
+ out.push(splitTableRow(trimmed));
1495
+ }
1496
+ return out;
1497
+ }
1498
+ function splitTableRow(row) {
1499
+ const PLACEHOLDER = "\x00PIPE\x00";
1500
+ const escaped = row.replace(/\\\|/g, PLACEHOLDER);
1501
+ const parts = escaped.split("|").map((s) => s.trim());
1502
+ if (parts.length && parts[0] === "")
1503
+ parts.shift();
1504
+ if (parts.length && parts[parts.length - 1] === "")
1505
+ parts.pop();
1506
+ return parts.map((p) => p.split(PLACEHOLDER).join("|"));
1507
+ }
1461
1508
  // ../../packages/aeg-core/src/state-machine-model.ts
1462
1509
  var FORGE_FACT_INPUTS = [
1463
1510
  {
@@ -1961,23 +2008,65 @@ function checkIssueRationale(body) {
1961
2008
  function isTaskIssueLabelSet(labels) {
1962
2009
  return hasLabel("tranche", labels);
1963
2010
  }
2011
+ function isControlCodePoint(codePoint) {
2012
+ return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
2013
+ }
2014
+ function isDisplayControlCodePoint(codePoint) {
2015
+ return codePoint >= 8203 && codePoint <= 8207 || codePoint >= 8234 && codePoint <= 8238 || codePoint >= 8294 && codePoint <= 8297 || codePoint === 65279;
2016
+ }
2017
+ var MESSAGE_VALUE_COUNT_MAX = 5;
2018
+ function residueForMessage(values) {
2019
+ const shown = values.slice(0, MESSAGE_VALUE_COUNT_MAX).map((v) => `\`${forMessage(v)}\``);
2020
+ const rest = values.length - shown.length;
2021
+ return rest > 0 ? `${shown.join(", ")} and ${rest} more` : shown.join(", ");
2022
+ }
2023
+ var MESSAGE_VALUE_MAX = 64;
2024
+ function forMessage(value) {
2025
+ const kept = [];
2026
+ for (const ch of value.replace(/\s+/g, " ")) {
2027
+ const codePoint = ch.codePointAt(0) ?? 0;
2028
+ if (isControlCodePoint(codePoint) || isDisplayControlCodePoint(codePoint))
2029
+ continue;
2030
+ kept.push(ch);
2031
+ }
2032
+ const cleaned = kept.join("").trim();
2033
+ if (cleaned.length === 0)
2034
+ return "(unprintable)";
2035
+ const points = Array.from(cleaned);
2036
+ return points.length > MESSAGE_VALUE_MAX ? `${points.slice(0, MESSAGE_VALUE_MAX).join("")}…` : cleaned;
2037
+ }
1964
2038
  function checkProjectsRegistered(body, _labels, registeredNames) {
1965
2039
  if (registeredNames.length === 0)
1966
2040
  return { status: "pass", errors: [] };
1967
- const known = new Set(registeredNames.map((n) => n.trim()));
1968
- const unregistered = projectsFromBody(body).filter((p) => !known.has(p));
1969
- if (unregistered.length === 0)
2041
+ const field = projectFieldFromBody(body);
2042
+ if (!field.declared)
1970
2043
  return { status: "pass", errors: [] };
1971
- const caseHints = unregistered.map((p) => {
1972
- const row = [...known].find((k) => k.toLowerCase() === p.toLowerCase());
1973
- return row ? `\`${p}\` differs from the registered \`${row}\` only in case` : null;
1974
- }).filter((h) => h !== null);
1975
- return {
1976
- status: "fail",
1977
- errors: [
1978
- `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.`
1979
- ]
1980
- };
2044
+ const known = new Set(registeredNames.map((n) => n.trim()));
2045
+ const registeredList = [...registeredNames].map(forMessage).join(", ");
2046
+ const errors = [];
2047
+ if (field.unreadable) {
2048
+ return {
2049
+ status: "fail",
2050
+ errors: [
2051
+ `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.`
2052
+ ]
2053
+ };
2054
+ }
2055
+ const unregistered = field.names.filter((p) => !known.has(p));
2056
+ if (unregistered.length > 0) {
2057
+ const caseHints = unregistered.map((p) => {
2058
+ const row = [...known].find((k) => k.toLowerCase() === p.toLowerCase());
2059
+ return row ? `\`${forMessage(p)}\` differs from the registered \`${forMessage(row)}\` only in case` : null;
2060
+ }).filter((h) => h !== null);
2061
+ 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.`);
2062
+ }
2063
+ if (field.names.length === 0) {
2064
+ const residue = field.unparsed.length > 0 ? ` — ${residueForMessage(field.unparsed)}` : " — the field is empty";
2065
+ 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.`);
2066
+ } else if (field.unparsed.length > 0) {
2067
+ 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.`);
2068
+ }
2069
+ return errors.length > 0 ? { status: "fail", errors } : { status: "pass", errors: [] };
1981
2070
  }
1982
2071
 
1983
2072
  // ../../packages/aeg-core/src/coherence-checks.ts
@@ -2940,6 +3029,21 @@ function evaluateTestPlanGate(body, branch) {
2940
3029
  messages.push("All Test Plan items ticked — runtime verification complete.", "PASS.");
2941
3030
  return { verdict: "pass", messages };
2942
3031
  }
3032
+ // src/lib/diff-evidence.ts
3033
+ import { execFileSync as execFileSync2 } from "node:child_process";
3034
+ function fileDiffAgainst(ref, path) {
3035
+ let out;
3036
+ try {
3037
+ out = execFileSync2("git", ["diff", `${ref}...HEAD`, "--", path], {
3038
+ encoding: "utf8",
3039
+ stdio: ["ignore", "pipe", "pipe"]
3040
+ }).trim();
3041
+ } catch {
3042
+ return null;
3043
+ }
3044
+ return out === "" ? null : out;
3045
+ }
3046
+
2943
3047
  // src/checks/contract.ts
2944
3048
  var CHECK_SCHEMA_VERSION = 1;
2945
3049
  function emitCheckError(error) {
@@ -2948,7 +3052,7 @@ function emitCheckError(error) {
2948
3052
  }
2949
3053
 
2950
3054
  // src/lib/config.ts
2951
- import { execFileSync as execFileSync2 } from "node:child_process";
3055
+ import { execFileSync as execFileSync3 } from "node:child_process";
2952
3056
  import { homedir } from "node:os";
2953
3057
  import { dirname, join } from "node:path";
2954
3058
  import { z } from "zod";
@@ -3063,7 +3167,7 @@ function trustAnchorRepo() {
3063
3167
  return validated;
3064
3168
  }
3065
3169
  try {
3066
- const url = execFileSync2("git", ["remote", "get-url", "origin"], {
3170
+ const url = execFileSync3("git", ["remote", "get-url", "origin"], {
3067
3171
  encoding: "utf-8",
3068
3172
  stdio: ["ignore", "pipe", "pipe"]
3069
3173
  }).trim();
@@ -3077,7 +3181,7 @@ function ghFetchTrustAnchorConfig() {
3077
3181
  const repo = trustAnchorRepo();
3078
3182
  if (!repo)
3079
3183
  throw new Error("could not resolve repo identity for the trust-anchor read");
3080
- return execFileSync2("gh", ["api", `repos/${repo}/contents/${LOCAL_CONFIG_FILENAME}`, "--jq", ".content"], {
3184
+ return execFileSync3("gh", ["api", `repos/${repo}/contents/${LOCAL_CONFIG_FILENAME}`, "--jq", ".content"], {
3081
3185
  encoding: "utf-8",
3082
3186
  stdio: ["ignore", "pipe", "pipe"]
3083
3187
  });
@@ -3125,7 +3229,7 @@ function loadTrustAnchorConfig(fetcher = ghFetchTrustAnchorConfig) {
3125
3229
  var CHECK_NAME = "doc-coverage";
3126
3230
  function git(args) {
3127
3231
  try {
3128
- return execFileSync3("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
3232
+ return execFileSync4("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
3129
3233
  } catch {
3130
3234
  return "";
3131
3235
  }
@@ -3148,7 +3252,7 @@ function resolvePrBody() {
3148
3252
  }
3149
3253
  function fetchPrLabels(prNumber) {
3150
3254
  try {
3151
- const out = execFileSync3("gh", ["pr", "view", String(prNumber), "--json", "labels"], {
3255
+ const out = execFileSync4("gh", ["pr", "view", String(prNumber), "--json", "labels"], {
3152
3256
  encoding: "utf8",
3153
3257
  stdio: ["ignore", "pipe", "pipe"]
3154
3258
  });
@@ -3159,7 +3263,7 @@ function fetchPrLabels(prNumber) {
3159
3263
  }
3160
3264
  function fetchWaiverLabelActor(prNumber, label2) {
3161
3265
  try {
3162
- const out = execFileSync3("gh", ["api", `repos/{owner}/{repo}/issues/${prNumber}/timeline`, "--paginate"], {
3266
+ const out = execFileSync4("gh", ["api", `repos/{owner}/{repo}/issues/${prNumber}/timeline`, "--paginate"], {
3163
3267
  encoding: "utf8",
3164
3268
  stdio: ["ignore", "pipe", "pipe"]
3165
3269
  });
@@ -3188,14 +3292,17 @@ function waiverActive() {
3188
3292
  }
3189
3293
  function main() {
3190
3294
  const base = process.env.BASE_SHA || "origin/main";
3191
- let changed = changedFiles(base);
3192
- if (changed.length === 0)
3193
- changed = changedFiles("main");
3295
+ let ref = base;
3296
+ let changed = changedFiles(ref);
3297
+ if (changed.length === 0) {
3298
+ ref = "main";
3299
+ changed = changedFiles(ref);
3300
+ }
3194
3301
  if (changed.length === 0) {
3195
3302
  process.exit(0);
3196
3303
  }
3197
3304
  const content = existsSync(DOC_OWNERS_PATH) ? readFileSync(DOC_OWNERS_PATH, "utf8") : null;
3198
- const result = evaluateC5(changed, content, resolvePrBody(), existsSync, waiverActive());
3305
+ const result = evaluateC5(changed, content, resolvePrBody(), existsSync, waiverActive(), (p) => fileDiffAgainst(ref, p));
3199
3306
  if (result.errors.length > 0) {
3200
3307
  for (const message of result.errors) {
3201
3308
  emitCheckError({