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