@attalabs/vinaya 0.18.0 → 0.19.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 +37 -1
- package/aeg-root/contracts/architect-planner.md +72 -0
- package/aeg-root/enforcement.md +3 -2
- package/aeg-root/roles/architect.md +91 -0
- package/aeg-root/roles/planner.md +14 -15
- package/aeg-root/tranche-model.md +9 -1
- package/dist/checks/bin/check-body-bare-digits.js +262 -26
- package/dist/checks/bin/check-branch-topology.js +171 -23
- package/dist/checks/bin/check-brief-shape.js +171 -23
- package/dist/checks/bin/check-closes-n.js +171 -23
- package/dist/checks/bin/check-coherence.js +265 -29
- package/dist/checks/bin/check-dead-branch-push.js +171 -23
- package/dist/checks/bin/check-dispatch-readiness.js +329 -53
- package/dist/checks/bin/check-doc-coverage-push.js +266 -30
- package/dist/checks/bin/check-doc-coverage.js +266 -30
- package/dist/checks/bin/check-evidence-fresh.js +171 -23
- package/dist/checks/bin/check-first-push-dispatch.js +330 -54
- package/dist/checks/bin/check-issue-assignment.js +171 -23
- package/dist/checks/bin/check-no-disk-state.js +171 -23
- package/dist/checks/bin/check-reader-resolvable-prose.js +465 -46
- package/dist/checks/bin/check-registry-gates.js +171 -23
- package/dist/checks/bin/check-retired-vocabulary.js +3595 -0
- package/dist/checks/bin/check-review-gate.js +262 -26
- package/dist/checks/bin/check-single-plan-pr.js +171 -23
- package/dist/checks/bin/check-test-plan.js +171 -23
- package/dist/index.js +2959 -1581
- 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]__0q56ohl._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__0z47hfl._.js → [root-of-the-server]__0xdkvod._.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/{2myi4phnshkfw.js → 1zaotsjjq5o6o.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{415k4k1rf3swg.js → 295mri9sjante.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3z3a3gb6he23i.js → 2opd70pfbc5yx.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/{8tXGbArNoU7_MriIVYvN0 → SG6gXRhit12wl-8HLX9d3}/_buildManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{8tXGbArNoU7_MriIVYvN0 → SG6gXRhit12wl-8HLX9d3}/_clientMiddlewareManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{8tXGbArNoU7_MriIVYvN0 → SG6gXRhit12wl-8HLX9d3}/_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);
|
|
@@ -275,6 +274,9 @@ function issueListByLabelArgs(owner, repo, label) {
|
|
|
275
274
|
"200"
|
|
276
275
|
];
|
|
277
276
|
}
|
|
277
|
+
function ghIssueListByLabel(owner, repo, label) {
|
|
278
|
+
return JSON.parse(run(issueListByLabelArgs(owner, repo, label)));
|
|
279
|
+
}
|
|
278
280
|
async function ghIssueListByLabelAsync(owner, repo, label) {
|
|
279
281
|
return JSON.parse(await runAsync(issueListByLabelArgs(owner, repo, label)));
|
|
280
282
|
}
|
|
@@ -290,18 +292,6 @@ async function ghIssueListByAnyLabelAsync(owner, repo, labels) {
|
|
|
290
292
|
return dedupeByNumber(perLabel.flat());
|
|
291
293
|
}
|
|
292
294
|
|
|
293
|
-
// ../../packages/aeg-forge-state/src/fetch-milestone.ts
|
|
294
|
-
function findMilestoneForSlug(owner, repo, slug) {
|
|
295
|
-
const milestones = ghApiGet(`repos/${owner}/${repo}/milestones?state=all&per_page=100`);
|
|
296
|
-
const match = milestones.find((m) => m.title === slug);
|
|
297
|
-
if (!match)
|
|
298
|
-
return null;
|
|
299
|
-
return {
|
|
300
|
-
goal: match.description ?? "",
|
|
301
|
-
lifecycle: match.state === "closed" ? "complete" : "active"
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
|
|
305
295
|
// ../../packages/aeg-forge-state/src/labels.ts
|
|
306
296
|
var LABELS = [
|
|
307
297
|
{
|
|
@@ -544,6 +534,66 @@ function resolveTaskIssueRef(title, labels) {
|
|
|
544
534
|
return { trancheSlug, taskId };
|
|
545
535
|
}
|
|
546
536
|
|
|
537
|
+
// ../../packages/aeg-forge-state/src/fetch-milestone.ts
|
|
538
|
+
var INTENTS_HEADING = /^#{1,6}\s*Tranche intents\s*$/im;
|
|
539
|
+
var NEXT_HEADING = /^#{1,6}\s+\S/m;
|
|
540
|
+
var INTENT_BULLET = /^-\s+([a-z0-9][a-z0-9-]*)\s*:\s*(.+)$/i;
|
|
541
|
+
function intentGoalForSlug(description, slug) {
|
|
542
|
+
const text = stripCode(description, { inlineSpans: "keep" });
|
|
543
|
+
const start = text.match(INTENTS_HEADING);
|
|
544
|
+
if (!start || start.index === undefined)
|
|
545
|
+
return "";
|
|
546
|
+
const rest = text.slice(start.index + start[0].length);
|
|
547
|
+
const next = rest.match(NEXT_HEADING);
|
|
548
|
+
const section = rest.slice(0, next && next.index !== undefined ? next.index : rest.length);
|
|
549
|
+
for (const line of section.split(`
|
|
550
|
+
`)) {
|
|
551
|
+
const trimmed = line.trim();
|
|
552
|
+
if (trimmed.length === 0)
|
|
553
|
+
continue;
|
|
554
|
+
const m = trimmed.match(INTENT_BULLET);
|
|
555
|
+
if (m && (m[1] ?? "").toLowerCase() === slug.toLowerCase())
|
|
556
|
+
return (m[2] ?? "").trim();
|
|
557
|
+
}
|
|
558
|
+
return "";
|
|
559
|
+
}
|
|
560
|
+
function matchesLegacyMilestone(milestones, slug) {
|
|
561
|
+
return milestones.find((m) => m.title === slug) ?? null;
|
|
562
|
+
}
|
|
563
|
+
function factsFromLegacyMilestone(milestone) {
|
|
564
|
+
return {
|
|
565
|
+
goal: milestone.description ?? "",
|
|
566
|
+
lifecycle: milestone.state === "closed" ? "complete" : "active"
|
|
567
|
+
};
|
|
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
|
+
}
|
|
575
|
+
function lifecycleFromIssues(issues) {
|
|
576
|
+
if (issues.length === 0)
|
|
577
|
+
return "planned";
|
|
578
|
+
return issues.some((i) => i.state === "OPEN") ? "active" : "complete";
|
|
579
|
+
}
|
|
580
|
+
function goalFromMilestones(milestones, slug) {
|
|
581
|
+
for (const m of milestones) {
|
|
582
|
+
const goal = intentGoalForSlug(m.description ?? "", slug);
|
|
583
|
+
if (goal)
|
|
584
|
+
return goal;
|
|
585
|
+
}
|
|
586
|
+
return "";
|
|
587
|
+
}
|
|
588
|
+
function findMilestoneForSlug(owner, repo, slug) {
|
|
589
|
+
const milestones = ghApiGet(`repos/${owner}/${repo}/milestones?state=all&per_page=100`);
|
|
590
|
+
const legacy = matchesLegacyMilestone(milestones, slug);
|
|
591
|
+
const issues = ghIssueListByLabel(owner, repo, trancheLabel(slug));
|
|
592
|
+
if (legacy)
|
|
593
|
+
return resolveLegacyFacts(legacy, issues);
|
|
594
|
+
return { goal: goalFromMilestones(milestones, slug), lifecycle: lifecycleFromIssues(issues) };
|
|
595
|
+
}
|
|
596
|
+
|
|
547
597
|
// ../../packages/aeg-forge-state/src/derive-from-forge.ts
|
|
548
598
|
function trancheFromIssues(slug, issues, known) {
|
|
549
599
|
return {
|
|
@@ -2727,16 +2777,18 @@ function checkDeadBranchPush(input) {
|
|
|
2727
2777
|
}
|
|
2728
2778
|
// ../../packages/aeg-core/src/reader-resolvable-prose.ts
|
|
2729
2779
|
var SHIPS_PREFIX = "aeg-root/";
|
|
2730
|
-
|
|
2780
|
+
function shipsArchivePrefix(shipsPrefix) {
|
|
2781
|
+
return `${shipsPrefix}tranches/completed/`;
|
|
2782
|
+
}
|
|
2731
2783
|
function isSpecFile2(path) {
|
|
2732
2784
|
return path.startsWith("apps/") && path.includes("/specs/") && path.endsWith(".md");
|
|
2733
2785
|
}
|
|
2734
2786
|
function isClaudeMdFile(path) {
|
|
2735
2787
|
return path === "CLAUDE.md" || path.endsWith("/CLAUDE.md");
|
|
2736
2788
|
}
|
|
2737
|
-
function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix) {
|
|
2738
|
-
if (path.startsWith(
|
|
2739
|
-
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";
|
|
2740
2792
|
}
|
|
2741
2793
|
if (path.startsWith(readerFacingPrefix) && path.endsWith(readerFacingSuffix)) {
|
|
2742
2794
|
return "reader-facing";
|
|
@@ -2771,7 +2823,7 @@ function legacySlugPattern(legacySlugs) {
|
|
|
2771
2823
|
const alternation = legacySlugs.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
2772
2824
|
return new RegExp(`(^|[^a-zA-Z0-9\\n-])(${alternation})([^a-zA-Z0-9\\n-]|$)`, "g");
|
|
2773
2825
|
}
|
|
2774
|
-
function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs = []) {
|
|
2826
|
+
function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs = [], shipsPrefix = SHIPS_PREFIX) {
|
|
2775
2827
|
const findings = [];
|
|
2776
2828
|
const legacyPattern = legacySlugPattern(legacySlugs);
|
|
2777
2829
|
const patterns = [
|
|
@@ -2780,7 +2832,7 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
|
|
|
2780
2832
|
...legacyPattern ? [{ pattern: legacyPattern, what: "an internal tranche slug", group: 2 }] : []
|
|
2781
2833
|
];
|
|
2782
2834
|
for (const file of files) {
|
|
2783
|
-
const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix);
|
|
2835
|
+
const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
|
|
2784
2836
|
if (!cls || !SWEPT_CLASSES.has(cls))
|
|
2785
2837
|
continue;
|
|
2786
2838
|
const scrubbed = stripNonProse(file.path, file.content);
|
|
@@ -2826,10 +2878,10 @@ function definesOrLinksGlossary(content, term) {
|
|
|
2826
2878
|
return true;
|
|
2827
2879
|
return /docs\/glossary|glossary\.md/i.test(content);
|
|
2828
2880
|
}
|
|
2829
|
-
function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix) {
|
|
2881
|
+
function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, shipsPrefix = SHIPS_PREFIX) {
|
|
2830
2882
|
const findings = [];
|
|
2831
2883
|
for (const file of files) {
|
|
2832
|
-
const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix);
|
|
2884
|
+
const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
|
|
2833
2885
|
if (!cls || !SWEPT_CLASSES.has(cls))
|
|
2834
2886
|
continue;
|
|
2835
2887
|
const scrubbed = stripNonProse(file.path, file.content);
|
|
@@ -2848,10 +2900,10 @@ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, read
|
|
|
2848
2900
|
}
|
|
2849
2901
|
return findings;
|
|
2850
2902
|
}
|
|
2851
|
-
function checkReaderResolvableProse(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, legacySlugs = []) {
|
|
2903
|
+
function checkReaderResolvableProse(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, legacySlugs = [], shipsPrefix = SHIPS_PREFIX) {
|
|
2852
2904
|
return [
|
|
2853
|
-
...checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs),
|
|
2854
|
-
...checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix)
|
|
2905
|
+
...checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs, shipsPrefix),
|
|
2906
|
+
...checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, shipsPrefix)
|
|
2855
2907
|
];
|
|
2856
2908
|
}
|
|
2857
2909
|
function parseGlossaryTerms(glossaryContent) {
|
|
@@ -2865,6 +2917,101 @@ function parseGlossaryTerms(glossaryContent) {
|
|
|
2865
2917
|
}
|
|
2866
2918
|
return terms;
|
|
2867
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
|
+
}
|
|
2868
3015
|
// ../../packages/aeg-core/src/dispatch-gate.ts
|
|
2869
3016
|
function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
|
|
2870
3017
|
return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);
|
|
@@ -3118,6 +3265,274 @@ function evaluateTestPlanGate(body, branch) {
|
|
|
3118
3265
|
messages.push("All Test Plan items ticked — runtime verification complete.", "PASS.");
|
|
3119
3266
|
return { verdict: "pass", messages };
|
|
3120
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
|
+
|
|
3121
3536
|
// src/checks/contract.ts
|
|
3122
3537
|
var CHECK_SCHEMA_VERSION = 1;
|
|
3123
3538
|
function emitCheckError(error) {
|
|
@@ -3126,12 +3541,14 @@ function emitCheckError(error) {
|
|
|
3126
3541
|
}
|
|
3127
3542
|
|
|
3128
3543
|
// src/checks/bin/check-reader-resolvable-prose.ts
|
|
3129
|
-
var REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "../../../../..");
|
|
3130
|
-
process.chdir(REPO_ROOT);
|
|
3131
3544
|
var CHECK_NAME = "reader-resolvable-prose";
|
|
3132
|
-
var
|
|
3133
|
-
var
|
|
3134
|
-
|
|
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 = []) {
|
|
3135
3552
|
let entries;
|
|
3136
3553
|
try {
|
|
3137
3554
|
entries = readdirSync(dir);
|
|
@@ -3139,7 +3556,7 @@ function collect(dir, match, out = []) {
|
|
|
3139
3556
|
return out;
|
|
3140
3557
|
}
|
|
3141
3558
|
for (const name of entries) {
|
|
3142
|
-
const full =
|
|
3559
|
+
const full = join2(dir, name);
|
|
3143
3560
|
let isDir;
|
|
3144
3561
|
try {
|
|
3145
3562
|
isDir = statSync(full).isDirectory();
|
|
@@ -3149,37 +3566,39 @@ function collect(dir, match, out = []) {
|
|
|
3149
3566
|
if (isDir) {
|
|
3150
3567
|
if (name === "node_modules" || name === ".next" || name === ".turbo")
|
|
3151
3568
|
continue;
|
|
3152
|
-
collect(full,
|
|
3153
|
-
} else
|
|
3569
|
+
collect(full, out);
|
|
3570
|
+
} else {
|
|
3154
3571
|
out.push(full);
|
|
3155
3572
|
}
|
|
3156
3573
|
}
|
|
3157
3574
|
return out;
|
|
3158
3575
|
}
|
|
3159
3576
|
function readAll(paths) {
|
|
3160
|
-
return paths.map((p) => ({ path: p, content:
|
|
3577
|
+
return paths.map((p) => ({ path: p, content: readFileSync2(p, "utf8") }));
|
|
3161
3578
|
}
|
|
3162
3579
|
function legacySlugs() {
|
|
3163
|
-
|
|
3164
|
-
if (!existsSync(dir))
|
|
3580
|
+
if (!existsSync2(LEGACY_SLUG_DIR))
|
|
3165
3581
|
return { slugs: [], dormant: true };
|
|
3166
3582
|
try {
|
|
3167
|
-
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));
|
|
3168
3584
|
return { slugs, dormant: false };
|
|
3169
3585
|
} catch (err) {
|
|
3170
|
-
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.`);
|
|
3171
3587
|
return { slugs: [], dormant: true };
|
|
3172
3588
|
}
|
|
3173
3589
|
}
|
|
3174
3590
|
function main() {
|
|
3175
|
-
const
|
|
3176
|
-
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)) : [];
|
|
3177
3594
|
const files = readAll([...shipsPaths, ...readerFacingPaths]);
|
|
3178
|
-
const
|
|
3595
|
+
const glossaryPath = join2(DOCTRINE_ROOT, "glossary.md");
|
|
3596
|
+
const glossaryTerms = existsSync2(glossaryPath) ? parseGlossaryTerms(readFileSync2(glossaryPath, "utf8")) : [];
|
|
3179
3597
|
const { slugs, dormant: legacySlugsDormant } = legacySlugs();
|
|
3180
|
-
const readerFacingPrefix =
|
|
3181
|
-
const
|
|
3182
|
-
|
|
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)`);
|
|
3183
3602
|
for (const finding of findings) {
|
|
3184
3603
|
emitCheckError({
|
|
3185
3604
|
schema: CHECK_SCHEMA_VERSION,
|
|
@@ -3188,7 +3607,7 @@ function main() {
|
|
|
3188
3607
|
message: `${finding.file}:${finding.line}: ${finding.message}`,
|
|
3189
3608
|
file: finding.file,
|
|
3190
3609
|
line: finding.line,
|
|
3191
|
-
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."
|
|
3192
3611
|
});
|
|
3193
3612
|
}
|
|
3194
3613
|
process.exit(0);
|