@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
@@ -273,6 +273,9 @@ function issueListByLabelArgs(owner, repo, label) {
273
273
  "200"
274
274
  ];
275
275
  }
276
+ function ghIssueListByLabel(owner, repo, label) {
277
+ return JSON.parse(run(issueListByLabelArgs(owner, repo, label)));
278
+ }
276
279
  async function ghIssueListByLabelAsync(owner, repo, label) {
277
280
  return JSON.parse(await runAsync(issueListByLabelArgs(owner, repo, label)));
278
281
  }
@@ -288,18 +291,6 @@ async function ghIssueListByAnyLabelAsync(owner, repo, labels) {
288
291
  return dedupeByNumber(perLabel.flat());
289
292
  }
290
293
 
291
- // ../../packages/aeg-forge-state/src/fetch-milestone.ts
292
- function findMilestoneForSlug(owner, repo, slug) {
293
- const milestones = ghApiGet(`repos/${owner}/${repo}/milestones?state=all&per_page=100`);
294
- const match = milestones.find((m) => m.title === slug);
295
- if (!match)
296
- return null;
297
- return {
298
- goal: match.description ?? "",
299
- lifecycle: match.state === "closed" ? "complete" : "active"
300
- };
301
- }
302
-
303
294
  // ../../packages/aeg-forge-state/src/labels.ts
