@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
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync18 } from "node:fs";
5
- import { dirname as dirname8, join as join20 } from "node:path";
4
+ import { readFileSync as readFileSync19 } from "node:fs";
5
+ import { dirname as dirname8, join as join21 } from "node:path";
6
6
  import { fileURLToPath as fileURLToPath2 } from "node:url";
7
7
 
8
8
  // src/commands/archive.ts
9
9
  import { execFileSync as execFileSync3 } from "node:child_process";
10
- // ../../packages/aeg-core/src/anchored-region.ts
10
+ // ../../packages/aeg-forge-state/src/strip-code.ts
11
11
  function maskCode(body) {
12
12
  const fill = (line) => " ".repeat(line.length);
13
13
  return maskIndentedCode(maskFencedCode(body, fill), fill).replace(/(`+)[^\n]*?\1/g, (m) => " ".repeat(m.length));
@@ -24,29 +24,34 @@ function stripCode(body, options = {}) {
24
24
  var FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})([^\r\n]*)\r?$/;
25
25
  var FENCE_CLOSE = /^ {0,3}(`+|~+)[ \t]*\r?$/;
26
26
  function maskFencedCode(body, fill) {
27
+ const { lines } = scanFencedCode(body);
28
+ return lines.map(({ line, inCode }) => inCode ? fill(line) : line).join(`
29
+ `);
30
+ }
31
+ function scanFencedCode(body) {
27
32
  let fenceChar = null;
28
33
  let fenceLen = 0;
29
- return body.split(`
34
+ const lines = body.split(`
30
35
  `).map((line) => {
31
36
  if (fenceChar === null) {
32
37
  const open = line.match(FENCE_OPEN);
33
38
  if (!open)
34
- return line;
39
+ return { line, inCode: false };
35
40
  const marker = open[1];
36
41
  if (marker[0] === "`" && open[2].includes("`"))
37
- return line;
42
+ return { line, inCode: false };
38
43
  fenceChar = marker[0];
39
44
  fenceLen = marker.length;
40
- return fill(line);
45
+ return { line, inCode: true };
41
46
  }
42
47
  const close = line.match(FENCE_CLOSE);
43
48
  if (close && close[1][0] === fenceChar && close[1].length >= fenceLen) {
44
49
  fenceChar = null;
45
50
  fenceLen = 0;
46
51
  }
47
- return fill(line);
48
- }).join(`
49
- `);
52
+ return { line, inCode: true };
53
+ });
54
+ return { lines, unterminated: fenceChar !== null };
50
55
  }
51
56
  var LIST_MARKER = /^ {0,3}(?:[-*+]|\d{1,9}[.)])(?:\s|$)/;
52
57
  function indentWidth(line) {
@@ -89,88 +94,7 @@ function maskIndentedCode(body, fill) {
89
94
  return out.join(`
90
95
  `);
91
96
  }
92
- function anchoredRegion(body, field) {
93
- const masked = maskCode(body);
94
- const start = new RegExp(`<!--\\s*AEG:${field}:START\\s*-->`).exec(masked);
95
- if (!start)
96
- return null;
97
- const afterStart = start.index + start[0].length;
98
- const end = new RegExp(`<!--\\s*AEG:${field}:END\\s*-->`).exec(masked.slice(afterStart));
99
- if (!end)
100
- return null;
101
- return body.slice(afterStart, afterStart + end.index);
102
- }
103
- // ../../packages/aeg-core/src/parse-registry.ts
104
- function parseRegistry(md) {
105
- const rows = readMarkdownTable(md, /^##\s+Registry\b/i);
106
- const projects = [];
107
- for (const row of rows) {
108
- if (row.length < 4)
109
- continue;
110
- const name = stripBackticks(row[0] ?? "").trim();
111
- const path = stripBackticks(row[1] ?? "").trim();
112
- const specsPath = stripBackticks(row[2] ?? "").trim();
113
- const stateCell = (row[3] ?? "").trim();
114
- if (!name || !path || !specsPath)
115
- continue;
116
- projects.push({
117
- name,
118
- path,
119
- specsPath,
120
- statePath: parseStateCell(stateCell)
121
- });
122
- }
123
- return projects;
124
- }
125
- function parseStateCell(cell) {
126
- if (!cell)
127
- return null;
128
- const stripped = stripBackticks(cell).trim();
129
- if (/^[\w./@-]+\/?$/.test(stripped) && cell.includes("`")) {
130
- return stripped;
131
- }
132
- return null;
133
- }
134
- function stripBackticks(s) {
135
- return s.replace(/`/g, "");
136
- }
137
- function readMarkdownTable(md, headingMatcher) {
138
- const lines = md.split(/\r?\n/);
139
- let i = 0;
140
- for (;i < lines.length; i++) {
141
- if (headingMatcher.test(lines[i] ?? "")) {
142
- i++;
143
- break;
144
- }
145
- }
146
- if (i >= lines.length)
147
- return [];
148
- for (;i < lines.length; i++) {
149
- if ((lines[i] ?? "").trim().startsWith("|"))
150
- break;
151
- }
152
- if (i + 1 >= lines.length)
153
- return [];
154
- i += 2;
155
- const out = [];
156
- for (;i < lines.length; i++) {
157
- const trimmed = (lines[i] ?? "").trim();
158
- if (!trimmed.startsWith("|"))
159
- break;
160
- out.push(splitTableRow(trimmed));
161
- }
162
- return out;
163
- }
164
- function splitTableRow(row) {
165
- const PLACEHOLDER = "\x00PIPE\x00";
166
- const escaped = row.replace(/\\\|/g, PLACEHOLDER);
167
- const parts = escaped.split("|").map((s) => s.trim());
168
- if (parts.length && parts[0] === "")
169
- parts.shift();
170
- if (parts.length && parts[parts.length - 1] === "")
171
- parts.pop();
172
- return parts.map((p) => p.split(PLACEHOLDER).join("|"));
173
- }
97
+
174
98
  // ../../packages/aeg-forge-state/src/parse-rationale-deps.ts
175
99
  var SECTION_HEADER = /\*\*Dependency rationale\*\*/i;
176
100
  // ../../packages/aeg-forge-state/src/gh.ts
@@ -378,6 +302,96 @@ function parseOwnerRepo(value) {
378
302
  return null;
379
303
  return { owner: match[1], repo: match[2] };
380
304
  }
305
+ // ../../packages/aeg-core/src/anchored-region.ts
306
+ function anchoredRegionBounds(body, field) {
307
+ const masked = maskCode(body);
308
+ const start = new RegExp(`<!--\\s*AEG:${field}:START\\s*-->`).exec(masked);
309
+ if (!start)
310
+ return null;
311
+ const innerStart = start.index + start[0].length;
312
+ const end = new RegExp(`<!--\\s*AEG:${field}:END\\s*-->`).exec(masked.slice(innerStart));
313
+ if (!end)
314
+ return null;
315
+ const innerEnd = innerStart + end.index;
316
+ return { outerStart: start.index, outerEnd: innerEnd + end[0].length, innerStart, innerEnd };
317
+ }
318
+ function anchoredRegion(body, field) {
319
+ const bounds = anchoredRegionBounds(body, field);
320
+ if (!bounds)
321
+ return null;
322
+ return body.slice(bounds.innerStart, bounds.innerEnd);
323
+ }
324
+ // ../../packages/aeg-core/src/parse-registry.ts
325
+ function parseRegistry(md) {
326
+ const rows = readMarkdownTable(md, /^##\s+Registry\b/i);
327
+ const projects = [];
328
+ for (const row of rows) {
329
+ if (row.length < 4)
330
+ continue;
331
+ const name = stripBackticks(row[0] ?? "").trim();
332
+ const path = stripBackticks(row[1] ?? "").trim();
333
+ const specsPath = stripBackticks(row[2] ?? "").trim();
334
+ const stateCell = (row[3] ?? "").trim();
335
+ if (!name || !path || !specsPath)
336
+ continue;
337
+ projects.push({
338
+ name,
339
+ path,
340
+ specsPath,
341
+ statePath: parseStateCell(stateCell)
342
+ });
343
+ }
344
+ return projects;
345
+ }
346
+ function parseStateCell(cell) {
347
+ if (!cell)
348
+ return null;
349
+ const stripped = stripBackticks(cell).trim();
350
+ if (/^[\w./@-]+\/?$/.test(stripped) && cell.includes("`")) {
351
+ return stripped;
352
+ }
353
+ return null;
354
+ }
355
+ function stripBackticks(s) {
356
+ return s.replace(/`/g, "");
357
+ }
358
+ function readMarkdownTable(md, headingMatcher) {
359
+ const lines = md.split(/\r?\n/);
360
+ let i = 0;
361
+ for (;i < lines.length; i++) {
362
+ if (headingMatcher.test(lines[i] ?? "")) {
363
+ i++;
364
+ break;
365
+ }
366
+ }
367
+ if (i >= lines.length)
368
+ return [];
369
+ for (;i < lines.length; i++) {
370
+ if ((lines[i] ?? "").trim().startsWith("|"))
371
+ break;
372
+ }
373
+ if (i + 1 >= lines.length)
374
+ return [];
375
+ i += 2;
376
+ const out = [];
377
+ for (;i < lines.length; i++) {
378
+ const trimmed = (lines[i] ?? "").trim();
379
+ if (!trimmed.startsWith("|"))
380
+ break;
381
+ out.push(splitTableRow(trimmed));
382
+ }
383
+ return out;
384
+ }
385
+ function splitTableRow(row) {
386
+ const PLACEHOLDER = "\x00PIPE\x00";
387
+ const escaped = row.replace(/\\\|/g, PLACEHOLDER);
388
+ const parts = escaped.split("|").map((s) => s.trim());
389
+ if (parts.length && parts[0] === "")
390
+ parts.shift();
391
+ if (parts.length && parts[parts.length - 1] === "")
392
+ parts.pop();
393
+ return parts.map((p) => p.split(PLACEHOLDER).join("|"));
394
+ }
381
395
  // ../../packages/aeg-core/src/state-machine-model.ts
382
396
  var FORGE_FACT_INPUTS = [
383
397
  {
@@ -970,6 +984,10 @@ function starterConfig() {
970
984
  }
971
985
  };
972
986
  }
987
+ function ownVersion() {
988
+ const pkg = JSON.parse(readFileSync(join2(packageRoot(import.meta.url), "package.json"), "utf-8"));
989
+ return pkg.version;
990
+ }
973
991
  var SETUP_BUN_SHA = "0c5077e51419868618aeaa5fe8019c62421857d6";
974
992
  function vinayaSetupSteps(selfHost) {
975
993
  if (!selfHost)
@@ -1001,7 +1019,7 @@ function vinayaSetupSteps(selfHost) {
1001
1019
  `;
1002
1020
  }
1003
1021
  function vinayaRun(selfHost, args) {
1004
- return selfHost ? `node ${selfHost.bin} ${args}` : `npx --yes @attalabs/vinaya ${args}`;
1022
+ return selfHost ? `node ${selfHost.bin} ${args}` : `npx --yes @attalabs/vinaya@${ownVersion()} ${args}`;
1005
1023
  }
1006
1024
  function adopterSetupStep(ciSetup) {
1007
1025
  if (!ciSetup)
@@ -1427,10 +1445,6 @@ ${vinayaSetupSteps(selfHost)} - name: Run vinaya audit --only=direct-push
1427
1445
  }
1428
1446
  var HOOK_PREAMBLE = `#!/usr/bin/env sh
1429
1447
  `;
1430
- function ownVersion() {
1431
- const pkg = JSON.parse(readFileSync(join2(packageRoot(import.meta.url), "package.json"), "utf-8"));
1432
- return pkg.version;
1433
- }
1434
1448
  function hookRun(selfHost, args) {
1435
1449
  if (!selfHost)
1436
1450
  return `npx --yes @attalabs/vinaya@${ownVersion()} ${args} || exit 1`;
@@ -3132,6 +3146,20 @@ function coreCheckRegistry() {
3132
3146
  GITHUB_TOKEN: { optional: true },
3133
3147
  GH_TOKEN: { optional: true }
3134
3148
  }
3149
+ },
3150
+ {
3151
+ name: "evidence-fresh",
3152
+ run: bin("check-evidence-fresh"),
3153
+ scope: "diff",
3154
+ timeoutMs: 15000,
3155
+ requiresOpenPr: true,
3156
+ env: {
3157
+ PR_NUMBER: { optional: true },
3158
+ PR_BODY: { optional: true },
3159
+ BASE_SHA: { optional: true },
3160
+ GITHUB_TOKEN: { optional: true },
3161
+ GH_TOKEN: { optional: true }
3162
+ }
3135
3163
  }
3136
3164
  ];
3137
3165
  }
@@ -3177,11 +3205,16 @@ function resolveChecks(core, configChecks) {
3177
3205
  }
3178
3206
  return { resolved, failures };
3179
3207
  }
