@attalabs/vinaya 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/aeg-root/contracts/planner-brief.md +1 -1
- package/aeg-root/enforcement.md +2 -2
- package/aeg-root/roles/developer.md +1 -0
- package/aeg-root/roles/planner.md +2 -2
- package/aeg-root/roles/reviewer.md +1 -1
- package/aeg-root/skills/aeg/SKILL.md +1 -1
- package/aeg-root/state-machine.md +1 -1
- package/aeg-root/templates/pr-report-template.md +2 -0
- package/aeg-root/tranche-model.md +1 -1
- package/dist/checks/bin/check-body-bare-digits.js +3545 -0
- package/dist/checks/bin/check-branch-topology.js +72 -6
- package/dist/checks/bin/check-brief-shape.js +72 -6
- package/dist/checks/bin/check-closes-n.js +72 -6
- package/dist/checks/bin/check-coherence.js +77 -7
- package/dist/checks/bin/check-dead-branch-push.js +72 -6
- package/dist/checks/bin/check-dispatch-readiness.js +77 -7
- package/dist/checks/bin/check-doc-coverage-push.js +77 -7
- package/dist/checks/bin/check-doc-coverage.js +80 -11
- package/dist/checks/bin/check-evidence-fresh.js +72 -6
- package/dist/checks/bin/check-first-push-dispatch.js +77 -7
- package/dist/checks/bin/check-issue-assignment.js +72 -6
- package/dist/checks/bin/check-no-disk-state.js +72 -6
- package/dist/checks/bin/check-reader-resolvable-prose.js +72 -6
- package/dist/checks/bin/check-registry-gates.js +72 -6
- package/dist/checks/bin/check-review-gate.js +77 -18
- package/dist/checks/bin/check-single-plan-pr.js +72 -6
- package/dist/checks/bin/check-test-plan.js +72 -6
- package/dist/index.js +591 -206
- 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 +1 -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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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]__0gw1_g2._.js +1 -1
- 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 +1 -1
- 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]__1qpl4m_._.js → [root-of-the-server]__1ccgndq._.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]__1dfzjrd._.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/_0-n77dx._.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/_01605g7._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0o54suy._.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/{_1u3abhy._.js → _0x999pb._.js} +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_0ndy563._.js → _1-6rzip._.js} +2 -2
- 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/{_087xa25._.js → _1_talrs._.js} +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_11vnfnm._.js → _1ar5uqe._.js} +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{_02drwa_._.js → _1c9b9j-._.js} +2 -2
- 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_18kqbvs._.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/chunks/ssr/packages_ui_libraries_animate_components_index_ts_186fje1._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/packages_ui_libraries_basic_components_index_ts_0xk1vda._.js +1 -1
- 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/{1mexqnauhtbtp.js → 0_jnwbarv-cqk.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0ek6qlqhokys0.js → 0l_vubs1ephe5.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/0ojfbn3yvy-8_.js +1 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1jx5_q1g9l-cm.js → 0phqfqwbnt_ey.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0qcu17x3h94a9.js → 17r0szvqeeyhx.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0ve1-nz-mu_lr.js → 18x3rbqzeutpz.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{22y4d5yrovcv-.js → 1bqpfvromtwn1.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{39w6jbw3rleez.js → 24ojyzmt_fkg3.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0tmcnl-z8yt2_.js → 30lyvn0yzwdiz.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0fm4p9os2lsom.js → 3m0_gk59s-rbx.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0ywkq74falx-x.js → 40x7f5hm7a5tk.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0-08g_jjalnd9.js → 453zhecht_m1q.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/server.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1s9ixp5nhnevp.js +0 -1
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{R7xvB2iuNFp_jxENpL7xv → p5sWGp4ANwORiaX4Kk6U2}/_buildManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{R7xvB2iuNFp_jxENpL7xv → p5sWGp4ANwORiaX4Kk6U2}/_clientMiddlewareManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{R7xvB2iuNFp_jxENpL7xv → p5sWGp4ANwORiaX4Kk6U2}/_ssgManifest.js +0 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
5
5
|
import { dirname as dirname8, join as join22 } from "node:path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
7
7
|
|
|
@@ -10,7 +10,10 @@ import { execFileSync as execFileSync4 } from "node:child_process";
|
|
|
10
10
|
// ../../packages/aeg-forge-state/src/strip-code.ts
|
|
11
11
|
function maskCode(body) {
|
|
12
12
|
const fill = (line) => " ".repeat(line.length);
|
|
13
|
-
|
|
13
|
+
const blocksMasked = maskIndentedCode(maskFencedCode(body, fill), fill);
|
|
14
|
+
return blocksMasked.split(`
|
|
15
|
+
`).map((line) => replaceInlineSpans(line, (span) => " ".repeat(span.length))).join(`
|
|
16
|
+
`);
|
|
14
17
|
}
|
|
15
18
|
function stripCode(body, options = {}) {
|
|
16
19
|
const normalised = body.replace(/\r\n?/g, `
|
|
@@ -19,7 +22,50 @@ function stripCode(body, options = {}) {
|
|
|
19
22
|
const blocksStripped = maskIndentedCode(maskFencedCode(normalised, blank), blank);
|
|
20
23
|
if (options.inlineSpans === "keep")
|
|
21
24
|
return blocksStripped;
|
|
22
|
-
return blocksStripped.
|
|
25
|
+
return blocksStripped.split(`
|
|
26
|
+
`).map((line) => replaceInlineSpans(line, () => "")).join(`
|
|
27
|
+
`);
|
|
28
|
+
}
|
|
29
|
+
function replaceInlineSpans(line, replace) {
|
|
30
|
+
let result = "";
|
|
31
|
+
let i = 0;
|
|
32
|
+
const runLengthAt = (at) => {
|
|
33
|
+
let j = at;
|
|
34
|
+
while (j < line.length && line[j] === "`")
|
|
35
|
+
j++;
|
|
36
|
+
return j - at;
|
|
37
|
+
};
|
|
38
|
+
while (i < line.length) {
|
|
39
|
+
if (line[i] !== "`") {
|
|
40
|
+
result += line[i];
|
|
41
|
+
i++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const openLen = runLengthAt(i);
|
|
45
|
+
const openEnd = i + openLen;
|
|
46
|
+
let j = openEnd;
|
|
47
|
+
let closeEnd = -1;
|
|
48
|
+
while (j < line.length) {
|
|
49
|
+
if (line[j] !== "`") {
|
|
50
|
+
j++;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const runLen = runLengthAt(j);
|
|
54
|
+
if (runLen === openLen) {
|
|
55
|
+
closeEnd = j + runLen;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
j += runLen;
|
|
59
|
+
}
|
|
60
|
+
if (closeEnd === -1) {
|
|
61
|
+
result += line.slice(i, openEnd);
|
|
62
|
+
i = openEnd;
|
|
63
|
+
} else {
|
|
64
|
+
result += replace(line.slice(i, closeEnd));
|
|
65
|
+
i = closeEnd;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
23
69
|
}
|
|
24
70
|
var FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})([^\r\n]*)\r?$/;
|
|
25
71
|
var FENCE_CLOSE = /^ {0,3}(`+|~+)[ \t]*\r?$/;
|
|
@@ -53,6 +99,10 @@ function scanFencedCode(body) {
|
|
|
53
99
|
});
|
|
54
100
|
return { lines, unterminated: fenceChar !== null };
|
|
55
101
|
}
|
|
102
|
+
function hasUnterminatedFence(body) {
|
|
103
|
+
return scanFencedCode(body.replace(/\r\n?/g, `
|
|
104
|
+
`)).unterminated;
|
|
105
|
+
}
|
|
56
106
|
var LIST_MARKER = /^ {0,3}(?:[-*+]|\d{1,9}[.)])(?:\s|$)/;
|
|
57
107
|
function indentWidth(line) {
|
|
58
108
|
let width = 0;
|
|
@@ -240,6 +290,42 @@ function matchesLabel(key, name) {
|
|
|
240
290
|
function hasLabel(key, names) {
|
|
241
291
|
return names.some((n) => matchesLabel(key, n));
|
|
242
292
|
}
|
|
293
|
+
|
|
294
|
+
// ../../packages/aeg-forge-state/src/list-tasks.ts
|
|
295
|
+
var PROJECT_SLUG = /^[a-z0-9][a-z0-9-]*$/i;
|
|
296
|
+
var PROJECT_FIELD = /^\s*(?:\*\*)?Project(?:\(s\))?(?:\*\*)?\s*:\s*(?:\*\*)?\s*(.+)$/im;
|
|
297
|
+
var VALUE_EDGE_MARKUP = /^[`.;\s]+|[`.;\s]+$/g;
|
|
298
|
+
function unwrapValue(raw) {
|
|
299
|
+
return raw.replace(/\*\*/g, "").replace(VALUE_EDGE_MARKUP, "");
|
|
300
|
+
}
|
|
301
|
+
function projectFieldFromBody(body) {
|
|
302
|
+
const m = stripCode(body, { inlineSpans: "keep" }).match(PROJECT_FIELD);
|
|
303
|
+
if (m)
|
|
304
|
+
return parseFieldValue(m[1] ?? "");
|
|
305
|
+
if (hasUnterminatedFence(body)) {
|
|
306
|
+
const raw = body.match(PROJECT_FIELD);
|
|
307
|
+
if (raw)
|
|
308
|
+
return { declared: true, names: [], unparsed: [(raw[1] ?? "").trim()], unreadable: true };
|
|
309
|
+
}
|
|
310
|
+
return { declared: false, names: [], unparsed: [], unreadable: false };
|
|
311
|
+
}
|
|
312
|
+
function parseFieldValue(value) {
|
|
313
|
+
const names = [];
|
|
314
|
+
const unparsed = [];
|
|
315
|
+
for (const raw of value.split(",")) {
|
|
316
|
+
const name = unwrapValue(raw);
|
|
317
|
+
if (name.length === 0)
|
|
318
|
+
continue;
|
|
319
|
+
if (PROJECT_SLUG.test(name))
|
|
320
|
+
names.push(name);
|
|
321
|
+
else
|
|
322
|
+
unparsed.push(raw.trim());
|
|
323
|
+
}
|
|
324
|
+
return { declared: true, names: [...new Set(names)], unparsed, unreadable: false };
|
|
325
|
+
}
|
|
326
|
+
function projectsFromBody(body) {
|
|
327
|
+
return projectFieldFromBody(body).names;
|
|
328
|
+
}
|
|
243
329
|
// ../../packages/aeg-forge-state/src/github-token.ts
|
|
244
330
|
import { execFile as execFile2 } from "node:child_process";
|
|
245
331
|
import { promisify as promisify2 } from "node:util";
|
|
@@ -613,9 +699,10 @@ function pointerToPath(p) {
|
|
|
613
699
|
}
|
|
614
700
|
var SEPARATOR = /(?:[ \t]*[—–][ \t]*|[ \t]+-[ \t]+)/.source;
|
|
615
701
|
// ../../packages/aeg-core/src/pr-tier.ts
|
|
702
|
+
var TIER_FIELD = /(\*\*)?\s*Tier\s*(\*\*)?\s*:\s*(\*\*)?\s*([013])\b/i;
|
|
616
703
|
function readTierFromPrBody(prBody) {
|
|
617
704
|
const searchIn = anchoredRegion(prBody, "TIER") ?? prBody;
|
|
618
|
-
const m = searchIn.match(
|
|
705
|
+
const m = searchIn.match(TIER_FIELD);
|
|
619
706
|
if (!m)
|
|
620
707
|
return null;
|
|
621
708
|
const t = Number(m[4]);
|
|
@@ -893,6 +980,82 @@ function checkIssueRationale(body) {
|
|
|
893
980
|
function isTaskIssueLabelSet(labels) {
|
|
894
981
|
return hasLabel("tranche", labels);
|
|
895
982
|
}
|
|
983
|
+
var PATH_TEXT = (body) => stripCode(body, { inlineSpans: "keep" });
|
|
984
|
+
var DOC_EXTENSION_RE = /\.(?:md|mdx|txt)$/i;
|
|
985
|
+
function namesPath(text, path) {
|
|
986
|
+
const escaped = path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
987
|
+
const occurrences = new RegExp(`(?:^|[^\\w./-])(${escaped}(?:/[\\w.@-]+)*)(?![\\w-])`, "gm");
|
|
988
|
+
for (const m of text.matchAll(occurrences)) {
|
|
989
|
+
if (!DOC_EXTENSION_RE.test(m[1]))
|
|
990
|
+
return true;
|
|
991
|
+
}
|
|
992
|
+
return false;
|
|
993
|
+
}
|
|
994
|
+
var BLAST_RADIUS_ACK_RE = /(?:\*\*)?blast-radius-ack(?:\*\*)?\s*[:—–-]/i;
|
|
995
|
+
function checkBlastRadiusScope(body, _labels, sharedPackages, projectPaths) {
|
|
996
|
+
if (sharedPackages.length === 0)
|
|
997
|
+
return { status: "pass", errors: [] };
|
|
998
|
+
const text = [
|
|
999
|
+
rationaleFieldText(PATH_TEXT(body), "Boundary"),
|
|
1000
|
+
rationaleFieldText(PATH_TEXT(body), "Project\\(s\\)|Project(?:s)?\\s*\\+|blast radius")
|
|
1001
|
+
].join(`
|
|
1002
|
+
`);
|
|
1003
|
+
const named = sharedPackages.filter((d) => namesPath(text, d));
|
|
1004
|
+
if (named.length === 0)
|
|
1005
|
+
return { status: "pass", errors: [] };
|
|
1006
|
+
const registered = new Set(projectPaths.map((p) => p.name.trim()));
|
|
1007
|
+
const declared = projectsFromBody(body);
|
|
1008
|
+
const projects = declared.filter((p) => registered.has(p));
|
|
1009
|
+
const ownedPaths = projectPaths.filter((p) => projects.includes(p.name)).map((p) => p.path.replace(/\/+$/, ""));
|
|
1010
|
+
const unowned = named.filter((d) => !ownedPaths.some((owned) => d === owned || d.startsWith(`${owned}/`)));
|
|
1011
|
+
if (unowned.length === 0)
|
|
1012
|
+
return { status: "pass", errors: [] };
|
|
1013
|
+
if (projects.length > 1)
|
|
1014
|
+
return { status: "pass", errors: [] };
|
|
1015
|
+
if (BLAST_RADIUS_ACK_RE.test(PATH_TEXT(body)))
|
|
1016
|
+
return { status: "pass", errors: [] };
|
|
1017
|
+
const unregistered = declared.filter((p) => !registered.has(p));
|
|
1018
|
+
return {
|
|
1019
|
+
status: "fail",
|
|
1020
|
+
errors: [
|
|
1021
|
+
`issue-validation blast radius: the rationale names ${unowned.join(", ")} — a shared collision domain no declared project (${projects.join(", ") || "none"}) owns — but declares a single registered project and no \`blast-radius-ack:\` line.${unregistered.length > 0 ? ` Not counted: ${unregistered.join(", ")} — no row in \`.vinaya/projects.md\`, so it adds no review lens.` : ""} Project(s) drives the review fan-out (.vinaya/projects.md); list every consumer in the blast radius, or add \`blast-radius-ack: <why one lens is enough>\`.`
|
|
1022
|
+
]
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
var BRIEF_MARKERS = [
|
|
1026
|
+
{ name: "## References", pattern: /(?:^#{1,6}\s*|\*\*)\s*References\b/im },
|
|
1027
|
+
{ name: "Technical surface map", pattern: /(?:^#{1,6}\s*|\*\*)\s*Technical surface map\b/im },
|
|
1028
|
+
{ name: "Premise", pattern: /(?:^#{1,6}\s*|\*\*)\s*Premise(?:\*\*)?\s*[:—–]/im },
|
|
1029
|
+
{ name: "Step 0", pattern: /(?:^#{1,6}\s*|\*\*)\s*Step 0\b/im },
|
|
1030
|
+
{ name: "Test Plan", pattern: /(?:^#{1,6}\s*|\*\*)\s*Test Plan\b/im }
|
|
1031
|
+
];
|
|
1032
|
+
function checkNoBriefContent(body) {
|
|
1033
|
+
const text = stripCode(body);
|
|
1034
|
+
const errors = BRIEF_MARKERS.filter((m) => m.pattern.test(text)).map((m) => `issue-validation brief content: the Issue body carries a brief-shaped "${m.name}" section. Brief-time content (surface pointers, skills-to-read, premise, test plan) belongs in the brief, not the Issue — it goes stale before work starts (aeg-root/skills/brief-authoring/SKILL.md). Move it to the brief.`);
|
|
1035
|
+
return { status: errors.length > 0 ? "fail" : "pass", errors };
|
|
1036
|
+
}
|
|
1037
|
+
function rationaleFieldText(text, labelPattern) {
|
|
1038
|
+
const re = new RegExp(`(?:\\*\\*|^#{1,4}\\s+)\\s*(?:${labelPattern})[^\\n]*\\n?([\\s\\S]*?)(?=\\n\\s*(?:\\*\\*[A-Z]|#{1,4}\\s)|(?![\\s\\S]))`, "im");
|
|
1039
|
+
const m = re.exec(text);
|
|
1040
|
+
return m ? m[0] : "";
|
|
1041
|
+
}
|
|
1042
|
+
var DOC_PATH_RE = /(?:(?:aeg-root|apps|packages|specs|docs|tools|\.claude|\.github)\/[\w./@-]*\.(?:md|mdx)|\.claude\/(?:skills|rules)\/[\w./-]+|\b(?:docs-index|decisions|projects|state-machine|enforcement|process|README|CLAUDE)\.md\b|\b[\w-]+-(?:spec|decisions|backlog)\.md\b)/i;
|
|
1043
|
+
var NO_DOC_SURFACE_RE = /(?:\*\*)?\s*no-doc-surface/i;
|
|
1044
|
+
function checkRationaleNamesDocs(body) {
|
|
1045
|
+
const text = PATH_TEXT(body);
|
|
1046
|
+
const scope = [rationaleFieldText(text, "Docs to keep coherent|§7"), rationaleFieldText(text, "Traps")].join(`
|
|
1047
|
+
`);
|
|
1048
|
+
if (NO_DOC_SURFACE_RE.test(scope))
|
|
1049
|
+
return { status: "pass", errors: [] };
|
|
1050
|
+
if (DOC_PATH_RE.test(scope))
|
|
1051
|
+
return { status: "pass", errors: [] };
|
|
1052
|
+
return {
|
|
1053
|
+
status: "fail",
|
|
1054
|
+
errors: [
|
|
1055
|
+
'issue-validation docs read: neither "Docs to keep coherent" nor "Traps to avoid" names a concrete doc path (aeg-root/…, .claude/skills/…, apps/*/CLAUDE.md, apps/*/specs/…). Naming one is the artifact of having read the surface being planned — the forge write triggers no skill-check hook, so this field is the only read-obligation signal. For a genuinely doc-less surface, write the explicit `no-doc-surface` sentinel.'
|
|
1056
|
+
]
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
896
1059
|
|
|
897
1060
|
// ../../packages/aeg-core/src/coherence-checks.ts
|
|
898
1061
|
var R1_GRANDFATHERED_ISSUES = new Set([279, 280, 281, 282]);
|
|
@@ -1077,6 +1240,7 @@ var CHECKS_WORKFLOW_PATH = ".github/workflows/vinaya-checks.yml";
|
|
|
1077
1240
|
var REVIEW_WORKFLOW_PATH = ".github/workflows/vinaya-review.yml";
|
|
1078
1241
|
var REVIEW_VERDICT_WORKFLOW_PATH = ".github/workflows/vinaya-review-verdict.yml";
|
|
1079
1242
|
var ARCHIVIST_WORKFLOW_PATH = ".github/workflows/vinaya-archivist.yml";
|
|
1243
|
+
var BODY_CHECKS_WORKFLOW_PATH = ".github/workflows/vinaya-body-checks.yml";
|
|
1080
1244
|
var MANAGED_NOTE = "Managed by Vinaya — created by `vinaya init`. `vinaya upgrade` regenerates it; `vinaya eject` removes it.";
|
|
1081
1245
|
function starterConfig() {
|
|
1082
1246
|
return {
|
|
@@ -1103,9 +1267,31 @@ function ownVersion() {
|
|
|
1103
1267
|
return pkg.version;
|
|
1104
1268
|
}
|
|
1105
1269
|
var SETUP_BUN_SHA = "0c5077e51419868618aeaa5fe8019c62421857d6";
|
|
1106
|
-
function vinayaSetupSteps(selfHost) {
|
|
1270
|
+
function vinayaSetupSteps(selfHost, sourceTrust = "pull-request") {
|
|
1107
1271
|
if (!selfHost)
|
|
1108
1272
|
return "";
|
|
1273
|
+
if (sourceTrust === "trusted") {
|
|
1274
|
+
return ` # Pinned to a commit, not the mutable \`v2\` tag. A repoint of
|
|
1275
|
+
# \`v2\` would execute new upstream code in every adopter on the next run,
|
|
1276
|
+
# with no diff anywhere to review. Resolved from the \`v2\` tag on
|
|
1277
|
+
# 2026-08-14. Bun's own version still comes from the repo's
|
|
1278
|
+
# \`packageManager\` field, not from this pin.
|
|
1279
|
+
- uses: oven-sh/setup-bun@${SETUP_BUN_SHA}
|
|
1280
|
+
# This repo declares the \`@attalabs/vinaya\` workspace package itself, so
|
|
1281
|
+
# \`npx @attalabs/vinaya\` resolves to that local member instead of the
|
|
1282
|
+
# registry and dies on its unbuilt \`bin\`. Build and run the trusted
|
|
1283
|
+
# default-branch copy directly.
|
|
1284
|
+
#
|
|
1285
|
+
# This workflow executes only a trusted checkout selected by its event:
|
|
1286
|
+
# never the pull request head or merge ref. The review-authority decision
|
|
1287
|
+
# therefore comes from code the pull request cannot alter. \`--ignore-scripts\`
|
|
1288
|
+
# remains defense in depth against lifecycle hooks in that trusted tree.
|
|
1289
|
+
- name: Build the trusted Vinaya CLI
|
|
1290
|
+
run: |
|
|
1291
|
+
bun install --frozen-lockfile --ignore-scripts
|
|
1292
|
+
bun run --cwd ${selfHost.dir} build
|
|
1293
|
+
`;
|
|
1294
|
+
}
|
|
1109
1295
|
return ` # Pinned to a commit, not the mutable \`v2\` tag. This is the first
|
|
1110
1296
|
# THIRD-PARTY action this generator writes into an adopter's repository,
|
|
1111
1297
|
# and it runs in the same job that then builds and executes pull-request
|
|
@@ -1209,7 +1395,7 @@ jobs:
|
|
|
1209
1395
|
- uses: actions/setup-node@v4
|
|
1210
1396
|
with:
|
|
1211
1397
|
node-version: 20
|
|
1212
|
-
${vinayaSetupSteps(selfHost)}${adopterSetupStep(ciSetup)} - name: Run checks
|
|
1398
|
+
${vinayaSetupSteps(selfHost, "pull-request")}${adopterSetupStep(ciSetup)} - name: Run checks
|
|
1213
1399
|
env:
|
|
1214
1400
|
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
1215
1401
|
PR_NUMBER: \${{ github.event.pull_request.number }}
|
|
@@ -1243,21 +1429,27 @@ ${vinayaSetupSteps(selfHost)}${adopterSetupStep(ciSetup)} - name: Run check
|
|
|
1243
1429
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
1244
1430
|
`;
|
|
1245
1431
|
}
|
|
1246
|
-
function reviewWorkflow(selfHost
|
|
1432
|
+
function reviewWorkflow(selfHost) {
|
|
1247
1433
|
return `# ${MANAGED_NOTE}
|
|
1248
1434
|
#
|
|
1249
|
-
# The required review gate
|
|
1435
|
+
# The required review gate. \`pull_request_target\` is the trust boundary:
|
|
1436
|
+
# GitHub loads this workflow and its default checkout from the repository's
|
|
1437
|
+
# default branch, never from the pull request being judged. The job does not
|
|
1438
|
+
# fetch, check out, install, build, or execute pull-request content.
|
|
1439
|
+
#
|
|
1440
|
+
# The verdict-comment
|
|
1250
1441
|
# half lives in its own workflow (vinaya-review-verdict.yml): a new PR
|
|
1251
|
-
# comment fires a different GitHub event that this
|
|
1442
|
+
# comment fires a different GitHub event that this
|
|
1252
1443
|
# workflow structurally cannot receive — and keeping the comment path in a
|
|
1253
1444
|
# separate FILE means this workflow's runs never list permanently-skipped
|
|
1254
1445
|
# comment jobs on the PR's checks panel. When a clean final verdict lands,
|
|
1255
1446
|
# the verdict workflow re-runs this one, so the required check below goes
|
|
1256
1447
|
# green natively with no manual rerun.
|
|
1257
1448
|
name: Vinaya Review Gate
|
|
1449
|
+
run-name: "Vinaya Review Gate PR #\${{ github.event.pull_request.number }} @ \${{ github.event.pull_request.head.sha }}"
|
|
1258
1450
|
|
|
1259
1451
|
on:
|
|
1260
|
-
|
|
1452
|
+
pull_request_target:
|
|
1261
1453
|
types: [opened, synchronize, reopened, labeled, unlabeled]
|
|
1262
1454
|
|
|
1263
1455
|
# One run per pull request COMMIT, always. Several \`types:\` above can fire in the
|
|
@@ -1297,30 +1489,96 @@ jobs:
|
|
|
1297
1489
|
steps:
|
|
1298
1490
|
- uses: actions/checkout@v4
|
|
1299
1491
|
with:
|
|
1300
|
-
#
|
|
1301
|
-
|
|
1492
|
+
# Explicit trusted ref: never use the PR head/merge ref in this job.
|
|
1493
|
+
ref: \${{ github.event.repository.default_branch }}
|
|
1302
1494
|
persist-credentials: false
|
|
1303
1495
|
fetch-depth: 0
|
|
1304
1496
|
- uses: actions/setup-node@v4
|
|
1305
1497
|
with:
|
|
1306
1498
|
node-version: 20
|
|
1307
|
-
${vinayaSetupSteps(selfHost)}
|
|
1499
|
+
${vinayaSetupSteps(selfHost, "trusted")} - name: Review gate
|
|
1308
1500
|
env:
|
|
1309
1501
|
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
1310
1502
|
# PR_NUMBER is what makes the review-gate check EVALUATE: without
|
|
1311
1503
|
# it the adapter reads "no PR yet — local dev" and exits 0, and
|
|
1312
1504
|
# the gate is green regardless of review state.
|
|
1313
1505
|
PR_NUMBER: \${{ github.event.pull_request.number }}
|
|
1314
|
-
BRANCH: \${{ github.head_ref }}
|
|
1315
1506
|
run: ${vinayaRun(selfHost, "check review-gate")}
|
|
1316
1507
|
`;
|
|
1317
1508
|
}
|
|
1318
|
-
function
|
|
1509
|
+
function bodyChecksWorkflow(selfHost) {
|
|
1510
|
+
return `# ${MANAGED_NOTE}
|
|
1511
|
+
#
|
|
1512
|
+
# Required, PR-content-independent checks that need a trust anchor no pull
|
|
1513
|
+
# request can rewrite. \`pull_request_target\` is the same boundary
|
|
1514
|
+
# \`vinaya-review.yml\` uses: GitHub loads this workflow and its default
|
|
1515
|
+
# checkout from the repository's default branch, never from the pull
|
|
1516
|
+
# request being judged. The job does not fetch, check out, install, build,
|
|
1517
|
+
# or execute pull-request content — every check registered here reads only
|
|
1518
|
+
# live-fetched PR metadata (\`gh pr view\`), never the diff or repo tree, the
|
|
1519
|
+
# same shape \`review-gate\` already requires of its own checks.
|
|
1520
|
+
#
|
|
1521
|
+
# \`body-bare-digits\` is the one check here today: its Changesets-release
|
|
1522
|
+
# exemption live-fetches the PR's real author, keyed on \`PR_NUMBER\` — on a
|
|
1523
|
+
# \`pull_request\` trigger that value is PR-editable (the PR's own workflow
|
|
1524
|
+
# YAML controls it), so an attacker could redirect it to any
|
|
1525
|
+
# already-approved PR by the configured release actor. Found live (round 5,
|
|
1526
|
+
# security review, PR #165), and verified no env-var or git-state signal
|
|
1527
|
+
# inside a \`pull_request\` job closes it — \`pull_request_target\` does,
|
|
1528
|
+
# because the workflow text assigning \`PR_NUMBER\` comes from THIS file on
|
|
1529
|
+
# the default branch, which a pull request cannot edit.
|
|
1530
|
+
name: Vinaya Body Checks
|
|
1531
|
+
run-name: "Vinaya Body Checks PR #\${{ github.event.pull_request.number }} @ \${{ github.event.pull_request.head.sha }}"
|
|
1532
|
+
|
|
1533
|
+
on:
|
|
1534
|
+
pull_request_target:
|
|
1535
|
+
types: [opened, synchronize, reopened, edited]
|
|
1536
|
+
|
|
1537
|
+
# Same collapsing rationale as \`vinaya-review.yml\`'s concurrency group — see
|
|
1538
|
+
# that file's own comment for the two measured failure modes (duplicate
|
|
1539
|
+
# runs from simultaneous event types; an old rerun cancelling the current
|
|
1540
|
+
# commit's run) this group exists to prevent.
|
|
1541
|
+
concurrency:
|
|
1542
|
+
group: vinaya-body-checks-\${{ github.event.pull_request.number || github.ref }}-\${{ github.event.pull_request.head.sha || github.sha }}
|
|
1543
|
+
cancel-in-progress: true
|
|
1544
|
+
|
|
1545
|
+
jobs:
|
|
1546
|
+
vinaya-body-checks:
|
|
1547
|
+
name: vinaya check body-bare-digits
|
|
1548
|
+
runs-on: ubuntu-latest
|
|
1549
|
+
permissions:
|
|
1550
|
+
contents: read
|
|
1551
|
+
pull-requests: read
|
|
1552
|
+
steps:
|
|
1553
|
+
- uses: actions/checkout@v4
|
|
1554
|
+
with:
|
|
1555
|
+
# Explicit trusted ref: never use the PR head/merge ref in this job.
|
|
1556
|
+
ref: \${{ github.event.repository.default_branch }}
|
|
1557
|
+
persist-credentials: false
|
|
1558
|
+
fetch-depth: 0
|
|
1559
|
+
- uses: actions/setup-node@v4
|
|
1560
|
+
with:
|
|
1561
|
+
node-version: 20
|
|
1562
|
+
${vinayaSetupSteps(selfHost, "trusted")} - name: Body checks
|
|
1563
|
+
env:
|
|
1564
|
+
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
1565
|
+
# PR_NUMBER is what makes the Changesets-release exemption
|
|
1566
|
+
# EVALUATE its live gh pr view fetch: without it the check reads
|
|
1567
|
+
# "no PR yet — local dev" and falls through to the ordinary
|
|
1568
|
+
# bare-digit scan.
|
|
1569
|
+
PR_NUMBER: \${{ github.event.pull_request.number }}
|
|
1570
|
+
# PR_BODY is what makes body-bare-digits EVALUATE at all — the bin
|
|
1571
|
+
# reads \`process.env.PR_BODY\` only, never fetches it itself.
|
|
1572
|
+
PR_BODY: \${{ github.event.pull_request.body }}
|
|
1573
|
+
run: ${vinayaRun(selfHost, "check body-bare-digits")}
|
|
1574
|
+
`;
|
|
1575
|
+
}
|
|
1576
|
+
function reviewVerdictWorkflow(selfHost) {
|
|
1319
1577
|
return `# ${MANAGED_NOTE}
|
|
1320
1578
|
#
|
|
1321
1579
|
# The verdict-comment half of the review gate. A reviewer's verdict arrives
|
|
1322
1580
|
# as a PR comment (\`VERDICT: APPROVE\` / \`VERDICT: PASS\`), which fires
|
|
1323
|
-
# GitHub's \`issue_comment\` event — an event the required
|
|
1581
|
+
# GitHub's \`issue_comment\` event — an event the required PR-event
|
|
1324
1582
|
# workflow cannot receive. This workflow evaluates the gate on that comment
|
|
1325
1583
|
# and, when the evaluation is clean, RE-RUNS the required workflow so its
|
|
1326
1584
|
# check goes green natively with no manual rerun. (Writing check-run
|
|
@@ -1328,11 +1586,10 @@ function reviewVerdictWorkflow(selfHost, ciSetup) {
|
|
|
1328
1586
|
# restricts check-run updates to the owning workflow — re-running is the
|
|
1329
1587
|
# supported channel.)
|
|
1330
1588
|
#
|
|
1331
|
-
# Privilege split, deliberate: \`evaluate\`
|
|
1332
|
-
#
|
|
1333
|
-
# \`actions: write\` but checks out and executes nothing
|
|
1334
|
-
#
|
|
1335
|
-
# content ran. A malicious branch can at worst fail its own evaluation.
|
|
1589
|
+
# Privilege split, deliberate: \`evaluate\` executes only the default branch's
|
|
1590
|
+
# trusted gate code and holds NO write permission; \`retrigger\` holds
|
|
1591
|
+
# \`actions: write\` but checks out and executes nothing. Neither job checks
|
|
1592
|
+
# out or executes pull-request content.
|
|
1336
1593
|
name: Vinaya Review Gate (on verdict)
|
|
1337
1594
|
|
|
1338
1595
|
on:
|
|
@@ -1349,38 +1606,36 @@ jobs:
|
|
|
1349
1606
|
pull-requests: read
|
|
1350
1607
|
issues: read
|
|
1351
1608
|
outputs:
|
|
1352
|
-
|
|
1609
|
+
number: \${{ steps.pr.outputs.number }}
|
|
1353
1610
|
sha: \${{ steps.pr.outputs.sha }}
|
|
1354
1611
|
steps:
|
|
1355
|
-
# issue_comment payloads carry no PR head SHA
|
|
1356
|
-
#
|
|
1357
|
-
# ref is the repo's default branch, not the PR head).
|
|
1612
|
+
# issue_comment payloads carry no PR head SHA — resolve it for immutable
|
|
1613
|
+
# run identity only. It never selects executable code.
|
|
1358
1614
|
- name: Resolve PR head
|
|
1359
1615
|
id: pr
|
|
1360
1616
|
env:
|
|
1361
1617
|
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
1362
1618
|
run: |
|
|
1363
1619
|
NUMBER="\${{ github.event.issue.number }}"
|
|
1364
|
-
BRANCH=$(gh pr view "$NUMBER" --repo "\${{ github.repository }}" --json headRefName -q .headRefName)
|
|
1365
1620
|
SHA=$(gh pr view "$NUMBER" --repo "\${{ github.repository }}" --json headRefOid -q .headRefOid)
|
|
1366
1621
|
echo "number=$NUMBER" >> "$GITHUB_OUTPUT"
|
|
1367
|
-
echo "branch=$BRANCH" >> "$GITHUB_OUTPUT"
|
|
1368
1622
|
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
|
|
1369
1623
|
- uses: actions/checkout@v4
|
|
1370
1624
|
with:
|
|
1371
|
-
|
|
1625
|
+
# issue_comment already sources workflows from the default branch;
|
|
1626
|
+
# make the trusted code checkout explicit too.
|
|
1627
|
+
ref: \${{ github.event.repository.default_branch }}
|
|
1372
1628
|
persist-credentials: false
|
|
1373
1629
|
fetch-depth: 0
|
|
1374
1630
|
- uses: actions/setup-node@v4
|
|
1375
1631
|
with:
|
|
1376
1632
|
node-version: 20
|
|
1377
|
-
${vinayaSetupSteps(selfHost)}
|
|
1633
|
+
${vinayaSetupSteps(selfHost, "trusted")} - name: Review gate (verdict evaluation)
|
|
1378
1634
|
env:
|
|
1379
1635
|
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
1380
1636
|
# Same wiring as the required workflow: PR_NUMBER is what makes
|
|
1381
1637
|
# the adapter evaluate instead of no-op'ing as "local dev".
|
|
1382
1638
|
PR_NUMBER: \${{ steps.pr.outputs.number }}
|
|
1383
|
-
BRANCH: \${{ steps.pr.outputs.branch }}
|
|
1384
1639
|
run: ${vinayaRun(selfHost, "check review-gate")}
|
|
1385
1640
|
|
|
1386
1641
|
# Executes nothing; consumes only the evaluator's outputs.
|
|
@@ -1401,16 +1656,11 @@ ${vinayaSetupSteps(selfHost)}${adopterSetupStep(ciSetup)} - name: Review ga
|
|
|
1401
1656
|
- name: Re-run the required review gate for this branch
|
|
1402
1657
|
env:
|
|
1403
1658
|
GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
|
|
1404
|
-
|
|
1659
|
+
PR_NUMBER: \${{ needs.evaluate.outputs.number }}
|
|
1405
1660
|
HEAD_SHA: \${{ needs.evaluate.outputs.sha }}
|
|
1406
1661
|
run: |
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
# gate. Reachable now that this job runs on a failed evaluation —
|
|
1410
|
-
# \`Resolve PR head\` is the only producer of this output, and its
|
|
1411
|
-
# failure used to be unreachable from here.
|
|
1412
|
-
if [ -z "$BRANCH" ]; then
|
|
1413
|
-
echo "No branch resolved (the evaluate job failed before resolving it) - nothing to re-run."
|
|
1662
|
+
if [ -z "$PR_NUMBER" ]; then
|
|
1663
|
+
echo "No PR number resolved (the evaluate job failed before resolving it) - nothing to re-run."
|
|
1414
1664
|
exit 0
|
|
1415
1665
|
fi
|
|
1416
1666
|
# Same guard, same producer: an empty SHA would make the selection
|
|
@@ -1420,43 +1670,28 @@ ${vinayaSetupSteps(selfHost)}${adopterSetupStep(ciSetup)} - name: Review ga
|
|
|
1420
1670
|
echo "No head SHA resolved (the evaluate job failed before resolving it) - nothing to re-run."
|
|
1421
1671
|
exit 0
|
|
1422
1672
|
fi
|
|
1423
|
-
#
|
|
1424
|
-
#
|
|
1425
|
-
#
|
|
1426
|
-
#
|
|
1427
|
-
#
|
|
1428
|
-
#
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
# single-expression flag of its own, NOT a passthrough to jq, so jq's
|
|
1441
|
-
# variable-binding flag cannot be used with it: pflag swallows that
|
|
1442
|
-
# flag as the expression and gh exits 1 on the stray token — which
|
|
1443
|
-
# under \`bash -e\` kills the step before the empty-RUN_ID no-op can
|
|
1444
|
-
# run, so every verdict reddens and nothing reruns. Filter on the
|
|
1445
|
-
# server and keep the jq expression free of variables.
|
|
1446
|
-
RUN_ID=$(gh run list --repo "\${{ github.repository }}" \\
|
|
1447
|
-
--workflow vinaya-review.yml --branch "$BRANCH" \\
|
|
1448
|
-
--commit "$HEAD_SHA" \\
|
|
1449
|
-
--json databaseId,event,status,conclusion \\
|
|
1450
|
-
--jq '[.[]
|
|
1451
|
-
| select(.event=="pull_request")
|
|
1452
|
-
| select(.status=="completed")
|
|
1453
|
-
| select(.conclusion!="cancelled")]
|
|
1454
|
-
| .[0].databaseId // empty')
|
|
1673
|
+
# pull_request_target runs execute at the DEFAULT branch SHA, so a
|
|
1674
|
+
# run's head_sha cannot identify the PR commit. GitHub's nested
|
|
1675
|
+
# pull_requests snapshot is also not immutable: an old rerun can
|
|
1676
|
+
# expose the PR's current head. The required workflow therefore
|
|
1677
|
+
# records PR number + head SHA in run-name at creation, and this
|
|
1678
|
+
# query matches that immutable display_title exactly.
|
|
1679
|
+
set -o pipefail
|
|
1680
|
+
RUN_TITLE="Vinaya Review Gate PR #$PR_NUMBER @ $HEAD_SHA"
|
|
1681
|
+
RUN_ID=$(gh api --paginate \\
|
|
1682
|
+
"repos/\${{ github.repository }}/actions/workflows/vinaya-review.yml/runs?event=pull_request_target&per_page=100" \\
|
|
1683
|
+
| jq -sr --arg title "$RUN_TITLE" '
|
|
1684
|
+
[.[].workflow_runs[]
|
|
1685
|
+
| select(.display_title == $title)
|
|
1686
|
+
| select(.event == "pull_request_target")
|
|
1687
|
+
| select(.status == "completed")
|
|
1688
|
+
| select(.conclusion != "cancelled")
|
|
1689
|
+
| .id][0] // empty')
|
|
1455
1690
|
if [ -z "$RUN_ID" ]; then
|
|
1456
|
-
echo "No completed, non-cancelled
|
|
1691
|
+
echo "No completed, non-cancelled pull_request_target run of vinaya-review.yml for PR #$PR_NUMBER at $HEAD_SHA - nothing to re-run."
|
|
1457
1692
|
exit 0
|
|
1458
1693
|
fi
|
|
1459
|
-
echo "Re-running vinaya-review.yml run $RUN_ID for $HEAD_SHA"
|
|
1694
|
+
echo "Re-running vinaya-review.yml run $RUN_ID for PR #$PR_NUMBER at $HEAD_SHA"
|
|
1460
1695
|
# Two verdict comments landing together run two retriggers in
|
|
1461
1696
|
# parallel (this workflow has no concurrency group, deliberately —
|
|
1462
1697
|
# a queued retrigger is a delayed one). Both can select the same
|
|
@@ -1697,6 +1932,13 @@ var BRANCH_PROTECTION_NOTE = `Recommended (run yourself — vinaya never applies
|
|
|
1697
1932
|
-F required_status_checks.strict=true \\
|
|
1698
1933
|
-F 'required_status_checks.contexts[]=vinaya-checks' \\
|
|
1699
1934
|
-F enforce_admins=true -F restrictions=`;
|
|
1935
|
+
var CODEOWNERS_NOTE = `Recommended: require review on vinaya's own workflow files.
|
|
1936
|
+
|
|
1937
|
+
echo '/.github/workflows/** @your-github-login' >> .github/CODEOWNERS
|
|
1938
|
+
|
|
1939
|
+
Then add this flag to the branch-protection command above:
|
|
1940
|
+
|
|
1941
|
+
-F required_pull_request_reviews.require_code_owner_reviews=true`;
|
|
1700
1942
|
function buildInitOps(ctx) {
|
|
1701
1943
|
const ops = [];
|
|
1702
1944
|
const hookMode = 493;
|
|
@@ -1709,13 +1951,13 @@ function buildInitOps(ctx) {
|
|
|
1709
1951
|
ops.push({
|
|
1710
1952
|
kind: "create-file",
|
|
1711
1953
|
path: REVIEW_WORKFLOW_PATH,
|
|
1712
|
-
content: reviewWorkflow(ctx.selfHost
|
|
1954
|
+
content: reviewWorkflow(ctx.selfHost),
|
|
1713
1955
|
group: "CI workflows"
|
|
1714
1956
|
});
|
|
1715
1957
|
ops.push({
|
|
1716
1958
|
kind: "create-file",
|
|
1717
1959
|
path: REVIEW_VERDICT_WORKFLOW_PATH,
|
|
1718
|
-
content: reviewVerdictWorkflow(ctx.selfHost
|
|
1960
|
+
content: reviewVerdictWorkflow(ctx.selfHost),
|
|
1719
1961
|
group: "CI workflows"
|
|
1720
1962
|
});
|
|
1721
1963
|
ops.push({
|
|
@@ -1724,6 +1966,12 @@ function buildInitOps(ctx) {
|
|
|
1724
1966
|
content: archivistWorkflow(ctx.selfHost),
|
|
1725
1967
|
group: "CI workflows"
|
|
1726
1968
|
});
|
|
1969
|
+
ops.push({
|
|
1970
|
+
kind: "create-file",
|
|
1971
|
+
path: BODY_CHECKS_WORKFLOW_PATH,
|
|
1972
|
+
content: bodyChecksWorkflow(ctx.selfHost),
|
|
1973
|
+
group: "CI workflows"
|
|
1974
|
+
});
|
|
1727
1975
|
ops.push({
|
|
1728
1976
|
kind: "managed-block",
|
|
1729
1977
|
path: `${ctx.hookDir}/pre-commit`,
|
|
@@ -1774,6 +2022,7 @@ function buildInitOps(ctx) {
|
|
|
1774
2022
|
});
|
|
1775
2023
|
ops.push(...labelOps());
|
|
1776
2024
|
ops.push({ kind: "print", message: BRANCH_PROTECTION_NOTE, group: "Branch protection (printed, never applied)" });
|
|
2025
|
+
ops.push({ kind: "print", message: CODEOWNERS_NOTE, group: "Branch protection (printed, never applied)" });
|
|
1777
2026
|
return ops;
|
|
1778
2027
|
}
|
|
1779
2028
|
function buildInitProductOps(name) {
|
|
@@ -1923,6 +2172,7 @@ var VinayaConfigSchema = z.object({
|
|
|
1923
2172
|
briefSchema: BriefSchemaSchema.optional(),
|
|
1924
2173
|
managed: ManagedManifestSchema.optional(),
|
|
1925
2174
|
principals: z.array(z.string()).min(1).optional(),
|
|
2175
|
+
releaseActor: z.string().min(1).optional(),
|
|
1926
2176
|
ci: z.object({ setup: z.string().min(1) }).optional(),
|
|
1927
2177
|
blastRadius: z.object({ extraDomains: z.array(z.string()).optional() }).optional()
|
|
1928
2178
|
});
|
|
@@ -1957,6 +2207,9 @@ function globalChecksIgnoredWarning(path) {
|
|
|
1957
2207
|
function globalPrincipalsIgnoredWarning(path) {
|
|
1958
2208
|
return `${path}: "principals" in the global config is ignored — principals may only be declared from a repo-local vinaya.config.json.`;
|
|
1959
2209
|
}
|
|
2210
|
+
function globalReleaseActorIgnoredWarning(path) {
|
|
2211
|
+
return `${path}: "releaseActor" in the global config is ignored — releaseActor may only be declared from a repo-local vinaya.config.json.`;
|
|
2212
|
+
}
|
|
1960
2213
|
function stripGlobalOnlyKeys(config, path) {
|
|
1961
2214
|
if (path !== GLOBAL_CONFIG_PATH)
|
|
1962
2215
|
return config;
|
|
@@ -1969,6 +2222,10 @@ function stripGlobalOnlyKeys(config, path) {
|
|
|
1969
2222
|
console.error(`⚠ ${globalPrincipalsIgnoredWarning(path)}`);
|
|
1970
2223
|
result = { ...result, principals: undefined };
|
|
1971
2224
|
}
|
|
2225
|
+
if (result.releaseActor) {
|
|
2226
|
+
console.error(`⚠ ${globalReleaseActorIgnoredWarning(path)}`);
|
|
2227
|
+
result = { ...result, releaseActor: undefined };
|
|
2228
|
+
}
|
|
1972
2229
|
return result;
|
|
1973
2230
|
}
|
|
1974
2231
|
function resolvePrincipalAllowlist(config) {
|
|
@@ -2016,7 +2273,7 @@ function isMissingFileError(err) {
|
|
|
2016
2273
|
return /\b404\b|not found/i.test(haystack);
|
|
2017
2274
|
}
|
|
2018
2275
|
function loadTrustAnchorConfig(fetcher = ghFetchTrustAnchorConfig) {
|
|
2019
|
-
const warn = (why) => process.stdout.write(`⚠ could not read \`principals\` from the default branch (${why}) — falling back to vinaya's built-in
|
|
2276
|
+
const warn = (why) => process.stdout.write(`⚠ could not read the trust-anchor config (\`principals\`/\`releaseActor\`) from the default branch (${why}) — falling back to vinaya's built-in defaults.
|
|
2020
2277
|
`);
|
|
2021
2278
|
let base64;
|
|
2022
2279
|
try {
|
|
@@ -3244,6 +3501,21 @@ function coreCheckRegistry() {
|
|
|
3244
3501
|
BRANCH: { optional: true }
|
|
3245
3502
|
}
|
|
3246
3503
|
},
|
|
3504
|
+
{
|
|
3505
|
+
name: "body-bare-digits",
|
|
3506
|
+
run: bin("check-body-bare-digits"),
|
|
3507
|
+
scope: "diff",
|
|
3508
|
+
timeoutMs: 15000,
|
|
3509
|
+
requiresOpenPr: true,
|
|
3510
|
+
ownWorkflow: true,
|
|
3511
|
+
env: {
|
|
3512
|
+
PR_BODY: { optional: true },
|
|
3513
|
+
PR_NUMBER: { optional: true },
|
|
3514
|
+
GITHUB_REPOSITORY: { optional: true },
|
|
3515
|
+
GITHUB_TOKEN: { optional: true },
|
|
3516
|
+
GH_TOKEN: { optional: true }
|
|
3517
|
+
}
|
|
3518
|
+
},
|
|
3247
3519
|
{
|
|
3248
3520
|
name: "no-disk-state",
|
|
3249
3521
|
run: bin("check-no-disk-state"),
|
|
@@ -3269,7 +3541,6 @@ function coreCheckRegistry() {
|
|
|
3269
3541
|
timeoutMs: 30000,
|
|
3270
3542
|
ownWorkflow: true,
|
|
3271
3543
|
env: {
|
|
3272
|
-
BRANCH: { optional: true },
|
|
3273
3544
|
PR_NUMBER: { optional: true },
|
|
3274
3545
|
GITHUB_REPOSITORY: { optional: true },
|
|
3275
3546
|
GITHUB_TOKEN: { optional: true },
|
|
@@ -3981,12 +4252,12 @@ Test Plan: unit-tests-only
|
|
|
3981
4252
|
## Stop Conditions
|
|
3982
4253
|
Autonomy: Do not stop to ask clarifying questions — this is a scripted demo; proceed and clean up automatically.
|
|
3983
4254
|
|
|
3984
|
-
Illustrative Step 0 (never actually run by this demo):
|
|
4255
|
+
Illustrative \`Step 0\` (never actually run by this demo):
|
|
3985
4256
|
\`\`\`
|
|
3986
4257
|
git worktree add .worktrees/demo-fixture -b demo/fixture origin/main
|
|
3987
4258
|
\`\`\`
|
|
3988
4259
|
|
|
3989
|
-
Closes
|
|
4260
|
+
Closes \`#0\` (fixture value — no real Issue; discarded with the demo branch)
|
|
3990
4261
|
`;
|
|
3991
4262
|
}
|
|
3992
4263
|
function cleanup(repoRoot, originalBranch, demoBranch, statePath) {
|
|
@@ -4524,6 +4795,25 @@ async function diagnoseBranchProtection(deps, owner, repo) {
|
|
|
4524
4795
|
}
|
|
4525
4796
|
return info("branch-protection", "main branch protection could not be determined (no gh auth, no remote, or a permission gap).");
|
|
4526
4797
|
}
|
|
4798
|
+
function coversWorkflowsDir(pattern) {
|
|
4799
|
+
const bare = pattern.replace(/\*+$/, "").replace(/\/+$/, "");
|
|
4800
|
+
return bare === ".github/workflows" || bare === "/.github/workflows";
|
|
4801
|
+
}
|
|
4802
|
+
function diagnoseCodeowners(repoRoot) {
|
|
4803
|
+
const path = join9(repoRoot, ".github", "CODEOWNERS");
|
|
4804
|
+
if (!existsSync8(path)) {
|
|
4805
|
+
return info("codeowners", "no .github/CODEOWNERS — vinaya's workflow files have no required-review protection; see `vinaya init`'s printed recommendation.");
|
|
4806
|
+
}
|
|
4807
|
+
let body;
|
|
4808
|
+
try {
|
|
4809
|
+
body = readFileSync8(path, "utf-8");
|
|
4810
|
+
} catch (err) {
|
|
4811
|
+
return warn("codeowners", `.github/CODEOWNERS exists but could not be read: ${err.message}`);
|
|
4812
|
+
}
|
|
4813
|
+
const covered = body.split(`
|
|
4814
|
+
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).some((line) => coversWorkflowsDir(line.split(/\s+/)[0] ?? ""));
|
|
4815
|
+
return covered ? info("codeowners", ".github/CODEOWNERS covers .github/workflows/**.") : warn("codeowners", ".github/CODEOWNERS exists but has no entry covering .github/workflows/** — workflow file edits can merge unreviewed.");
|
|
4816
|
+
}
|
|
4527
4817
|
var TEST_INVOCATION_SUBSTRINGS = ["npm test", "npm run test", "bun test", "bunx turbo test"];
|
|
4528
4818
|
function diagnoseTestCi(repoRoot) {
|
|
4529
4819
|
let pkg;
|
|
@@ -4608,6 +4898,7 @@ async function runDoctor(args, deps) {
|
|
|
4608
4898
|
findings.push(...diagnoseGlobalConfigChecks());
|
|
4609
4899
|
findings.push(...await diagnoseEnvironment(deps, hasDrift));
|
|
4610
4900
|
findings.push(await diagnoseBranchProtection(deps, repo.owner, repo.repo));
|
|
4901
|
+
findings.push(diagnoseCodeowners(repo.repoRoot));
|
|
4611
4902
|
findings.push(...diagnoseTestCi(repo.repoRoot));
|
|
4612
4903
|
const healthy = findings.every((f) => f.severity === "ok" || f.severity === "info");
|
|
4613
4904
|
if (jsonOutput) {
|
|
@@ -4622,8 +4913,9 @@ async function doctorCommand(args) {
|
|
|
4622
4913
|
}
|
|
4623
4914
|
|
|
4624
4915
|
// src/commands/doctrine.ts
|
|
4625
|
-
import { existsSync as existsSync9, readdirSync as readdirSync4 } from "node:fs";
|
|
4916
|
+
import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
4626
4917
|
import { dirname as dirname4, join as join10, sep as sep3 } from "node:path";
|
|
4918
|
+
import matter2 from "gray-matter";
|
|
4627
4919
|
var ENTRY_SEGMENTS = ["skills", "aeg", "SKILL.md"];
|
|
4628
4920
|
function resolveDoctrineRoot(pkg = packageRoot(import.meta.url)) {
|
|
4629
4921
|
const candidates = [join10(pkg, "aeg-root")];
|
|
@@ -4639,7 +4931,10 @@ function listRoleNames(root) {
|
|
|
4639
4931
|
const rolesDir = join10(root, "roles");
|
|
4640
4932
|
if (!existsSync9(rolesDir))
|
|
4641
4933
|
return [];
|
|
4642
|
-
return readdirSync4(rolesDir).filter((name) => name.endsWith(".md")).map((name) => name.slice(0, -".md".length)).
|
|
4934
|
+
return readdirSync4(rolesDir).filter((name) => name.endsWith(".md")).map((name) => name.slice(0, -".md".length)).filter((roleName) => {
|
|
4935
|
+
const { data } = matter2(readFileSync9(join10(rolesDir, `${roleName}.md`), "utf8"));
|
|
4936
|
+
return data.actor !== "human";
|
|
4937
|
+
}).sort();
|
|
4643
4938
|
}
|
|
4644
4939
|
function doctrineCommand(args) {
|
|
4645
4940
|
const root = resolveDoctrineRoot();
|
|
@@ -4676,7 +4971,7 @@ function doctrineCommand(args) {
|
|
|
4676
4971
|
}
|
|
4677
4972
|
|
|
4678
4973
|
// src/commands/eject.ts
|
|
4679
|
-
import { existsSync as existsSync10, readFileSync as
|
|
4974
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "node:fs";
|
|
4680
4975
|
import { join as join11 } from "node:path";
|
|
4681
4976
|
function realDeps4() {
|
|
4682
4977
|
return {
|
|
@@ -4699,7 +4994,7 @@ function readManifest(repoRoot) {
|
|
|
4699
4994
|
return { kind: "none" };
|
|
4700
4995
|
let raw;
|
|
4701
4996
|
try {
|
|
4702
|
-
raw = JSON.parse(
|
|
4997
|
+
raw = JSON.parse(readFileSync10(p, "utf-8"));
|
|
4703
4998
|
} catch (err) {
|
|
4704
4999
|
return { kind: "orphan", reason: `vinaya.config.json is not valid JSON: ${err.message}` };
|
|
4705
5000
|
}
|
|
@@ -4789,11 +5084,11 @@ async function ejectCommand(args) {
|
|
|
4789
5084
|
}
|
|
4790
5085
|
|
|
4791
5086
|
// src/commands/init.ts
|
|
4792
|
-
import { existsSync as existsSync12, readFileSync as
|
|
5087
|
+
import { existsSync as existsSync12, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4793
5088
|
import { join as join13 } from "node:path";
|
|
4794
5089
|
|
|
4795
5090
|
// src/lib/registry-write.ts
|
|
4796
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as
|
|
5091
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4797
5092
|
import { dirname as dirname5, join as join12 } from "node:path";
|
|
4798
5093
|
var PROJECTS_REGISTRY_PATH = ".vinaya/projects.md";
|
|
4799
5094
|
function rowLine(name, path, specsPath) {
|
|
@@ -4864,7 +5159,7 @@ function planRegistryRow(repoRoot, name, path, specsPath) {
|
|
|
4864
5159
|
if (!existsSync11(abs2)) {
|
|
4865
5160
|
return { action: "create-host", rowLine: line, path: PROJECTS_REGISTRY_PATH };
|
|
4866
5161
|
}
|
|
4867
|
-
const existing =
|
|
5162
|
+
const existing = readFileSync11(abs2, "utf-8");
|
|
4868
5163
|
const already = parseRegistry(existing).some((p) => p.name === name);
|
|
4869
5164
|
return { action: already ? "skip-present" : "append-row", rowLine: line, path: PROJECTS_REGISTRY_PATH };
|
|
4870
5165
|
}
|
|
@@ -4874,7 +5169,7 @@ function applyRegistryRow(repoRoot, plan, name, path, specsPath) {
|
|
|
4874
5169
|
mkdirSync3(dirname5(abs2), { recursive: true });
|
|
4875
5170
|
writeFileSync4(abs2, freshProjectsRegistry(name, path, specsPath), "utf-8");
|
|
4876
5171
|
} else if (plan.action === "append-row") {
|
|
4877
|
-
const existing =
|
|
5172
|
+
const existing = readFileSync11(abs2, "utf-8");
|
|
4878
5173
|
writeFileSync4(abs2, appendRegistryRow(existing, name, path, specsPath), "utf-8");
|
|
4879
5174
|
}
|
|
4880
5175
|
}
|
|
@@ -4913,14 +5208,14 @@ function readManifest2(repoRoot) {
|
|
|
4913
5208
|
if (!existsSync12(p))
|
|
4914
5209
|
return null;
|
|
4915
5210
|
try {
|
|
4916
|
-
return VinayaConfigSchema.parse(JSON.parse(
|
|
5211
|
+
return VinayaConfigSchema.parse(JSON.parse(readFileSync12(p, "utf-8"))).managed ?? null;
|
|
4917
5212
|
} catch {
|
|
4918
5213
|
return null;
|
|
4919
5214
|
}
|
|
4920
5215
|
}
|
|
4921
5216
|
function writeManifest(repoRoot, manifest) {
|
|
4922
5217
|
const configAbs = join13(repoRoot, CONFIG_PATH);
|
|
4923
|
-
const seed = JSON.parse(
|
|
5218
|
+
const seed = JSON.parse(readFileSync12(configAbs, "utf-8"));
|
|
4924
5219
|
writeFileSync5(configAbs, `${JSON.stringify({ ...seed, managed: manifest }, null, 2)}
|
|
4925
5220
|
`, "utf-8");
|
|
4926
5221
|
}
|
|
@@ -5082,11 +5377,12 @@ async function initProductCommand(args) {
|
|
|
5082
5377
|
}
|
|
5083
5378
|
|
|
5084
5379
|
// src/commands/issue.ts
|
|
5085
|
-
import { execFileSync as
|
|
5380
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
5086
5381
|
|
|
5087
5382
|
// src/lib/forge-write.ts
|
|
5088
|
-
import {
|
|
5089
|
-
import {
|
|
5383
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
5384
|
+
import { existsSync as existsSync13, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
5385
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
5090
5386
|
import { tmpdir } from "node:os";
|
|
5091
5387
|
import { join as join14 } from "node:path";
|
|
5092
5388
|
class ForgeArgError extends Error {
|
|
@@ -5098,13 +5394,13 @@ function locateBody(args) {
|
|
|
5098
5394
|
const p = args[i + 1];
|
|
5099
5395
|
if (!p)
|
|
5100
5396
|
throw new ForgeArgError("`--body-file` was given with no path.");
|
|
5101
|
-
return { body:
|
|
5397
|
+
return { body: readFileSync13(p, "utf8"), source: { kind: "file", argIndex: i + 1, inlineForm: false } };
|
|
5102
5398
|
}
|
|
5103
5399
|
if (a.startsWith("--body-file=")) {
|
|
5104
5400
|
const p = a.slice("--body-file=".length);
|
|
5105
5401
|
if (!p)
|
|
5106
5402
|
throw new ForgeArgError("`--body-file=` was given with no path.");
|
|
5107
|
-
return { body:
|
|
5403
|
+
return { body: readFileSync13(p, "utf8"), source: { kind: "file", argIndex: i, inlineForm: true } };
|
|
5108
5404
|
}
|
|
5109
5405
|
if (a === "--body" || a === "-b") {
|
|
5110
5406
|
const v = args[i + 1];
|
|
@@ -5288,6 +5584,86 @@ function validateForgeWrite(input) {
|
|
|
5288
5584
|
}
|
|
5289
5585
|
return errors;
|
|
5290
5586
|
}
|
|
5587
|
+
function git2(args) {
|
|
5588
|
+
try {
|
|
5589
|
+
return execFileSync9("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
5590
|
+
} catch {
|
|
5591
|
+
return "";
|
|
5592
|
+
}
|
|
5593
|
+
}
|
|
5594
|
+
function repoRoot() {
|
|
5595
|
+
return git2(["rev-parse", "--show-toplevel"]);
|
|
5596
|
+
}
|
|
5597
|
+
function readWorkspaceGlobs(root) {
|
|
5598
|
+
const fromPackageJson = () => {
|
|
5599
|
+
try {
|
|
5600
|
+
const pkg = JSON.parse(readFileSync13(join14(root, "package.json"), "utf8"));
|
|
5601
|
+
return Array.isArray(pkg.workspaces) ? pkg.workspaces.filter((w) => typeof w === "string") : [];
|
|
5602
|
+
} catch {
|
|
5603
|
+
return [];
|
|
5604
|
+
}
|
|
5605
|
+
};
|
|
5606
|
+
const fromPnpmWorkspaceYaml = () => {
|
|
5607
|
+
try {
|
|
5608
|
+
return parsePnpmWorkspaceYaml(readFileSync13(join14(root, "pnpm-workspace.yaml"), "utf8"));
|
|
5609
|
+
} catch {
|
|
5610
|
+
return [];
|
|
5611
|
+
}
|
|
5612
|
+
};
|
|
5613
|
+
return [...fromPackageJson(), ...fromPnpmWorkspaceYaml()];
|
|
5614
|
+
}
|
|
5615
|
+
function listWorkspaceChildDirs(dir, root) {
|
|
5616
|
+
try {
|
|
5617
|
+
return readdirSync5(join14(root, dir), { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
5618
|
+
} catch {
|
|
5619
|
+
return [];
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
function readConfigExtraDomains(root) {
|
|
5623
|
+
try {
|
|
5624
|
+
const parsed = VinayaConfigSchema.safeParse(JSON.parse(readFileSync13(join14(root, "vinaya.config.json"), "utf8")));
|
|
5625
|
+
return parsed.success ? parsed.data.blastRadius?.extraDomains ?? [] : [];
|
|
5626
|
+
} catch {
|
|
5627
|
+
return [];
|
|
5628
|
+
}
|
|
5629
|
+
}
|
|
5630
|
+
function readSharedPackages(root = repoRoot()) {
|
|
5631
|
+
if (!root)
|
|
5632
|
+
return [];
|
|
5633
|
+
const derived = deriveWorkspacePackageDomains(readWorkspaceGlobs(root), (dir) => listWorkspaceChildDirs(dir, root));
|
|
5634
|
+
const defaults = deriveBuiltinCrossCuttingDefaults((p) => existsSync13(join14(root, p)));
|
|
5635
|
+
const configExtra = readConfigExtraDomains(root);
|
|
5636
|
+
return [...new Set([...derived, ...defaults, ...configExtra])];
|
|
5637
|
+
}
|
|
5638
|
+
function readProjectPaths(root = repoRoot()) {
|
|
5639
|
+
if (!root)
|
|
5640
|
+
return [];
|
|
5641
|
+
try {
|
|
5642
|
+
return parseRegistry(readFileSync13(join14(root, ".vinaya/projects.md"), "utf8"));
|
|
5643
|
+
} catch {
|
|
5644
|
+
return [];
|
|
5645
|
+
}
|
|
5646
|
+
}
|
|
5647
|
+
var CHECK_ISSUE_CONTENT = "issue-content";
|
|
5648
|
+
var ISSUE_CONTENT_RECOVERY = {
|
|
5649
|
+
blastRadius: "Add a second registered `Project(s)` this task also touches, or a `blast-radius-ack: <why one lens is enough>` line, then re-run `{cmd}`.",
|
|
5650
|
+
noBriefContent: "Move the brief-shaped section named above out of the Issue body and into the brief — the Issue carries the Planner's durable rationale, not the brief's just-in-time surface — then re-run `{cmd}`.",
|
|
5651
|
+
rationaleNamesDocs: 'Name a concrete doc/skill path (e.g. `aeg-root/…`, `.claude/skills/…/SKILL.md`) in "Docs to keep coherent" or "Traps", or write the `no-doc-surface` sentinel if the surface genuinely has none, then re-run `{cmd}`.'
|
|
5652
|
+
};
|
|
5653
|
+
function validateIssueContent(input) {
|
|
5654
|
+
const findings = [
|
|
5655
|
+
[checkBlastRadiusScope(input.body, input.labels, input.sharedPackages, input.projectPaths).errors, "blastRadius"],
|
|
5656
|
+
[checkNoBriefContent(input.body).errors, "noBriefContent"],
|
|
5657
|
+
[checkRationaleNamesDocs(input.body).errors, "rationaleNamesDocs"]
|
|
5658
|
+
];
|
|
5659
|
+
const errors = [];
|
|
5660
|
+
for (const [messages, kind] of findings) {
|
|
5661
|
+
const recovery = ISSUE_CONTENT_RECOVERY[kind].replace("{cmd}", input.retryCommand);
|
|
5662
|
+
for (const message of messages)
|
|
5663
|
+
errors.push(makeCheckError(CHECK_ISSUE_CONTENT, message, recovery));
|
|
5664
|
+
}
|
|
5665
|
+
return errors;
|
|
5666
|
+
}
|
|
5291
5667
|
|
|
5292
5668
|
// src/commands/issue.ts
|
|
5293
5669
|
var RETRY_CREATE = "vinaya issue create --validate-only …";
|
|
@@ -5305,7 +5681,7 @@ function locateBodyOrRefuse(ghArgs, retryCommand) {
|
|
|
5305
5681
|
function fetchForgeLabels(issueRef) {
|
|
5306
5682
|
let out;
|
|
5307
5683
|
try {
|
|
5308
|
-
out =
|
|
5684
|
+
out = execFileSync10("gh", ["issue", "view", issueRef, "--json", "labels"], {
|
|
5309
5685
|
encoding: "utf8",
|
|
5310
5686
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5311
5687
|
});
|
|
@@ -5333,7 +5709,7 @@ function reportPass(json, command) {
|
|
|
5333
5709
|
function runGhWrite(ghCmd, ghArgs, bodyResult, json) {
|
|
5334
5710
|
const { finalArgs, cleanup: cleanup2 } = resolveShippableArgs(ghArgs, bodyResult);
|
|
5335
5711
|
try {
|
|
5336
|
-
const out =
|
|
5712
|
+
const out = execFileSync10("gh", [...ghCmd, ...finalArgs], { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] });
|
|
5337
5713
|
const url = out.trim();
|
|
5338
5714
|
if (json)
|
|
5339
5715
|
printJson({ validated: true, written: true, url });
|
|
@@ -5344,22 +5720,31 @@ function runGhWrite(ghCmd, ghArgs, bodyResult, json) {
|
|
|
5344
5720
|
cleanup2();
|
|
5345
5721
|
}
|
|
5346
5722
|
}
|
|
5347
|
-
function validateTaskIssue(body, title, retryCommand) {
|
|
5723
|
+
function validateTaskIssue(body, title, labels, retryCommand) {
|
|
5348
5724
|
if (body === null) {
|
|
5349
5725
|
refuse([
|
|
5350
5726
|
makeCheckError("forge-args", "A task Issue (a `vinaya/tranche:*` label) requires a `--body-file <path>` so the rationale gate can validate it.", `Add \`--body-file <path>\`, then re-run \`${retryCommand}\`.`)
|
|
5351
5727
|
]);
|
|
5352
5728
|
}
|
|
5353
5729
|
const sections = resolveSections("issue", retryCommand);
|
|
5354
|
-
const
|
|
5730
|
+
const schemaErrors = validateForgeWrite({
|
|
5355
5731
|
body,
|
|
5356
5732
|
title,
|
|
5357
5733
|
sections,
|
|
5358
5734
|
changedFiles: [],
|
|
5359
5735
|
retryCommand
|
|
5360
5736
|
});
|
|
5361
|
-
if (
|
|
5362
|
-
refuse(
|
|
5737
|
+
if (schemaErrors.length > 0)
|
|
5738
|
+
refuse(schemaErrors);
|
|
5739
|
+
const contentErrors = validateIssueContent({
|
|
5740
|
+
body,
|
|
5741
|
+
labels,
|
|
5742
|
+
sharedPackages: readSharedPackages(),
|
|
5743
|
+
projectPaths: readProjectPaths(),
|
|
5744
|
+
retryCommand
|
|
5745
|
+
});
|
|
5746
|
+
if (contentErrors.length > 0)
|
|
5747
|
+
refuse(contentErrors);
|
|
5363
5748
|
}
|
|
5364
5749
|
function issueCreateCommand(args) {
|
|
5365
5750
|
const json = args.includes("--json");
|
|
@@ -5370,7 +5755,7 @@ function issueCreateCommand(args) {
|
|
|
5370
5755
|
const title = extractTitle(ghArgs);
|
|
5371
5756
|
const labels = extractLabels(ghArgs);
|
|
5372
5757
|
if (isTaskIssueLabelSet(labels)) {
|
|
5373
|
-
validateTaskIssue(body, title, RETRY_CREATE);
|
|
5758
|
+
validateTaskIssue(body, title, labels, RETRY_CREATE);
|
|
5374
5759
|
}
|
|
5375
5760
|
if (validateOnly) {
|
|
5376
5761
|
reportPass(json, "issue create");
|
|
@@ -5394,7 +5779,7 @@ function issueEditCommand(args) {
|
|
|
5394
5779
|
const title = extractTitle(ghArgs);
|
|
5395
5780
|
const labels = [...new Set([...fetchForgeLabels(issueRef), ...extractLabels(ghArgs)])];
|
|
5396
5781
|
if (isTaskIssueLabelSet(labels)) {
|
|
5397
|
-
validateTaskIssue(body, title, RETRY_EDIT);
|
|
5782
|
+
validateTaskIssue(body, title, labels, RETRY_EDIT);
|
|
5398
5783
|
}
|
|
5399
5784
|
if (validateOnly) {
|
|
5400
5785
|
reportPass(json, "issue edit");
|
|
@@ -5404,7 +5789,7 @@ function issueEditCommand(args) {
|
|
|
5404
5789
|
}
|
|
5405
5790
|
|
|
5406
5791
|
// src/commands/new-check.ts
|
|
5407
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
5792
|
+
import { chmodSync as chmodSync2, existsSync as existsSync14, mkdirSync as mkdirSync4, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "node:fs";
|
|
5408
5793
|
import { join as join15 } from "node:path";
|
|
5409
5794
|
var TEMPLATE_PATH = join15(packageRoot(import.meta.url), "templates", "custom-check.template.ts");
|
|
5410
5795
|
var CHECKS_DIR = join15("scripts", "vinaya-checks");
|
|
@@ -5425,15 +5810,15 @@ function newCheckCommand(args) {
|
|
|
5425
5810
|
process.exit(2);
|
|
5426
5811
|
}
|
|
5427
5812
|
const scriptsDir = join15(process.cwd(), CHECKS_DIR);
|
|
5428
|
-
if (!
|
|
5813
|
+
if (!existsSync14(scriptsDir))
|
|
5429
5814
|
mkdirSync4(scriptsDir, { recursive: true });
|
|
5430
5815
|
const fileStem = name.slice(name.indexOf("/") + 1);
|
|
5431
5816
|
const targetPath = join15(scriptsDir, `${fileStem}.ts`);
|
|
5432
|
-
if (
|
|
5817
|
+
if (existsSync14(targetPath)) {
|
|
5433
5818
|
console.error(`Error: ${targetPath} already exists.`);
|
|
5434
5819
|
process.exit(1);
|
|
5435
5820
|
}
|
|
5436
|
-
const template =
|
|
5821
|
+
const template = readFileSync14(TEMPLATE_PATH, "utf-8");
|
|
5437
5822
|
const contents = template.split("{{CHECK_NAME}}").join(name);
|
|
5438
5823
|
writeFileSync7(targetPath, contents, "utf-8");
|
|
5439
5824
|
chmodSync2(targetPath, 493);
|
|
@@ -5447,21 +5832,21 @@ ${registration}
|
|
|
5447
5832
|
}
|
|
5448
5833
|
|
|
5449
5834
|
// src/commands/pr.ts
|
|
5450
|
-
import { execFileSync as
|
|
5835
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
5451
5836
|
var RETRY_CREATE2 = "vinaya pr create --validate-only …";
|
|
5452
5837
|
var RETRY_EDIT2 = "vinaya pr edit <n> --validate-only …";
|
|
5453
|
-
function
|
|
5838
|
+
function git3(args) {
|
|
5454
5839
|
try {
|
|
5455
|
-
return
|
|
5840
|
+
return execFileSync11("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
5456
5841
|
} catch {
|
|
5457
5842
|
return "";
|
|
5458
5843
|
}
|
|
5459
5844
|
}
|
|
5460
5845
|
function localChangedFiles() {
|
|
5461
5846
|
const base = process.env.BASE_SHA || "origin/main";
|
|
5462
|
-
let out =
|
|
5847
|
+
let out = git3(["diff", "--name-only", `${base}...HEAD`]);
|
|
5463
5848
|
if (!out)
|
|
5464
|
-
out =
|
|
5849
|
+
out = git3(["diff", "--name-only", "main...HEAD"]);
|
|
5465
5850
|
return out.split(`
|
|
5466
5851
|
`).map((s) => s.trim()).filter(Boolean);
|
|
5467
5852
|
}
|
|
@@ -5486,7 +5871,7 @@ function reportPass2(json, command) {
|
|
|
5486
5871
|
function runGhWrite2(ghCmd, ghArgs, bodyResult, json) {
|
|
5487
5872
|
const { finalArgs, cleanup: cleanup2 } = resolveShippableArgs(ghArgs, bodyResult);
|
|
5488
5873
|
try {
|
|
5489
|
-
const out =
|
|
5874
|
+
const out = execFileSync11("gh", [...ghCmd, ...finalArgs], { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] });
|
|
5490
5875
|
const url = out.trim();
|
|
5491
5876
|
if (json)
|
|
5492
5877
|
printJson({ validated: true, written: true, url });
|
|
@@ -5500,7 +5885,7 @@ function runGhWrite2(ghCmd, ghArgs, bodyResult, json) {
|
|
|
5500
5885
|
function fetchPrForgeContext(prRef) {
|
|
5501
5886
|
let viewOut;
|
|
5502
5887
|
try {
|
|
5503
|
-
viewOut =
|
|
5888
|
+
viewOut = execFileSync11("gh", ["pr", "view", prRef, "--json", "headRefName,files"], {
|
|
5504
5889
|
encoding: "utf8",
|
|
5505
5890
|
stdio: ["ignore", "pipe", "pipe"]
|
|
5506
5891
|
});
|
|
@@ -5539,8 +5924,8 @@ function prCreateCommand(args) {
|
|
|
5539
5924
|
}
|
|
5540
5925
|
const sections = resolveSections("pr", RETRY_CREATE2);
|
|
5541
5926
|
const changedFiles2 = localChangedFiles();
|
|
5542
|
-
const headCommit =
|
|
5543
|
-
const branch = headCommit === "" ? "" :
|
|
5927
|
+
const headCommit = git3(["rev-parse", "--verify", "--quiet", "HEAD"]);
|
|
5928
|
+
const branch = headCommit === "" ? "" : git3(["symbolic-ref", "--quiet", "--short", "HEAD"]);
|
|
5544
5929
|
const errors = validateForgeWrite({
|
|
5545
5930
|
body,
|
|
5546
5931
|
title,
|
|
@@ -5602,14 +5987,14 @@ function prEditCommand(args) {
|
|
|
5602
5987
|
}
|
|
5603
5988
|
|
|
5604
5989
|
// src/commands/pr-report.ts
|
|
5605
|
-
import { execFileSync as
|
|
5606
|
-
import { existsSync as
|
|
5990
|
+
import { execFileSync as execFileSync12, spawnSync as spawnSync2 } from "node:child_process";
|
|
5991
|
+
import { existsSync as existsSync15, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
|
|
5607
5992
|
import { join as join16 } from "node:path";
|
|
5608
5993
|
var EVIDENCE_START = "<!-- AEG:EVIDENCE:START -->";
|
|
5609
5994
|
var EVIDENCE_END = "<!-- AEG:EVIDENCE:END -->";
|
|
5610
|
-
function
|
|
5995
|
+
function git4(args) {
|
|
5611
5996
|
try {
|
|
5612
|
-
return
|
|
5997
|
+
return execFileSync12("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
5613
5998
|
} catch {
|
|
5614
5999
|
return "";
|
|
5615
6000
|
}
|
|
@@ -5627,7 +6012,7 @@ class GitCommandError extends Error {
|
|
|
5627
6012
|
}
|
|
5628
6013
|
function gitStrict(args) {
|
|
5629
6014
|
try {
|
|
5630
|
-
return
|
|
6015
|
+
return execFileSync12("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
5631
6016
|
} catch (err) {
|
|
5632
6017
|
const stderr = err.stderr;
|
|
5633
6018
|
throw new GitCommandError(args, String(stderr ?? err.message).trim() || "non-zero exit");
|
|
@@ -5648,7 +6033,7 @@ function resolveMergeBase(head) {
|
|
|
5648
6033
|
const primary = process.env.BASE_SHA || "origin/main";
|
|
5649
6034
|
const tried = primary === "main" ? [primary] : [primary, "main"];
|
|
5650
6035
|
for (const ref of tried) {
|
|
5651
|
-
const base =
|
|
6036
|
+
const base = git4(["merge-base", ref, head]);
|
|
5652
6037
|
if (base)
|
|
5653
6038
|
return base;
|
|
5654
6039
|
}
|
|
@@ -5663,7 +6048,7 @@ function computeGroupA() {
|
|
|
5663
6048
|
function resolveSelfEntry() {
|
|
5664
6049
|
const root = packageRoot(import.meta.url);
|
|
5665
6050
|
const dist = join16(root, "dist", "index.js");
|
|
5666
|
-
return
|
|
6051
|
+
return existsSync15(dist) ? dist : join16(root, "src", "index.ts");
|
|
5667
6052
|
}
|
|
5668
6053
|
var FAILING_STATUSES = new Set(["fail", "error", "timeout"]);
|
|
5669
6054
|
function anyGateFailed(outcomes) {
|
|
@@ -5768,7 +6153,7 @@ async function prReportCommand(args) {
|
|
|
5768
6153
|
throw err;
|
|
5769
6154
|
}
|
|
5770
6155
|
if (writePath) {
|
|
5771
|
-
const existing =
|
|
6156
|
+
const existing = existsSync15(writePath) ? readFileSync15(writePath, "utf8") : "";
|
|
5772
6157
|
const updated = replaceEvidenceBlock(existing, result.blockInner);
|
|
5773
6158
|
writeFileSync8(writePath, updated);
|
|
5774
6159
|
process.stdout.write(`Wrote AEG:EVIDENCE block to ${writePath}
|
|
@@ -5781,12 +6166,12 @@ async function prReportCommand(args) {
|
|
|
5781
6166
|
}
|
|
5782
6167
|
|
|
5783
6168
|
// src/commands/quickstart.ts
|
|
5784
|
-
import { execFileSync as
|
|
5785
|
-
import { existsSync as
|
|
6169
|
+
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
6170
|
+
import { existsSync as existsSync17, readFileSync as readFileSync17 } from "node:fs";
|
|
5786
6171
|
import { join as join18 } from "node:path";
|
|
5787
6172
|
|
|
5788
6173
|
// src/lib/doc-owners-write.ts
|
|
5789
|
-
import { existsSync as
|
|
6174
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "node:fs";
|
|
5790
6175
|
import { dirname as dirname6, join as join17 } from "node:path";
|
|
5791
6176
|
function bindingLine(glob, pointer) {
|
|
5792
6177
|
return `${glob} ${pointer}`;
|
|
@@ -5802,23 +6187,23 @@ function appendDocOwnersBinding(content, glob, pointer) {
|
|
|
5802
6187
|
return `${content}${sep4}${bindingLine(glob, pointer)}
|
|
5803
6188
|
`;
|
|
5804
6189
|
}
|
|
5805
|
-
function planDocOwnersBinding(
|
|
5806
|
-
const abs2 = join17(
|
|
6190
|
+
function planDocOwnersBinding(repoRoot2, glob, pointer) {
|
|
6191
|
+
const abs2 = join17(repoRoot2, DOC_OWNERS_PATH);
|
|
5807
6192
|
const line = bindingLine(glob, pointer);
|
|
5808
|
-
if (!
|
|
6193
|
+
if (!existsSync16(abs2)) {
|
|
5809
6194
|
return { action: "create-host", line, path: DOC_OWNERS_PATH };
|
|
5810
6195
|
}
|
|
5811
|
-
const existing =
|
|
6196
|
+
const existing = readFileSync16(abs2, "utf-8");
|
|
5812
6197
|
const already = parseDocOwners(existing).bindings.some((b) => b.glob === glob);
|
|
5813
6198
|
return { action: already ? "skip-present" : "append-row", line, path: DOC_OWNERS_PATH };
|
|
5814
6199
|
}
|
|
5815
|
-
function applyDocOwnersBinding(
|
|
5816
|
-
const abs2 = join17(
|
|
6200
|
+
function applyDocOwnersBinding(repoRoot2, plan, glob, pointer) {
|
|
6201
|
+
const abs2 = join17(repoRoot2, DOC_OWNERS_PATH);
|
|
5817
6202
|
if (plan.action === "create-host") {
|
|
5818
6203
|
mkdirSync5(dirname6(abs2), { recursive: true });
|
|
5819
6204
|
writeFileSync9(abs2, freshDocOwners(glob, pointer), "utf-8");
|
|
5820
6205
|
} else if (plan.action === "append-row") {
|
|
5821
|
-
const existing =
|
|
6206
|
+
const existing = readFileSync16(abs2, "utf-8");
|
|
5822
6207
|
writeFileSync9(abs2, appendDocOwnersBinding(existing, glob, pointer), "utf-8");
|
|
5823
6208
|
}
|
|
5824
6209
|
}
|
|
@@ -5875,13 +6260,13 @@ var GLOB_INJECTION_RE = /[\s\r\n]/;
|
|
|
5875
6260
|
var POINTER_INJECTION_RE = /[\r\n]/;
|
|
5876
6261
|
var NAME_INJECTION_RE = /[\s|\r\n]/;
|
|
5877
6262
|
var PATH_INJECTION_RE2 = /[|\r\n]/;
|
|
5878
|
-
function pointerIsMissingLocalFile(
|
|
6263
|
+
function pointerIsMissingLocalFile(repoRoot2, pointer) {
|
|
5879
6264
|
if (/^https?:\/\//i.test(pointer))
|
|
5880
6265
|
return false;
|
|
5881
6266
|
const path = pointer.split("#")[0] ?? pointer;
|
|
5882
|
-
return !
|
|
6267
|
+
return !existsSync17(join18(repoRoot2, path));
|
|
5883
6268
|
}
|
|
5884
|
-
async function bindDocOwnerLoop(deps,
|
|
6269
|
+
async function bindDocOwnerLoop(deps, repoRoot2) {
|
|
5885
6270
|
let any = false;
|
|
5886
6271
|
for (;; ) {
|
|
5887
6272
|
if (any && !await deps.confirm("Bind another doc→code pair?", false))
|
|
@@ -5896,11 +6281,11 @@ async function bindDocOwnerLoop(deps, repoRoot) {
|
|
|
5896
6281
|
}
|
|
5897
6282
|
if (GLOB_INJECTION_RE.test(glob) || POINTER_INJECTION_RE.test(pointer)) {
|
|
5898
6283
|
console.error(red("Error: glob must not contain whitespace, and the pointer must not contain a newline."));
|
|
5899
|
-
} else if (pointerIsMissingLocalFile(
|
|
6284
|
+
} else if (pointerIsMissingLocalFile(repoRoot2, pointer)) {
|
|
5900
6285
|
console.error(red(`✗ "${pointer}" does not exist in this repo (and isn't a URL).`));
|
|
5901
6286
|
} else {
|
|
5902
|
-
const plan = planDocOwnersBinding(
|
|
5903
|
-
applyDocOwnersBinding(
|
|
6287
|
+
const plan = planDocOwnersBinding(repoRoot2, glob, pointer);
|
|
6288
|
+
applyDocOwnersBinding(repoRoot2, plan, glob, pointer);
|
|
5904
6289
|
process.stdout.write(`${green(renderDocOwnersBindingDiffLine(plan))}
|
|
5905
6290
|
`);
|
|
5906
6291
|
any = true;
|
|
@@ -5946,7 +6331,7 @@ async function registerProjectLoop(deps) {
|
|
|
5946
6331
|
}
|
|
5947
6332
|
}
|
|
5948
6333
|
function readPackageVersion() {
|
|
5949
|
-
const pkg = JSON.parse(
|
|
6334
|
+
const pkg = JSON.parse(readFileSync17(join18(packageRoot(import.meta.url), "package.json"), "utf-8"));
|
|
5950
6335
|
return pkg.version;
|
|
5951
6336
|
}
|
|
5952
6337
|
function realDeps6() {
|
|
@@ -5976,8 +6361,8 @@ function realDeps6() {
|
|
|
5976
6361
|
closeStdin
|
|
5977
6362
|
};
|
|
5978
6363
|
}
|
|
5979
|
-
function execGit(
|
|
5980
|
-
return
|
|
6364
|
+
function execGit(repoRoot2, args) {
|
|
6365
|
+
return execFileSync13("git", args, { cwd: repoRoot2, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
5981
6366
|
}
|
|
5982
6367
|
function errorDetail(err) {
|
|
5983
6368
|
const stderr = err.stderr;
|
|
@@ -5985,31 +6370,31 @@ function errorDetail(err) {
|
|
|
5985
6370
|
return stderr.toString().trim();
|
|
5986
6371
|
return err instanceof Error ? err.message : String(err);
|
|
5987
6372
|
}
|
|
5988
|
-
function gitStatusShort(
|
|
5989
|
-
return execGit(
|
|
6373
|
+
function gitStatusShort(repoRoot2) {
|
|
6374
|
+
return execGit(repoRoot2, ["status", "--short"]);
|
|
5990
6375
|
}
|
|
5991
|
-
function commitInstall(
|
|
5992
|
-
execGit(
|
|
5993
|
-
const staged = execGit(
|
|
6376
|
+
function commitInstall(repoRoot2) {
|
|
6377
|
+
execGit(repoRoot2, ["add", "-A"]);
|
|
6378
|
+
const staged = execGit(repoRoot2, ["diff", "--cached", "--stat"]);
|
|
5994
6379
|
if (!staged)
|
|
5995
6380
|
return { committed: false, message: "Nothing to commit — working tree already clean." };
|
|
5996
6381
|
try {
|
|
5997
|
-
execGit(
|
|
6382
|
+
execGit(repoRoot2, ["commit", "-m", "Chore: install Vinaya"]);
|
|
5998
6383
|
return { committed: true, message: "✓ Committed `Chore: install Vinaya`." };
|
|
5999
6384
|
} catch (err) {
|
|
6000
6385
|
return { committed: false, message: `git commit failed: ${errorDetail(err)}` };
|
|
6001
6386
|
}
|
|
6002
6387
|
}
|
|
6003
|
-
function pushInstall(
|
|
6388
|
+
function pushInstall(repoRoot2) {
|
|
6004
6389
|
try {
|
|
6005
|
-
const branch = execGit(
|
|
6390
|
+
const branch = execGit(repoRoot2, ["symbolic-ref", "--short", "HEAD"]);
|
|
6006
6391
|
let hasUpstream = true;
|
|
6007
6392
|
try {
|
|
6008
|
-
execGit(
|
|
6393
|
+
execGit(repoRoot2, ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]);
|
|
6009
6394
|
} catch {
|
|
6010
6395
|
hasUpstream = false;
|
|
6011
6396
|
}
|
|
6012
|
-
execGit(
|
|
6397
|
+
execGit(repoRoot2, hasUpstream ? ["push"] : ["push", "-u", "origin", branch]);
|
|
6013
6398
|
return { pushed: true, message: "✓ Pushed." };
|
|
6014
6399
|
} catch (err) {
|
|
6015
6400
|
return { pushed: false, message: `git push failed: ${errorDetail(err)}` };
|
|
@@ -6094,8 +6479,8 @@ async function quickstartCommand(args) {
|
|
|
6094
6479
|
}
|
|
6095
6480
|
|
|
6096
6481
|
// src/commands/review-post.ts
|
|
6097
|
-
import { execFileSync as
|
|
6098
|
-
import { readFileSync as
|
|
6482
|
+
import { execFileSync as execFileSync14 } from "node:child_process";
|
|
6483
|
+
import { readFileSync as readFileSync18, rmSync as rmSync4, writeFileSync as writeFileSync10 } from "node:fs";
|
|
6099
6484
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
6100
6485
|
import { join as join19 } from "node:path";
|
|
6101
6486
|
class FindingsParseError extends Error {
|
|
@@ -6258,7 +6643,7 @@ function readFindingsFile(path, allowedSeverities) {
|
|
|
6258
6643
|
return [];
|
|
6259
6644
|
let content;
|
|
6260
6645
|
try {
|
|
6261
|
-
content =
|
|
6646
|
+
content = readFileSync18(path, "utf8");
|
|
6262
6647
|
} catch {
|
|
6263
6648
|
refuseCmd(`Could not read findings file at ${path}.`, "Check the path and re-run.");
|
|
6264
6649
|
}
|
|
@@ -6289,7 +6674,7 @@ function normalizeSecurityVerdict(raw) {
|
|
|
6289
6674
|
}
|
|
6290
6675
|
function gh(args) {
|
|
6291
6676
|
try {
|
|
6292
|
-
return
|
|
6677
|
+
return execFileSync14("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
6293
6678
|
} catch (err) {
|
|
6294
6679
|
const stderr = err.stderr;
|
|
6295
6680
|
throw new Error(String(stderr ?? err.message).trim() || "gh command failed");
|
|
@@ -6374,7 +6759,7 @@ async function reviewPostCommand(args) {
|
|
|
6374
6759
|
let secretsEvidence = null;
|
|
6375
6760
|
if (secretsEvidenceFile) {
|
|
6376
6761
|
try {
|
|
6377
|
-
secretsEvidence =
|
|
6762
|
+
secretsEvidence = readFileSync18(secretsEvidenceFile, "utf8");
|
|
6378
6763
|
} catch {
|
|
6379
6764
|
refuseCmd(`Could not read secrets evidence file at ${secretsEvidenceFile}.`, "Check the path and re-run.");
|
|
6380
6765
|
}
|
|
@@ -6415,7 +6800,7 @@ Self-verification: clean — re-parsed VERDICT is bound to head ${headSha}.
|
|
|
6415
6800
|
|
|
6416
6801
|
// src/commands/studio.ts
|
|
6417
6802
|
import { execFile as execFile5, spawn as spawn2 } from "node:child_process";
|
|
6418
|
-
import { existsSync as
|
|
6803
|
+
import { existsSync as existsSync18, readFileSync as readFileSync19, renameSync } from "node:fs";
|
|
6419
6804
|
import net from "node:net";
|
|
6420
6805
|
import { dirname as dirname7, join as join20 } from "node:path";
|
|
6421
6806
|
import { promisify as promisify5 } from "node:util";
|
|
@@ -6435,15 +6820,15 @@ function resolveStudioTarget(cwd, moduleUrl = import.meta.url) {
|
|
|
6435
6820
|
for (;; ) {
|
|
6436
6821
|
const webDir = join20(dir, "apps", "vinaya-studio", "web");
|
|
6437
6822
|
const pkgPath = join20(webDir, "package.json");
|
|
6438
|
-
if (
|
|
6823
|
+
if (existsSync18(pkgPath)) {
|
|
6439
6824
|
try {
|
|
6440
|
-
const pkg = JSON.parse(
|
|
6825
|
+
const pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
|
|
6441
6826
|
if (pkg.name === "@atta/vinaya-studio-web") {
|
|
6442
6827
|
return { kind: "workspace", webDir };
|
|
6443
6828
|
}
|
|
6444
6829
|
} catch {}
|
|
6445
6830
|
}
|
|
6446
|
-
if (
|
|
6831
|
+
if (existsSync18(join20(dir, ".git")))
|
|
6447
6832
|
break;
|
|
6448
6833
|
const parent = dirname7(dir);
|
|
6449
6834
|
if (parent === dir)
|
|
@@ -6451,7 +6836,7 @@ function resolveStudioTarget(cwd, moduleUrl = import.meta.url) {
|
|
|
6451
6836
|
dir = parent;
|
|
6452
6837
|
}
|
|
6453
6838
|
const standaloneWebDir = join20(packageRoot(moduleUrl), "studio-standalone", "apps", "vinaya-studio", "web");
|
|
6454
|
-
if (
|
|
6839
|
+
if (existsSync18(join20(standaloneWebDir, "server.js"))) {
|
|
6455
6840
|
return { kind: "package", packageDir: standaloneWebDir };
|
|
6456
6841
|
}
|
|
6457
6842
|
return { kind: "missing" };
|
|
@@ -6473,7 +6858,7 @@ function isPortFree(port) {
|
|
|
6473
6858
|
function ensureStudioNodeModules(bundleRoot) {
|
|
6474
6859
|
const real = join20(bundleRoot, "node_modules");
|
|
6475
6860
|
const packed = join20(bundleRoot, STUDIO_NODE_MODULES_PACKED_DIRNAME);
|
|
6476
|
-
if (!
|
|
6861
|
+
if (!existsSync18(real) && existsSync18(packed)) {
|
|
6477
6862
|
renameSync(packed, real);
|
|
6478
6863
|
}
|
|
6479
6864
|
}
|
|
@@ -6509,7 +6894,7 @@ async function runStudio(cwd, args, moduleUrl = import.meta.url) {
|
|
|
6509
6894
|
}
|
|
6510
6895
|
|
|
6511
6896
|
// src/commands/upgrade.ts
|
|
6512
|
-
import { existsSync as
|
|
6897
|
+
import { existsSync as existsSync19, readFileSync as readFileSync20, rmSync as rmSync5, writeFileSync as writeFileSync11 } from "node:fs";
|
|
6513
6898
|
import { join as join21 } from "node:path";
|
|
6514
6899
|
function realDeps7() {
|
|
6515
6900
|
return {
|
|
@@ -6527,13 +6912,13 @@ function realDeps7() {
|
|
|
6527
6912
|
function flags2(args) {
|
|
6528
6913
|
return { dryRun: args.includes("--dry-run"), yes: args.includes("--yes") };
|
|
6529
6914
|
}
|
|
6530
|
-
function readManifest3(
|
|
6531
|
-
const p = join21(
|
|
6532
|
-
if (!
|
|
6915
|
+
function readManifest3(repoRoot2) {
|
|
6916
|
+
const p = join21(repoRoot2, CONFIG_PATH);
|
|
6917
|
+
if (!existsSync19(p))
|
|
6533
6918
|
return { kind: "missing" };
|
|
6534
6919
|
let raw;
|
|
6535
6920
|
try {
|
|
6536
|
-
raw = JSON.parse(
|
|
6921
|
+
raw = JSON.parse(readFileSync20(p, "utf-8"));
|
|
6537
6922
|
} catch (err) {
|
|
6538
6923
|
return { kind: "invalid", error: `invalid JSON: ${err.message}` };
|
|
6539
6924
|
}
|
|
@@ -6548,31 +6933,31 @@ function readManifest3(repoRoot) {
|
|
|
6548
6933
|
return { kind: "not-initialized" };
|
|
6549
6934
|
return { kind: "ok", manifest: parsed.data.managed };
|
|
6550
6935
|
}
|
|
6551
|
-
function writeManifestVersion(
|
|
6552
|
-
const configAbs = join21(
|
|
6553
|
-
const seed = JSON.parse(
|
|
6936
|
+
function writeManifestVersion(repoRoot2, manifest) {
|
|
6937
|
+
const configAbs = join21(repoRoot2, CONFIG_PATH);
|
|
6938
|
+
const seed = JSON.parse(readFileSync20(configAbs, "utf-8"));
|
|
6554
6939
|
const updated = { ...manifest, version: MANAGED_MANIFEST_VERSION };
|
|
6555
6940
|
writeFileSync11(configAbs, `${JSON.stringify({ ...seed, managed: updated }, null, 2)}
|
|
6556
6941
|
`, "utf-8");
|
|
6557
6942
|
}
|
|
6558
|
-
function stripFor(
|
|
6559
|
-
const abs2 = resolveManagedBlockPath(
|
|
6560
|
-
if (!
|
|
6943
|
+
function stripFor(repoRoot2, path, marker, comment) {
|
|
6944
|
+
const abs2 = resolveManagedBlockPath(repoRoot2, path);
|
|
6945
|
+
if (!existsSync19(abs2))
|
|
6561
6946
|
return { path, marker, comment, present: false, removesHost: false };
|
|
6562
|
-
const stripped = stripBlockFromContent(
|
|
6947
|
+
const stripped = stripBlockFromContent(readFileSync20(abs2, "utf-8"), marker, comment);
|
|
6563
6948
|
if (stripped === null)
|
|
6564
6949
|
return { path, marker, comment, present: false, removesHost: false };
|
|
6565
6950
|
return { path, marker, comment, present: true, removesHost: blockStripLeavesEmpty(stripped) };
|
|
6566
6951
|
}
|
|
6567
|
-
function planHookRouting(
|
|
6952
|
+
function planHookRouting(repoRoot2, manifest, recorded, hooksPathValue) {
|
|
6568
6953
|
const none = { target: recorded, strips: [], arm: false, migratesManifest: false, blockedReason: null };
|
|
6569
6954
|
if (recorded === ".husky")
|
|
6570
6955
|
return none;
|
|
6571
6956
|
if (recorded === TRACKED_HOOK_DIR) {
|
|
6572
|
-
const strips = manifest.blocks.filter((b) => b.path.startsWith(`${TRACKED_HOOK_DIR}/`)).map((b) => stripFor(
|
|
6957
|
+
const strips = manifest.blocks.filter((b) => b.path.startsWith(`${TRACKED_HOOK_DIR}/`)).map((b) => stripFor(repoRoot2, `.git/hooks/${b.marker}`, b.marker, b.comment)).filter((s) => s.present);
|
|
6573
6958
|
if (hooksPathValue === TRACKED_HOOK_DIR)
|
|
6574
6959
|
return { ...none, strips };
|
|
6575
|
-
const foreign = foreignRawHooks(
|
|
6960
|
+
const foreign = foreignRawHooks(repoRoot2);
|
|
6576
6961
|
if (foreign.length > 0) {
|
|
6577
6962
|
return {
|
|
6578
6963
|
...none,
|
|
@@ -6585,17 +6970,17 @@ function planHookRouting(repoRoot, manifest, recorded, hooksPathValue) {
|
|
|
6585
6970
|
const legacy = manifest.blocks.filter((b) => b.path.startsWith(".git/hooks/"));
|
|
6586
6971
|
const blocked = [];
|
|
6587
6972
|
for (const b of legacy) {
|
|
6588
|
-
const abs2 = resolveManagedBlockPath(
|
|
6589
|
-
if (!
|
|
6973
|
+
const abs2 = resolveManagedBlockPath(repoRoot2, b.path);
|
|
6974
|
+
if (!existsSync19(abs2))
|
|
6590
6975
|
continue;
|
|
6591
|
-
const stripped = stripBlockFromContent(
|
|
6976
|
+
const stripped = stripBlockFromContent(readFileSync20(abs2, "utf-8"), b.marker, b.comment);
|
|
6592
6977
|
if (stripped === null)
|
|
6593
6978
|
blocked.push(`${b.path} exists without vinaya's managed block`);
|
|
6594
6979
|
else if (!blockStripLeavesEmpty(stripped))
|
|
6595
6980
|
blocked.push(`${b.path} carries your own lines alongside the managed block`);
|
|
6596
6981
|
}
|
|
6597
6982
|
const legacyNames = new Set(legacy.map((b) => b.path.slice(".git/hooks/".length)));
|
|
6598
|
-
for (const f of activeRawHooks(
|
|
6983
|
+
for (const f of activeRawHooks(repoRoot2)) {
|
|
6599
6984
|
if (!legacyNames.has(f))
|
|
6600
6985
|
blocked.push(`.git/hooks/${f} is an active raw hook vinaya does not manage`);
|
|
6601
6986
|
}
|
|
@@ -6603,7 +6988,7 @@ function planHookRouting(repoRoot, manifest, recorded, hooksPathValue) {
|
|
|
6603
6988
|
return { ...none, blockedReason: blocked.join("; ") };
|
|
6604
6989
|
return {
|
|
6605
6990
|
target: TRACKED_HOOK_DIR,
|
|
6606
|
-
strips: legacy.map((b) => stripFor(
|
|
6991
|
+
strips: legacy.map((b) => stripFor(repoRoot2, b.path, b.marker, b.comment)).filter((s) => s.present),
|
|
6607
6992
|
arm: hooksPathValue !== TRACKED_HOOK_DIR,
|
|
6608
6993
|
migratesManifest: true,
|
|
6609
6994
|
blockedReason: null
|
|
@@ -6615,7 +7000,7 @@ function translateHookPaths(manifest) {
|
|
|
6615
7000
|
blocks: manifest.blocks.map((b) => b.path.startsWith(".git/hooks/") ? { ...b, path: `${TRACKED_HOOK_DIR}/${b.path.slice(".git/hooks/".length)}` } : b)
|
|
6616
7001
|
};
|
|
6617
7002
|
}
|
|
6618
|
-
function planUpgrade(ops,
|
|
7003
|
+
function planUpgrade(ops, repoRoot2, manifest, routing) {
|
|
6619
7004
|
const entries = [];
|
|
6620
7005
|
let hasChanges = routing.arm || routing.migratesManifest || routing.strips.some((s) => s.present);
|
|
6621
7006
|
const ownedFiles = new Set(manifest.files);
|
|
@@ -6623,8 +7008,8 @@ function planUpgrade(ops, repoRoot, manifest, routing) {
|
|
|
6623
7008
|
const ownedBlocks = new Set(manifest.blocks.map((b) => blockKey(b.path, b.marker)));
|
|
6624
7009
|
for (const op of ops) {
|
|
6625
7010
|
if (op.kind === "create-file") {
|
|
6626
|
-
const abs2 = join21(
|
|
6627
|
-
const exists =
|
|
7011
|
+
const abs2 = join21(repoRoot2, op.path);
|
|
7012
|
+
const exists = existsSync19(abs2);
|
|
6628
7013
|
const owned = ownedFiles.has(op.path);
|
|
6629
7014
|
let action;
|
|
6630
7015
|
if (op.path === CONFIG_PATH || op.path === DOC_OWNERS_PATH) {
|
|
@@ -6634,7 +7019,7 @@ function planUpgrade(ops, repoRoot, manifest, routing) {
|
|
|
6634
7019
|
} else if (!exists) {
|
|
6635
7020
|
action = "recreate";
|
|
6636
7021
|
hasChanges = true;
|
|
6637
|
-
} else if (
|
|
7022
|
+
} else if (readFileSync20(abs2, "utf-8") !== op.content) {
|
|
6638
7023
|
action = "regenerate";
|
|
6639
7024
|
hasChanges = true;
|
|
6640
7025
|
} else {
|
|
@@ -6642,16 +7027,16 @@ function planUpgrade(ops, repoRoot, manifest, routing) {
|
|
|
6642
7027
|
}
|
|
6643
7028
|
entries.push({ kind: "create-file", op, action });
|
|
6644
7029
|
} else if (op.kind === "managed-block") {
|
|
6645
|
-
const abs2 = resolveManagedBlockPath(
|
|
7030
|
+
const abs2 = resolveManagedBlockPath(repoRoot2, op.path);
|
|
6646
7031
|
const owned = ownedBlocks.has(blockKey(op.path, op.marker));
|
|
6647
7032
|
let action;
|
|
6648
7033
|
if (!owned) {
|
|
6649
7034
|
action = "not-installed";
|
|
6650
|
-
} else if (!
|
|
7035
|
+
} else if (!existsSync19(abs2)) {
|
|
6651
7036
|
action = "recreate-host";
|
|
6652
7037
|
hasChanges = true;
|
|
6653
7038
|
} else {
|
|
6654
|
-
const content =
|
|
7039
|
+
const content = readFileSync20(abs2, "utf-8");
|
|
6655
7040
|
const { begin, end } = markerLines(op.marker, op.comment);
|
|
6656
7041
|
if (!(content.includes(begin) && content.includes(end))) {
|
|
6657
7042
|
action = "recreate-append";
|
|
@@ -6749,39 +7134,39 @@ function renderUpgradeDiff(plan) {
|
|
|
6749
7134
|
return lines.join(`
|
|
6750
7135
|
`);
|
|
6751
7136
|
}
|
|
6752
|
-
function regenerateBlock(
|
|
6753
|
-
const abs2 = resolveManagedBlockPath(
|
|
6754
|
-
const content =
|
|
7137
|
+
function regenerateBlock(repoRoot2, op) {
|
|
7138
|
+
const abs2 = resolveManagedBlockPath(repoRoot2, op.path);
|
|
7139
|
+
const content = readFileSync20(abs2, "utf-8");
|
|
6755
7140
|
const stripped = stripBlockFromContent(content, op.marker, op.comment);
|
|
6756
7141
|
if (stripped !== null) {
|
|
6757
7142
|
writeFileSync11(abs2, stripped.endsWith(`
|
|
6758
7143
|
`) ? stripped : `${stripped}
|
|
6759
7144
|
`, "utf-8");
|
|
6760
7145
|
}
|
|
6761
|
-
appendBlock(
|
|
7146
|
+
appendBlock(repoRoot2, op);
|
|
6762
7147
|
}
|
|
6763
|
-
function applyUpgrade(plan,
|
|
7148
|
+
function applyUpgrade(plan, repoRoot2) {
|
|
6764
7149
|
for (const e of plan.entries) {
|
|
6765
7150
|
if (e.kind === "create-file") {
|
|
6766
7151
|
if (e.action === "regenerate" || e.action === "recreate") {
|
|
6767
|
-
writeFileWithDirs(join21(
|
|
7152
|
+
writeFileWithDirs(join21(repoRoot2, e.op.path), e.op.content, e.op.mode);
|
|
6768
7153
|
}
|
|
6769
7154
|
} else {
|
|
6770
7155
|
if (e.action === "recreate-host")
|
|
6771
|
-
createHost(
|
|
7156
|
+
createHost(repoRoot2, e.op);
|
|
6772
7157
|
else if (e.action === "recreate-append")
|
|
6773
|
-
appendBlock(
|
|
7158
|
+
appendBlock(repoRoot2, e.op);
|
|
6774
7159
|
else if (e.action === "regenerate-block")
|
|
6775
|
-
regenerateBlock(
|
|
7160
|
+
regenerateBlock(repoRoot2, e.op);
|
|
6776
7161
|
}
|
|
6777
7162
|
}
|
|
6778
7163
|
for (const s of plan.routing.strips) {
|
|
6779
7164
|
if (!s.present)
|
|
6780
7165
|
continue;
|
|
6781
|
-
const abs2 = resolveManagedBlockPath(
|
|
6782
|
-
if (!
|
|
7166
|
+
const abs2 = resolveManagedBlockPath(repoRoot2, s.path);
|
|
7167
|
+
if (!existsSync19(abs2))
|
|
6783
7168
|
continue;
|
|
6784
|
-
const stripped = stripBlockFromContent(
|
|
7169
|
+
const stripped = stripBlockFromContent(readFileSync20(abs2, "utf-8"), s.marker, s.comment);
|
|
6785
7170
|
if (stripped === null)
|
|
6786
7171
|
continue;
|
|
6787
7172
|
if (blockStripLeavesEmpty(stripped))
|
|
@@ -6869,12 +7254,12 @@ async function upgradeCommand(args) {
|
|
|
6869
7254
|
}
|
|
6870
7255
|
|
|
6871
7256
|
// src/commands/waiver.ts
|
|
6872
|
-
import { execFileSync as
|
|
7257
|
+
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
6873
7258
|
function labelFor(kind) {
|
|
6874
7259
|
return kind === "docs" ? WAIVER_LABEL : WAIVER_LABEL_REVIEW;
|
|
6875
7260
|
}
|
|
6876
7261
|
function gh2(args) {
|
|
6877
|
-
return
|
|
7262
|
+
return execFileSync15("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], env: process.env }).trim();
|
|
6878
7263
|
}
|
|
6879
7264
|
function detectPrNumber() {
|
|
6880
7265
|
try {
|
|
@@ -6974,8 +7359,8 @@ Reason: ${reason}`];
|
|
|
6974
7359
|
}
|
|
6975
7360
|
process.stdout.write(`Applying \`${label2}\` to PR #${prNumber} under your authenticated \`gh\` identity — vinaya never fabricates an actor.
|
|
6976
7361
|
`);
|
|
6977
|
-
|
|
6978
|
-
|
|
7362
|
+
execFileSync15("gh", editArgs, { stdio: "inherit", env: process.env });
|
|
7363
|
+
execFileSync15("gh", commentArgs, { stdio: "inherit", env: process.env });
|
|
6979
7364
|
process.stdout.write(`
|
|
6980
7365
|
✓ Label applied and reason posted as a PR comment.
|
|
6981
7366
|
`);
|
|
@@ -7324,7 +7709,7 @@ function printHelp() {
|
|
|
7324
7709
|
// src/index.ts
|
|
7325
7710
|
var PACKAGE_ROOT = join22(dirname8(fileURLToPath2(import.meta.url)), "..");
|
|
7326
7711
|
function readVersion2() {
|
|
7327
|
-
const pkg = JSON.parse(
|
|
7712
|
+
const pkg = JSON.parse(readFileSync21(join22(PACKAGE_ROOT, "package.json"), "utf-8"));
|
|
7328
7713
|
return pkg.version;
|
|
7329
7714
|
}
|
|
7330
7715
|
var [, , command, ...args] = process.argv;
|