@attalabs/vinaya 0.19.0 → 0.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +37 -1
  2. package/aeg-root/contracts/architect-planner.md +1 -1
  3. package/aeg-root/enforcement.md +3 -2
  4. package/aeg-root/milestone-model.md +75 -0
  5. package/aeg-root/roles/architect.md +4 -4
  6. package/aeg-root/skills/aeg/SKILL.md +1 -1
  7. package/aeg-root/task-model.md +45 -0
  8. package/aeg-root/tranche-model.md +16 -11
  9. package/dist/checks/bin/check-body-bare-digits.js +204 -15
  10. package/dist/checks/bin/check-branch-topology.js +116 -13
  11. package/dist/checks/bin/check-brief-shape.js +116 -13
  12. package/dist/checks/bin/check-closes-n.js +116 -13
  13. package/dist/checks/bin/check-coherence.js +207 -18
  14. package/dist/checks/bin/check-dead-branch-push.js +116 -13
  15. package/dist/checks/bin/check-dispatch-readiness.js +204 -15
  16. package/dist/checks/bin/check-doc-coverage-push.js +208 -19
  17. package/dist/checks/bin/check-doc-coverage.js +208 -19
  18. package/dist/checks/bin/check-evidence-fresh.js +116 -13
  19. package/dist/checks/bin/check-first-push-dispatch.js +204 -15
  20. package/dist/checks/bin/check-issue-assignment.js +116 -13
  21. package/dist/checks/bin/check-no-disk-state.js +116 -13
  22. package/dist/checks/bin/check-reader-resolvable-prose.js +410 -36
  23. package/dist/checks/bin/check-registry-gates.js +116 -13
  24. package/dist/checks/bin/check-retired-vocabulary.js +3595 -0
  25. package/dist/checks/bin/check-review-gate.js +204 -15
  26. package/dist/checks/bin/check-single-plan-pr.js +116 -13
  27. package/dist/checks/bin/check-test-plan.js +116 -13
  28. package/dist/index.js +2362 -1602
  29. package/package.json +1 -1
  30. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  31. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  32. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  33. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  34. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  35. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  36. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  37. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  38. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  39. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  40. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  41. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
  42. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  43. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  44. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  45. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  46. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  47. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  48. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  49. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  50. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  51. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  52. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  53. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  54. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  55. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  56. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  57. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  58. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  59. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  60. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  61. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  62. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  63. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  64. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  65. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  66. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/[root-of-the-server]__0e95dv5._.js +1 -1
  67. 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
  68. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__029-1ch._.js +1 -1
  69. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__0ulzsom._.js → [root-of-the-server]__0h3web3._.js} +1 -1
  70. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0q56ohl._.js +1 -1
  71. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1_ojzp1._.js +1 -1
  72. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1d_8nm4._.js +1 -1
  73. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1zkwtx8._.js +1 -1
  74. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__210ehg9._.js +1 -1
  75. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_00yjcv1._.js +1 -1
  76. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0rix02i._.js +1 -1
  77. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_18-d2be._.js +1 -1
  78. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1fqw88f._.js +5 -5
  79. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1gw6vc9._.js +1 -1
  80. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  81. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  82. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  83. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  84. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  85. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{2r36w820aqwb9.js → 1hko4y648-9sa.js} +1 -1
  86. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3ocvtlhgckh81.js → 20f41k165euzy.js} +1 -1
  87. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{2xoclb4g7d9n7.js → 3mhcu_-shkfgs.js} +1 -1
  88. package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
  89. package/templates/noop-check.template.ts +18 -0
  90. package/templates/role.template.md +20 -0
  91. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{RISz5nirLr1mUfMqzSaFS → Ye7cOzO7rtvyiBouNOFES}/_buildManifest.js +0 -0
  92. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{RISz5nirLr1mUfMqzSaFS → Ye7cOzO7rtvyiBouNOFES}/_clientMiddlewareManifest.js +0 -0
  93. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{RISz5nirLr1mUfMqzSaFS → Ye7cOzO7rtvyiBouNOFES}/_ssgManifest.js +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/checks/bin/check-doc-coverage.ts
4
4
  import { execFileSync as execFileSync4 } from "node:child_process";
5
- import { existsSync, readFileSync } from "node:fs";
5
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
6
6
  // ../../packages/aeg-forge-state/src/strip-code.ts