304
295
  var LABELS = [
305
296
  {
@@ -542,6 +533,66 @@ function resolveTaskIssueRef(title, labels) {
542
533
  return { trancheSlug, taskId };
543
534
  }
544
535
 
536
+ // ../../packages/aeg-forge-state/src/fetch-milestone.ts
537
+ var INTENTS_HEADING = /^#{1,6}\s*Tranche intents\s*$/im;
538
+ var NEXT_HEADING = /^#{1,6}\s+\S/m;
539
+ var INTENT_BULLET = /^-\s+([a-z0-9][a-z0-9-]*)\s*:\s*(.+)$/i;
540
+ function intentGoalForSlug(description, slug) {
541
+ const text = stripCode(description, { inlineSpans: "keep" });
542
+ const start = text.match(INTENTS_HEADING);
543
+ if (!start || start.index === undefined)
544
+ return "";
545
+ const rest = text.slice(start.index + start[0].length);
546
+ const next = rest.match(NEXT_HEADING);
547
+ const section = rest.slice(0, next && next.index !== undefined ? next.index : rest.length);
548
+ for (const line of section.split(`
549
+ `)) {
550
+ const trimmed = line.trim();
551
+ if (trimmed.length === 0)
552
+ continue;
553
+ const m = trimmed.match(INTENT_BULLET);
554
+ if (m && (m[1] ?? "").toLowerCase() === slug.toLowerCase())
555
+ return (m[2] ?? "").trim();
556
+ }
557
+ return "";
558
+ }
559
+ function matchesLegacyMilestone(milestones, slug) {
560
+ return milestones.find((m) => m.title === slug) ?? null;
561
+ }
562
+ function factsFromLegacyMilestone(milestone) {
563
+ return {
564
+ goal: milestone.description ?? "",
565
+ lifecycle: milestone.state === "closed" ? "complete" : "active"
566
+ };
567
+ }
568
+ function resolveLegacyFacts(milestone, labelIssues) {
569
+ if (milestone.state === "closed" && labelIssues.length > 0) {
570
+ return { goal: milestone.description ?? "", lifecycle: lifecycleFromIssues(labelIssues) };
571
+ }
572
+ return factsFromLegacyMilestone(milestone);
573
+ }
574
+ function lifecycleFromIssues(issues) {
575
+ if (issues.length === 0)
576
+ return "planned";
577
+ return issues.some((i) => i.state === "OPEN") ? "active" : "complete";
578
+ }
579
+ function goalFromMilestones(milestones, slug) {
580
+ for (const m of milestones) {
581
+ const goal = intentGoalForSlug(m.description ?? "", slug);
582
+ if (goal)
583
+ return goal;
584
+ }
585
+ return "";
586
+ }
587
+ function findMilestoneForSlug(owner, repo, slug) {
588
+ const milestones = ghApiGet(`repos/${owner}/${repo}/milestones?state=all&per_page=100`);
589
+ const legacy = matchesLegacyMilestone(milestones, slug);
590
+ const issues = ghIssueListByLabel(owner, repo, trancheLabel(slug));
591
+ if (legacy)
592
+ return resolveLegacyFacts(legacy, issues);
593
+ return { goal: goalFromMilestones(milestones, slug), lifecycle: lifecycleFromIssues(issues) };
594
+ }
595
+
545
596
  // ../../packages/aeg-forge-state/src/derive-from-forge.ts
546
597
  function trancheFromIssues(slug, issues, known) {
547
598
  return {
@@ -2725,16 +2776,18 @@ function checkDeadBranchPush(input) {
2725
2776
  }
2726
2777
  // ../../packages/aeg-core/src/reader-resolvable-prose.ts
2727
2778
  var SHIPS_PREFIX = "aeg-root/";
2728
- var SHIPS_ARCHIVE_PREFIX = "aeg-root/tranches/completed/";
2779
+ function shipsArchivePrefix(shipsPrefix) {
2780
+ return `${shipsPrefix}tranches/completed/`;
2781
+ }
2729
2782
  function isSpecFile2(path) {
2730
2783
  return path.startsWith("apps/") && path.includes("/specs/") && path.endsWith(".md");
2731
2784
  }
2732
2785
  function isClaudeMdFile(path) {
2733
2786
  return path === "CLAUDE.md" || path.endsWith("/CLAUDE.md");
2734
2787
  }
2735
- function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix) {
2736
- if (path.startsWith(SHIPS_PREFIX)) {
2737
- return path.startsWith(SHIPS_ARCHIVE_PREFIX) ? "internal" : "ships";
2788
+ function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix, shipsPrefix = SHIPS_PREFIX) {
2789
+ if (path.startsWith(shipsPrefix)) {
2790
+ return path.startsWith(shipsArchivePrefix(shipsPrefix)) ? "internal" : "ships";
2738
2791
  }
2739
2792
  if (path.startsWith(readerFacingPrefix) && path.endsWith(readerFacingSuffix)) {
2740
2793
  return "reader-facing";
@@ -2769,7 +2822,7 @@ function legacySlugPattern(legacySlugs) {
2769
2822
  const alternation = legacySlugs.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
2770
2823
  return new RegExp(`(^|[^a-zA-Z0-9\\n-])(${alternation})([^a-zA-Z0-9\\n-]|$)`, "g");
2771
2824
  }
2772
- function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs = []) {
2825
+ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs = [], shipsPrefix = SHIPS_PREFIX) {
2773
2826
  const findings = [];
2774
2827
  const legacyPattern = legacySlugPattern(legacySlugs);
2775
2828
  const patterns = [
@@ -2778,7 +2831,7 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
2778
2831
  ...legacyPattern ? [{ pattern: legacyPattern, what: "an internal tranche slug", group: 2 }] : []
2779
2832
  ];
2780
2833
  for (const file of files) {
2781
- const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix);
2834
+ const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
2782
2835
  if (!cls || !SWEPT_CLASSES.has(cls))
2783
2836
  continue;
2784
2837
  const scrubbed = stripNonProse(file.path, file.content);
@@ -2824,10 +2877,10 @@ function definesOrLinksGlossary(content, term) {
2824
2877
  return true;
2825
2878
  return /docs\/glossary|glossary\.md/i.test(content);
2826
2879
  }
2827
- function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix) {
2880
+ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, shipsPrefix = SHIPS_PREFIX) {
2828
2881
  const findings = [];
2829
2882
  for (const file of files) {
2830
- const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix);
2883
+ const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
2831
2884
  if (!cls || !SWEPT_CLASSES.has(cls))
2832
2885
  continue;
2833
2886
  const scrubbed = stripNonProse(file.path, file.content);
@@ -2846,10 +2899,10 @@ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, read
2846
2899
  }
2847
2900
  return findings;
2848
2901
  }
2849
- function checkReaderResolvableProse(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, legacySlugs = []) {
2902
+ function checkReaderResolvableProse(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, legacySlugs = [], shipsPrefix = SHIPS_PREFIX) {
2850
2903
  return [
2851
- ...checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs),
2852
- ...checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix)
2904
+ ...checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs, shipsPrefix),
2905
+ ...checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, shipsPrefix)
2853
2906
  ];
2854
2907
  }
2855
2908
  function parseGlossaryTerms(glossaryContent) {
@@ -2863,6 +2916,101 @@ function parseGlossaryTerms(glossaryContent) {
2863
2916
  }
2864
2917
  return terms;
2865
2918
  }
2919
+ // ../../packages/aeg-core/src/retired-vocabulary.ts
2920
+ var RETIRED_PATTERNS = [
2921
+ String.raw`\bD-[0-9]{3}\b`,
2922
+ "D-(###|NNN|nnn|xxx)",
2923
+ "decision (entry|logged)",
2924
+ "decision-log entry",
2925
+ "decision log entry",
2926
+ "decision log(s|ged|ging)?([^a-z]|$)",
2927
+ "decision-log",
2928
+ "decision entr",
2929
+ "decisions? (are |is )?logged",
2930
+ "log entries",
2931
+ String.raw`decisions\.md`,
2932
+ "decisions-legacy",
2933
+ "CONTRADICTION",
2934
+ "assumes Tier 3",
2935
+ "Lock: ?YES",
2936
+ "Lock: ?NO",
2937
+ "lock approvals?",
2938
+ "approves locks",
2939
+ "Conforms to lock",
2940
+ "Challenges lock",
2941
+ "checkDecisionNumbersFresh",
2942
+ String.raw`roles/team-leader\.md`,
2943
+ String.raw`\(,`,
2944
+ String.raw`,\)`,
2945
+ String.raw`\(\.\)`
2946
+ ];
2947
+ var RETIRED_EXEMPT_SUBSTRINGS = [
2948
+ "docs/decisions-legacy.md",
2949
+ "apps/herald-ai/docs/",
2950
+ "apps/vada-ai/docs/",
2951
+ "aeg-root/tranches/completed/",
2952
+ "packages/aeg-core/src/docs/published-prose",
2953
+ "packages/aeg-core/src/retired-vocabulary.test.ts",
2954
+ "packages/aeg-core/src/retired-vocabulary.ts",
2955
+ "/fixtures/",
2956
+ "/node_modules/",
2957
+ "/.next/",
2958
+ "/.turbo/"
2959
+ ];
2960
+ var PATTERN_EXEMPT = {
2961
+ [String.raw`\bD-[0-9]{3}\b`]: ["./CLAUDE.md"],
2962
+ [String.raw`decisions\.md`]: [
2963
+ "docs-index.md",
2964
+ "apps/vada-ai/specs/legacy/README.md",
2965
+ "apps/vada-ai/specs/vada-product-spec.md",
2966
+ "apps/vada-ai/web/CLAUDE.md",
2967
+ "apps/vada-ai/CLAUDE.md"
2968
+ ],
2969
+ "decisions-legacy": [
2970
+ "packages/aeg-core/src/file-classify.ts",
2971
+ "packages/aeg-core/src/file-classify.test.ts",
2972
+ "packages/aeg-core/src/pr-tier.test.ts",
2973
+ "docs-index.md",
2974
+ ".claude/skills/database/SKILL.md",
2975
+ ".claude/skills/vada-architecture/SKILL.md",
2976
+ "apps/vada-ai/specs/vada-byok-principles.md",
2977
+ "apps/vada-ai/web/CLAUDE.md",
2978
+ "apps/vada-ai/CLAUDE.md"
2979
+ ],
2980
+ CONTRADICTION: ["apps/vada-ai/specs/vada-reviewers-spec.md"]
2981
+ };
2982
+ function lineNumbers(content, pattern) {
2983
+ const out = [];
2984
+ const lines = content.split(`
2985
+ `);
2986
+ for (let i = 0;i < lines.length; i++) {
2987
+ if (pattern.test(lines[i] ?? ""))
2988
+ out.push(i + 1);
2989
+ }
2990
+ return out;
2991
+ }
2992
+ function scanRetiredVocabulary(files) {
2993
+ const findings = [];
2994
+ for (const file of files) {
2995
+ if (RETIRED_EXEMPT_SUBSTRINGS.some((e) => file.path.includes(e)))
2996
+ continue;
2997
+ for (const pattern of RETIRED_PATTERNS) {
2998
+ const patternExempt = PATTERN_EXEMPT[pattern] ?? [];
2999
+ if (patternExempt.some((e) => file.path.includes(e)))
3000
+ continue;
3001
+ const re = new RegExp(pattern);
3002
+ for (const line of lineNumbers(file.content, re)) {
3003
+ findings.push({
3004
+ file: file.path,
3005
+ line,
3006
+ pattern,
3007
+ message: `claims a retired AEG mechanism is still live (matches /${pattern}/)`
3008
+ });
3009
+ }
3010
+ }
3011
+ }
3012
+ return findings;
3013
+ }
2866
3014
  // ../../packages/aeg-core/src/dispatch-gate.ts
2867
3015
  function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
2868
3016
  return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);