3180
- function overriddenNextMinorWarning(name) {
3181
- return `check "${name}" shares its name with a core check — it will replace the core check starting next minor (currently it runs alongside it under the flat registry).`;
3208
+ var MAX_KEY_DISPLAY_LENGTH = 80;
3209
+ function sanitizeKeyForDisplay(key) {
3210
+ const stripped = key.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ");
3211
+ return stripped.length > MAX_KEY_DISPLAY_LENGTH ? `${stripped.slice(0, MAX_KEY_DISPLAY_LENGTH)}…` : stripped;
3182
3212
  }
3183
- function bareKeyNextMinorWarning(key) {
3184
- return `check "${key}" has no namespace and matches no core check — it will be rejected starting next minor. Rename it to "<yourname>/x".`;
3213
+ function overriddenReplacesCoreDiagnostic(name) {
3214
+ return `check "${sanitizeKeyForDisplay(name)}" shares its name with a core check — it REPLACES that core check. The core check does not run.`;
3215
+ }
3216
+ function bareKeyRejectedDiagnostic(key) {
3217
+ return `check "${sanitizeKeyForDisplay(key)}" has no namespace and matches no core check — it is REJECTED, and \`vinaya check\` refuses the whole run rather than executing a partial ruleset. Rename it to "<yourname>/<id>", both segments matching [a-z0-9][a-z0-9-]* — prefixing alone is not enough when the bare name itself breaks that grammar ("my_check", "QALint" need a real rename, not just a prefix).`;
3185
3218
  }
3186
3219
 
3187
3220
  // src/checks/runner.ts
@@ -3466,26 +3499,6 @@ function parseParallel(args) {
3466
3499
  }
3467
3500
  return;
3468
3501
  }
3469
- function configErrorOutcome(path, error) {
3470
- const finding = {
3471
- schema: 1,
3472
- check: "config",
3473
- severity: "error",
3474
- message: `${path}: invalid \`checks\` registration — ${error}`,
3475
- agent_recovery_prompt: `Fix the invalid key/value named above in ${path}, then re-run \`vinaya check\`.`
3476
- };
3477
- return { name: "config", status: "error", exitCode: null, errors: [finding], durationMs: 0 };
3478
- }
3479
- function customSpecsFromConfig() {
3480
- const result = loadConfigChecked();
3481
- if (!result.ok)
3482
- return { specs: [], errorOutcome: configErrorOutcome(result.path, result.error), checks: undefined };
3483
- const checks = result.config?.checks;
3484
- if (!checks)
3485
- return { specs: [], errorOutcome: null, checks: undefined };
3486
- const specs = Object.entries(checks).map(([name, entry2]) => ({ name, ...entry2 }));
3487
- return { specs, errorOutcome: null, checks };
3488
- }
3489
3502
  function classifyEnvValue(value) {
3490
3503
  if (value === true)
3491
3504
  return "passthrough";
@@ -3539,27 +3552,46 @@ function renderPlanTable(result) {
3539
3552
  return table.map((row) => row.map((cell, col) => (cell ?? "").padEnd(widths[col] ?? 0)).join(" ")).join(`
3540
3553
  `);
3541
3554
  }
3542
- function resolveForPlan(configResult) {
3543
- if (configResult.ok) {
3544
- return resolveChecks(coreCheckRegistry(), configResult.config?.checks);
3555
+ function duplicateIdFailures(resolved) {
3556
+ const seen = new Set;
3557
+ const reported = new Set;
3558
+ const failures = [];
3559
+ for (const entry2 of resolved) {
3560
+ if (!seen.has(entry2.name)) {
3561
+ seen.add(entry2.name);
3562
+ continue;
3563
+ }
3564
+ if (reported.has(entry2.name))
3565
+ continue;
3566
+ reported.add(entry2.name);
3567
+ failures.push({ key: entry2.name, reason: "duplicate check id — two resolved entries claim the same name" });
3545
3568
  }
3546
- const core = resolveChecks(coreCheckRegistry(), undefined);
3547
- return {
3548
- resolved: core.resolved,
3569
+ return failures;
3570
+ }
3571
+ var FAIL_CLOSED_RECOVERY = "Fix or remove the rejected `checks` entries named above in vinaya.config.json, then re-run `vinaya check`. Until every entry resolves, NO check runs — vinaya refuses the whole run rather than executing a partial ruleset. `vinaya check --plan` prints the same resolution, and `vinaya doctor` carries the permanent diagnostic for each rejected entry.";
3572
+ function refusal(message) {
3573
+ return { schema: 1, check: "config", severity: "error", message, agent_recovery_prompt: FAIL_CLOSED_RECOVERY };
3574
+ }
3575
+ function resolveForRun(configResult) {
3576
+ const base = configResult.ok ? resolveChecks(coreCheckRegistry(), configResult.config?.checks) : {
3577
+ resolved: resolveChecks(coreCheckRegistry(), undefined).resolved,
3549
3578
  failures: [{ key: configResult.path, reason: `invalid \`checks\` registration — ${configResult.error}` }]
3550
3579
  };
3580
+ const duplicates = duplicateIdFailures(base.resolved);
3581
+ const refusals = [
3582
+ ...base.failures.map((failure) => configResult.ok ? refusal(bareKeyRejectedDiagnostic(failure.key)) : refusal(`${failure.key}: ${failure.reason} — refusing to run any check.`)),
3583
+ ...duplicates.map((failure) => refusal(`${failure.key}: ${failure.reason} — refusing to run any check.`))
3584
+ ];
3585
+ return { result: { resolved: base.resolved, failures: [...base.failures, ...duplicates] }, refusals };
3551
3586
  }
3552
- function printClassificationWarnings(configChecks) {
3553
- const classification = resolveChecks(coreCheckRegistry(), configChecks);
3554
- for (const entry2 of classification.resolved) {
3555
- if (entry2.state === "overridden")
3556
- process.stdout.write(`${overriddenNextMinorWarning(entry2.name)}
3557
- `);
3558
- }
3559
- for (const failure of classification.failures) {
3560
- process.stdout.write(`${bareKeyNextMinorWarning(failure.key)}
3561
- `);
3562
- }
3587
+ function substitutionNotices(resolved) {
3588
+ return resolved.filter((entry2) => entry2.state === "overridden").map((entry2) => ({
3589
+ schema: 1,
3590
+ check: "config",
3591
+ severity: "warning",
3592
+ message: overriddenReplacesCoreDiagnostic(entry2.name),
3593
+ agent_recovery_prompt: `If replacing the core check "${entry2.name}" was intended, nothing to do — this is a notice, not a failure. If it was not, remove or rename that \`checks\` entry in vinaya.config.json so the core check runs again.`
3594
+ }));
3563
3595
  }
3564
3596
  async function checkCommand(args) {
3565
3597
  const jsonOutput = args.includes("--json");
@@ -3570,8 +3602,8 @@ async function checkCommand(args) {
3570
3602
  const planRequested = args.includes("--plan");
3571
3603
  const positional = args.filter((a) => !a.startsWith("--"));
3572
3604
  const requestedName = positional[0];
3605
+ const { result, refusals } = resolveForRun(loadConfigChecked());
3573
3606
  if (planRequested) {
3574
- const result = resolveForPlan(loadConfigChecked());
3575
3607
  if (jsonOutput) {
3576
3608
  process.stdout.write(`${JSON.stringify(renderPlanJson(result), null, 2)}
3577
3609
  `);
@@ -3585,8 +3617,36 @@ async function checkCommand(args) {
3585
3617
  console.error("Usage: vinaya check <name> | --all | --plan [--json] [--diff-only] [--local] [--parallel[=n]]");
3586
3618
  process.exit(2);
3587
3619
  }
3588
- const { specs: customSpecs, errorOutcome, checks: configChecks } = customSpecsFromConfig();
3589
- const allSpecs = [...coreCheckRegistry(), ...customSpecs];
3620
+ if (refusals.length > 0) {
3621
+ const outcome = {
3622
+ name: "config",
3623
+ status: "error",
3624
+ exitCode: null,
3625
+ errors: refusals,
3626
+ durationMs: 0
3627
+ };
3628
+ for (const e of refusals)
3629
+ emitCheckError(e);
3630
+ if (jsonOutput) {
3631
+ printJson({ checks: [outcome] });
3632
+ } else {
3633
+ process.stdout.write(`✗ ${outcome.name}: refused — no checks ran
3634
+ `);
3635
+ for (const e of refusals)
3636
+ process.stdout.write(` ${e.severity}: ${e.message}
3637
+ `);
3638
+ }
3639
+ process.exit(1);
3640
+ }
3641
+ const allSpecs = result.resolved.map((entry2) => entry2.spec);
3642
+ const notices = substitutionNotices(result.resolved);
3643
+ for (const notice of notices)
3644
+ emitCheckError(notice);
3645
+ if (!jsonOutput) {
3646
+ for (const notice of notices)
3647
+ process.stdout.write(`⚠ ${notice.message}
3648
+ `);
3649
+ }
3590
3650
  const specsToRun = allRequested ? allSpecs.filter(runsUnderAll) : allSpecs.filter((s) => s.name === requestedName);
3591
3651
  if (!allRequested && specsToRun.length === 0) {
3592
3652
  console.error(`Unknown check: ${requestedName}`);
@@ -3600,15 +3660,14 @@ async function checkCommand(args) {
3600
3660
  defaultTimeoutMs: 30000,
3601
3661
  localOnly
3602
3662
  }) : [];
3603
- const allOutcomes = errorOutcome ? [...outcomes, errorOutcome] : outcomes;
3604
- for (const o of allOutcomes) {
3663
+ for (const o of outcomes) {
3605
3664
  for (const e of o.errors)
3606
3665
  emitCheckError(e);
3607
3666
  }
3608
3667
  if (jsonOutput) {
3609
- printJson({ checks: allOutcomes });
3668
+ printJson({ checks: outcomes });
3610
3669
  } else {
3611
- for (const o of allOutcomes) {
3670
+ for (const o of outcomes) {
3612
3671
  const symbol = o.status === "pass" ? "✓" : o.status === "skipped" ? "·" : "✗";
3613
3672
  process.stdout.write(`${symbol} ${o.name}: ${o.status} (${Math.round(o.durationMs)}ms)
3614
3673
  `);
@@ -3616,9 +3675,8 @@ async function checkCommand(args) {
3616
3675
  process.stdout.write(` ${e.severity}: ${e.message}
3617
3676
  `);
3618
3677
  }
3619
- printClassificationWarnings(configChecks);
3620
3678
  }
3621
- const failed = allOutcomes.some((o) => o.status === "fail" || o.status === "error" || o.status === "timeout");
3679
+ const failed = outcomes.some((o) => o.status === "fail" || o.status === "error" || o.status === "timeout");
3622
3680
  process.exit(failed ? 1 : 0);
3623
3681
  }
3624
3682
 
@@ -4125,12 +4183,9 @@ function diagnoseCustomChecks(repoRoot, config) {
4125
4183
  return findings;
4126
4184
  }
4127
4185
  function diagnoseEnvDeclarations(repoRoot, config) {
4128
- const customSpecs = Object.entries(config?.checks ?? {}).map(([name, entry2]) => ({
4129
- name,
4130
- ...entry2,
4131
- run: join9(repoRoot, entry2.run)
4132
- }));
4133
- const missing = checksMissingEnvDeclaration([...coreCheckRegistry(), ...customSpecs]);
4186
+ const resolved = resolveChecks(coreCheckRegistry(), config?.checks).resolved;
4187
+ const specs = resolved.map((entry2) => entry2.source === "config" ? { ...entry2.spec, run: join9(repoRoot, entry2.spec.run) } : entry2.spec);
4188
+ const missing = checksMissingEnvDeclaration(specs);
4134
4189
  const findings = missing.map((name) => info("env", envDeclarationWarning(name)));
4135
4190
  for (const message of lintEnvDeclarations(config?.checks)) {
4136
4191
  findings.push(warn("env", message));
@@ -4142,10 +4197,10 @@ function diagnoseCheckClassification(config) {
4142
4197
  const findings = [];
4143
4198
  for (const entry2 of classification.resolved) {
4144
4199
  if (entry2.state === "overridden")
4145
- findings.push(warn("checks", overriddenNextMinorWarning(entry2.name)));
4200
+ findings.push(warn("checks", overriddenReplacesCoreDiagnostic(entry2.name)));
4146
4201
  }
4147
4202
  for (const failure of classification.failures) {
4148
- findings.push(warn("checks", bareKeyNextMinorWarning(failure.key)));
4203
+ findings.push(error("checks", bareKeyRejectedDiagnostic(failure.key)));
4149
4204
  }
4150
4205
  return findings;
4151
4206
  }
@@ -5021,17 +5076,27 @@ import { chmodSync as chmodSync2, existsSync as existsSync13, mkdirSync as mkdir
5021
5076
  import { join as join15 } from "node:path";
5022
5077
  var TEMPLATE_PATH = join15(packageRoot(import.meta.url), "templates", "custom-check.template.ts");
5023
5078
  var CHECKS_DIR = join15("scripts", "vinaya-checks");
5024
- var VALID_NAME = /^[a-z0-9][a-z0-9-]*$/;
5079
+ var USAGE = "Usage: vinaya new check <yourname>/<id> (both segments: lowercase letters, digits, hyphens; e.g. myteam/vocab-check)";
5025
5080
  function newCheckCommand(args) {
5026
5081
  const name = args[0];
5027
- if (!name || !VALID_NAME.test(name)) {
5028
- console.error("Usage: vinaya new check <name> (name: lowercase letters, digits, hyphens)");
5082
+ if (!name) {
5083
+ console.error(USAGE);
5084
+ process.exit(2);
5085
+ }
5086
+ if (coreCheckRegistry().some((spec) => spec.name === name)) {
5087
+ console.error(`Refusing: "${name}" is a core check id. Registering it under \`checks\` REPLACES that core check — the core one stops running — so a scaffolded stub is never the right shape for it. Pick a namespaced name, or write the override entry by hand if you really mean to replace the core check.`);
5088
+ process.exit(2);
5089
+ }
5090
+ if (!isValidNamespacedKey(name)) {
5091
+ console.error(`Refusing: "${name}" is not a valid check id. ${USAGE.slice("Usage: ".length)}
5092
+ \`vinaya check\` refuses the entire run over a key it cannot resolve, so a bare, un-namespaced name would brick every check invocation in this repo.`);
5029
5093
  process.exit(2);
5030
5094
  }
5031
5095
  const scriptsDir = join15(process.cwd(), CHECKS_DIR);
5032
5096
  if (!existsSync13(scriptsDir))
5033
5097
  mkdirSync4(scriptsDir, { recursive: true });
5034
- const targetPath = join15(scriptsDir, `${name}.ts`);
5098
+ const fileStem = name.slice(name.indexOf("/") + 1);
5099
+ const targetPath = join15(scriptsDir, `${fileStem}.ts`);
5035
5100
  if (existsSync13(targetPath)) {
5036
5101
  console.error(`Error: ${targetPath} already exists.`);
5037
5102
  process.exit(1);
@@ -5040,7 +5105,7 @@ function newCheckCommand(args) {
5040
5105
  const contents = template.split("{{CHECK_NAME}}").join(name);
5041
5106
  writeFileSync7(targetPath, contents, "utf-8");
5042
5107
  chmodSync2(targetPath, 493);
5043
- const relPath = join15(CHECKS_DIR, `${name}.ts`);
5108
+ const relPath = join15(CHECKS_DIR, `${fileStem}.ts`);
5044
5109
  const registration = JSON.stringify({ checks: { [name]: { run: `./${relPath}`, scope: "diff" } } }, null, 2);
5045
5110
  process.stdout.write(`Created ${relPath}
5046
5111
 
@@ -5204,14 +5269,193 @@ function prEditCommand(args) {
5204
5269
  runGhWrite2(["pr", "edit", prRef], ghArgs, bodyResult, json);
5205
5270
  }
5206
5271
 
5272
+ // src/commands/pr-report.ts
5273
+ import { execFileSync as execFileSync9, spawnSync as spawnSync2 } from "node:child_process";
5274
+ import { existsSync as existsSync14, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "node:fs";
5275
+ import { join as join16 } from "node:path";
5276
+ var EVIDENCE_START = "<!-- AEG:EVIDENCE:START -->";
5277
+ var EVIDENCE_END = "<!-- AEG:EVIDENCE:END -->";
5278
+ function git3(args) {
5279
+ try {
5280
+ return execFileSync9("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
5281
+ } catch {
5282
+ return "";
5283
+ }
5284
+ }
5285
+
5286
+ class GitCommandError extends Error {
5287
+ args;
5288
+ cause;
5289
+ constructor(args, cause) {
5290
+ super(`\`git ${args.join(" ")}\` failed: ${cause}`);
5291
+ this.args = args;
5292
+ this.cause = cause;
5293
+ this.name = "GitCommandError";
5294
+ }
5295
+ }
5296
+ function gitStrict(args) {
5297
+ try {
5298
+ return execFileSync9("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
5299
+ } catch (err) {
5300
+ const stderr = err.stderr;
5301
+ throw new GitCommandError(args, String(stderr ?? err.message).trim() || "non-zero exit");
5302
+ }
5303
+ }
5304
+
5305
+ class UnresolvableMergeBaseError extends Error {
5306
+ head;
5307
+ triedRefs;
5308
+ constructor(head, triedRefs) {
5309
+ super(`could not resolve a merge-base for HEAD (${head}) against any of: ${triedRefs.join(", ")}. ` + `This repo's default branch may not be named "main", or "origin" may not be configured — set BASE_SHA to override.`);
5310
+ this.head = head;
5311
+ this.triedRefs = triedRefs;
5312
+ this.name = "UnresolvableMergeBaseError";
5313
+ }
5314
+ }
5315
+ function resolveMergeBase(head) {
5316
+ const primary = process.env.BASE_SHA || "origin/main";
5317
+ const tried = primary === "main" ? [primary] : [primary, "main"];
5318
+ for (const ref of tried) {
5319
+ const base = git3(["merge-base", ref, head]);
5320
+ if (base)
5321
+ return base;
5322
+ }
5323
+ throw new UnresolvableMergeBaseError(head, tried);
5324
+ }
5325
+ function computeGroupA() {
5326
+ const head = gitStrict(["rev-parse", "HEAD"]);
5327
+ const base = resolveMergeBase(head);
5328
+ const numstat = gitStrict(["diff", `${base}...${head}`, "--numstat"]);
5329
+ return { head, base, numstat };
5330
+ }
5331
+ function resolveSelfEntry() {
5332
+ const root = packageRoot(import.meta.url);
5333
+ const dist = join16(root, "dist", "index.js");
5334
+ return existsSync14(dist) ? dist : join16(root, "src", "index.ts");
5335
+ }
5336
+ var FAILING_STATUSES = new Set(["fail", "error", "timeout"]);
5337
+ function anyGateFailed(outcomes) {
5338
+ return outcomes.some((o) => FAILING_STATUSES.has(o.status));
5339
+ }
5340
+ function runRealGates() {
5341
+ const entry2 = resolveSelfEntry();
5342
+ const proc = spawnSync2(process.execPath, [entry2, "check", "--all", "--diff-only", "--json"], {
5343
+ cwd: process.cwd(),
5344
+ encoding: "utf8",
5345
+ stdio: ["ignore", "pipe", "ignore"],
5346
+ maxBuffer: 32 * 1024 * 1024
5347
+ });
5348
+ const stdout = proc.stdout ?? "";
5349
+ try {
5350
+ const parsed = JSON.parse(stdout);
5351
+ const outcomes = parsed.data.checks;
5352
+ return { outcomes, failed: anyGateFailed(outcomes) };
5353
+ } catch {
5354
+ return {
5355
+ outcomes: [
5356
+ {
5357
+ name: "check",
5358
+ status: "error",
5359
+ errors: [
5360
+ { severity: "error", message: "`vinaya check --all --diff-only --json` did not produce parseable output." }
5361
+ ]
5362
+ }
5363
+ ],
5364
+ failed: true
5365
+ };
5366
+ }
5367
+ }
5368
+ function renderGroupA(groupA) {
5369
+ return [
5370
+ "### Group A — recomputable",
5371
+ "",
5372
+ `\`git diff ${groupA.base}...${groupA.head} --numstat\``,
5373
+ "",
5374
+ "```",
5375
+ groupA.numstat,
5376
+ "```"
5377
+ ].join(`
5378
+ `);
5379
+ }
5380
+ function renderGroupB(outcomes) {
5381
+ const sorted = [...outcomes].sort((a, b) => a.name.localeCompare(b.name));
5382
+ const lines = sorted.flatMap((o) => {
5383
+ const rows = [`${o.name}: ${o.status}`];
5384
+ for (const e of o.errors)
5385
+ rows.push(` ${e.severity}: ${e.message}`);
5386
+ return rows;
5387
+ });
5388
+ return ["### Group B — attested", "", "`vinaya check --all --diff-only`", "", "```", lines.join(`
5389
+ `), "```"].join(`
5390
+ `);
5391
+ }
5392
+ function buildBlockInner(groupA, gateOutcomes) {
5393
+ return [`Head: ${groupA.head}`, "", renderGroupA(groupA), "", renderGroupB(gateOutcomes)].join(`
5394
+ `);
5395
+ }
5396
+ function replaceEvidenceBlock(body, blockInner) {
5397
+ const full = `${EVIDENCE_START}
5398
+ ${blockInner}
5399
+ ${EVIDENCE_END}`;
5400
+ const bounds = anchoredRegionBounds(body, "EVIDENCE");
5401
+ if (bounds) {
5402
+ return `${body.slice(0, bounds.outerStart)}${full}${body.slice(bounds.outerEnd)}`;
5403
+ }
5404
+ const sep4 = body.length === 0 || body.endsWith(`
5405
+ `) ? "" : `
5406
+ `;
5407
+ return `${body}${sep4}
5408
+ ${full}
5409
+ `;
5410
+ }
5411
+ async function buildReport(opts = {}) {
5412
+ const groupA = opts.groupA ?? computeGroupA();
5413
+ const gateRunner = opts.gateRunner ?? runRealGates;
5414
+ const gateResult = await gateRunner();
5415
+ const blockInner = buildBlockInner(groupA, gateResult.outcomes);
5416
+ const block = `${EVIDENCE_START}
5417
+ ${blockInner}
5418
+ ${EVIDENCE_END}`;
5419
+ return { block, blockInner, gatesFailed: gateResult.failed, gateOutcomes: gateResult.outcomes };
5420
+ }
5421
+ async function prReportCommand(args) {
5422
+ const writeIdx = args.indexOf("--write");
5423
+ const writePath = writeIdx !== -1 ? args[writeIdx + 1] : undefined;
5424
+ if (writeIdx !== -1 && !writePath) {
5425
+ console.error("Usage: vinaya pr report [--write <body-file>]");
5426
+ process.exit(2);
5427
+ }
5428
+ let result;
5429
+ try {
5430
+ result = await buildReport();
5431
+ } catch (err) {
5432
+ if (err instanceof UnresolvableMergeBaseError || err instanceof GitCommandError) {
5433
+ console.error(`vinaya pr report: refused — ${err.message}`);
5434
+ process.exit(1);
5435
+ }
5436
+ throw err;
5437
+ }
5438
+ if (writePath) {
5439
+ const existing = existsSync14(writePath) ? readFileSync14(writePath, "utf8") : "";
5440
+ const updated = replaceEvidenceBlock(existing, result.blockInner);
5441
+ writeFileSync8(writePath, updated);
5442
+ process.stdout.write(`Wrote AEG:EVIDENCE block to ${writePath}
5443
+ `);
5444
+ } else {
5445
+ process.stdout.write(`${result.block}
5446
+ `);
5447
+ }
5448
+ process.exit(result.gatesFailed ? 1 : 0);
5449
+ }
5450
+
5207
5451
  // src/commands/quickstart.ts
5208
- import { execFileSync as execFileSync9 } from "node:child_process";
5209
- import { existsSync as existsSync15, readFileSync as readFileSync15 } from "node:fs";
5210
- import { join as join17 } from "node:path";
5452
+ import { execFileSync as execFileSync10 } from "node:child_process";
5453
+ import { existsSync as existsSync16, readFileSync as readFileSync16 } from "node:fs";
5454
+ import { join as join18 } from "node:path";
5211
5455
 
5212
5456
  // src/lib/doc-owners-write.ts
5213
- import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "node:fs";
5214
- import { dirname as dirname6, join as join16 } from "node:path";
5457
+ import { existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync15, writeFileSync as writeFileSync9 } from "node:fs";
5458
+ import { dirname as dirname6, join as join17 } from "node:path";
5215
5459
  function bindingLine(glob, pointer) {
5216
5460
  return `${glob} ${pointer}`;
5217
5461
  }
@@ -5227,23 +5471,23 @@ function appendDocOwnersBinding(content, glob, pointer) {
5227
5471
  `;
5228
5472
  }
5229
5473
  function planDocOwnersBinding(repoRoot, glob, pointer) {
5230
- const abs2 = join16(repoRoot, DOC_OWNERS_PATH);
5474
+ const abs2 = join17(repoRoot, DOC_OWNERS_PATH);
5231
5475
  const line = bindingLine(glob, pointer);
5232
- if (!existsSync14(abs2)) {
5476
+ if (!existsSync15(abs2)) {
5233
5477
  return { action: "create-host", line, path: DOC_OWNERS_PATH };
5234
5478
  }
5235
- const existing = readFileSync14(abs2, "utf-8");
5479
+ const existing = readFileSync15(abs2, "utf-8");
5236
5480
  const already = parseDocOwners(existing).bindings.some((b) => b.glob === glob);
5237
5481
  return { action: already ? "skip-present" : "append-row", line, path: DOC_OWNERS_PATH };
5238
5482
  }
5239
5483
  function applyDocOwnersBinding(repoRoot, plan, glob, pointer) {
5240
- const abs2 = join16(repoRoot, DOC_OWNERS_PATH);
5484
+ const abs2 = join17(repoRoot, DOC_OWNERS_PATH);
5241
5485
  if (plan.action === "create-host") {
5242
5486
  mkdirSync5(dirname6(abs2), { recursive: true });
5243
- writeFileSync8(abs2, freshDocOwners(glob, pointer), "utf-8");
5487
+ writeFileSync9(abs2, freshDocOwners(glob, pointer), "utf-8");
5244
5488
  } else if (plan.action === "append-row") {
5245
- const existing = readFileSync14(abs2, "utf-8");
5246
- writeFileSync8(abs2, appendDocOwnersBinding(existing, glob, pointer), "utf-8");
5489
+ const existing = readFileSync15(abs2, "utf-8");
5490
+ writeFileSync9(abs2, appendDocOwnersBinding(existing, glob, pointer), "utf-8");
5247
5491
  }
5248
5492
  }
5249
5493
  function renderDocOwnersBindingDiffLine(plan) {
@@ -5303,7 +5547,7 @@ function pointerIsMissingLocalFile(repoRoot, pointer) {
5303
5547
  if (/^https?:\/\//i.test(pointer))
5304
5548
  return false;
5305
5549
  const path = pointer.split("#")[0] ?? pointer;
5306
- return !existsSync15(join17(repoRoot, path));
5550
+ return !existsSync16(join18(repoRoot, path));
5307
5551
  }
5308
5552
  async function bindDocOwnerLoop(deps, repoRoot) {
5309
5553
  let any = false;
@@ -5370,7 +5614,7 @@ async function registerProjectLoop(deps) {
5370
5614
  }
5371
5615
  }
5372
5616
  function readPackageVersion() {
5373
- const pkg = JSON.parse(readFileSync15(join17(packageRoot(import.meta.url), "package.json"), "utf-8"));
5617
+ const pkg = JSON.parse(readFileSync16(join18(packageRoot(import.meta.url), "package.json"), "utf-8"));
5374
5618
  return pkg.version;
5375
5619
  }
5376
5620
  function realDeps6() {
@@ -5401,7 +5645,7 @@ function realDeps6() {
5401
5645
  };
5402
5646
  }
5403
5647
  function execGit(repoRoot, args) {
5404
- return execFileSync9("git", args, { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
5648
+ return execFileSync10("git", args, { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
5405
5649
  }
5406
5650
  function errorDetail(err) {
5407
5651
  const stderr = err.stderr;
@@ -5519,9 +5763,9 @@ async function quickstartCommand(args) {
5519
5763
 
5520
5764
  // src/commands/studio.ts
5521
5765
  import { execFile as execFile5, spawn as spawn2 } from "node:child_process";
5522
- import { existsSync as existsSync16, readFileSync as readFileSync16, renameSync } from "node:fs";
5766
+ import { existsSync as existsSync17, readFileSync as readFileSync17, renameSync } from "node:fs";
5523
5767
  import net from "node:net";
5524
- import { dirname as dirname7, join as join18 } from "node:path";
5768
+ import { dirname as dirname7, join as join19 } from "node:path";
5525
5769
  import { promisify as promisify5 } from "node:util";
5526
5770
 
5527
5771
  // src/lib/studio-bundle.ts
@@ -5537,25 +5781,25 @@ var FALLBACK_PORT = 3108;
5537
5781
  function resolveStudioTarget(cwd, moduleUrl = import.meta.url) {
5538
5782
  let dir = cwd;
5539
5783
  for (;; ) {
5540
- const webDir = join18(dir, "apps", "vinaya-studio", "web");
5541
- const pkgPath = join18(webDir, "package.json");
5542
- if (existsSync16(pkgPath)) {
5784
+ const webDir = join19(dir, "apps", "vinaya-studio", "web");
5785
+ const pkgPath = join19(webDir, "package.json");
5786
+ if (existsSync17(pkgPath)) {
5543
5787
  try {
5544
- const pkg = JSON.parse(readFileSync16(pkgPath, "utf-8"));
5788
+ const pkg = JSON.parse(readFileSync17(pkgPath, "utf-8"));
5545
5789
  if (pkg.name === "@atta/vinaya-studio-web") {
5546
5790
  return { kind: "workspace", webDir };
5547
5791
  }
5548
5792
  } catch {}
5549
5793
  }
5550
- if (existsSync16(join18(dir, ".git")))
5794
+ if (existsSync17(join19(dir, ".git")))
5551
5795
  break;
5552
5796
  const parent = dirname7(dir);
5553
5797
  if (parent === dir)
5554
5798
  break;
5555
5799
  dir = parent;
5556
5800
  }
5557
- const standaloneWebDir = join18(packageRoot(moduleUrl), "studio-standalone", "apps", "vinaya-studio", "web");
5558
- if (existsSync16(join18(standaloneWebDir, "server.js"))) {
5801
+ const standaloneWebDir = join19(packageRoot(moduleUrl), "studio-standalone", "apps", "vinaya-studio", "web");
5802
+ if (existsSync17(join19(standaloneWebDir, "server.js"))) {
5559
5803
  return { kind: "package", packageDir: standaloneWebDir };
5560
5804
  }
5561
5805
  return { kind: "missing" };
@@ -5575,9 +5819,9 @@ function isPortFree(port) {
5575
5819
  });
5576
5820
  }
5577
5821
  function ensureStudioNodeModules(bundleRoot) {
5578
- const real = join18(bundleRoot, "node_modules");
5579
- const packed = join18(bundleRoot, STUDIO_NODE_MODULES_PACKED_DIRNAME);
5580
- if (!existsSync16(real) && existsSync16(packed)) {
5822
+ const real = join19(bundleRoot, "node_modules");
5823
+ const packed = join19(bundleRoot, STUDIO_NODE_MODULES_PACKED_DIRNAME);
5824
+ if (!existsSync17(real) && existsSync17(packed)) {
5581
5825
  renameSync(packed, real);
5582
5826
  }
5583
5827
  }
@@ -5603,8 +5847,8 @@ async function runStudio(cwd, args, moduleUrl = import.meta.url) {
5603
5847
  case "workspace":
5604
5848
  return spawnDev(target.webDir, args);
5605
5849
  case "package": {
5606
- const bundleRoot = join18(target.packageDir, "..", "..", "..");
5607
- return spawnStandalone(cwd, join18(target.packageDir, "server.js"), bundleRoot);
5850
+ const bundleRoot = join19(target.packageDir, "..", "..", "..");
5851
+ return spawnStandalone(cwd, join19(target.packageDir, "server.js"), bundleRoot);
5608
5852
  }
5609
5853
  case "missing":
5610
5854
  console.error("Vinaya Studio isn't available in this install — no published @attalabs/vinaya build bundles the Studio app yet. Inside a checkout that contains Studio's source (apps/vinaya-studio/web), `vinaya studio` runs it directly.");
@@ -5613,8 +5857,8 @@ async function runStudio(cwd, args, moduleUrl = import.meta.url) {
5613
5857
  }
5614
5858
 
5615
5859
  // src/commands/upgrade.ts
5616
- import { existsSync as existsSync17, readFileSync as readFileSync17, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "node:fs";
5617
- import { join as join19 } from "node:path";
5860
+ import { existsSync as existsSync18, readFileSync as readFileSync18, rmSync as rmSync4, writeFileSync as writeFileSync10 } from "node:fs";
5861
+ import { join as join20 } from "node:path";
5618
5862
  function realDeps7() {
5619
5863
  return {
5620
5864
  detectRepo: detectGitRepo,
@@ -5632,12 +5876,12 @@ function flags2(args) {
5632
5876
  return { dryRun: args.includes("--dry-run"), yes: args.includes("--yes") };
5633
5877
  }
5634
5878
  function readManifest3(repoRoot) {
5635
- const p = join19(repoRoot, CONFIG_PATH);
5636
- if (!existsSync17(p))
5879
+ const p = join20(repoRoot, CONFIG_PATH);
5880
+ if (!existsSync18(p))
5637
5881
  return { kind: "missing" };
5638
5882
  let raw;
5639
5883
  try {
5640
- raw = JSON.parse(readFileSync17(p, "utf-8"));
5884
+ raw = JSON.parse(readFileSync18(p, "utf-8"));
5641
5885
  } catch (err) {
5642
5886
  return { kind: "invalid", error: `invalid JSON: ${err.message}` };
5643
5887
  }
@@ -5653,17 +5897,17 @@ function readManifest3(repoRoot) {
5653
5897
  return { kind: "ok", manifest: parsed.data.managed };
5654
5898
  }
5655
5899
  function writeManifestVersion(repoRoot, manifest) {
5656
- const configAbs = join19(repoRoot, CONFIG_PATH);
5657
- const seed = JSON.parse(readFileSync17(configAbs, "utf-8"));
5900
+ const configAbs = join20(repoRoot, CONFIG_PATH);
5901
+ const seed = JSON.parse(readFileSync18(configAbs, "utf-8"));
5658
5902
  const updated = { ...manifest, version: MANAGED_MANIFEST_VERSION };
5659
- writeFileSync9(configAbs, `${JSON.stringify({ ...seed, managed: updated }, null, 2)}
5903
+ writeFileSync10(configAbs, `${JSON.stringify({ ...seed, managed: updated }, null, 2)}
5660
5904
  `, "utf-8");
5661
5905
  }
5662
5906
  function stripFor(repoRoot, path, marker, comment) {
5663
5907
  const abs2 = resolveManagedBlockPath(repoRoot, path);
5664
- if (!existsSync17(abs2))
5908
+ if (!existsSync18(abs2))
5665
5909
  return { path, marker, comment, present: false, removesHost: false };
5666
- const stripped = stripBlockFromContent(readFileSync17(abs2, "utf-8"), marker, comment);
5910
+ const stripped = stripBlockFromContent(readFileSync18(abs2, "utf-8"), marker, comment);
5667
5911
  if (stripped === null)
5668
5912
  return { path, marker, comment, present: false, removesHost: false };
5669
5913
  return { path, marker, comment, present: true, removesHost: blockStripLeavesEmpty(stripped) };
@@ -5690,9 +5934,9 @@ function planHookRouting(repoRoot, manifest, recorded, hooksPathValue) {
5690
5934
  const blocked = [];
5691
5935
  for (const b of legacy) {
5692
5936
  const abs2 = resolveManagedBlockPath(repoRoot, b.path);
5693
- if (!existsSync17(abs2))
5937
+ if (!existsSync18(abs2))
5694
5938
  continue;
5695
- const stripped = stripBlockFromContent(readFileSync17(abs2, "utf-8"), b.marker, b.comment);
5939
+ const stripped = stripBlockFromContent(readFileSync18(abs2, "utf-8"), b.marker, b.comment);
5696
5940
  if (stripped === null)
5697
5941
  blocked.push(`${b.path} exists without vinaya's managed block`);
5698
5942
  else if (!blockStripLeavesEmpty(stripped))
@@ -5727,8 +5971,8 @@ function planUpgrade(ops, repoRoot, manifest, routing) {
5727
5971
  const ownedBlocks = new Set(manifest.blocks.map((b) => blockKey(b.path, b.marker)));
5728
5972
  for (const op of ops) {
5729
5973
  if (op.kind === "create-file") {
5730
- const abs2 = join19(repoRoot, op.path);
5731
- const exists = existsSync17(abs2);
5974
+ const abs2 = join20(repoRoot, op.path);
5975
+ const exists = existsSync18(abs2);
5732
5976
  const owned = ownedFiles.has(op.path);
5733
5977
  let action;
5734
5978
  if (op.path === CONFIG_PATH || op.path === DOC_OWNERS_PATH) {
@@ -5738,7 +5982,7 @@ function planUpgrade(ops, repoRoot, manifest, routing) {
5738
5982
  } else if (!exists) {
5739
5983
  action = "recreate";
5740
5984
  hasChanges = true;
5741
- } else if (readFileSync17(abs2, "utf-8") !== op.content) {
5985
+ } else if (readFileSync18(abs2, "utf-8") !== op.content) {
5742
5986
  action = "regenerate";
5743
5987
  hasChanges = true;
5744
5988
  } else {
@@ -5751,11 +5995,11 @@ function planUpgrade(ops, repoRoot, manifest, routing) {
5751
5995
  let action;
5752
5996
  if (!owned) {
5753
5997
  action = "not-installed";
5754
- } else if (!existsSync17(abs2)) {
5998
+ } else if (!existsSync18(abs2)) {
5755
5999
  action = "recreate-host";
5756
6000
  hasChanges = true;
5757
6001
  } else {
5758
- const content = readFileSync17(abs2, "utf-8");
6002
+ const content = readFileSync18(abs2, "utf-8");
5759
6003
  const { begin, end } = markerLines(op.marker, op.comment);
5760
6004
  if (!(content.includes(begin) && content.includes(end))) {
5761
6005
  action = "recreate-append";
@@ -5855,10 +6099,10 @@ function renderUpgradeDiff(plan) {
5855
6099
  }
5856
6100
  function regenerateBlock(repoRoot, op) {
5857
6101
  const abs2 = resolveManagedBlockPath(repoRoot, op.path);
5858
- const content = readFileSync17(abs2, "utf-8");
6102
+ const content = readFileSync18(abs2, "utf-8");
5859
6103
  const stripped = stripBlockFromContent(content, op.marker, op.comment);
5860
6104
  if (stripped !== null) {
5861
- writeFileSync9(abs2, stripped.endsWith(`
6105
+ writeFileSync10(abs2, stripped.endsWith(`
5862
6106
  `) ? stripped : `${stripped}
5863
6107
  `, "utf-8");
5864
6108
  }
@@ -5868,7 +6112,7 @@ function applyUpgrade(plan, repoRoot) {
5868
6112
  for (const e of plan.entries) {
5869
6113
  if (e.kind === "create-file") {
5870
6114
  if (e.action === "regenerate" || e.action === "recreate") {
5871
- writeFileWithDirs(join19(repoRoot, e.op.path), e.op.content, e.op.mode);
6115
+ writeFileWithDirs(join20(repoRoot, e.op.path), e.op.content, e.op.mode);
5872
6116
  }
5873
6117
  } else {
5874
6118
  if (e.action === "recreate-host")
@@ -5883,15 +6127,15 @@ function applyUpgrade(plan, repoRoot) {
5883
6127
  if (!s.present)
5884
6128
  continue;
5885
6129
  const abs2 = resolveManagedBlockPath(repoRoot, s.path);
5886
- if (!existsSync17(abs2))
6130
+ if (!existsSync18(abs2))
5887
6131
  continue;
5888
- const stripped = stripBlockFromContent(readFileSync17(abs2, "utf-8"), s.marker, s.comment);
6132
+ const stripped = stripBlockFromContent(readFileSync18(abs2, "utf-8"), s.marker, s.comment);
5889
6133
  if (stripped === null)
5890
6134
  continue;
5891
6135
  if (blockStripLeavesEmpty(stripped))
5892
6136
  rmSync4(abs2, { force: true });
5893
6137
  else
5894
- writeFileSync9(abs2, stripped.endsWith(`
6138
+ writeFileSync10(abs2, stripped.endsWith(`
5895
6139
  `) ? stripped : `${stripped}
5896
6140
  `, "utf-8");
5897
6141
  }
@@ -5973,12 +6217,12 @@ async function upgradeCommand(args) {
5973
6217
  }
5974
6218
 
5975
6219
  // src/commands/waiver.ts
5976
- import { execFileSync as execFileSync10 } from "node:child_process";
6220
+ import { execFileSync as execFileSync11 } from "node:child_process";
5977
6221
  function labelFor(kind) {
5978
6222
  return kind === "docs" ? WAIVER_LABEL : WAIVER_LABEL_REVIEW;
5979
6223
  }
5980
6224
  function gh(args) {
5981
- return execFileSync10("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], env: process.env }).trim();
6225
+ return execFileSync11("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], env: process.env }).trim();
5982
6226
  }
5983
6227
  function detectPrNumber() {
5984
6228
  try {
@@ -6078,8 +6322,8 @@ Reason: ${reason}`];
6078
6322
  }
6079
6323
  process.stdout.write(`Applying \`${label2}\` to PR #${prNumber} under your authenticated \`gh\` identity — vinaya never fabricates an actor.
6080
6324
  `);
6081
- execFileSync10("gh", editArgs, { stdio: "inherit", env: process.env });
6082
- execFileSync10("gh", commentArgs, { stdio: "inherit", env: process.env });
6325
+ execFileSync11("gh", editArgs, { stdio: "inherit", env: process.env });
6326
+ execFileSync11("gh", commentArgs, { stdio: "inherit", env: process.env });
6083
6327
  process.stdout.write(`
6084
6328
  ✓ Label applied and reason posted as a PR comment.
6085
6329
  `);
@@ -6149,7 +6393,8 @@ var COMMANDS = [
6149
6393
  ],
6150
6394
  details: [
6151
6395
  "Each spawned check's child process sees only a fixed baseline (`PATH`, `LANG`, `HOME`, `HTTPS_PROXY`, `HTTP_PROXY`, `NO_PROXY`, `TMPDIR`) plus whatever its `CheckSpec['env']` declaration explicitly forwards — never the full parent environment. A required (`true`) or unsatisfied `anyOf` declaration missing from the caller's environment synthesizes a `CheckError` before the check ever spawns. Declare `env` (a core check's own registration, or `vinaya.config.json`'s `checks.<name>.env` for a custom one) for any check that reads `process.env`/`Bun.env`/`Deno.env` directly — `vinaya doctor` carries the permanent diagnostic for one that doesn't.",
6152
- "`--plan` composes with `--json`. It requires zero env vars and never prints an env value only how each one resolves (passthrough, optional, literal, or anyOf). A `FAIL_CLOSED` entry (a bare key with no namespace matching no core check) always renders inline rather than being dropped, and exits non-zero. `--plan` previews what the next minor's execution will enforce `vinaya check` itself still runs the flat, unvalidated registry this release.",
6396
+ "The resolved registry IS what runs. A `checks` key that exactly matches a core check id REPLACES that core check (the core one does not run); any other key must be namespaced `<yourname>/<id>`. Anything the resolver cannot classify — a malformed entry, a bare un-namespaced key matching no core id, a duplicate id makes `vinaya check` refuse the ENTIRE run: exit 1, nothing executes, never a partial ruleset and never a core-only fallback. `vinaya doctor` carries the permanent diagnostic for each rejected entry, so a refused config is still diagnosable.",
6397
+ "`--plan` composes with `--json`. It requires zero env vars and never prints an env value — only how each one resolves (passthrough, optional, literal, or anyOf). A `FAIL_CLOSED` entry always renders inline rather than being dropped, and exits non-zero. `--plan` and execution read the same resolution, so what the plan prints is what runs.",
6153
6398
  "`--local` exists because a `requiresOpenPr` check (the core `closes-n`/`test-plan`, or a custom check declaring the same field) can only evaluate for real once a pull request exists — the generated `pre-commit`/`pre-push` hooks pass it so the first commit on a fresh task branch is never asked to satisfy a PR-body field before a PR can possibly exist. CI's `vinaya-checks.yml` omits it, so these checks always run for real once a PR is open."
6154
6399
  ],
6155
6400
  status: "shipped"
@@ -6157,6 +6402,9 @@ var COMMANDS = [
6157
6402
  {
6158
6403
  name: "new check",
6159
6404
  description: "Scaffold a custom check into ./scripts/vinaya-checks/",
6405
+ details: [
6406
+ "Takes the REGISTRATION KEY, not a bare name: `vinaya new check <yourname>/<id>` writes `./scripts/vinaya-checks/<id>.ts` and prints the namespaced `checks` entry to paste. It refuses a bare, un-namespaced name — `vinaya check` refuses its entire run over a key it cannot resolve, so scaffolding one would brick every check invocation in the repo — and refuses a core check id, since registering one REPLACES that core gate and a scaffolded stub is never what an adopter means by that."
6407
+ ],
6160
6408
  status: "shipped"
6161
6409
  },
6162
6410
  {
@@ -6191,6 +6439,21 @@ var COMMANDS = [
6191
6439
  ],
6192
6440
  status: "shipped"
6193
6441
  },
6442
+ {
6443
+ name: "pr report",
6444
+ description: "Emit the AEG:EVIDENCE block — a PR body's factual claims, from commands, never typed",
6445
+ flags: [
6446
+ {
6447
+ flag: "--write",
6448
+ description: "Path to the PR body file; replaces the content between the AEG:EVIDENCE anchors in place"
6449
+ }
6450
+ ],
6451
+ details: [
6452
+ "Two groups: Group A (recomputable) is the head sha and a width-invariant `git diff --numstat` against the merge-base with `BASE_SHA` (else `origin/main`, then `main`) — `check-evidence-fresh` recomputes and byte-compares this exactly. Group B (attested) is the result of `vinaya check --all --diff-only`, this CLI's own portable gate suite — `check-evidence-fresh` can only check it for staleness (the block's recorded head still matches the PR's real head), never re-run it.",
6453
+ "With no `--write`, prints the block to stdout instead of writing a file. Exits non-zero whenever the gate run failed, whether or not `--write` was given — the block records a failing result rather than hiding one, and a non-zero exit stops a scripted `--write && open-pr` from carrying a failing suite onto the forge."
6454
+ ],
6455
+ status: "shipped"
6456
+ },
6194
6457
  {
6195
6458
  name: "issue create",
6196
6459
  description: "Open an issue after full brief-schema validation",
@@ -6225,7 +6488,8 @@ var COMMANDS = [
6225
6488
  description: "Diagnose hook, workflow, and config health — report only, never mutates",
6226
6489
  flags: [{ flag: "--json", description: "Enveloped JSON output (schema: 1)" }],
6227
6490
  details: [
6228
- 'Carries the same env-declaration diagnostic `vinaya check` warns with — permanently, at `info` severity, not just ahead of the spawn-default flip — plus a `warn`-severity lint over suspicious `env` literal forms (a stray `"true"`/`"false"` string, or a high-entropy literal that reads like a leaked secret committed to config).'
6491
+ 'Carries the same env-declaration diagnostic `vinaya check` warns with — permanently, at `info` severity, not just ahead of the spawn-default flip — plus a `warn`-severity lint over suspicious `env` literal forms (a stray `"true"`/`"false"` string, or a high-entropy literal that reads like a leaked secret committed to config).',
6492
+ "Also carries the two permanent `checks`-classification diagnostics: a config key that REPLACES a core check (`warn`), and a bare un-namespaced key that is REJECTED (`error`, naming the rename requirement). These are the reason a config `vinaya check` now refuses outright is still diagnosable — the refusal runs nothing, so `doctor` is the surface that explains why."
6229
6493
  ],
6230
6494
  status: "shipped"
6231
6495
  },
@@ -6364,9 +6628,9 @@ function printHelp() {
6364
6628
  }
6365
6629
 
6366
6630
  // src/index.ts
6367
- var PACKAGE_ROOT = join20(dirname8(fileURLToPath2(import.meta.url)), "..");
6631
+ var PACKAGE_ROOT = join21(dirname8(fileURLToPath2(import.meta.url)), "..");
6368
6632
  function readVersion2() {
6369
- const pkg = JSON.parse(readFileSync18(join20(PACKAGE_ROOT, "package.json"), "utf-8"));
6633
+ const pkg = JSON.parse(readFileSync19(join21(PACKAGE_ROOT, "package.json"), "utf-8"));
6370
6634
  return pkg.version;
6371
6635
  }
6372
6636
  var [, , command, ...args] = process.argv;
@@ -6449,8 +6713,10 @@ try {
6449
6713
  prCreateCommand(rest);
6450
6714
  } else if (subcommand === "edit") {
6451
6715
  prEditCommand(rest);
6716
+ } else if (subcommand === "report") {
6717
+ await prReportCommand(rest);
6452
6718
  } else {
6453
- console.error(`Unknown 'pr' subcommand: ${subcommand ?? "(none)"} (expected 'create' or 'edit')`);
6719
+ console.error(`Unknown 'pr' subcommand: ${subcommand ?? "(none)"} (expected 'create', 'edit', or 'report')`);
6454
6720
  process.exit(2);
6455
6721
  }
6456
6722
  break;