7
7
  function maskCode(body) {
8
8
  const fill = (line) => " ".repeat(line.length);
@@ -566,6 +566,12 @@ function factsFromLegacyMilestone(milestone) {
566
566
  lifecycle: milestone.state === "closed" ? "complete" : "active"
567
567
  };
568
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
+ }
569
575
  function lifecycleFromIssues(issues) {
570
576
  if (issues.length === 0)
571
577
  return "planned";
@@ -582,9 +588,9 @@ function goalFromMilestones(milestones, slug) {
582
588
  function findMilestoneForSlug(owner, repo, slug) {
583
589
  const milestones = ghApiGet(`repos/${owner}/${repo}/milestones?state=all&per_page=100`);
584
590
  const legacy = matchesLegacyMilestone(milestones, slug);
585
- if (legacy)
586
- return factsFromLegacyMilestone(legacy);
587
591
  const issues = ghIssueListByLabel(owner, repo, trancheLabel(slug));
592
+ if (legacy)
593
+ return resolveLegacyFacts(legacy, issues);
588
594
  return { goal: goalFromMilestones(milestones, slug), lifecycle: lifecycleFromIssues(issues) };
589
595
  }
590
596
 
@@ -2771,16 +2777,18 @@ function checkDeadBranchPush(input) {
2771
2777
  }
2772
2778
  // ../../packages/aeg-core/src/reader-resolvable-prose.ts
2773
2779
  var SHIPS_PREFIX = "aeg-root/";
2774
- var SHIPS_ARCHIVE_PREFIX = "aeg-root/tranches/completed/";
2780
+ function shipsArchivePrefix(shipsPrefix) {
2781
+ return `${shipsPrefix}tranches/completed/`;
2782
+ }
2775
2783
  function isSpecFile2(path) {
2776
2784
  return path.startsWith("apps/") && path.includes("/specs/") && path.endsWith(".md");
2777
2785
  }
2778
2786
  function isClaudeMdFile(path) {
2779
2787
  return path === "CLAUDE.md" || path.endsWith("/CLAUDE.md");
2780
2788
  }
2781
- function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix) {
2782
- if (path.startsWith(SHIPS_PREFIX)) {
2783
- 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";
2784
2792
  }
2785
2793
  if (path.startsWith(readerFacingPrefix) && path.endsWith(readerFacingSuffix)) {
2786
2794
  return "reader-facing";
@@ -2815,7 +2823,7 @@ function legacySlugPattern(legacySlugs) {
2815
2823
  const alternation = legacySlugs.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
2816
2824
  return new RegExp(`(^|[^a-zA-Z0-9\\n-])(${alternation})([^a-zA-Z0-9\\n-]|$)`, "g");
2817
2825
  }
2818
- function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs = []) {
2826
+ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs = [], shipsPrefix = SHIPS_PREFIX) {
2819
2827
  const findings = [];
2820
2828
  const legacyPattern = legacySlugPattern(legacySlugs);
2821
2829
  const patterns = [
@@ -2824,7 +2832,7 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
2824
2832
  ...legacyPattern ? [{ pattern: legacyPattern, what: "an internal tranche slug", group: 2 }] : []
2825
2833
  ];
2826
2834
  for (const file of files) {
2827
- const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix);
2835
+ const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
2828
2836
  if (!cls || !SWEPT_CLASSES.has(cls))
2829
2837
  continue;
2830
2838
  const scrubbed = stripNonProse(file.path, file.content);
@@ -2870,10 +2878,10 @@ function definesOrLinksGlossary(content, term) {
2870
2878
  return true;
2871
2879
  return /docs\/glossary|glossary\.md/i.test(content);
2872
2880
  }
2873
- function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix) {
2881
+ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, shipsPrefix = SHIPS_PREFIX) {
2874
2882
  const findings = [];
2875
2883
  for (const file of files) {
2876
- const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix);
2884
+ const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
2877
2885
  if (!cls || !SWEPT_CLASSES.has(cls))
2878
2886
  continue;
2879
2887
  const scrubbed = stripNonProse(file.path, file.content);
@@ -2892,10 +2900,10 @@ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, read
2892
2900
  }
2893
2901
  return findings;
2894
2902
  }
2895
- function checkReaderResolvableProse(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, legacySlugs = []) {
2903
+ function checkReaderResolvableProse(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, legacySlugs = [], shipsPrefix = SHIPS_PREFIX) {
2896
2904
  return [
2897
- ...checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs),
2898
- ...checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix)
2905
+ ...checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs, shipsPrefix),
2906
+ ...checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, shipsPrefix)
2899
2907
  ];
2900
2908
  }
2901
2909
  function parseGlossaryTerms(glossaryContent) {
@@ -2909,6 +2917,101 @@ function parseGlossaryTerms(glossaryContent) {
2909
2917
  }
2910
2918
  return terms;
2911
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
+ }
2912
3015
  // ../../packages/aeg-core/src/dispatch-gate.ts
