@attalabs/vinaya 0.19.3 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/aeg-root/contracts/archivist-tranche-archivist.md +2 -2
- package/aeg-root/contracts/tranche-archivist-planner.md +8 -8
- package/aeg-root/enforcement.md +6 -4
- package/aeg-root/process.md +5 -5
- package/aeg-root/roles/archivist.md +9 -10
- package/aeg-root/roles/brief-author.md +1 -1
- package/aeg-root/roles/developer.md +1 -1
- package/aeg-root/roles/planner.md +1 -1
- package/aeg-root/roles/principal.md +1 -1
- package/aeg-root/roles/tranche-archivist.md +3 -16
- package/aeg-root/skills/aeg/SKILL.md +30 -1
- package/aeg-root/skills/aeg-roles/SKILL.md +1 -1
- package/aeg-root/skills/brief-authoring/SKILL.md +2 -2
- package/aeg-root/state-machine.md +8 -6
- package/aeg-root/task-model.md +1 -1
- package/aeg-root/templates/pr-report-template.md +4 -0
- package/dist/checks/bin/check-body-bare-digits.js +192 -27
- package/dist/checks/bin/check-branch-topology.js +518 -11
- package/dist/checks/bin/check-brief-shape.js +104 -11
- package/dist/checks/bin/check-closes-n.js +518 -11
- package/dist/checks/bin/check-coherence.js +526 -12
- package/dist/checks/bin/check-dead-branch-push.js +104 -11
- package/dist/checks/bin/check-dispatch-readiness.js +621 -14
- package/dist/checks/bin/check-doc-coverage-push.js +170 -20
- package/dist/checks/bin/check-doc-coverage.js +170 -20
- package/dist/checks/bin/check-doctrine-portability.js +112 -12
- package/dist/checks/bin/check-evidence-fresh.js +213 -20
- package/dist/checks/bin/check-first-push-dispatch.js +526 -12
- package/dist/checks/bin/check-issue-assignment.js +518 -11
- package/dist/checks/bin/check-main-branch-refusal.js +3724 -0
- package/dist/checks/bin/check-no-disk-state.js +104 -11
- package/dist/checks/bin/check-reader-resolvable-prose.js +186 -17
- package/dist/checks/bin/check-registry-gates.js +185 -16
- package/dist/checks/bin/check-retired-vocabulary.js +185 -16
- package/dist/checks/bin/check-review-gate.js +112 -12
- package/dist/checks/bin/check-single-plan-pr.js +104 -11
- package/dist/checks/bin/check-test-plan.js +104 -11
- package/dist/checks/bin/check-workspace-escape.js +104 -11
- package/dist/index.js +664 -56
- package/package.json +1 -1
- package/studio-standalone/_node_modules/@attalabs/aeg-core/bin/verify-coherence.ts +1089 -0
- 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 +5 -0
- 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/_head.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_index.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/api/coherence/route.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.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.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/{[externals]__0pvgf28._.js → [externals]__1agj1ww._.js} +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/[root-of-the-server]__02a-3g8._.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]__0053k9k._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0112h-k._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0o771t1._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0puovz5._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__03jepya._.js → [root-of-the-server]__16cuydh._.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1hs0dcu._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1wc4-ip._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_03x_w6q._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0gvm3og._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0lwxg63._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1n0cnq-._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3noqvds-2bvrd.js → 0ea8-8nopz29h.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/0la0hhmjprdxm.css +1 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1lk7h-0buf4ab.js +1 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3gehliafvmjo9.js → 2twnwe77d4n-m.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/server.js +1 -1
- package/aeg-root/coordination.md +0 -292
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/2iysedx-n06xc.js +0 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/42wxpzjhw7oy4.css +0 -1
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{5Lv83SnT--7YJDjSbxona → b2VfZK2AItZwlghK_pUN0}/_buildManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{5Lv83SnT--7YJDjSbxona → b2VfZK2AItZwlghK_pUN0}/_clientMiddlewareManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{5Lv83SnT--7YJDjSbxona → b2VfZK2AItZwlghK_pUN0}/_ssgManifest.js +0 -0
|
@@ -6,17 +6,17 @@ import { readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
|
6
6
|
import { join as join2 } from "node:path";
|
|
7
7
|
// ../../packages/aeg-core/src/gate-audience.ts
|
|
8
8
|
var GATE_AUDIENCE = {
|
|
9
|
-
"check-branch-topology": { shippedAs: "branch-topology" },
|
|
10
|
-
"check-first-push-dispatch": { shippedAs: "first-push-dispatch" },
|
|
11
|
-
"check-no-disk-state": { shippedAs: "no-disk-state" },
|
|
12
|
-
"verify-brief": { shippedAs: "brief-shape" },
|
|
13
|
-
"verify-coherence": { shippedAs: "coherence" },
|
|
14
|
-
"verify-dispatch": { shippedAs: "dispatch-readiness" },
|
|
15
|
-
"verify-docs": { shippedAs: ["doc-coverage", "doc-coverage-push"] },
|
|
16
|
-
"verify-registry": { shippedAs: "registry-gates" },
|
|
17
|
-
"verify-review-gate": { shippedAs: "review-gate" },
|
|
18
|
-
"verify-single-plan-pr": { shippedAs: "single-plan-pr" },
|
|
19
|
-
"verify-test-plan": { shippedAs: "test-plan" },
|
|
9
|
+
"check-branch-topology": { shippedAs: "branch-topology", ring: 0 },
|
|
10
|
+
"check-first-push-dispatch": { shippedAs: "first-push-dispatch", ring: 0 },
|
|
11
|
+
"check-no-disk-state": { shippedAs: "no-disk-state", ring: 0 },
|
|
12
|
+
"verify-brief": { shippedAs: "brief-shape", ring: 0 },
|
|
13
|
+
"verify-coherence": { shippedAs: "coherence", ring: 0 },
|
|
14
|
+
"verify-dispatch": { shippedAs: "dispatch-readiness", ring: 0 },
|
|
15
|
+
"verify-docs": { shippedAs: ["doc-coverage", "doc-coverage-push"], ring: 0 },
|
|
16
|
+
"verify-registry": { shippedAs: "registry-gates", ring: 0 },
|
|
17
|
+
"verify-review-gate": { shippedAs: "review-gate", ring: 1 },
|
|
18
|
+
"verify-single-plan-pr": { shippedAs: "single-plan-pr", ring: 0 },
|
|
19
|
+
"verify-test-plan": { shippedAs: "test-plan", ring: 1 },
|
|
20
20
|
"check-direct-main-push": {
|
|
21
21
|
internal: "Reaches adopters through `vinaya audit --only=direct-push`, not the check registry: it is a ring-2 sweep over merge history keyed on `GITHUB_SHA`, run by the generated archivist job on push to the default branch. It has no diff to key on, so there is nothing for `vinaya check` to run it against."
|
|
22
22
|
},
|
|
@@ -328,6 +328,13 @@ var LABELS = [
|
|
|
328
328
|
form: "literal",
|
|
329
329
|
carries: "Execution is halted pending an external unblock; wins over every other derived status."
|
|
330
330
|
},
|
|
331
|
+
{
|
|
332
|
+
key: "backlog",
|
|
333
|
+
id: "vinaya/backlog",
|
|
334
|
+
category: "state",
|
|
335
|
+
form: "literal",
|
|
336
|
+
carries: "The Issue is deliberately unplanned — belongs to no tranche and is excluded from dispatch, until a Planner promotes it."
|
|
337
|
+
},
|
|
331
338
|
{
|
|
332
339
|
key: "tier-0",
|
|
333
340
|
id: "vinaya/tier:0",
|
|
@@ -1986,7 +1993,59 @@ function overrideActive(opts) {
|
|
|
1986
1993
|
return false;
|
|
1987
1994
|
}
|
|
1988
1995
|
// ../../packages/aeg-core/src/premise-check.ts
|
|
1996
|
+
import { createHash } from "node:crypto";
|
|
1989
1997
|
var ASSERTION_KINDS = new Set(["contains", "absent", "sha256"]);
|
|
1998
|
+
function isPremiseHeader(line) {
|
|
1999
|
+
const stripped = line.replace(/[*#]/g, "").trim();
|
|
2000
|
+
return /^premise\s*:?$/i.test(stripped);
|
|
2001
|
+
}
|
|
2002
|
+
var PREMISE_LINE = /^[-*]\s*(\S+)\s+(contains|absent|sha256)\s*:\s*(.+)$/i;
|
|
2003
|
+
function parsePremiseBlock(prBody) {
|
|
2004
|
+
const searchIn = anchoredRegion(prBody, "PREMISE") ?? prBody;
|
|
2005
|
+
const lines = searchIn.split(/\r?\n/);
|
|
2006
|
+
const assertions = [];
|
|
2007
|
+
let inBlock = false;
|
|
2008
|
+
for (const raw of lines) {
|
|
2009
|
+
const line = raw.trim();
|
|
2010
|
+
if (!inBlock) {
|
|
2011
|
+
if (isPremiseHeader(line))
|
|
2012
|
+
inBlock = true;
|
|
2013
|
+
continue;
|
|
2014
|
+
}
|
|
2015
|
+
if (line === "")
|
|
2016
|
+
break;
|
|
2017
|
+
const m = line.match(PREMISE_LINE);
|
|
2018
|
+
if (!m)
|
|
2019
|
+
break;
|
|
2020
|
+
const [, path, kindRaw, value] = m;
|
|
2021
|
+
const kind = kindRaw.toLowerCase();
|
|
2022
|
+
if (!ASSERTION_KINDS.has(kind))
|
|
2023
|
+
continue;
|
|
2024
|
+
assertions.push({ kind, path, value: value.trim() });
|
|
2025
|
+
}
|
|
2026
|
+
return assertions;
|
|
2027
|
+
}
|
|
2028
|
+
function checkPremises(assertions, fileReader) {
|
|
2029
|
+
const failures = [];
|
|
2030
|
+
for (const a of assertions) {
|
|
2031
|
+
const content = fileReader(a.path);
|
|
2032
|
+
if (content === null) {
|
|
2033
|
+
failures.push(`premise-check: ${a.path} does not exist on disk (premise asserted \`${a.kind}: ${a.value}\`) — the surface moved since this brief was authored; re-dig before proceeding.`);
|
|
2034
|
+
continue;
|
|
2035
|
+
}
|
|
2036
|
+
if (a.kind === "contains" && !content.includes(a.value)) {
|
|
2037
|
+
failures.push(`premise-check: ${a.path} no longer contains "${a.value}" — the premise this brief pinned has moved; re-dig before proceeding.`);
|
|
2038
|
+
} else if (a.kind === "absent" && content.includes(a.value)) {
|
|
2039
|
+
failures.push(`premise-check: ${a.path} now contains "${a.value}", but the brief pinned it absent — the premise has moved; re-dig before proceeding.`);
|
|
2040
|
+
} else if (a.kind === "sha256") {
|
|
2041
|
+
const actual = createHash("sha256").update(content).digest("hex");
|
|
2042
|
+
if (actual !== a.value.toLowerCase()) {
|
|
2043
|
+
failures.push(`premise-check: ${a.path} sha256 mismatch (brief pinned ${a.value}, file is now ${actual}) — the file changed since this brief was authored; re-dig before proceeding.`);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
return { pass: failures.length === 0, failures };
|
|
2048
|
+
}
|
|
1990
2049
|
|
|
1991
2050
|
// ../../packages/aeg-core/src/test-plan-section.ts
|
|
1992
2051
|
var HEADING_FORM_RE = /^#{1,4}[ \t]*(?:\d+[a-z]?\.[ \t]*)?\*{0,2}Test Plan\*{0,2}[ \t]*$/im;
|
|
@@ -2790,6 +2849,7 @@ function checkG1(rows, existsFn) {
|
|
|
2790
2849
|
}
|
|
2791
2850
|
return { check: "G1", status: findings.length > 0 ? "fail" : "pass", findings };
|
|
2792
2851
|
}
|
|
2852
|
+
var SCAFFOLD_PLACEHOLDER = "[undocumented — fill in why]";
|
|
2793
2853
|
function checkG2(rows, candidateFiles) {
|
|
2794
2854
|
const implementations = new Set(rows.map((r) => r.implementation).filter((p) => p !== ""));
|
|
2795
2855
|
const findings = [];
|
|
@@ -2801,6 +2861,16 @@ function checkG2(rows, candidateFiles) {
|
|
|
2801
2861
|
});
|
|
2802
2862
|
}
|
|
2803
2863
|
}
|
|
2864
|
+
for (const row of rows) {
|
|
2865
|
+
const carriesPlaceholder = [row.summary, row.description, row.spec].some((cell) => cell === SCAFFOLD_PLACEHOLDER);
|
|
2866
|
+
if (carriesPlaceholder) {
|
|
2867
|
+
findings.push({
|
|
2868
|
+
row: row.action,
|
|
2869
|
+
path: row.implementation,
|
|
2870
|
+
reason: `${row.ring} row "${row.action}" still carries the scaffold placeholder marker — the why is owed`
|
|
2871
|
+
});
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2804
2874
|
return { check: "G2", status: findings.length > 0 ? "info" : "pass", findings };
|
|
2805
2875
|
}
|
|
2806
2876
|
function checkG3(ring0Rows, crossingFiles) {
|
|
@@ -3320,6 +3390,29 @@ function isNewDiskStateFile(path, status) {
|
|
|
3320
3390
|
return false;
|
|
3321
3391
|
return ANY_DEPTH_TRANCHES_MD.test(path) || TOKENS_FILE.test(path);
|
|
3322
3392
|
}
|
|
3393
|
+
// ../../packages/aeg-core/src/main-branch-refusal.ts
|
|
3394
|
+
function checkMainBranchRefusal(facts) {
|
|
3395
|
+
const { currentSymbolicBranch, defaultBranch } = facts;
|
|
3396
|
+
if (currentSymbolicBranch === null)
|
|
3397
|
+
return null;
|
|
3398
|
+
if (defaultBranch === null) {
|
|
3399
|
+
return {
|
|
3400
|
+
reason: "default-branch-undetermined",
|
|
3401
|
+
severity: "warning",
|
|
3402
|
+
currentBranch: currentSymbolicBranch,
|
|
3403
|
+
defaultBranch: null
|
|
3404
|
+
};
|
|
3405
|
+
}
|
|
3406
|
+
if (currentSymbolicBranch === defaultBranch) {
|
|
3407
|
+
return {
|
|
3408
|
+
reason: "on-default-branch",
|
|
3409
|
+
severity: "error",
|
|
3410
|
+
currentBranch: currentSymbolicBranch,
|
|
3411
|
+
defaultBranch
|
|
3412
|
+
};
|
|
3413
|
+
}
|
|
3414
|
+
return null;
|
|
3415
|
+
}
|
|
3323
3416
|
// ../../packages/aeg-core/src/branch-topology-gate.ts
|
|
3324
3417
|
function taskBranchTopologyFields(branch) {
|
|
3325
3418
|
if (!/^task\/.*\/.*$/.test(branch))
|
|
@@ -3666,8 +3759,15 @@ function isSafeRepoRelPath(p) {
|
|
|
3666
3759
|
var SafeRepoRelPath = z.string().refine(isSafeRepoRelPath, {
|
|
3667
3760
|
message: "must be a repo-root-relative path with no `..` segment or absolute root"
|
|
3668
3761
|
});
|
|
3762
|
+
var CANONICAL_HOOK_BLOCK_PREFIXES = [".git/", ".husky/", ".vinaya/hooks/", ".claude/hooks/"];
|
|
3763
|
+
function isCanonicalHookBlockPath(p) {
|
|
3764
|
+
return CANONICAL_HOOK_BLOCK_PREFIXES.some((prefix) => p.startsWith(prefix) && p.length > prefix.length);
|
|
3765
|
+
}
|
|
3766
|
+
var ManagedHookBlockPath = SafeRepoRelPath.refine(isCanonicalHookBlockPath, {
|
|
3767
|
+
message: `must start with one of ${CANONICAL_HOOK_BLOCK_PREFIXES.join(", ")} (byte-exact, case-sensitive)`
|
|
3768
|
+
});
|
|
3669
3769
|
var ManagedBlockRecordSchema = z.object({
|
|
3670
|
-
path:
|
|
3770
|
+
path: ManagedHookBlockPath,
|
|
3671
3771
|
marker: z.string(),
|
|
3672
3772
|
comment: z.enum(["hash", "html"])
|
|
3673
3773
|
});
|
|
@@ -2,19 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
// src/checks/bin/check-evidence-fresh.ts
|
|
4
4
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
// src/checks/contract.ts
|
|
7
|
+
var CHECK_SCHEMA_VERSION = 1;
|
|
8
|
+
function emitCheckError(error) {
|
|
9
|
+
process.stderr.write(`${JSON.stringify(error)}
|
|
10
|
+
`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/lib/numstat.ts
|
|
14
|
+
function summariseNumstat(numstat) {
|
|
15
|
+
const rows = numstat.split(`
|
|
16
|
+
`).filter((l) => l.trim() !== "");
|
|
17
|
+
let added = 0;
|
|
18
|
+
let deleted = 0;
|
|
19
|
+
let binary = 0;
|
|
20
|
+
for (const row of rows) {
|
|
21
|
+
const [a, d] = row.split("\t");
|
|
22
|
+
if (a === "-" || d === "-") {
|
|
23
|
+
binary++;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
added += Number(a) || 0;
|
|
27
|
+
deleted += Number(d) || 0;
|
|
28
|
+
}
|
|
29
|
+
const files = `${rows.length} file${rows.length === 1 ? "" : "s"} changed`;
|
|
30
|
+
const bin = binary > 0 ? `, ${binary} binary` : "";
|
|
31
|
+
return `${files}, ${added} insertion${added === 1 ? "" : "s"}(+), ${deleted} deletion${deleted === 1 ? "" : "s"}(-)${bin}`;
|
|
32
|
+
}
|
|
33
|
+
var EVIDENCE_SUMMARY_PREFIX = "Summary: ";
|
|
5
34
|
// ../../packages/aeg-core/src/gate-audience.ts
|
|
6
35
|
var GATE_AUDIENCE = {
|
|
7
|
-
"check-branch-topology": { shippedAs: "branch-topology" },
|
|
8
|
-
"check-first-push-dispatch": { shippedAs: "first-push-dispatch" },
|
|
9
|
-
"check-no-disk-state": { shippedAs: "no-disk-state" },
|
|
10
|
-
"verify-brief": { shippedAs: "brief-shape" },
|
|
11
|
-
"verify-coherence": { shippedAs: "coherence" },
|
|
12
|
-
"verify-dispatch": { shippedAs: "dispatch-readiness" },
|
|
13
|
-
"verify-docs": { shippedAs: ["doc-coverage", "doc-coverage-push"] },
|
|
14
|
-
"verify-registry": { shippedAs: "registry-gates" },
|
|
15
|
-
"verify-review-gate": { shippedAs: "review-gate" },
|
|
16
|
-
"verify-single-plan-pr": { shippedAs: "single-plan-pr" },
|
|
17
|
-
"verify-test-plan": { shippedAs: "test-plan" },
|
|
36
|
+
"check-branch-topology": { shippedAs: "branch-topology", ring: 0 },
|
|
37
|
+
"check-first-push-dispatch": { shippedAs: "first-push-dispatch", ring: 0 },
|
|
38
|
+
"check-no-disk-state": { shippedAs: "no-disk-state", ring: 0 },
|
|
39
|
+
"verify-brief": { shippedAs: "brief-shape", ring: 0 },
|
|
40
|
+
"verify-coherence": { shippedAs: "coherence", ring: 0 },
|
|
41
|
+
"verify-dispatch": { shippedAs: "dispatch-readiness", ring: 0 },
|
|
42
|
+
"verify-docs": { shippedAs: ["doc-coverage", "doc-coverage-push"], ring: 0 },
|
|
43
|
+
"verify-registry": { shippedAs: "registry-gates", ring: 0 },
|
|
44
|
+
"verify-review-gate": { shippedAs: "review-gate", ring: 1 },
|
|
45
|
+
"verify-single-plan-pr": { shippedAs: "single-plan-pr", ring: 0 },
|
|
46
|
+
"verify-test-plan": { shippedAs: "test-plan", ring: 1 },
|
|
18
47
|
"check-direct-main-push": {
|
|
19
48
|
internal: "Reaches adopters through `vinaya audit --only=direct-push`, not the check registry: it is a ring-2 sweep over merge history keyed on `GITHUB_SHA`, run by the generated archivist job on push to the default branch. It has no diff to key on, so there is nothing for `vinaya check` to run it against."
|
|
20
49
|
},
|
|
@@ -326,6 +355,13 @@ var LABELS = [
|
|
|
326
355
|
form: "literal",
|
|
327
356
|
carries: "Execution is halted pending an external unblock; wins over every other derived status."
|
|
328
357
|
},
|
|
358
|
+
{
|
|
359
|
+
key: "backlog",
|
|
360
|
+
id: "vinaya/backlog",
|
|
361
|
+
category: "state",
|
|
362
|
+
form: "literal",
|
|
363
|
+
carries: "The Issue is deliberately unplanned — belongs to no tranche and is excluded from dispatch, until a Planner promotes it."
|
|
364
|
+
},
|
|
329
365
|
{
|
|
330
366
|
key: "tier-0",
|
|
331
367
|
id: "vinaya/tier:0",
|
|
@@ -1984,7 +2020,59 @@ function overrideActive(opts) {
|
|
|
1984
2020
|
return false;
|
|
1985
2021
|
}
|
|
1986
2022
|
// ../../packages/aeg-core/src/premise-check.ts
|
|
2023
|
+
import { createHash } from "node:crypto";
|
|
1987
2024
|
var ASSERTION_KINDS = new Set(["contains", "absent", "sha256"]);
|
|
2025
|
+
function isPremiseHeader(line) {
|
|
2026
|
+
const stripped = line.replace(/[*#]/g, "").trim();
|
|
2027
|
+
return /^premise\s*:?$/i.test(stripped);
|
|
2028
|
+
}
|
|
2029
|
+
var PREMISE_LINE = /^[-*]\s*(\S+)\s+(contains|absent|sha256)\s*:\s*(.+)$/i;
|
|
2030
|
+
function parsePremiseBlock(prBody) {
|
|
2031
|
+
const searchIn = anchoredRegion(prBody, "PREMISE") ?? prBody;
|
|
2032
|
+
const lines = searchIn.split(/\r?\n/);
|
|
2033
|
+
const assertions = [];
|
|
2034
|
+
let inBlock = false;
|
|
2035
|
+
for (const raw of lines) {
|
|
2036
|
+
const line = raw.trim();
|
|
2037
|
+
if (!inBlock) {
|
|
2038
|
+
if (isPremiseHeader(line))
|
|
2039
|
+
inBlock = true;
|
|
2040
|
+
continue;
|
|
2041
|
+
}
|
|
2042
|
+
if (line === "")
|
|
2043
|
+
break;
|
|
2044
|
+
const m = line.match(PREMISE_LINE);
|
|
2045
|
+
if (!m)
|
|
2046
|
+
break;
|
|
2047
|
+
const [, path, kindRaw, value] = m;
|
|
2048
|
+
const kind = kindRaw.toLowerCase();
|
|
2049
|
+
if (!ASSERTION_KINDS.has(kind))
|
|
2050
|
+
continue;
|
|
2051
|
+
assertions.push({ kind, path, value: value.trim() });
|
|
2052
|
+
}
|
|
2053
|
+
return assertions;
|
|
2054
|
+
}
|
|
2055
|
+
function checkPremises(assertions, fileReader) {
|
|
2056
|
+
const failures = [];
|
|
2057
|
+
for (const a of assertions) {
|
|
2058
|
+
const content = fileReader(a.path);
|
|
2059
|
+
if (content === null) {
|
|
2060
|
+
failures.push(`premise-check: ${a.path} does not exist on disk (premise asserted \`${a.kind}: ${a.value}\`) — the surface moved since this brief was authored; re-dig before proceeding.`);
|
|
2061
|
+
continue;
|
|
2062
|
+
}
|
|
2063
|
+
if (a.kind === "contains" && !content.includes(a.value)) {
|
|
2064
|
+
failures.push(`premise-check: ${a.path} no longer contains "${a.value}" — the premise this brief pinned has moved; re-dig before proceeding.`);
|
|
2065
|
+
} else if (a.kind === "absent" && content.includes(a.value)) {
|
|
2066
|
+
failures.push(`premise-check: ${a.path} now contains "${a.value}", but the brief pinned it absent — the premise has moved; re-dig before proceeding.`);
|
|
2067
|
+
} else if (a.kind === "sha256") {
|
|
2068
|
+
const actual = createHash("sha256").update(content).digest("hex");
|
|
2069
|
+
if (actual !== a.value.toLowerCase()) {
|
|
2070
|
+
failures.push(`premise-check: ${a.path} sha256 mismatch (brief pinned ${a.value}, file is now ${actual}) — the file changed since this brief was authored; re-dig before proceeding.`);
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
return { pass: failures.length === 0, failures };
|
|
2075
|
+
}
|
|
1988
2076
|
|
|
1989
2077
|
// ../../packages/aeg-core/src/test-plan-section.ts
|
|
1990
2078
|
var HEADING_FORM_RE = /^#{1,4}[ \t]*(?:\d+[a-z]?\.[ \t]*)?\*{0,2}Test Plan\*{0,2}[ \t]*$/im;
|
|
@@ -2788,6 +2876,7 @@ function checkG1(rows, existsFn) {
|
|
|
2788
2876
|
}
|
|
2789
2877
|
return { check: "G1", status: findings.length > 0 ? "fail" : "pass", findings };
|
|
2790
2878
|
}
|
|
2879
|
+
var SCAFFOLD_PLACEHOLDER = "[undocumented — fill in why]";
|
|
2791
2880
|
function checkG2(rows, candidateFiles) {
|
|
2792
2881
|
const implementations = new Set(rows.map((r) => r.implementation).filter((p) => p !== ""));
|
|
2793
2882
|
const findings = [];
|
|
@@ -2799,6 +2888,16 @@ function checkG2(rows, candidateFiles) {
|
|
|
2799
2888
|
});
|
|
2800
2889
|
}
|
|
2801
2890
|
}
|
|
2891
|
+
for (const row of rows) {
|
|
2892
|
+
const carriesPlaceholder = [row.summary, row.description, row.spec].some((cell) => cell === SCAFFOLD_PLACEHOLDER);
|
|
2893
|
+
if (carriesPlaceholder) {
|
|
2894
|
+
findings.push({
|
|
2895
|
+
row: row.action,
|
|
2896
|
+
path: row.implementation,
|
|
2897
|
+
reason: `${row.ring} row "${row.action}" still carries the scaffold placeholder marker — the why is owed`
|
|
2898
|
+
});
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2802
2901
|
return { check: "G2", status: findings.length > 0 ? "info" : "pass", findings };
|
|
2803
2902
|
}
|
|
2804
2903
|
function checkG3(ring0Rows, crossingFiles) {
|
|
@@ -3318,6 +3417,29 @@ function isNewDiskStateFile(path, status) {
|
|
|
3318
3417
|
return false;
|
|
3319
3418
|
return ANY_DEPTH_TRANCHES_MD.test(path) || TOKENS_FILE.test(path);
|
|
3320
3419
|
}
|
|
3420
|
+
// ../../packages/aeg-core/src/main-branch-refusal.ts
|
|
3421
|
+
function checkMainBranchRefusal(facts) {
|
|
3422
|
+
const { currentSymbolicBranch, defaultBranch } = facts;
|
|
3423
|
+
if (currentSymbolicBranch === null)
|
|
3424
|
+
return null;
|
|
3425
|
+
if (defaultBranch === null) {
|
|
3426
|
+
return {
|
|
3427
|
+
reason: "default-branch-undetermined",
|
|
3428
|
+
severity: "warning",
|
|
3429
|
+
currentBranch: currentSymbolicBranch,
|
|
3430
|
+
defaultBranch: null
|
|
3431
|
+
};
|
|
3432
|
+
}
|
|
3433
|
+
if (currentSymbolicBranch === defaultBranch) {
|
|
3434
|
+
return {
|
|
3435
|
+
reason: "on-default-branch",
|
|
3436
|
+
severity: "error",
|
|
3437
|
+
currentBranch: currentSymbolicBranch,
|
|
3438
|
+
defaultBranch
|
|
3439
|
+
};
|
|
3440
|
+
}
|
|
3441
|
+
return null;
|
|
3442
|
+
}
|
|
3321
3443
|
// ../../packages/aeg-core/src/branch-topology-gate.ts
|
|
3322
3444
|
function taskBranchTopologyFields(branch) {
|
|
3323
3445
|
if (!/^task\/.*\/.*$/.test(branch))
|
|
@@ -3571,17 +3693,64 @@ function findWorkspaceEscapes(files, knownPaths, workspaceDirs = DEFAULT_WORKSPA
|
|
|
3571
3693
|
}
|
|
3572
3694
|
return findings;
|
|
3573
3695
|
}
|
|
3574
|
-
// src/checks/
|
|
3575
|
-
var
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3696
|
+
// src/checks/scan-context.ts
|
|
3697
|
+
var ZERO_WIDTH = /[\u200B-\u200D\u2060\uFEFF]/g;
|
|
3698
|
+
var HTML_ENTITIES = {
|
|
3699
|
+
""": '"',
|
|
3700
|
+
"'": "'",
|
|
3701
|
+
"<": "<",
|
|
3702
|
+
">": ">",
|
|
3703
|
+
"&": "&"
|
|
3704
|
+
};
|
|
3705
|
+
function decodeNamedEntities(body) {
|
|
3706
|
+
return body.replace(/&(?:quot|apos|lt|gt|amp);/g, (m) => HTML_ENTITIES[m] ?? m);
|
|
3707
|
+
}
|
|
3708
|
+
function normalizeBody(rawBody) {
|
|
3709
|
+
return decodeNamedEntities(rawBody.replace(ZERO_WIDTH, ""));
|
|
3710
|
+
}
|
|
3711
|
+
function buildAnchorLookupMask(body) {
|
|
3712
|
+
return maskDetailsBlocks(maskCode(body));
|
|
3713
|
+
}
|
|
3714
|
+
|
|
3715
|
+
class ScanContext {
|
|
3716
|
+
raw;
|
|
3717
|
+
normalised;
|
|
3718
|
+
masked;
|
|
3719
|
+
constructor(raw, normalised, masked) {
|
|
3720
|
+
this.raw = raw;
|
|
3721
|
+
this.normalised = normalised;
|
|
3722
|
+
this.masked = masked;
|
|
3723
|
+
}
|
|
3724
|
+
static from(rawBody) {
|
|
3725
|
+
const normalised = normalizeBody(rawBody);
|
|
3726
|
+
return new ScanContext(rawBody, normalised, buildAnchorLookupMask(normalised));
|
|
3727
|
+
}
|
|
3728
|
+
rawResolutionAgrees(field) {
|
|
3729
|
+
const rawHasPair = anchoredRegionBounds(this.raw, field) !== null;
|
|
3730
|
+
return rawHasPair === (anchoredRegionBounds(this.masked, field) !== null);
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
function resolveAnchoredRegion(ctx, field) {
|
|
3734
|
+
const bounds = anchoredRegionBounds(ctx.masked, field);
|
|
3735
|
+
if (bounds === null) {
|
|
3736
|
+
return anchoredRegionBounds(ctx.normalised, field) !== null ? "hidden" : null;
|
|
3737
|
+
}
|
|
3738
|
+
return {
|
|
3739
|
+
region: ctx.normalised.slice(bounds.innerStart, bounds.innerEnd),
|
|
3740
|
+
maskedRegion: ctx.masked.slice(bounds.innerStart, bounds.innerEnd)
|
|
3741
|
+
};
|
|
3742
|
+
}
|
|
3743
|
+
function summaryLineIndex(maskedRegion) {
|
|
3744
|
+
const index = maskedRegion.split(`
|
|
3745
|
+
`).findIndex((line) => line.startsWith(EVIDENCE_SUMMARY_PREFIX));
|
|
3746
|
+
return index === -1 ? null : index;
|
|
3579
3747
|
}
|
|
3580
3748
|
|
|
3581
3749
|
// src/checks/evidence-fresh-logic.ts
|
|
3582
3750
|
var HEAD_LINE = /^Head:\s*([0-9a-f]{7,40})\s*$/m;
|
|
3583
3751
|
var FENCE = /```[^\n]*\n?([\s\S]*?)```/g;
|
|
3584
|
-
function compareEvidenceBlock(
|
|
3752
|
+
function compareEvidenceBlock(resolved, resolvedHead, actualNumstat) {
|
|
3753
|
+
const region = resolved.region;
|
|
3585
3754
|
const headMatch = region.match(HEAD_LINE);
|
|
3586
3755
|
const fences = [...region.matchAll(FENCE)].map((m) => (m[1] ?? "").trim());
|
|
3587
3756
|
if (!headMatch || fences.length < 1) {
|
|
@@ -3607,6 +3776,20 @@ function compareEvidenceBlock(region, resolvedHead, actualNumstat) {
|
|
|
3607
3776
|
].join(`
|
|
3608
3777
|
`));
|
|
3609
3778
|
}
|
|
3779
|
+
const summaryIndex = summaryLineIndex(resolved.maskedRegion);
|
|
3780
|
+
if (summaryIndex !== null) {
|
|
3781
|
+
const storedSummary = resolved.region.split(`
|
|
3782
|
+
`)[summaryIndex];
|
|
3783
|
+
const expectedSummary = `${EVIDENCE_SUMMARY_PREFIX}\`${summariseNumstat(actual)}\``;
|
|
3784
|
+
if (storedSummary !== expectedSummary) {
|
|
3785
|
+
errors.push([
|
|
3786
|
+
"evidence-fresh: the Summary line does not match a fresh recompute of the diff it summarises.",
|
|
3787
|
+
` block: ${JSON.stringify(storedSummary)}`,
|
|
3788
|
+
` actual: ${JSON.stringify(expectedSummary)}`
|
|
3789
|
+
].join(`
|
|
3790
|
+
`));
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3610
3793
|
return errors.length > 0 ? { status: "fail", errors } : { status: "pass" };
|
|
3611
3794
|
}
|
|
3612
3795
|
|
|
@@ -3665,10 +3848,20 @@ function main() {
|
|
|
3665
3848
|
if (!body) {
|
|
3666
3849
|
process.exit(0);
|
|
3667
3850
|
}
|
|
3668
|
-
const
|
|
3669
|
-
if (
|
|
3851
|
+
const resolved = resolveAnchoredRegion(ScanContext.from(body), "EVIDENCE");
|
|
3852
|
+
if (resolved === null) {
|
|
3670
3853
|
process.exit(0);
|
|
3671
3854
|
}
|
|
3855
|
+
if (resolved === "hidden") {
|
|
3856
|
+
emitCheckError({
|
|
3857
|
+
schema: CHECK_SCHEMA_VERSION,
|
|
3858
|
+
check: CHECK_NAME,
|
|
3859
|
+
severity: "error",
|
|
3860
|
+
message: "evidence-fresh: the AEG:EVIDENCE block sits inside a `<details>` block, where its digits are exempt from `body-bare-digits` and nothing can verify what it claims. Move the block out of the collapsed section.",
|
|
3861
|
+
agent_recovery_prompt: "Move the AEG:EVIDENCE anchor pair out of the `<details>` block (the canonical home is its own `## Evidence` section), then re-run `vinaya pr report --write <body-file>`."
|
|
3862
|
+
});
|
|
3863
|
+
process.exit(1);
|
|
3864
|
+
}
|
|
3672
3865
|
const prNumberStr = process.env.PR_NUMBER;
|
|
3673
3866
|
if (!prNumberStr) {
|
|
3674
3867
|
process.exit(0);
|
|
@@ -3699,7 +3892,7 @@ function main() {
|
|
|
3699
3892
|
});
|
|
3700
3893
|
process.exit(1);
|
|
3701
3894
|
}
|
|
3702
|
-
const result = compareEvidenceBlock(
|
|
3895
|
+
const result = compareEvidenceBlock(resolved, resolvedHead, actualNumstat);
|
|
3703
3896
|
if (result.status === "fail") {
|
|
3704
3897
|
for (const message of result.errors) {
|
|
3705
3898
|
emitCheckError({
|