@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.
Files changed (91) hide show
  1. package/README.md +37 -1
  2. package/aeg-root/contracts/architect-planner.md +72 -0
  3. package/aeg-root/enforcement.md +3 -2
  4. package/aeg-root/roles/architect.md +91 -0
  5. package/aeg-root/roles/planner.md +14 -15
  6. package/aeg-root/tranche-model.md +9 -1
  7. package/dist/checks/bin/check-body-bare-digits.js +262 -26
  8. package/dist/checks/bin/check-branch-topology.js +171 -23
  9. package/dist/checks/bin/check-brief-shape.js +171 -23
  10. package/dist/checks/bin/check-closes-n.js +171 -23
  11. package/dist/checks/bin/check-coherence.js +265 -29
  12. package/dist/checks/bin/check-dead-branch-push.js +171 -23
  13. package/dist/checks/bin/check-dispatch-readiness.js +329 -53
  14. package/dist/checks/bin/check-doc-coverage-push.js +266 -30
  15. package/dist/checks/bin/check-doc-coverage.js +266 -30
  16. package/dist/checks/bin/check-evidence-fresh.js +171 -23
  17. package/dist/checks/bin/check-first-push-dispatch.js +330 -54
  18. package/dist/checks/bin/check-issue-assignment.js +171 -23
  19. package/dist/checks/bin/check-no-disk-state.js +171 -23
  20. package/dist/checks/bin/check-reader-resolvable-prose.js +465 -46
  21. package/dist/checks/bin/check-registry-gates.js +171 -23
  22. package/dist/checks/bin/check-retired-vocabulary.js +3595 -0
  23. package/dist/checks/bin/check-review-gate.js +262 -26
  24. package/dist/checks/bin/check-single-plan-pr.js +171 -23
  25. package/dist/checks/bin/check-test-plan.js +171 -23
  26. package/dist/index.js +2959 -1581
  27. package/package.json +1 -1
  28. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  29. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  30. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  31. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  32. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  33. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  34. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  35. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  36. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  37. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  38. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  39. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
  40. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  41. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  42. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  43. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  44. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  45. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  46. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  47. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  48. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  49. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  50. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  51. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  52. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  53. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  54. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  55. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  56. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  57. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  58. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  59. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  60. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  61. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  62. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  63. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  64. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/[root-of-the-server]__0e95dv5._.js +1 -1
  65. 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
  66. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__029-1ch._.js +1 -1
  67. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0q56ohl._.js +1 -1
  68. 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
  69. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1_ojzp1._.js +1 -1
  70. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1d_8nm4._.js +1 -1
  71. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1zkwtx8._.js +1 -1
  72. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__210ehg9._.js +1 -1
  73. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_00yjcv1._.js +1 -1
  74. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0rix02i._.js +1 -1
  75. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_18-d2be._.js +1 -1
  76. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1fqw88f._.js +5 -5
  77. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1gw6vc9._.js +1 -1
  78. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  79. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  80. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  81. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  82. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  83. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{2myi4phnshkfw.js → 1zaotsjjq5o6o.js} +1 -1
  84. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{415k4k1rf3swg.js → 295mri9sjante.js} +1 -1
  85. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3z3a3gb6he23i.js → 2opd70pfbc5yx.js} +1 -1
  86. package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
  87. package/templates/noop-check.template.ts +18 -0
  88. package/templates/role.template.md +20 -0
  89. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{8tXGbArNoU7_MriIVYvN0 → SG6gXRhit12wl-8HLX9d3}/_buildManifest.js +0 -0
  90. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{8tXGbArNoU7_MriIVYvN0 → SG6gXRhit12wl-8HLX9d3}/_clientMiddlewareManifest.js +0 -0
  91. /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 { dirname, join } from "node:path";
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
- var SHIPS_ARCHIVE_PREFIX = "aeg-root/tranches/completed/";
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(SHIPS_PREFIX)) {
2739
- return path.startsWith(SHIPS_ARCHIVE_PREFIX) ? "internal" : "ships";
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 READER_FACING_ROOT = null;
3133
- var READER_FACING_SUFFIX = "/page.tsx";
3134
- function collect(dir, match, out = []) {
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 = join(dir, name);
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, match, out);
3153
- } else if (match(name)) {
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: readFileSync(p, "utf8") }));
3577
+ return paths.map((p) => ({ path: p, content: readFileSync2(p, "utf8") }));
3161
3578
  }
3162
3579
  function legacySlugs() {
3163
- const dir = join(REPO_ROOT, "aeg-root/tranches/completed");
3164
- if (!existsSync(dir))
3580
+ if (!existsSync2(LEGACY_SLUG_DIR))
3165
3581
  return { slugs: [], dormant: true };
3166
3582
  try {
3167
- const slugs = readdirSync(dir).filter((f) => f.endsWith(".md") && !f.endsWith(".tokens.md")).map((f) => f.slice(0, -3)).filter((slug) => !/-v[0-9]+$/.test(slug));
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.error(`${CHECK_NAME}: could not read ${dir} (${err instanceof Error ? err.message : String(err)}) — legacy-slug class treated as dormant.`);
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 shipsPaths = collect("aeg-root", (name) => name.endsWith(".md"));
3176
- const readerFacingPaths = READER_FACING_ROOT !== null ? collect(READER_FACING_ROOT, (name) => name === "page.tsx") : [];
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 glossaryTerms = parseGlossaryTerms(readFileSync(join(REPO_ROOT, "aeg-root/glossary.md"), "utf8"));
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 = READER_FACING_ROOT !== null ? `${READER_FACING_ROOT}/` : "/no-reader-facing-surface";
3181
- const findings = checkReaderResolvableProse(files, glossaryTerms, readerFacingPrefix, READER_FACING_SUFFIX, slugs);
3182
- console.error(`${CHECK_NAME}: reader-facing class ${READER_FACING_ROOT !== null ? "ran" : "dormant — no reader-facing surface (e.g. apps/*/web) in this repo"}; ` + `legacy-slug class ${legacySlugsDormant ? "dormant — aeg-root/tranches/completed is absent in this repo" : `ran (${slugs.length} slug(s))`}; ` + `${findings.length} finding(s)`);
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 `aeg-root/glossary.md` uses) at its first use on ' + "this page, or link to `/docs/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."
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);