@attalabs/vinaya 0.19.0 → 0.19.2
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 +37 -1
- package/aeg-root/contracts/architect-planner.md +1 -1
- package/aeg-root/enforcement.md +3 -2
- package/aeg-root/milestone-model.md +75 -0
- package/aeg-root/roles/architect.md +4 -4
- package/aeg-root/skills/aeg/SKILL.md +1 -1
- package/aeg-root/task-model.md +45 -0
- package/aeg-root/tranche-model.md +16 -11
- package/dist/checks/bin/check-body-bare-digits.js +204 -15
- package/dist/checks/bin/check-branch-topology.js +116 -13
- package/dist/checks/bin/check-brief-shape.js +116 -13
- package/dist/checks/bin/check-closes-n.js +116 -13
- package/dist/checks/bin/check-coherence.js +207 -18
- package/dist/checks/bin/check-dead-branch-push.js +116 -13
- package/dist/checks/bin/check-dispatch-readiness.js +204 -15
- package/dist/checks/bin/check-doc-coverage-push.js +208 -19
- package/dist/checks/bin/check-doc-coverage.js +208 -19
- package/dist/checks/bin/check-evidence-fresh.js +116 -13
- package/dist/checks/bin/check-first-push-dispatch.js +204 -15
- package/dist/checks/bin/check-issue-assignment.js +116 -13
- package/dist/checks/bin/check-no-disk-state.js +116 -13
- package/dist/checks/bin/check-reader-resolvable-prose.js +410 -36
- package/dist/checks/bin/check-registry-gates.js +116 -13
- package/dist/checks/bin/check-retired-vocabulary.js +3595 -0
- package/dist/checks/bin/check-review-gate.js +204 -15
- package/dist/checks/bin/check-single-plan-pr.js +116 -13
- package/dist/checks/bin/check-test-plan.js +116 -13
- package/dist/index.js +2362 -1602
- 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/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/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/[root-of-the-server]__0e95dv5._.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]__0ulzsom._.js → [root-of-the-server]__0h3web3._.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]__1_ojzp1._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1d_8nm4._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1zkwtx8._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__210ehg9._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_00yjcv1._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0rix02i._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_18-d2be._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1fqw88f._.js +5 -5
- 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/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/{2r36w820aqwb9.js → 1hko4y648-9sa.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3ocvtlhgckh81.js → 20f41k165euzy.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{2xoclb4g7d9n7.js → 3mhcu_-shkfgs.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
- package/templates/noop-check.template.ts +18 -0
- package/templates/role.template.md +20 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{RISz5nirLr1mUfMqzSaFS → Ye7cOzO7rtvyiBouNOFES}/_buildManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{RISz5nirLr1mUfMqzSaFS → Ye7cOzO7rtvyiBouNOFES}/_clientMiddlewareManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{RISz5nirLr1mUfMqzSaFS → Ye7cOzO7rtvyiBouNOFES}/_ssgManifest.js +0 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/checks/bin/check-reader-resolvable-prose.ts
|
|
4
|
-
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
5
|
-
import {
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
5
|
+
import { join as join2 } from "node:path";
|
|
7
6
|
// ../../packages/aeg-forge-state/src/strip-code.ts
|
|
8
7
|
function maskCode(body) {
|
|
9
8
|
const fill = (line) => " ".repeat(line.length);
|
|
@@ -567,6 +566,12 @@ function factsFromLegacyMilestone(milestone) {
|
|
|
567
566
|
lifecycle: milestone.state === "closed" ? "complete" : "active"
|
|
568
567
|
};
|
|
569
568
|
}
|
|
569
|
+
function resolveLegacyFacts(milestone, labelIssues) {
|
|
570
|
+
if (milestone.state === "closed" && labelIssues.length > 0) {
|
|
571
|
+
return { goal: milestone.description ?? "", lifecycle: lifecycleFromIssues(labelIssues) };
|
|
572
|
+
}
|
|
573
|
+
return factsFromLegacyMilestone(milestone);
|
|
574
|
+
}
|
|
570
575
|
function lifecycleFromIssues(issues) {
|
|
571
576
|
if (issues.length === 0)
|
|
572
577
|
return "planned";
|
|
@@ -583,9 +588,9 @@ function goalFromMilestones(milestones, slug) {
|
|
|
583
588
|
function findMilestoneForSlug(owner, repo, slug) {
|
|
584
589
|
const milestones = ghApiGet(`repos/${owner}/${repo}/milestones?state=all&per_page=100`);
|
|
585
590
|
const legacy = matchesLegacyMilestone(milestones, slug);
|
|
586
|
-
if (legacy)
|
|
587
|
-
return factsFromLegacyMilestone(legacy);
|
|
588
591
|
const issues = ghIssueListByLabel(owner, repo, trancheLabel(slug));
|
|
592
|
+
if (legacy)
|
|
593
|
+
return resolveLegacyFacts(legacy, issues);
|
|
589
594
|
return { goal: goalFromMilestones(milestones, slug), lifecycle: lifecycleFromIssues(issues) };
|
|
590
595
|
}
|
|
591
596
|
|
|
@@ -2772,16 +2777,18 @@ function checkDeadBranchPush(input) {
|
|
|
2772
2777
|
}
|
|
2773
2778
|
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
2774
2779
|
var SHIPS_PREFIX = "aeg-root/";
|
|
2775
|
-
|
|
2780
|
+
function shipsArchivePrefix(shipsPrefix) {
|
|
2781
|
+
return `${shipsPrefix}tranches/completed/`;
|
|
2782
|
+
}
|
|
2776
2783
|
function isSpecFile2(path) {
|
|
2777
2784
|
return path.startsWith("apps/") && path.includes("/specs/") && path.endsWith(".md");
|
|
2778
2785
|
}
|
|
2779
2786
|
function isClaudeMdFile(path) {
|
|
2780
2787
|
return path === "CLAUDE.md" || path.endsWith("/CLAUDE.md");
|
|
2781
2788
|
}
|
|
2782
|
-
function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix) {
|
|
2783
|
-
if (path.startsWith(
|
|
2784
|
-
return path.startsWith(
|
|
2789
|
+
function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix, shipsPrefix = SHIPS_PREFIX) {
|
|
2790
|
+
if (path.startsWith(shipsPrefix)) {
|
|
2791
|
+
return path.startsWith(shipsArchivePrefix(shipsPrefix)) ? "internal" : "ships";
|
|
2785
2792
|
}
|
|
2786
2793
|
if (path.startsWith(readerFacingPrefix) && path.endsWith(readerFacingSuffix)) {
|
|
2787
2794
|
return "reader-facing";
|
|
@@ -2816,7 +2823,7 @@ function legacySlugPattern(legacySlugs) {
|
|
|
2816
2823
|
const alternation = legacySlugs.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
2817
2824
|
return new RegExp(`(^|[^a-zA-Z0-9\\n-])(${alternation})([^a-zA-Z0-9\\n-]|$)`, "g");
|
|
2818
2825
|
}
|
|
2819
|
-
function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs = []) {
|
|
2826
|
+
function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs = [], shipsPrefix = SHIPS_PREFIX) {
|
|
2820
2827
|
const findings = [];
|
|
2821
2828
|
const legacyPattern = legacySlugPattern(legacySlugs);
|
|
2822
2829
|
const patterns = [
|
|
@@ -2825,7 +2832,7 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
|
|
|
2825
2832
|
...legacyPattern ? [{ pattern: legacyPattern, what: "an internal tranche slug", group: 2 }] : []
|
|
2826
2833
|
];
|
|
2827
2834
|
for (const file of files) {
|
|
2828
|
-
const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix);
|
|
2835
|
+
const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
|
|
2829
2836
|
if (!cls || !SWEPT_CLASSES.has(cls))
|
|
2830
2837
|
continue;
|
|
2831
2838
|
const scrubbed = stripNonProse(file.path, file.content);
|
|
@@ -2871,10 +2878,10 @@ function definesOrLinksGlossary(content, term) {
|
|
|
2871
2878
|
return true;
|
|
2872
2879
|
return /docs\/glossary|glossary\.md/i.test(content);
|
|
2873
2880
|
}
|
|
2874
|
-
function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix) {
|
|
2881
|
+
function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, shipsPrefix = SHIPS_PREFIX) {
|
|
2875
2882
|
const findings = [];
|
|
2876
2883
|
for (const file of files) {
|
|
2877
|
-
const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix);
|
|
2884
|
+
const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
|
|
2878
2885
|
if (!cls || !SWEPT_CLASSES.has(cls))
|
|
2879
2886
|
continue;
|
|
2880
2887
|
const scrubbed = stripNonProse(file.path, file.content);
|
|
@@ -2893,10 +2900,10 @@ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, read
|
|
|
2893
2900
|
}
|
|
2894
2901
|
return findings;
|
|
2895
2902
|
}
|
|
2896
|
-
function checkReaderResolvableProse(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, legacySlugs = []) {
|
|
2903
|
+
function checkReaderResolvableProse(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, legacySlugs = [], shipsPrefix = SHIPS_PREFIX) {
|
|
2897
2904
|
return [
|
|
2898
|
-
...checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs),
|
|
2899
|
-
...checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix)
|
|
2905
|
+
...checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs, shipsPrefix),
|
|
2906
|
+
...checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, shipsPrefix)
|
|
2900
2907
|
];
|
|
2901
2908
|
}
|
|
2902
2909
|
function parseGlossaryTerms(glossaryContent) {
|
|
@@ -2910,6 +2917,101 @@ function parseGlossaryTerms(glossaryContent) {
|
|
|
2910
2917
|
}
|
|
2911
2918
|
return terms;
|
|
2912
2919
|
}
|
|
2920
|
+
// ../../packages/aeg-core/src/retired-vocabulary.ts
|
|
2921
|
+
var RETIRED_PATTERNS = [
|
|
2922
|
+
String.raw`\bD-[0-9]{3}\b`,
|
|
2923
|
+
"D-(###|NNN|nnn|xxx)",
|
|
2924
|
+
"decision (entry|logged)",
|
|
2925
|
+
"decision-log entry",
|
|
2926
|
+
"decision log entry",
|
|
2927
|
+
"decision log(s|ged|ging)?([^a-z]|$)",
|
|
2928
|
+
"decision-log",
|
|
2929
|
+
"decision entr",
|
|
2930
|
+
"decisions? (are |is )?logged",
|
|
2931
|
+
"log entries",
|
|
2932
|
+
String.raw`decisions\.md`,
|
|
2933
|
+
"decisions-legacy",
|
|
2934
|
+
"CONTRADICTION",
|
|
2935
|
+
"assumes Tier 3",
|
|
2936
|
+
"Lock: ?YES",
|
|
2937
|
+
"Lock: ?NO",
|
|
2938
|
+
"lock approvals?",
|
|
2939
|
+
"approves locks",
|
|
2940
|
+
"Conforms to lock",
|
|
2941
|
+
"Challenges lock",
|
|
2942
|
+
"checkDecisionNumbersFresh",
|
|
2943
|
+
String.raw`roles/team-leader\.md`,
|
|
2944
|
+
String.raw`\(,`,
|
|
2945
|
+
String.raw`,\)`,
|
|
2946
|
+
String.raw`\(\.\)`
|
|
2947
|
+
];
|
|
2948
|
+
var RETIRED_EXEMPT_SUBSTRINGS = [
|
|
2949
|
+
"docs/decisions-legacy.md",
|
|
2950
|
+
"apps/herald-ai/docs/",
|
|
2951
|
+
"apps/vada-ai/docs/",
|
|
2952
|
+
"aeg-root/tranches/completed/",
|
|
2953
|
+
"packages/aeg-core/src/docs/published-prose",
|
|
2954
|
+
"packages/aeg-core/src/retired-vocabulary.test.ts",
|
|
2955
|
+
"packages/aeg-core/src/retired-vocabulary.ts",
|
|
2956
|
+
"/fixtures/",
|
|
2957
|
+
"/node_modules/",
|
|
2958
|
+
"/.next/",
|
|
2959
|
+
"/.turbo/"
|
|
2960
|
+
];
|
|
2961
|
+
var PATTERN_EXEMPT = {
|
|
2962
|
+
[String.raw`\bD-[0-9]{3}\b`]: ["./CLAUDE.md"],
|
|
2963
|
+
[String.raw`decisions\.md`]: [
|
|
2964
|
+
"docs-index.md",
|
|
2965
|
+
"apps/vada-ai/specs/legacy/README.md",
|
|
2966
|
+
"apps/vada-ai/specs/vada-product-spec.md",
|
|
2967
|
+
"apps/vada-ai/web/CLAUDE.md",
|
|
2968
|
+
"apps/vada-ai/CLAUDE.md"
|
|
2969
|
+
],
|
|
2970
|
+
"decisions-legacy": [
|
|
2971
|
+
"packages/aeg-core/src/file-classify.ts",
|
|
2972
|
+
"packages/aeg-core/src/file-classify.test.ts",
|
|
2973
|
+
"packages/aeg-core/src/pr-tier.test.ts",
|
|
2974
|
+
"docs-index.md",
|
|
2975
|
+
".claude/skills/database/SKILL.md",
|
|
2976
|
+
".claude/skills/vada-architecture/SKILL.md",
|
|
2977
|
+
"apps/vada-ai/specs/vada-byok-principles.md",
|
|
2978
|
+
"apps/vada-ai/web/CLAUDE.md",
|
|
2979
|
+
"apps/vada-ai/CLAUDE.md"
|
|
2980
|
+
],
|
|
2981
|
+
CONTRADICTION: ["apps/vada-ai/specs/vada-reviewers-spec.md"]
|
|
2982
|
+
};
|
|
2983
|
+
function lineNumbers(content, pattern) {
|
|
2984
|
+
const out = [];
|
|
2985
|
+
const lines = content.split(`
|
|
2986
|
+
`);
|
|
2987
|
+
for (let i = 0;i < lines.length; i++) {
|
|
2988
|
+
if (pattern.test(lines[i] ?? ""))
|
|
2989
|
+
out.push(i + 1);
|
|
2990
|
+
}
|
|
2991
|
+
return out;
|
|
2992
|
+
}
|
|
2993
|
+
function scanRetiredVocabulary(files) {
|
|
2994
|
+
const findings = [];
|
|
2995
|
+
for (const file of files) {
|
|
2996
|
+
if (RETIRED_EXEMPT_SUBSTRINGS.some((e) => file.path.includes(e)))
|
|
2997
|
+
continue;
|
|
2998
|
+
for (const pattern of RETIRED_PATTERNS) {
|
|
2999
|
+
const patternExempt = PATTERN_EXEMPT[pattern] ?? [];
|
|
3000
|
+
if (patternExempt.some((e) => file.path.includes(e)))
|
|
3001
|
+
continue;
|
|
3002
|
+
const re = new RegExp(pattern);
|
|
3003
|
+
for (const line of lineNumbers(file.content, re)) {
|
|
3004
|
+
findings.push({
|
|
3005
|
+
file: file.path,
|
|
3006
|
+
line,
|
|
3007
|
+
pattern,
|
|
3008
|
+
message: `claims a retired AEG mechanism is still live (matches /${pattern}/)`
|
|
3009
|
+
});
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
return findings;
|
|
3014
|
+
}
|
|
2913
3015
|
// ../../packages/aeg-core/src/dispatch-gate.ts
|
|
2914
3016
|
function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
|
|
2915
3017
|
return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);
|
|
@@ -3163,6 +3265,274 @@ function evaluateTestPlanGate(body, branch) {
|
|
|
3163
3265
|
messages.push("All Test Plan items ticked — runtime verification complete.", "PASS.");
|
|
3164
3266
|
return { verdict: "pass", messages };
|
|
3165
3267
|
}
|
|
3268
|
+
// src/lib/config.ts
|
|
3269
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
3270
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3271
|
+
import { homedir } from "node:os";
|
|
3272
|
+
import { dirname, join } from "node:path";
|
|
3273
|
+
import { z } from "zod";
|
|
3274
|
+
|
|
3275
|
+
// src/lib/agent-vendors.ts
|
|
3276
|
+
var AGENT_VENDORS = ["skills", "claude", "gemini"];
|
|
3277
|
+
|
|
3278
|
+
// src/lib/config.ts
|
|
3279
|
+
var EnvEntrySchema = z.union([
|
|
3280
|
+
z.literal(true),
|
|
3281
|
+
z.object({ optional: z.literal(true) }),
|
|
3282
|
+
z.object({ anyOf: z.array(z.string()).min(2) }),
|
|
3283
|
+
z.string()
|
|
3284
|
+
]);
|
|
3285
|
+
var CheckEntrySchema = z.object({
|
|
3286
|
+
run: z.string(),
|
|
3287
|
+
scope: z.enum(["diff", "full"]),
|
|
3288
|
+
include: z.array(z.string()).optional(),
|
|
3289
|
+
args: z.array(z.string()).optional(),
|
|
3290
|
+
timeoutMs: z.number().optional(),
|
|
3291
|
+
env: z.record(z.string(), EnvEntrySchema).optional(),
|
|
3292
|
+
requiresOpenPr: z.boolean().optional(),
|
|
3293
|
+
ownWorkflow: z.boolean().optional()
|
|
3294
|
+
}).superRefine((entry2, ctx) => {
|
|
3295
|
+
if (!entry2.env)
|
|
3296
|
+
return;
|
|
3297
|
+
for (const [key, value] of Object.entries(entry2.env)) {
|
|
3298
|
+
if (typeof value !== "object" || value === null || !("anyOf" in value))
|
|
3299
|
+
continue;
|
|
3300
|
+
const members = value.anyOf;
|
|
3301
|
+
if (new Set(members).size !== members.length) {
|
|
3302
|
+
ctx.addIssue({
|
|
3303
|
+
code: z.ZodIssueCode.custom,
|
|
3304
|
+
message: `env.${key}.anyOf has duplicate members`,
|
|
3305
|
+
path: ["env", key, "anyOf"]
|
|
3306
|
+
});
|
|
3307
|
+
}
|
|
3308
|
+
if (!members.includes(key)) {
|
|
3309
|
+
ctx.addIssue({
|
|
3310
|
+
code: z.ZodIssueCode.custom,
|
|
3311
|
+
message: `env.${key}.anyOf must include "${key}" itself as a member`,
|
|
3312
|
+
path: ["env", key, "anyOf"]
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
});
|
|
3317
|
+
var RoleEntrySchema = z.object({
|
|
3318
|
+
contract: z.string().refine((p) => p.includes("/"), {
|
|
3319
|
+
message: 'must be a path (contain at least one "/"), not a bare filename'
|
|
3320
|
+
})
|
|
3321
|
+
});
|
|
3322
|
+
var BRIEF_BUILTINS = [
|
|
3323
|
+
"tier",
|
|
3324
|
+
"testPlan",
|
|
3325
|
+
"testPlanExclusivity",
|
|
3326
|
+
"principalPlaceholder",
|
|
3327
|
+
"surfaceMap",
|
|
3328
|
+
"docUpdateList",
|
|
3329
|
+
"worktreeStep0",
|
|
3330
|
+
"stopConditions",
|
|
3331
|
+
"autonomyClause",
|
|
3332
|
+
"project",
|
|
3333
|
+
"for",
|
|
3334
|
+
"closesN",
|
|
3335
|
+
"premiseCoverage",
|
|
3336
|
+
"issueRationale",
|
|
3337
|
+
"milestoneShape"
|
|
3338
|
+
];
|
|
3339
|
+
var BriefSectionSchema = z.union([
|
|
3340
|
+
z.object({ builtin: z.enum(BRIEF_BUILTINS) }),
|
|
3341
|
+
z.object({ heading: z.string().min(1), name: z.string().optional() }),
|
|
3342
|
+
z.object({ field: z.string().min(1), name: z.string().optional() }),
|
|
3343
|
+
z.object({ phrase: z.string().min(1), name: z.string().optional() })
|
|
3344
|
+
]);
|
|
3345
|
+
var BriefSchemaSchema = z.object({
|
|
3346
|
+
pr: z.object({ sections: z.array(BriefSectionSchema) }).optional(),
|
|
3347
|
+
issue: z.object({ sections: z.array(BriefSectionSchema) }).optional(),
|
|
3348
|
+
milestone: z.object({ sections: z.array(BriefSectionSchema) }).optional(),
|
|
3349
|
+
ack: z.array(z.enum(BRIEF_BUILTINS)).optional()
|
|
3350
|
+
});
|
|
3351
|
+
function isSafeRepoRelPath(p) {
|
|
3352
|
+
if (p.length === 0)
|
|
3353
|
+
return false;
|
|
3354
|
+
if (p.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(p))
|
|
3355
|
+
return false;
|
|
3356
|
+
return p.split(/[\\/]/).every((seg) => seg !== ".." && seg !== "");
|
|
3357
|
+
}
|
|
3358
|
+
var SafeRepoRelPath = z.string().refine(isSafeRepoRelPath, {
|
|
3359
|
+
message: "must be a repo-root-relative path with no `..` segment or absolute root"
|
|
3360
|
+
});
|
|
3361
|
+
var ManagedBlockRecordSchema = z.object({
|
|
3362
|
+
path: SafeRepoRelPath,
|
|
3363
|
+
marker: z.string(),
|
|
3364
|
+
comment: z.enum(["hash", "html"])
|
|
3365
|
+
});
|
|
3366
|
+
var ManagedManifestSchema = z.object({
|
|
3367
|
+
version: z.number().int().positive(),
|
|
3368
|
+
files: z.array(SafeRepoRelPath),
|
|
3369
|
+
blocks: z.array(ManagedBlockRecordSchema),
|
|
3370
|
+
labels: z.array(z.string()),
|
|
3371
|
+
agents: z.array(z.enum(AGENT_VENDORS)).optional()
|
|
3372
|
+
});
|
|
3373
|
+
var VinayaConfigSchema = z.object({
|
|
3374
|
+
rings: z.object({
|
|
3375
|
+
ring1_forgeWriteInterception: z.boolean(),
|
|
3376
|
+
ring2_asyncAudits: z.boolean()
|
|
3377
|
+
}).optional(),
|
|
3378
|
+
checks: z.record(z.string(), CheckEntrySchema).optional(),
|
|
3379
|
+
roles: z.record(z.string(), RoleEntrySchema).optional(),
|
|
3380
|
+
briefSchema: BriefSchemaSchema.optional(),
|
|
3381
|
+
managed: ManagedManifestSchema.optional(),
|
|
3382
|
+
principals: z.array(z.string()).min(1).optional(),
|
|
3383
|
+
releaseActor: z.string().min(1).optional(),
|
|
3384
|
+
ci: z.object({ setup: z.string().min(1) }).optional(),
|
|
3385
|
+
blastRadius: z.object({ extraDomains: z.array(z.string()).optional() }).optional(),
|
|
3386
|
+
proseGates: z.object({
|
|
3387
|
+
doctrineRoot: z.string().min(1).optional(),
|
|
3388
|
+
readerFacingPrefix: z.string().min(1).optional(),
|
|
3389
|
+
readerFacingSuffix: z.string().min(1).optional(),
|
|
3390
|
+
legacySlugDir: z.string().min(1).optional()
|
|
3391
|
+
}).optional()
|
|
3392
|
+
});
|
|
3393
|
+
var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
|
|
3394
|
+
var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");
|
|
3395
|
+
var LOCAL_CONFIG_FILENAME = "vinaya.config.json";
|
|
3396
|
+
function findLocalConfig() {
|
|
3397
|
+
let dir = process.cwd();
|
|
3398
|
+
while (true) {
|
|
3399
|
+
const candidate = join(dir, LOCAL_CONFIG_FILENAME);
|
|
3400
|
+
if (existsSync(candidate))
|
|
3401
|
+
return candidate;
|
|
3402
|
+
if (existsSync(join(dir, ".git")))
|
|
3403
|
+
return null;
|
|
3404
|
+
const parent = dirname(dir);
|
|
3405
|
+
if (parent === dir)
|
|
3406
|
+
return null;
|
|
3407
|
+
dir = parent;
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
function configPath() {
|
|
3411
|
+
const local = findLocalConfig();
|
|
3412
|
+
if (local)
|
|
3413
|
+
return local;
|
|
3414
|
+
if (existsSync(GLOBAL_CONFIG_PATH))
|
|
3415
|
+
return GLOBAL_CONFIG_PATH;
|
|
3416
|
+
return null;
|
|
3417
|
+
}
|
|
3418
|
+
function globalChecksIgnoredWarning(path) {
|
|
3419
|
+
return `${path}: "checks" registration in the global config is ignored — checks may only be registered from a repo-local vinaya.config.json.`;
|
|
3420
|
+
}
|
|
3421
|
+
function globalPrincipalsIgnoredWarning(path) {
|
|
3422
|
+
return `${path}: "principals" in the global config is ignored — principals may only be declared from a repo-local vinaya.config.json.`;
|
|
3423
|
+
}
|
|
3424
|
+
function globalRolesIgnoredWarning(path) {
|
|
3425
|
+
return `${path}: "roles" registration in the global config is ignored — roles may only be registered from a repo-local vinaya.config.json.`;
|
|
3426
|
+
}
|
|
3427
|
+
function globalReleaseActorIgnoredWarning(path) {
|
|
3428
|
+
return `${path}: "releaseActor" in the global config is ignored — releaseActor may only be declared from a repo-local vinaya.config.json.`;
|
|
3429
|
+
}
|
|
3430
|
+
function stripGlobalOnlyKeys(config, path) {
|
|
3431
|
+
if (path !== GLOBAL_CONFIG_PATH)
|
|
3432
|
+
return config;
|
|
3433
|
+
let result = config;
|
|
3434
|
+
if (result.checks && Object.keys(result.checks).length > 0) {
|
|
3435
|
+
console.error(`⚠ ${globalChecksIgnoredWarning(path)}`);
|
|
3436
|
+
result = { ...result, checks: undefined };
|
|
3437
|
+
}
|
|
3438
|
+
if (result.roles && Object.keys(result.roles).length > 0) {
|
|
3439
|
+
console.error(`⚠ ${globalRolesIgnoredWarning(path)}`);
|
|
3440
|
+
result = { ...result, roles: undefined };
|
|
3441
|
+
}
|
|
3442
|
+
if (result.principals && result.principals.length > 0) {
|
|
3443
|
+
console.error(`⚠ ${globalPrincipalsIgnoredWarning(path)}`);
|
|
3444
|
+
result = { ...result, principals: undefined };
|
|
3445
|
+
}
|
|
3446
|
+
if (result.releaseActor) {
|
|
3447
|
+
console.error(`⚠ ${globalReleaseActorIgnoredWarning(path)}`);
|
|
3448
|
+
result = { ...result, releaseActor: undefined };
|
|
3449
|
+
}
|
|
3450
|
+
return result;
|
|
3451
|
+
}
|
|
3452
|
+
function resolvePrincipalAllowlist(config) {
|
|
3453
|
+
return config?.principals ?? PRINCIPAL_ALLOWLIST;
|
|
3454
|
+
}
|
|
3455
|
+
function resolveReleaseActor(config) {
|
|
3456
|
+
return config?.releaseActor ?? DEFAULT_RELEASE_ACTOR;
|
|
3457
|
+
}
|
|
3458
|
+
function trustAnchorRepo() {
|
|
3459
|
+
const wellFormed = (slug) => /^[^/\s]+\/[^/\s]+$/.test(slug) ? slug : null;
|
|
3460
|
+
const fromRunner = process.env.GITHUB_REPOSITORY?.trim();
|
|
3461
|
+
if (fromRunner) {
|
|
3462
|
+
const validated = wellFormed(fromRunner);
|
|
3463
|
+
if (validated)
|
|
3464
|
+
return validated;
|
|
3465
|
+
}
|
|
3466
|
+
try {
|
|
3467
|
+
const url = execFileSync2("git", ["remote", "get-url", "origin"], {
|
|
3468
|
+
encoding: "utf-8",
|
|
3469
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3470
|
+
}).trim();
|
|
3471
|
+
const m = url.match(/github\.com[:/]([^/]+)\/(.+?)(?:\.git)?\/?$/);
|
|
3472
|
+
return m?.[1] && m[2] ? wellFormed(`${m[1]}/${m[2]}`) : null;
|
|
3473
|
+
} catch {
|
|
3474
|
+
return null;
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
function ghFetchTrustAnchorConfig() {
|
|
3478
|
+
const repo = trustAnchorRepo();
|
|
3479
|
+
if (!repo)
|
|
3480
|
+
throw new Error("could not resolve repo identity for the trust-anchor read");
|
|
3481
|
+
return execFileSync2("gh", ["api", `repos/${repo}/contents/${LOCAL_CONFIG_FILENAME}`, "--jq", ".content"], {
|
|
3482
|
+
encoding: "utf-8",
|
|
3483
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
3484
|
+
});
|
|
3485
|
+
}
|
|
3486
|
+
function firstLine(err) {
|
|
3487
|
+
return err?.message?.split(`
|
|
3488
|
+
`)[0] ?? "unknown error";
|
|
3489
|
+
}
|
|
3490
|
+
function isMissingFileError(err) {
|
|
3491
|
+
const stderr = err?.stderr;
|
|
3492
|
+
const haystack = [
|
|
3493
|
+
err?.message ?? "",
|
|
3494
|
+
typeof stderr === "string" ? stderr : stderr?.toString() ?? ""
|
|
3495
|
+
].join(`
|
|
3496
|
+
`);
|
|
3497
|
+
return /\b404\b|not found/i.test(haystack);
|
|
3498
|
+
}
|
|
3499
|
+
function loadTrustAnchorConfig(fetcher = ghFetchTrustAnchorConfig) {
|
|
3500
|
+
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.
|
|
3501
|
+
`);
|
|
3502
|
+
let base64;
|
|
3503
|
+
try {
|
|
3504
|
+
base64 = fetcher().trim();
|
|
3505
|
+
} catch (err) {
|
|
3506
|
+
if (!isMissingFileError(err))
|
|
3507
|
+
warn(firstLine(err));
|
|
3508
|
+
return null;
|
|
3509
|
+
}
|
|
3510
|
+
if (!base64)
|
|
3511
|
+
return null;
|
|
3512
|
+
try {
|
|
3513
|
+
const raw = Buffer.from(base64, "base64").toString("utf-8");
|
|
3514
|
+
const parsed = VinayaConfigSchema.safeParse(JSON.parse(raw));
|
|
3515
|
+
if (parsed.success)
|
|
3516
|
+
return parsed.data;
|
|
3517
|
+
warn("it did not satisfy the config schema");
|
|
3518
|
+
return null;
|
|
3519
|
+
} catch {
|
|
3520
|
+
warn("it was not readable as JSON");
|
|
3521
|
+
return null;
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
function loadConfig() {
|
|
3525
|
+
const path = configPath();
|
|
3526
|
+
if (!path)
|
|
3527
|
+
return null;
|
|
3528
|
+
try {
|
|
3529
|
+
const raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
3530
|
+
return stripGlobalOnlyKeys(VinayaConfigSchema.parse(raw), path);
|
|
3531
|
+
} catch {
|
|
3532
|
+
return null;
|
|
3533
|
+
}
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3166
3536
|
// src/checks/contract.ts
|
|
3167
3537
|
var CHECK_SCHEMA_VERSION = 1;
|
|
3168
3538
|
function emitCheckError(error) {
|
|
@@ -3171,12 +3541,14 @@ function emitCheckError(error) {
|
|
|
3171
3541
|
}
|
|
3172
3542
|
|
|
3173
3543
|
// src/checks/bin/check-reader-resolvable-prose.ts
|
|
3174
|
-
var REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "../../../../..");
|
|
3175
|
-
process.chdir(REPO_ROOT);
|
|
3176
3544
|
var CHECK_NAME = "reader-resolvable-prose";
|
|
3177
|
-
var
|
|
3178
|
-
var
|
|
3179
|
-
|
|
3545
|
+
var proseGates = loadConfig()?.proseGates;
|
|
3546
|
+
var DOCTRINE_ROOT = proseGates?.doctrineRoot ?? "aeg-root";
|
|
3547
|
+
var READER_FACING_PREFIX = proseGates?.readerFacingPrefix ?? null;
|
|
3548
|
+
var READER_FACING_SUFFIX = proseGates?.readerFacingSuffix ?? null;
|
|
3549
|
+
var READER_FACING_ACTIVE = READER_FACING_PREFIX !== null && READER_FACING_SUFFIX !== null;
|
|
3550
|
+
var LEGACY_SLUG_DIR = proseGates?.legacySlugDir ?? `${DOCTRINE_ROOT}/tranches/completed`;
|
|
3551
|
+
function collect(dir, out = []) {
|
|
3180
3552
|
let entries;
|
|
3181
3553
|
try {
|
|
3182
3554
|
entries = readdirSync(dir);
|
|
@@ -3184,7 +3556,7 @@ function collect(dir, match, out = []) {
|
|
|
3184
3556
|
return out;
|
|
3185
3557
|
}
|
|
3186
3558
|
for (const name of entries) {
|
|
3187
|
-
const full =
|
|
3559
|
+
const full = join2(dir, name);
|
|
3188
3560
|
let isDir;
|
|
3189
3561
|
try {
|
|
3190
3562
|
isDir = statSync(full).isDirectory();
|
|
@@ -3194,37 +3566,39 @@ function collect(dir, match, out = []) {
|
|
|
3194
3566
|
if (isDir) {
|
|
3195
3567
|
if (name === "node_modules" || name === ".next" || name === ".turbo")
|
|
3196
3568
|
continue;
|
|
3197
|
-
collect(full,
|
|
3198
|
-
} else
|
|
3569
|
+
collect(full, out);
|
|
3570
|
+
} else {
|
|
3199
3571
|
out.push(full);
|
|
3200
3572
|
}
|
|
3201
3573
|
}
|
|
3202
3574
|
return out;
|
|
3203
3575
|
}
|
|
3204
3576
|
function readAll(paths) {
|
|
3205
|
-
return paths.map((p) => ({ path: p, content:
|
|
3577
|
+
return paths.map((p) => ({ path: p, content: readFileSync2(p, "utf8") }));
|
|
3206
3578
|
}
|
|
3207
3579
|
function legacySlugs() {
|
|
3208
|
-
|
|
3209
|
-
if (!existsSync(dir))
|
|
3580
|
+
if (!existsSync2(LEGACY_SLUG_DIR))
|
|
3210
3581
|
return { slugs: [], dormant: true };
|
|
3211
3582
|
try {
|
|
3212
|
-
const slugs = readdirSync(
|
|
3583
|
+
const slugs = readdirSync(LEGACY_SLUG_DIR).filter((f) => f.endsWith(".md") && !f.endsWith(".tokens.md")).map((f) => f.slice(0, -3)).filter((slug) => !/-v[0-9]+$/.test(slug));
|
|
3213
3584
|
return { slugs, dormant: false };
|
|
3214
3585
|
} catch (err) {
|
|
3215
|
-
console.
|
|
3586
|
+
console.log(`${CHECK_NAME}: could not read ${LEGACY_SLUG_DIR} (${err instanceof Error ? err.message : String(err)}) — legacy-slug class treated as dormant.`);
|
|
3216
3587
|
return { slugs: [], dormant: true };
|
|
3217
3588
|
}
|
|
3218
3589
|
}
|
|
3219
3590
|
function main() {
|
|
3220
|
-
const
|
|
3221
|
-
const
|
|
3591
|
+
const shipsPrefix = `${DOCTRINE_ROOT}/`;
|
|
3592
|
+
const shipsPaths = collect(DOCTRINE_ROOT).filter((p) => p.endsWith(".md"));
|
|
3593
|
+
const readerFacingPaths = READER_FACING_ACTIVE && READER_FACING_SUFFIX !== null ? collect(READER_FACING_PREFIX).filter((p) => p.endsWith(READER_FACING_SUFFIX)) : [];
|
|
3222
3594
|
const files = readAll([...shipsPaths, ...readerFacingPaths]);
|
|
3223
|
-
const
|
|
3595
|
+
const glossaryPath = join2(DOCTRINE_ROOT, "glossary.md");
|
|
3596
|
+
const glossaryTerms = existsSync2(glossaryPath) ? parseGlossaryTerms(readFileSync2(glossaryPath, "utf8")) : [];
|
|
3224
3597
|
const { slugs, dormant: legacySlugsDormant } = legacySlugs();
|
|
3225
|
-
const readerFacingPrefix =
|
|
3226
|
-
const
|
|
3227
|
-
|
|
3598
|
+
const readerFacingPrefix = READER_FACING_ACTIVE && READER_FACING_PREFIX !== null ? `${READER_FACING_PREFIX}/` : "/no-reader-facing-surface";
|
|
3599
|
+
const readerFacingSuffix = READER_FACING_ACTIVE && READER_FACING_SUFFIX !== null ? READER_FACING_SUFFIX : "/page.tsx";
|
|
3600
|
+
const findings = checkReaderResolvableProse(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, slugs, shipsPrefix);
|
|
3601
|
+
console.log(`${CHECK_NAME}: doctrine root "${DOCTRINE_ROOT}"; reader-facing class ${READER_FACING_ACTIVE ? "ran" : "dormant — proseGates.readerFacingPrefix/readerFacingSuffix not both set"}; ` + `legacy-slug class ${legacySlugsDormant ? `dormant — ${LEGACY_SLUG_DIR} is absent` : `ran (${slugs.length} slug(s))`}; ` + `${findings.length} finding(s)`);
|
|
3228
3602
|
for (const finding of findings) {
|
|
3229
3603
|
emitCheckError({
|
|
3230
3604
|
schema: CHECK_SCHEMA_VERSION,
|
|
@@ -3233,7 +3607,7 @@ function main() {
|
|
|
3233
3607
|
message: `${finding.file}:${finding.line}: ${finding.message}`,
|
|
3234
3608
|
file: finding.file,
|
|
3235
3609
|
line: finding.line,
|
|
3236
|
-
agent_recovery_prompt: finding.message.includes("coined term") ? "This page uses AEG/Vinaya-internal vocabulary a first-time reader cannot resolve. Either define the term " + 'inline (the same "Term — one-sentence definition" shape
|
|
3610
|
+
agent_recovery_prompt: finding.message.includes("coined term") ? "This page uses AEG/Vinaya-internal vocabulary a first-time reader cannot resolve. Either define the term " + 'inline (the same "Term — one-sentence definition" shape the glossary uses) at its first use on this page, ' + "or link to the glossary. Do not simply delete the word if the sentence needs it." : "This doctrine or page cites a forge number or an internal tranche slug the reader has no tracker to " + "resolve. Rewrite the sentence to state the fact plainly instead of pointing at the citation — say what " + "was learned/decided, not where it was logged."
|
|
3237
3611
|
});
|
|
3238
3612
|
}
|
|
3239
3613
|
process.exit(0);
|