@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.
- package/README.md +7 -5
- package/aeg-root/enforcement.md +3 -3
- package/aeg-root/glossary.md +4 -0
- package/aeg-root/roles/archivist.md +2 -2
- package/aeg-root/roles/developer.md +29 -5
- package/aeg-root/roles/planner.md +1 -1
- package/aeg-root/roles/reviewer.md +1 -1
- package/aeg-root/roles/security.md +1 -1
- package/aeg-root/roles/tranche-archivist.md +1 -1
- package/aeg-root/state-machine.md +6 -6
- package/aeg-root/templates/pr-report-template.md +8 -4
- package/aeg-root/tranche-model.md +33 -11
- package/dist/checks/bin/check-branch-topology.js +204 -115
- package/dist/checks/bin/check-brief-shape.js +204 -115
- package/dist/checks/bin/check-closes-n.js +204 -115
- package/dist/checks/bin/check-coherence.js +204 -115
- package/dist/checks/bin/check-dead-branch-push.js +204 -115
- package/dist/checks/bin/check-dispatch-readiness.js +204 -115
- package/dist/checks/bin/check-doc-coverage-push.js +231 -124
- package/dist/checks/bin/check-doc-coverage.js +233 -126
- package/dist/checks/bin/check-evidence-fresh.js +3173 -0
- package/dist/checks/bin/check-first-push-dispatch.js +204 -115
- package/dist/checks/bin/check-issue-assignment.js +204 -115
- package/dist/checks/bin/check-no-disk-state.js +204 -115
- package/dist/checks/bin/check-reader-resolvable-prose.js +204 -115
- package/dist/checks/bin/check-registry-gates.js +204 -115
- package/dist/checks/bin/check-review-gate.js +204 -115
- package/dist/checks/bin/check-single-plan-pr.js +204 -115
- package/dist/checks/bin/check-test-plan.js +204 -115
- package/dist/index.js +487 -221
- package/package.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/required-server-files.json +3 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js +2 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
- 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
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__029-1ch._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__05t6482._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0gw1_g2._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0mdkphe._.js +3 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0plfbyn._.js +45 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0q56ohl._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0rr_lq7._.js +51 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__19jyp3m._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1_ojzp1._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1d_8nm4._.js +1 -1
- 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
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1zkwtx8._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__210ehg9._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_00yjcv1._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0rix02i._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_18-d2be._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1fqw88f._.js +5 -4
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1gw6vc9._.js +1 -1
- 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
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3zyedaek9nvr_.js → 1eo32v1tbjy7c.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1t7yy_s34022p.js +1 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3antrj5z0uf_c.js → 2ieu4oi-pp_bz.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/server.js +1 -1
- package/templates/custom-check.template.ts +5 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1s7kj3s._.js +0 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1tswrm4._.js +0 -9
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_01jl99k._.js +0 -45
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/apps_vinaya-studio_web_src_20c6-3m._.js +0 -45
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/2e68sbtdhi5dg.js +0 -1
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{pY8RKWuVAUZOjj2-bAYCY → ZHVE9Y9G8XKR91g27SgVX}/_buildManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{pY8RKWuVAUZOjj2-bAYCY → ZHVE9Y9G8XKR91g27SgVX}/_clientMiddlewareManifest.js +0 -0
- /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
|
|
5
|
-
import { dirname as dirname8, join as
|
|
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-
|
|
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
|
-
|
|
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
|
|
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
|
|
48
|
-
})
|
|
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
|
-
|
|
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
|
-
|
|
3181
|
-
|
|
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
|
|
3184
|
-
return `check "${
|
|
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
|
|
3543
|
-
|
|
3544
|
-
|
|
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
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
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
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
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
|
-
|
|
3589
|
-
|
|
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
|
|
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:
|
|
3668
|
+
printJson({ checks: outcomes });
|
|
3610
3669
|
} else {
|
|
3611
|
-
for (const o of
|
|
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 =
|
|
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
|
|
4129
|
-
|
|
4130
|
-
|
|
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",
|
|
4200
|
+
findings.push(warn("checks", overriddenReplacesCoreDiagnostic(entry2.name)));
|
|
4146
4201
|
}
|
|
4147
4202
|
for (const failure of classification.failures) {
|
|
4148
|
-
findings.push(
|
|
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
|
|
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
|
|
5028
|
-
console.error(
|
|
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
|
|
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, `${
|
|
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
|
|
5209
|
-
import { existsSync as
|
|
5210
|
-
import { join as
|
|
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
|
|
5214
|
-
import { dirname as dirname6, join as
|
|
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 =
|
|
5474
|
+
const abs2 = join17(repoRoot, DOC_OWNERS_PATH);
|
|
5231
5475
|
const line = bindingLine(glob, pointer);
|
|
5232
|
-
if (!
|
|
5476
|
+
if (!existsSync15(abs2)) {
|
|
5233
5477
|
return { action: "create-host", line, path: DOC_OWNERS_PATH };
|
|
5234
5478
|
}
|
|
5235
|
-
const existing =
|
|
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 =
|
|
5484
|
+
const abs2 = join17(repoRoot, DOC_OWNERS_PATH);
|
|
5241
5485
|
if (plan.action === "create-host") {
|
|
5242
5486
|
mkdirSync5(dirname6(abs2), { recursive: true });
|
|
5243
|
-
|
|
5487
|
+
writeFileSync9(abs2, freshDocOwners(glob, pointer), "utf-8");
|
|
5244
5488
|
} else if (plan.action === "append-row") {
|
|
5245
|
-
const existing =
|
|
5246
|
-
|
|
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 !
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
5541
|
-
const pkgPath =
|
|
5542
|
-
if (
|
|
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(
|
|
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 (
|
|
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 =
|
|
5558
|
-
if (
|
|
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 =
|
|
5579
|
-
const packed =
|
|
5580
|
-
if (!
|
|
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 =
|
|
5607
|
-
return spawnStandalone(cwd,
|
|
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
|
|
5617
|
-
import { join as
|
|
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 =
|
|
5636
|
-
if (!
|
|
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(
|
|
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 =
|
|
5657
|
-
const seed = JSON.parse(
|
|
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
|
-
|
|
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 (!
|
|
5908
|
+
if (!existsSync18(abs2))
|
|
5665
5909
|
return { path, marker, comment, present: false, removesHost: false };
|
|
5666
|
-
const stripped = stripBlockFromContent(
|
|
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 (!
|
|
5937
|
+
if (!existsSync18(abs2))
|
|
5694
5938
|
continue;
|
|
5695
|
-
const stripped = stripBlockFromContent(
|
|
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 =
|
|
5731
|
-
const exists =
|
|
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 (
|
|
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 (!
|
|
5998
|
+
} else if (!existsSync18(abs2)) {
|
|
5755
5999
|
action = "recreate-host";
|
|
5756
6000
|
hasChanges = true;
|
|
5757
6001
|
} else {
|
|
5758
|
-
const content =
|
|
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 =
|
|
6102
|
+
const content = readFileSync18(abs2, "utf-8");
|
|
5859
6103
|
const stripped = stripBlockFromContent(content, op.marker, op.comment);
|
|
5860
6104
|
if (stripped !== null) {
|
|
5861
|
-
|
|
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(
|
|
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 (!
|
|
6130
|
+
if (!existsSync18(abs2))
|
|
5887
6131
|
continue;
|
|
5888
|
-
const stripped = stripBlockFromContent(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
6082
|
-
|
|
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
|
-
"
|
|
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 =
|
|
6631
|
+
var PACKAGE_ROOT = join21(dirname8(fileURLToPath2(import.meta.url)), "..");
|
|
6368
6632
|
function readVersion2() {
|
|
6369
|
-
const pkg = JSON.parse(
|
|
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 '
|
|
6719
|
+
console.error(`Unknown 'pr' subcommand: ${subcommand ?? "(none)"} (expected 'create', 'edit', or 'report')`);
|
|
6454
6720
|
process.exit(2);
|
|
6455
6721
|
}
|
|
6456
6722
|
break;
|