2913
3016
  function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
2914
3017
  return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);
@@ -3186,9 +3289,15 @@ function emitCheckError(error) {
3186
3289
 
3187
3290
  // src/lib/config.ts
3188
3291
  import { execFileSync as execFileSync3 } from "node:child_process";
3292
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3189
3293
  import { homedir } from "node:os";
3190
3294
  import { dirname, join } from "node:path";
3191
3295
  import { z } from "zod";
3296
+
3297
+ // src/lib/agent-vendors.ts
3298
+ var AGENT_VENDORS = ["skills", "claude", "gemini"];
3299
+
3300
+ // src/lib/config.ts
3192
3301
  var EnvEntrySchema = z.union([
3193
3302
  z.literal(true),
3194
3303
  z.object({ optional: z.literal(true) }),
@@ -3227,6 +3336,11 @@ var CheckEntrySchema = z.object({
3227
3336
  }
3228
3337
  }
3229
3338
  });
3339
+ var RoleEntrySchema = z.object({
3340
+ contract: z.string().refine((p) => p.includes("/"), {
3341
+ message: 'must be a path (contain at least one "/"), not a bare filename'
3342
+ })
3343
+ });
3230
3344
  var BRIEF_BUILTINS = [
3231
3345
  "tier",
3232
3346
  "testPlan",
@@ -3275,7 +3389,8 @@ var ManagedManifestSchema = z.object({
3275
3389
  version: z.number().int().positive(),
3276
3390
  files: z.array(SafeRepoRelPath),
3277
3391
  blocks: z.array(ManagedBlockRecordSchema),
3278
- labels: z.array(z.string())
3392
+ labels: z.array(z.string()),
3393
+ agents: z.array(z.enum(AGENT_VENDORS)).optional()
3279
3394
  });
3280
3395
  var VinayaConfigSchema = z.object({
3281
3396
  rings: z.object({
@@ -3283,16 +3398,79 @@ var VinayaConfigSchema = z.object({
3283
3398
  ring2_asyncAudits: z.boolean()
3284
3399
  }).optional(),
3285
3400
  checks: z.record(z.string(), CheckEntrySchema).optional(),
3401
+ roles: z.record(z.string(), RoleEntrySchema).optional(),
3286
3402
  briefSchema: BriefSchemaSchema.optional(),
3287
3403
  managed: ManagedManifestSchema.optional(),
3288
3404
  principals: z.array(z.string()).min(1).optional(),
3289
3405
  releaseActor: z.string().min(1).optional(),
3290
3406
  ci: z.object({ setup: z.string().min(1) }).optional(),
3291
- blastRadius: z.object({ extraDomains: z.array(z.string()).optional() }).optional()
3407
+ blastRadius: z.object({ extraDomains: z.array(z.string()).optional() }).optional(),
3408
+ proseGates: z.object({
3409
+ doctrineRoot: z.string().min(1).optional(),
3410
+ readerFacingPrefix: z.string().min(1).optional(),
3411
+ readerFacingSuffix: z.string().min(1).optional(),
3412
+ legacySlugDir: z.string().min(1).optional()
3413
+ }).optional()
3292
3414
  });
3293
3415
  var GLOBAL_VINAYA_HOME = join(homedir(), ".vinaya");
3294
3416
  var GLOBAL_CONFIG_PATH = join(GLOBAL_VINAYA_HOME, "config.json");
3295
3417
  var LOCAL_CONFIG_FILENAME = "vinaya.config.json";
3418
+ function findLocalConfig() {
3419
+ let dir = process.cwd();
3420
+ while (true) {
3421
+ const candidate = join(dir, LOCAL_CONFIG_FILENAME);
3422
+ if (existsSync(candidate))
3423
+ return candidate;
3424
+ if (existsSync(join(dir, ".git")))
3425
+ return null;
3426
+ const parent = dirname(dir);
3427
+ if (parent === dir)
3428
+ return null;
3429
+ dir = parent;
3430
+ }
3431
+ }
3432
+ function configPath() {
3433
+ const local = findLocalConfig();
3434
+ if (local)
3435
+ return local;
3436
+ if (existsSync(GLOBAL_CONFIG_PATH))
3437
+ return GLOBAL_CONFIG_PATH;
3438
+ return null;
3439
+ }
3440
+ function globalChecksIgnoredWarning(path) {
3441
+ return `${path}: "checks" registration in the global config is ignored — checks may only be registered from a repo-local vinaya.config.json.`;
3442
+ }
3443
+ function globalPrincipalsIgnoredWarning(path) {
3444
+ return `${path}: "principals" in the global config is ignored — principals may only be declared from a repo-local vinaya.config.json.`;
3445
+ }
3446
+ function globalRolesIgnoredWarning(path) {
3447
+ return `${path}: "roles" registration in the global config is ignored — roles may only be registered from a repo-local vinaya.config.json.`;
3448
+ }
3449
+ function globalReleaseActorIgnoredWarning(path) {
3450
+ return `${path}: "releaseActor" in the global config is ignored — releaseActor may only be declared from a repo-local vinaya.config.json.`;
3451
+ }
3452
+ function stripGlobalOnlyKeys(config, path) {
3453
+ if (path !== GLOBAL_CONFIG_PATH)
3454
+ return config;
3455
+ let result = config;
3456
+ if (result.checks && Object.keys(result.checks).length > 0) {
3457
+ console.error(`⚠ ${globalChecksIgnoredWarning(path)}`);
3458
+ result = { ...result, checks: undefined };
3459
+ }
3460
+ if (result.roles && Object.keys(result.roles).length > 0) {
3461
+ console.error(`⚠ ${globalRolesIgnoredWarning(path)}`);
3462
+ result = { ...result, roles: undefined };
3463
+ }
3464
+ if (result.principals && result.principals.length > 0) {
3465
+ console.error(`⚠ ${globalPrincipalsIgnoredWarning(path)}`);
3466
+ result = { ...result, principals: undefined };
3467
+ }
3468
+ if (result.releaseActor) {
3469
+ console.error(`⚠ ${globalReleaseActorIgnoredWarning(path)}`);
3470
+ result = { ...result, releaseActor: undefined };
3471
+ }
3472
+ return result;
3473
+ }
3296
3474
  function resolvePrincipalAllowlist(config) {
3297
3475
  return config?.principals ?? PRINCIPAL_ALLOWLIST;
3298
3476
  }
@@ -3365,6 +3543,17 @@ function loadTrustAnchorConfig(fetcher = ghFetchTrustAnchorConfig) {
3365
3543
  return null;
3366
3544
  }
3367
3545
  }
3546
+ function loadConfig() {
3547
+ const path = configPath();
3548
+ if (!path)
3549
+ return null;
3550
+ try {
3551
+ const raw = JSON.parse(readFileSync(path, "utf-8"));
3552
+ return stripGlobalOnlyKeys(VinayaConfigSchema.parse(raw), path);
3553
+ } catch {
3554
+ return null;
3555
+ }
3556
+ }
3368
3557
 
3369
3558
  // src/checks/bin/check-doc-coverage.ts
3370
3559
  var CHECK_NAME = "doc-coverage";
@@ -3384,7 +3573,7 @@ function resolvePrBody() {
3384
3573
  return process.env.PR_BODY;
3385
3574
  if (process.env.PR_BODY_FILE) {
3386
3575
  try {
3387
- return readFileSync(process.env.PR_BODY_FILE, "utf8");
3576
+ return readFileSync2(process.env.PR_BODY_FILE, "utf8");
3388
3577
  } catch {
3389
3578
  return "";
3390
3579
  }
@@ -3442,8 +3631,8 @@ function main() {
3442
3631
  if (changed.length === 0) {
3443
3632
  process.exit(0);
3444
3633
  }
3445
- const content = existsSync(DOC_OWNERS_PATH) ? readFileSync(DOC_OWNERS_PATH, "utf8") : null;
3446
- const result = evaluateC5(changed, content, resolvePrBody(), existsSync, waiverActive(), (p) => fileDiffAgainst(ref, p));
3634
+ const content = existsSync2(DOC_OWNERS_PATH) ? readFileSync2(DOC_OWNERS_PATH, "utf8") : null;
3635
+ const result = evaluateC5(changed, content, resolvePrBody(), existsSync2, waiverActive(), (p) => fileDiffAgainst(ref, p));
3447
3636
  if (result.errors.length > 0) {
3448
3637
  for (const message of result.errors) {
3449
3638
  emitCheckError({
@@ -565,6 +565,12 @@ function factsFromLegacyMilestone(milestone) {
565
565
  lifecycle: milestone.state === "closed" ? "complete" : "active"
566
566
  };
567
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
+ }
568
574
  function lifecycleFromIssues(issues) {
569
575
  if (issues.length === 0)
570
576
  return "planned";
@@ -581,9 +587,9 @@ function goalFromMilestones(milestones, slug) {
581
587
  function findMilestoneForSlug(owner, repo, slug) {
582
588
  const milestones = ghApiGet(`repos/${owner}/${repo}/milestones?state=all&per_page=100`);
583
589
  const legacy = matchesLegacyMilestone(milestones, slug);
584
- if (legacy)
585
- return factsFromLegacyMilestone(legacy);
586
590
  const issues = ghIssueListByLabel(owner, repo, trancheLabel(slug));
591
+ if (legacy)
592
+ return resolveLegacyFacts(legacy, issues);
587
593
  return { goal: goalFromMilestones(milestones, slug), lifecycle: lifecycleFromIssues(issues) };
588
594
  }
589
595
 
@@ -2770,16 +2776,18 @@ function checkDeadBranchPush(input) {
2770
2776
  }
2771
2777
  // ../../packages/aeg-core/src/reader-resolvable-prose.ts
2772
2778
  var SHIPS_PREFIX = "aeg-root/";
2773
- var SHIPS_ARCHIVE_PREFIX = "aeg-root/tranches/completed/";
2779
+ function shipsArchivePrefix(shipsPrefix) {
2780
+ return `${shipsPrefix}tranches/completed/`;
2781
+ }
2774
2782
  function isSpecFile2(path) {
2775
2783
  return path.startsWith("apps/") && path.includes("/specs/") && path.endsWith(".md");
2776
2784
  }
2777
2785
  function isClaudeMdFile(path) {
2778
2786
  return path === "CLAUDE.md" || path.endsWith("/CLAUDE.md");
2779
2787
  }
2780
- function classifyProseFile(path, readerFacingPrefix, readerFacingSuffix) {
2781
- if (path.startsWith(SHIPS_PREFIX)) {
2782
- 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";
2783
2791
  }
2784
2792
  if (path.startsWith(readerFacingPrefix) && path.endsWith(readerFacingSuffix)) {
2785
2793
  return "reader-facing";
@@ -2814,7 +2822,7 @@ function legacySlugPattern(legacySlugs) {
2814
2822
  const alternation = legacySlugs.map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
2815
2823
  return new RegExp(`(^|[^a-zA-Z0-9\\n-])(${alternation})([^a-zA-Z0-9\\n-]|$)`, "g");
2816
2824
  }
2817
- function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs = []) {
2825
+ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs = [], shipsPrefix = SHIPS_PREFIX) {
2818
2826
  const findings = [];
2819
2827
  const legacyPattern = legacySlugPattern(legacySlugs);
2820
2828
  const patterns = [
@@ -2823,7 +2831,7 @@ function checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuff
2823
2831
  ...legacyPattern ? [{ pattern: legacyPattern, what: "an internal tranche slug", group: 2 }] : []
2824
2832
  ];
2825
2833
  for (const file of files) {
2826
- const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix);
2834
+ const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
2827
2835
  if (!cls || !SWEPT_CLASSES.has(cls))
2828
2836
  continue;
2829
2837
  const scrubbed = stripNonProse(file.path, file.content);
@@ -2869,10 +2877,10 @@ function definesOrLinksGlossary(content, term) {
2869
2877
  return true;
2870
2878
  return /docs\/glossary|glossary\.md/i.test(content);
2871
2879
  }
2872
- function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix) {
2880
+ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, shipsPrefix = SHIPS_PREFIX) {
2873
2881
  const findings = [];
2874
2882
  for (const file of files) {
2875
- const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix);
2883
+ const cls = classifyProseFile(file.path, readerFacingPrefix, readerFacingSuffix, shipsPrefix);
2876
2884
  if (!cls || !SWEPT_CLASSES.has(cls))
2877
2885
  continue;
2878
2886
  const scrubbed = stripNonProse(file.path, file.content);
@@ -2891,10 +2899,10 @@ function checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, read
2891
2899
  }
2892
2900
  return findings;
2893
2901
  }
2894
- function checkReaderResolvableProse(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, legacySlugs = []) {
2902
+ function checkReaderResolvableProse(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, legacySlugs = [], shipsPrefix = SHIPS_PREFIX) {
2895
2903
  return [
2896
- ...checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs),
2897
- ...checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix)
2904
+ ...checkUnresolvableReferences(files, readerFacingPrefix, readerFacingSuffix, legacySlugs, shipsPrefix),
2905
+ ...checkUndefinedVocabulary(files, glossaryTerms, readerFacingPrefix, readerFacingSuffix, shipsPrefix)
2898
2906
  ];
2899
2907
  }
2900
2908
  function parseGlossaryTerms(glossaryContent) {
@@ -2908,6 +2916,101 @@ function parseGlossaryTerms(glossaryContent) {
2908
2916
  }
2909
2917
  return terms;
2910
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
+ }
2911
3014
  // ../../packages/aeg-core/src/dispatch-gate.ts
2912
3015
  function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
2913
3016
  return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);