@@ -3125,9 +3273,15 @@ function emitCheckError(error) {
3125
3273
 
3126
3274
  // src/lib/config.ts
3127
3275
  import { execFileSync as execFileSync2 } from "node:child_process";
3276
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3128
3277
  import { homedir } from "node:os";
3129
3278
  import { dirname, join } from "node:path";
3130
3279
  import { z } from "zod";
3280
+
3281
+ // src/lib/agent-vendors.ts
3282
+ var AGENT_VENDORS = ["skills", "claude", "gemini"];
3283
+
3284
+ // src/lib/config.ts
3131
3285
  var EnvEntrySchema = z.union([
3132
3286
  z.literal(true),
3133
3287
  z.object({ optional: z.literal(true) }),
@@ -3166,6 +3320,11 @@ var CheckEntrySchema = z.object({
3166
3320
  }
3167
3321
  }
3168
3322
  });
3323
+ var RoleEntrySchema = z.object({
3324
+ contract: z.string().refine((p) => p.includes("/"), {
3325
+ message: 'must be a path (contain at least one "/"), not a bare filename'
3326
+ })
3327
+ });
3169
3328
  var BRIEF_BUILTINS = [
3170
3329
  "tier",
3171
3330
  "testPlan",
@@ -3180,7 +3339,8 @@ var BRIEF_BUILTINS = [
3180
3339
  "for",
3181
3340
  "closesN",
3182
3341
  "premiseCoverage",
3183
- "issueRationale"
3342
+ "issueRationale",
3343
+ "milestoneShape"
3184
3344
  ];
3185
3345
  var BriefSectionSchema = z.union([
3186
3346
  z.object({ builtin: z.enum(BRIEF_BUILTINS) }),
@@ -3191,6 +3351,7 @@ var BriefSectionSchema = z.union([
3191
3351
  var BriefSchemaSchema = z.object({
3192
3352
  pr: z.object({ sections: z.array(BriefSectionSchema) }).optional(),
3193
3353
  issue: z.object({ sections: z.array(BriefSectionSchema) }).optional(),
3354
+ milestone: z.object({ sections: z.array(BriefSectionSchema) }).optional(),
3194
3355
  ack: z.array(z.enum(BRIEF_BUILTINS)).optional()
3195
3356
  });
3196
3357
  function isSafeRepoRelPath(p) {
@@ -3212,7 +3373,8 @@ var ManagedManifestSchema = z.object({
3212
3373
  version: z.number().int().positive(),
3213
3374
  files: z.array(SafeRepoRelPath),
3214
3375
  blocks: z.array(ManagedBlockRecordSchema),
3215
- labels: z.array(z.string())
3376
+ labels: z.array(z.string()),
3377
+ agents: z.array(z.enum(AGENT_VENDORS)).optional()
3216
3378
  });
3217
3379
  var VinayaConfigSchema = z.object({
3218
3380
  rings: z.object({
@@ -3220,16 +3382,79 @@ var VinayaConfigSchema = z.object({
3220
3382
  ring2_asyncAudits: z.boolean()
3221
3383
  }).optional(),
3222
3384
  checks: z.record(z.string(), CheckEntrySchema).optional(),
3385
+ roles: z.record(z.string(), RoleEntrySchema).optional(),
3223
3386
  briefSchema: BriefSchemaSchema.optional(),
3224
3387
  managed: ManagedManifestSchema.optional(),
3225
3388
  principals: z.array(z.string()).min(1).optional(),
3226
3389
  releaseActor: z.string().min(1).optional(),
3227
3390
  ci: z.object({ setup: z.string().min(1) }).optional(),
3228
- blastRadius: z.object({ extraDomains: z.array(z.string()).optional() }).optional()
3391
+ blastRadius: z.object({ extraDomains: z.array(z.string()).optional() }).optional(),
3392
+ proseGates: z.object({
3393
+ doctrineRoot: z.string().min(1).optional(),
3394
+ readerFacingPrefix: z.string().min(1).optional(),
3395
+ readerFacingSuffix: z.string().min(1).optional(),
3396
+ legacySlugDir: z.string().min(1).optional()
3397
+ }).optional()
3229
3398
  });
3230
3399
  var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
3231
3400
  var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");
3232
3401
  var LOCAL_CONFIG_FILENAME = "vinaya.config.json";
3402
+ function findLocalConfig() {
3403
+ let dir = process.cwd();
3404
+ while (true) {
3405
+ const candidate = join(dir, LOCAL_CONFIG_FILENAME);
3406
+ if (existsSync(candidate))
3407
+ return candidate;
3408
+ if (existsSync(join(dir, ".git")))
3409
+ return null;
3410
+ const parent = dirname(dir);
3411
+ if (parent === dir)
3412
+ return null;
3413
+ dir = parent;
3414
+ }
3415
+ }
3416
+ function configPath() {
3417
+ const local = findLocalConfig();
3418
+ if (local)
3419
+ return local;
3420
+ if (existsSync(GLOBAL_CONFIG_PATH))
3421
+ return GLOBAL_CONFIG_PATH;
3422
+ return null;
3423
+ }
3424
+ function globalChecksIgnoredWarning(path) {
3425
+ return `${path}: "checks" registration in the global config is ignored — checks may only be registered from a repo-local vinaya.config.json.`;
3426
+ }
3427
+ function globalPrincipalsIgnoredWarning(path) {
3428
+ return `${path}: "principals" in the global config is ignored — principals may only be declared from a repo-local vinaya.config.json.`;
3429
+ }
3430
+ function globalRolesIgnoredWarning(path) {
3431
+ return `${path}: "roles" registration in the global config is ignored — roles may only be registered from a repo-local vinaya.config.json.`;
3432
+ }
3433
+ function globalReleaseActorIgnoredWarning(path) {
3434
+ return `${path}: "releaseActor" in the global config is ignored — releaseActor may only be declared from a repo-local vinaya.config.json.`;
3435
+ }
3436
+ function stripGlobalOnlyKeys(config, path) {
3437
+ if (path !== GLOBAL_CONFIG_PATH)
3438
+ return config;
3439
+ let result = config;
3440
+ if (result.checks && Object.keys(result.checks).length > 0) {
3441
+ console.error(`⚠ ${globalChecksIgnoredWarning(path)}`);
3442
+ result = { ...result, checks: undefined };
3443
+ }
3444
+ if (result.roles && Object.keys(result.roles).length > 0) {
3445
+ console.error(`⚠ ${globalRolesIgnoredWarning(path)}`);
3446
+ result = { ...result, roles: undefined };
3447
+ }
3448
+ if (result.principals && result.principals.length > 0) {
3449
+ console.error(`⚠ ${globalPrincipalsIgnoredWarning(path)}`);
3450
+ result = { ...result, principals: undefined };
3451
+ }
3452
+ if (result.releaseActor) {
3453
+ console.error(`⚠ ${globalReleaseActorIgnoredWarning(path)}`);
3454
+ result = { ...result, releaseActor: undefined };
3455
+ }
3456
+ return result;
3457
+ }
3233
3458
  function resolvePrincipalAllowlist(config) {
3234
3459
  return config?.principals ?? PRINCIPAL_ALLOWLIST;
3235
3460
  }
@@ -3302,6 +3527,17 @@ function loadTrustAnchorConfig(fetcher = ghFetchTrustAnchorConfig) {
3302
3527
  return null;
3303
3528
  }
3304
3529
  }
3530
+ function loadConfig() {
3531
+ const path = configPath();
3532
+ if (!path)
3533
+ return null;
3534
+ try {
3535
+ const raw = JSON.parse(readFileSync(path, "utf-8"));
3536
+ return stripGlobalOnlyKeys(VinayaConfigSchema.parse(raw), path);
3537
+ } catch {
3538
+ return null;
3539
+ }
3540
+ }
3305
3541
 
3306
3542
  // src/checks/bin/check-review-gate.ts
3307
3543
  var CHECK_NAME = "review-gate";
@@ -273,6 +273,9 @@ function issueListByLabelArgs(owner, repo, label) {
273
273
  "200"
274
274
  ];
275
275
  }
276
+ function ghIssueListByLabel(owner, repo, label) {
277
+ return JSON.parse(run(issueListByLabelArgs(owner, repo, label)));
278
+ }
276
279
  async function ghIssueListByLabelAsync(owner, repo, label) {
277
280
  return JSON.parse(await runAsync(issueListByLabelArgs(owner, repo, label)));
278
281
  }
@@ -288,18 +291,6 @@ async function ghIssueListByAnyLabelAsync(owner, repo, labels) {
288
291
  return dedupeByNumber(perLabel.flat());
289
292
  }
290
293
 
291
- // ../../packages/aeg-forge-state/src/fetch-milestone.ts
292
- function findMilestoneForSlug(owner, repo, slug) {
293
- const milestones = ghApiGet(`repos/${owner}/${repo}/milestones?state=all&per_page=100`);
294
- const match = milestones.find((m) => m.title === slug);
295
- if (!match)
296
- return null;
297
- return {
298
- goal: match.description ?? "",
299
- lifecycle: match.state === "closed" ? "complete" : "active"
300
- };
301
- }
302
-
303
294
  // ../../packages/aeg-forge-state/src/labels.ts
304
295
  var LABELS = [
305
296
  {
@@ -542,6 +533,66 @@ function resolveTaskIssueRef(title, labels) {
542
533
  return { trancheSlug, taskId };
543
534
  }
544
535
 
536
+ // ../../packages/aeg-forge-state/src/fetch-milestone.ts
537
+ var INTENTS_HEADING = /^#{1,6}\s*Tranche intents\s*$/im;
538
+ var NEXT_HEADING = /^#{1,6}\s+\S/m;
539
+ var INTENT_BULLET = /^-\s+([a-z0-9][a-z0-9-]*)\s*:\s*(.+)$/i;
540
+ function intentGoalForSlug(description, slug) {
541
+ const text = stripCode(description, { inlineSpans: "keep" });
542
+ const start = text.match(INTENTS_HEADING);
543
+ if (!start || start.index === undefined)
544
+ return "";
545
+ const rest = text.slice(start.index + start[0].length);
546
+ const next = rest.match(NEXT_HEADING);
547
+ const section = rest.slice(0, next && next.index !== undefined ? next.index : rest.length);
548
+ for (const line of section.split(`
549
+ `)) {
550
+ const trimmed = line.trim();
551
+ if (trimmed.length === 0)
552
+ continue;
553
+ const m = trimmed.match(INTENT_BULLET);
554
+ if (m && (m[1] ?? "").toLowerCase() === slug.toLowerCase())
555
+ return (m[2] ?? "").trim();
556
+ }
557
+ return "";
558
+ }
559
+ function matchesLegacyMilestone(milestones, slug) {
560
+ return milestones.find((m) => m.title === slug) ?? null;
561
+ }
562
+ function factsFromLegacyMilestone(milestone) {
563
+ return {
564
+ goal: milestone.description ?? "",
565
+ lifecycle: milestone.state === "closed" ? "complete" : "active"
566
+ };
567
+ }
568
+ function resolveLegacyFacts(milestone, labelIssues) {
569
+ if (milestone.state === "closed" && labelIssues.length > 0) {
570
+ return { goal: milestone.description ?? "", lifecycle: lifecycleFromIssues(labelIssues) };
571
+ }
572
+ return factsFromLegacyMilestone(milestone);
573
+ }
574
+ function lifecycleFromIssues(issues) {
575
+ if (issues.length === 0)
576
+ return "planned";
577
+ return issues.some((i) => i.state === "OPEN") ? "active" : "complete";
578
+ }
579
+ function goalFromMilestones(milestones, slug) {
580
+ for (const m of milestones) {
581
+ const goal = intentGoalForSlug(m.description ?? "", slug);
582
+ if (goal)
583
+ return goal;
584
+ }
585
+ return "";
586
+ }
587
+ function findMilestoneForSlug(owner, repo, slug) {
588
+ const milestones = ghApiGet(`repos/${owner}/${repo}/milestones?state=all&per_page=100`);
589
+ const legacy = matchesLegacyMilestone(milestones, slug);
590
+ const issues = ghIssueListByLabel(owner, repo, trancheLabel(slug));
591
+ if (legacy)
592
+ return resolveLegacyFacts(legacy, issues);
593
+ return { goal: goalFromMilestones(milestones, slug), lifecycle: lifecycleFromIssues(issues) };
594
+ }
595
+
545
596
  // ../../packages/aeg-forge-state/src/derive-from-forge.ts
546
597
  function trancheFromIssues(slug, issues, known) {
547
598
  return {
@@ -2725,16 +2776,18 @@ function checkDeadBranchPush(input) {
2725
2776
  }
2726
2777
  // ../../packages/aeg-core/src/reader-resolvable-prose.ts
2727
2778
  var SHIPS_PREFIX = "aeg-root/";
2728
- var SHIPS_ARCHIVE_PREFIX = "aeg-root/tranches/completed/";
2779
+ function shipsArchivePrefix(shipsPrefix) {
2780
+ return `${shipsPrefix}tranches/completed/`;
2781
+ }
2729
2782
  function isSpecFile2(path) {
2730
2783
  return path.startsWith("apps/") && path.includes("/specs/") && path.endsWith(".md");
2731
2784
  }
2732
2785
  function isClaudeMdFile(path) {
2733
2786
  return path === "CLAUDE.md" || path.endsWith("/CLAUDE.md");
2734
2787
  }
2735
- function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix) {
2736
- if (path.startsWith(SHIPS_PREFIX)) {
2737
- return path.startsWith(SHIPS_ARCHIVE_PREFIX) ? "internal" : "ships";
2788
+ function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix, shipsPrefix = SHIPS_PREFIX) {
2789
+ if (path.startsWith(shipsPrefix)) {
2790
+ return path.startsWith(shipsArchivePrefix(shipsPrefix)) ? "internal" : "ships";
2738
2791
  }
2739
2792
  if (path.startsWith(readerFacingPrefix) && path.endsWith(readerFacingSuffix)) {
2740
2793
  return "reader-facing";
@@ -2769,7 +2822,7 @@ function legacySlugPattern(legacySlugs) {
2769
2822
  const alternation = legacySlugs.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
2770
2823
  return new RegExp(`(^|[^a-zA-Z0-9\\n-])(${alternation})([^a-zA-Z0-9\\n-]|$)`, "g");
2771
2824
  }
2772
- function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs = []) {
2825
+ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs = [], shipsPrefix = SHIPS_PREFIX) {
2773
2826
  const findings = [];
2774
2827
  const legacyPattern = legacySlugPattern(legacySlugs);
2775
2828
  const patterns = [
@@ -2778,7 +2831,7 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
2778
2831
  ...legacyPattern ? [{ pattern: legacyPattern, what: "an internal tranche slug", group: 2 }] : []
2779
2832
  ];
2780
2833
  for (const file of files) {
2781
- const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix);
2834
+ const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
2782
2835
  if (!cls || !SWEPT_CLASSES.has(cls))
2783
2836
  continue;
2784
2837
  const scrubbed = stripNonProse(file.path, file.content);
@@ -2824,10 +2877,10 @@ function definesOrLinksGlossary(content, term) {
2824
2877
  return true;
2825
2878
  return /docs\/glossary|glossary\.md/i.test(content);
2826
2879
  }
2827
- function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix) {
2880
+ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, shipsPrefix = SHIPS_PREFIX) {
2828
2881
  const findings = [];
2829
2882
  for (const file of files) {
2830
- const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix);
2883
+ const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
2831
2884
  if (!cls || !SWEPT_CLASSES.has(cls))
2832
2885
  continue;
2833
2886
  const scrubbed = stripNonProse(file.path, file.content);
@@ -2846,10 +2899,10 @@ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, read
2846
2899
  }
2847
2900
  return findings;
2848
2901
  }
2849
- function checkReaderResolvableProse(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, legacySlugs = []) {
2902
+ function checkReaderResolvableProse(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, legacySlugs = [], shipsPrefix = SHIPS_PREFIX) {
2850
2903
  return [
2851
- ...checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs),
2852
- ...checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix)
2904
+ ...checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs, shipsPrefix),
2905
+ ...checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, shipsPrefix)
2853
2906
  ];
2854
2907
  }
2855
2908
  function parseGlossaryTerms(glossaryContent) {
@@ -2863,6 +2916,101 @@ function parseGlossaryTerms(glossaryContent) {
2863
2916
  }
2864
2917
  return terms;
2865
2918
  }
2919
+ // ../../packages/aeg-core/src/retired-vocabulary.ts
2920
+ var RETIRED_PATTERNS = [
2921
+ String.raw`\bD-[0-9]{3}\b`,
2922
+ "D-(###|NNN|nnn|xxx)",
2923
+ "decision (entry|logged)",
2924
+ "decision-log entry",
2925
+ "decision log entry",
2926
+ "decision log(s|ged|ging)?([^a-z]|$)",
2927
+ "decision-log",
2928
+ "decision entr",
2929
+ "decisions? (are |is )?logged",
2930
+ "log entries",
2931
+ String.raw`decisions\.md`,
2932
+ "decisions-legacy",
2933
+ "CONTRADICTION",
2934
+ "assumes Tier 3",
2935
+ "Lock: ?YES",
2936
+ "Lock: ?NO",
2937
+ "lock approvals?",
2938
+ "approves locks",
2939
+ "Conforms to lock",
2940
+ "Challenges lock",
2941
+ "checkDecisionNumbersFresh",
2942
+ String.raw`roles/team-leader\.md`,
2943
+ String.raw`\(,`,
2944
+ String.raw`,\)`,
2945
+ String.raw`\(\.\)`
2946
+ ];
2947
+ var RETIRED_EXEMPT_SUBSTRINGS = [
2948
+ "docs/decisions-legacy.md",
2949
+ "apps/herald-ai/docs/",
2950
+ "apps/vada-ai/docs/",
2951
+ "aeg-root/tranches/completed/",
2952
+ "packages/aeg-core/src/docs/published-prose",
2953
+ "packages/aeg-core/src/retired-vocabulary.test.ts",
2954
+ "packages/aeg-core/src/retired-vocabulary.ts",
2955
+ "/fixtures/",
2956
+ "/node_modules/",
2957
+ "/.next/",
2958
+ "/.turbo/"
2959
+ ];
2960
+ var PATTERN_EXEMPT = {
2961
+ [String.raw`\bD-[0-9]{3}\b`]: ["./CLAUDE.md"],
2962
+ [String.raw`decisions\.md`]: [
2963
+ "docs-index.md",
2964
+ "apps/vada-ai/specs/legacy/README.md",
2965
+ "apps/vada-ai/specs/vada-product-spec.md",
2966
+ "apps/vada-ai/web/CLAUDE.md",
2967
+ "apps/vada-ai/CLAUDE.md"
2968
+ ],
2969
+ "decisions-legacy": [
2970
+ "packages/aeg-core/src/file-classify.ts",
2971
+ "packages/aeg-core/src/file-classify.test.ts",
2972
+ "packages/aeg-core/src/pr-tier.test.ts",
2973
+ "docs-index.md",
2974
+ ".claude/skills/database/SKILL.md",
2975
+ ".claude/skills/vada-architecture/SKILL.md",
2976
+ "apps/vada-ai/specs/vada-byok-principles.md",
2977
+ "apps/vada-ai/web/CLAUDE.md",
2978
+ "apps/vada-ai/CLAUDE.md"
2979
+ ],
2980
+ CONTRADICTION: ["apps/vada-ai/specs/vada-reviewers-spec.md"]
2981
+ };
2982
+ function lineNumbers(content, pattern) {
2983
+ const out = [];
2984
+ const lines = content.split(`
2985
+ `);
2986
+ for (let i = 0;i < lines.length; i++) {
2987
+ if (pattern.test(lines[i] ?? ""))
2988
+ out.push(i + 1);
2989
+ }
2990
+ return out;
2991
+ }
2992
+ function scanRetiredVocabulary(files) {
2993
+ const findings = [];
2994
+ for (const file of files) {
2995
+ if (RETIRED_EXEMPT_SUBSTRINGS.some((e) => file.path.includes(e)))
2996
+ continue;
2997
+ for (const pattern of RETIRED_PATTERNS) {
2998
+ const patternExempt = PATTERN_EXEMPT[pattern] ?? [];
2999
+ if (patternExempt.some((e) => file.path.includes(e)))
3000
+ continue;
3001
+ const re = new RegExp(pattern);
3002
+ for (const line of lineNumbers(file.content, re)) {
3003
+ findings.push({
3004
+ file: file.path,
3005
+ line,
3006
+ pattern,
3007
+ message: `claims a retired AEG mechanism is still live (matches /${pattern}/)`
3008
+ });
3009
+ }
3010
+ }
3011
+ }
3012
+ return findings;
3013
+ }
2866
3014
  // ../../packages/aeg-core/src/dispatch-gate.ts
2867
3015
  function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
2868
3016
  return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);