@attalabs/vinaya 0.4.3 → 0.4.4

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 (170) hide show
  1. package/aeg-root/enforcement.md +1 -1
  2. package/aeg-root/state-machine.md +1 -1
  3. package/dist/checks/bin/check-branch-topology.js +44 -31
  4. package/dist/checks/bin/check-brief-shape.js +44 -31
  5. package/dist/checks/bin/check-closes-n.js +44 -31
  6. package/dist/checks/bin/check-coherence.js +44 -31
  7. package/dist/checks/bin/check-dead-branch-push.js +44 -31
  8. package/dist/checks/bin/check-dispatch-readiness.js +44 -31
  9. package/dist/checks/bin/check-doc-coverage-push.js +221 -34
  10. package/dist/checks/bin/check-doc-coverage.js +221 -34
  11. package/dist/checks/bin/check-first-push-dispatch.js +44 -31
  12. package/dist/checks/bin/check-issue-assignment.js +44 -31
  13. package/dist/checks/bin/check-no-disk-state.js +44 -31
  14. package/dist/checks/bin/check-reader-resolvable-prose.js +44 -31
  15. package/dist/checks/bin/check-registry-gates.js +44 -31
  16. package/dist/checks/bin/check-review-gate.js +222 -36
  17. package/dist/checks/bin/check-single-plan-pr.js +44 -31
  18. package/dist/checks/bin/check-test-plan.js +44 -31
  19. package/dist/index.js +116 -34
  20. package/package.json +1 -1
  21. package/studio-standalone/.worktrees/fix/bump-0.4.4/.vinaya/projects.md +63 -0
  22. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/aeg-manual-flow.md +216 -0
  23. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/contracts/archivist-tranche-archivist.md +92 -0
  24. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/contracts/brief-developer.md +140 -0
  25. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/contracts/developer-reviewer.md +106 -0
  26. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/contracts/planner-brief.md +130 -0
  27. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/contracts/reviewer-archivist.md +96 -0
  28. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/contracts/tranche-archivist-planner.md +116 -0
  29. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/coordination.md +293 -0
  30. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/documentation-coherence.md +65 -0
  31. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/enforcement.md +161 -0
  32. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/glossary.md +36 -0
  33. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/process.md +371 -0
  34. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/reviewer-prompt.md +68 -0
  35. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/roles/archivist.md +167 -0
  36. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/roles/brief-author.md +108 -0
  37. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/roles/developer.md +429 -0
  38. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/roles/planner.md +285 -0
  39. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/roles/principal.md +107 -0
  40. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/roles/reviewer.md +130 -0
  41. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/roles/security.md +121 -0
  42. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/roles/tranche-archivist.md +257 -0
  43. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/skills/aeg/SKILL.md +90 -0
  44. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/skills/aeg-roles/SKILL.md +58 -0
  45. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/skills/brief-authoring/SKILL.md +497 -0
  46. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/state-machine.md +656 -0
  47. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/templates/brief-template.md +103 -0
  48. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/templates/issue-rationale-template.md +35 -0
  49. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/templates/pr-report-template.md +68 -0
  50. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/tranche-model.md +306 -0
  51. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/tranches/completed/aeg-coherence-v1.md +55 -0
  52. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/tranches/completed/aeg-coherence-v1.tokens.md +13 -0
  53. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/tranches/completed/aeg-consolidation.md +28 -0
  54. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/tranches/completed/aeg-governance-hardening.md +80 -0
  55. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/tranches/completed/aeg-governance-ui-v2.md +168 -0
  56. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/tranches/completed/aeg-governance-ui-v2.tokens.md +18 -0
  57. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/tranches/completed/aeg-studio-cleanup.md +37 -0
  58. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/tranches/completed/aeg-ui-v1.md +176 -0
  59. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/tranches/completed/aeg-ui-v1.tokens.md +17 -0
  60. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/tranches/completed/herald-agents-v2.md +35 -0
  61. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/tranches/completed/herald-agents-v2.tokens.md +11 -0
  62. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/tranches/completed/herald-onto-engine.md +99 -0
  63. package/studio-standalone/.worktrees/fix/bump-0.4.4/aeg-root/tranches/completed/vada-agents-v2.md +220 -0
  64. package/studio-standalone/.worktrees/fix/bump-0.4.4/apps/vinaya/cli/tests/fixtures/checks/vinaya.config.json +9 -0
  65. package/studio-standalone/.worktrees/fix/bump-0.4.4/node_modules/@attalabs/vinaya/tests/fixtures/checks/vinaya.config.json +9 -0
  66. package/studio-standalone/.worktrees/fix/bump-0.4.4/vinaya.config.json +36 -0
  67. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/.vinaya/projects.md +63 -0
  68. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/aeg-manual-flow.md +216 -0
  69. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/contracts/archivist-tranche-archivist.md +92 -0
  70. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/contracts/brief-developer.md +140 -0
  71. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/contracts/developer-reviewer.md +106 -0
  72. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/contracts/planner-brief.md +130 -0
  73. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/contracts/reviewer-archivist.md +96 -0
  74. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/contracts/tranche-archivist-planner.md +116 -0
  75. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/coordination.md +293 -0
  76. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/documentation-coherence.md +65 -0
  77. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/enforcement.md +161 -0
  78. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/glossary.md +36 -0
  79. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/process.md +371 -0
  80. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/reviewer-prompt.md +68 -0
  81. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/roles/archivist.md +167 -0
  82. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/roles/brief-author.md +108 -0
  83. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/roles/developer.md +429 -0
  84. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/roles/planner.md +285 -0
  85. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/roles/principal.md +107 -0
  86. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/roles/reviewer.md +130 -0
  87. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/roles/security.md +121 -0
  88. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/roles/tranche-archivist.md +257 -0
  89. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/skills/aeg/SKILL.md +90 -0
  90. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/skills/aeg-roles/SKILL.md +58 -0
  91. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/skills/brief-authoring/SKILL.md +497 -0
  92. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/state-machine.md +656 -0
  93. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/templates/brief-template.md +103 -0
  94. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/templates/issue-rationale-template.md +35 -0
  95. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/templates/pr-report-template.md +68 -0
  96. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/tranche-model.md +306 -0
  97. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/tranches/completed/aeg-coherence-v1.md +55 -0
  98. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/tranches/completed/aeg-coherence-v1.tokens.md +13 -0
  99. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/tranches/completed/aeg-consolidation.md +28 -0
  100. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/tranches/completed/aeg-governance-hardening.md +80 -0
  101. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/tranches/completed/aeg-governance-ui-v2.md +168 -0
  102. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/tranches/completed/aeg-governance-ui-v2.tokens.md +18 -0
  103. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/tranches/completed/aeg-studio-cleanup.md +37 -0
  104. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/tranches/completed/aeg-ui-v1.md +176 -0
  105. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/tranches/completed/aeg-ui-v1.tokens.md +17 -0
  106. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/tranches/completed/herald-agents-v2.md +35 -0
  107. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/tranches/completed/herald-agents-v2.tokens.md +11 -0
  108. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/tranches/completed/herald-onto-engine.md +99 -0
  109. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/aeg-root/tranches/completed/vada-agents-v2.md +220 -0
  110. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/apps/vinaya/cli/tests/fixtures/checks/vinaya.config.json +9 -0
  111. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/node_modules/@attalabs/vinaya/tests/fixtures/checks/vinaya.config.json +9 -0
  112. package/studio-standalone/.worktrees/fix/check-lifecycle-phase/vinaya.config.json +36 -0
  113. package/studio-standalone/_node_modules/@atta/vinaya-cli/aeg-root/enforcement.md +1 -1
  114. package/studio-standalone/_node_modules/@atta/vinaya-cli/aeg-root/state-machine.md +1 -1
  115. package/studio-standalone/_node_modules/@attalabs/vinaya/aeg-root/enforcement.md +1 -1
  116. package/studio-standalone/_node_modules/@attalabs/vinaya/aeg-root/state-machine.md +1 -1
  117. package/studio-standalone/apps/vinaya/cli/aeg-root/enforcement.md +1 -1
  118. package/studio-standalone/apps/vinaya/cli/aeg-root/state-machine.md +1 -1
  119. package/studio-standalone/apps/vinaya/web/.next/BUILD_ID +1 -1
  120. package/studio-standalone/apps/vinaya/web/.next/build-manifest.json +3 -3
  121. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/cli/page.js.nft.json +1 -1
  122. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/config/page.js.nft.json +1 -1
  123. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/docs/(standalone)/cli/page.js.nft.json +1 -1
  124. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/docs/(standalone)/harness/page.js.nft.json +1 -1
  125. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/docs/(standalone)/state-machine/page.js.nft.json +1 -1
  126. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/docs/(with-sidebar)/[...slug]/page.js.nft.json +1 -1
  127. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/docs/(with-sidebar)/reference/page.js.nft.json +1 -1
  128. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/docs/page.js.nft.json +1 -1
  129. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/install/page.js.nft.json +1 -1
  130. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/page.js.nft.json +1 -1
  131. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/roadmap/page.js.nft.json +1 -1
  132. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/start/archive/page.js.nft.json +1 -1
  133. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/start/brief/page.js.nft.json +1 -1
  134. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/start/develop/page.js.nft.json +1 -1
  135. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/start/overview/page.js.nft.json +1 -1
  136. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/start/page.js.nft.json +1 -1
  137. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/start/plan/page.js.nft.json +1 -1
  138. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/start/quick/page.js.nft.json +1 -1
  139. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/start/review/page.js.nft.json +1 -1
  140. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/start/security/page.js.nft.json +1 -1
  141. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/start/wrap-up/page.js.nft.json +1 -1
  142. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/state-machine/page.js.nft.json +1 -1
  143. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/the-harness/page.js.nft.json +1 -1
  144. package/studio-standalone/apps/vinaya/web/.next/server/app/(site)/the-studio/page.js.nft.json +1 -1
  145. package/studio-standalone/apps/vinaya/web/.next/server/app/_global-error/page.js.nft.json +1 -1
  146. package/studio-standalone/apps/vinaya/web/.next/server/app/_global-error.html +1 -1
  147. package/studio-standalone/apps/vinaya/web/.next/server/app/_global-error.rsc +1 -1
  148. package/studio-standalone/apps/vinaya/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  149. package/studio-standalone/apps/vinaya/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  150. package/studio-standalone/apps/vinaya/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  151. package/studio-standalone/apps/vinaya/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  152. package/studio-standalone/apps/vinaya/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  153. package/studio-standalone/apps/vinaya/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  154. package/studio-standalone/apps/vinaya/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
  155. package/studio-standalone/apps/vinaya/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  156. package/studio-standalone/apps/vinaya/web/.next/server/app/studio/docs/[...slug]/page.js.nft.json +1 -1
  157. package/studio-standalone/apps/vinaya/web/.next/server/app/studio/docs/page.js.nft.json +1 -1
  158. package/studio-standalone/apps/vinaya/web/.next/server/app/studio/page.js.nft.json +1 -1
  159. package/studio-standalone/apps/vinaya/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  160. package/studio-standalone/apps/vinaya/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  161. package/studio-standalone/apps/vinaya/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  162. package/studio-standalone/apps/vinaya/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  163. package/studio-standalone/apps/vinaya/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  164. package/studio-standalone/apps/vinaya/web/.next/server/chunks/ssr/[root-of-the-server]__0kqmrho._.js +1 -1
  165. package/studio-standalone/apps/vinaya/web/.next/server/chunks/ssr/[root-of-the-server]__1dxaovd._.js +2 -2
  166. package/studio-standalone/apps/vinaya/web/.next/server/middleware-build-manifest.js +3 -3
  167. package/studio-standalone/apps/vinaya/web/.next/server/pages/500.html +1 -1
  168. /package/studio-standalone/apps/vinaya/web/.next/static/{Afs41Lu9uNQ-T6uC-tm4_ → mSHYKKSLd91iaLw52dYpD}/_buildManifest.js +0 -0
  169. /package/studio-standalone/apps/vinaya/web/.next/static/{Afs41Lu9uNQ-T6uC-tm4_ → mSHYKKSLd91iaLw52dYpD}/_clientMiddlewareManifest.js +0 -0
  170. /package/studio-standalone/apps/vinaya/web/.next/static/{Afs41Lu9uNQ-T6uC-tm4_ → mSHYKKSLd91iaLw52dYpD}/_ssgManifest.js +0 -0
package/dist/index.js CHANGED
@@ -532,6 +532,27 @@ function parsePremiseBlock(prBody) {
532
532
  return assertions;
533
533
  }
534
534
 
535
+ // ../../../packages/aeg-core/src/test-plan-section.ts
536
+ var HEADING_FORM_RE = /^#{1,4}[ \t]*(?:\d+[a-z]?\.[ \t]*)?\*{0,2}Test Plan\*{0,2}[ \t]*$/im;
537
+ var INLINE_FORM_RE = /^[ \t]*\*{0,2}Test Plan:?\*{0,2}/im;
538
+ var NEXT_SECTION_RE = /\n(?:#{1,3}[ \t]|\*\*[A-Z][^*\n]*:\*\*)/;
539
+ function locateTestPlanSection(body) {
540
+ const region = anchoredRegion(body, "TEST-PLAN");
541
+ if (region !== null)
542
+ return { found: true, section: region };
543
+ const headingMatch = HEADING_FORM_RE.exec(body);
544
+ const match = headingMatch ?? INLINE_FORM_RE.exec(body);
545
+ if (!match || match.index === undefined)
546
+ return { found: false };
547
+ const sectionBody = body.slice(match.index);
548
+ const firstLineEnd = sectionBody.indexOf(`
549
+ `);
550
+ const searchFrom = firstLineEnd === -1 ? sectionBody.length : firstLineEnd + 1;
551
+ const nextMatch = sectionBody.slice(searchFrom).match(NEXT_SECTION_RE);
552
+ const sectionEnd = nextMatch ? searchFrom + (nextMatch.index ?? 0) : sectionBody.length;
553
+ return { found: true, section: sectionBody.slice(0, sectionEnd) };
554
+ }
555
+
535
556
  // ../../../packages/aeg-core/src/brief-validation.ts
536
557
  function headerRegion(prBody) {
537
558
  const m = prBody.match(/^##\s/m);
@@ -570,10 +591,15 @@ function checkTierField(prBody, readTier) {
570
591
  };
571
592
  }
572
593
  var TEST_PLAN_UNIT_TESTS_ONLY_RE = /(?:\*\*)?Test Plan(?:\*\*)?\s*:\s*(?:\*\*)?\s*unit-tests-only/i;
594
+ function testPlanRegion(prBody) {
595
+ const located = locateTestPlanSection(prBody);
596
+ return located.found ? located.section : prBody;
597
+ }
573
598
  function checkTestPlan(prBody) {
574
- if (TEST_PLAN_UNIT_TESTS_ONLY_RE.test(prBody))
599
+ const region = testPlanRegion(prBody);
600
+ if (TEST_PLAN_UNIT_TESTS_ONLY_RE.test(region))
575
601
  return { status: "pass", errors: [] };
576
- if (/\*\*\[(?:agent|principal)\]\*\*/.test(prBody))
602
+ if (/\*\*\[(?:agent|principal)\]\*\*/.test(region))
577
603
  return { status: "pass", errors: [] };
578
604
  return {
579
605
  status: "fail",
@@ -583,9 +609,10 @@ function checkTestPlan(prBody) {
583
609
  };
584
610
  }
585
611
  function checkTestPlanExclusivity(prBody) {
586
- if (!TEST_PLAN_UNIT_TESTS_ONLY_RE.test(prBody))
612
+ const region = testPlanRegion(prBody);
613
+ if (!TEST_PLAN_UNIT_TESTS_ONLY_RE.test(region))
587
614
  return { status: "pass", errors: [] };
588
- if (/^-\s*\[[ xX]\]\s*\*{2}\[(?:agent|principal)\]\*{2}/im.test(prBody)) {
615
+ if (/^-\s*\[[ xX]\]\s*\*{2}\[(?:agent|principal)\]\*{2}/im.test(region)) {
589
616
  return {
590
617
  status: "fail",
591
618
  errors: [
@@ -596,8 +623,9 @@ function checkTestPlanExclusivity(prBody) {
596
623
  return { status: "pass", errors: [] };
597
624
  }
598
625
  function checkPrincipalPlaceholder(prBody) {
626
+ const region = testPlanRegion(prBody);
599
627
  const lineRe = /^-\s*\[[ xX]\]\s*\*{2}\[principal\]\*{2}(.*)$/gim;
600
- for (const m of prBody.matchAll(lineRe)) {
628
+ for (const m of region.matchAll(lineRe)) {
601
629
  const content = m[1] ?? "";
602
630
  if (/^\s*None\b/i.test(content)) {
603
631
  return {
@@ -1543,7 +1571,10 @@ function coreCheckRegistry() {
1543
1571
  PR_BODY: { optional: true },
1544
1572
  PR_BODY_FILE: { optional: true },
1545
1573
  PR_LABELS: { optional: true },
1546
- WAIVER_LABEL_ACTOR: { optional: true }
1574
+ WAIVER_LABEL_ACTOR: { optional: true },
1575
+ GITHUB_REPOSITORY: { optional: true },
1576
+ GITHUB_TOKEN: { optional: true },
1577
+ GH_TOKEN: { optional: true }
1547
1578
  }
1548
1579
  },
1549
1580
  {
@@ -1575,6 +1606,7 @@ function coreCheckRegistry() {
1575
1606
  run: bin("check-closes-n"),
1576
1607
  scope: "diff",
1577
1608
  timeoutMs: 15000,
1609
+ requiresOpenPr: true,
1578
1610
  env: {
1579
1611
  BRANCH: { optional: true },
1580
1612
  PR_BODY: { optional: true },
@@ -1600,6 +1632,7 @@ function coreCheckRegistry() {
1600
1632
  run: bin("check-test-plan"),
1601
1633
  scope: "diff",
1602
1634
  timeoutMs: 15000,
1635
+ requiresOpenPr: true,
1603
1636
  env: {
1604
1637
  PR_BODY: { optional: true },
1605
1638
  BRANCH: { optional: true }
@@ -1631,6 +1664,7 @@ function coreCheckRegistry() {
1631
1664
  env: {
1632
1665
  BRANCH: { optional: true },
1633
1666
  PR_NUMBER: { optional: true },
1667
+ GITHUB_REPOSITORY: { optional: true },
1634
1668
  GITHUB_TOKEN: { optional: true },
1635
1669
  GH_TOKEN: { optional: true }
1636
1670
  }
@@ -1681,7 +1715,10 @@ function coreCheckRegistry() {
1681
1715
  PR_BODY: { optional: true },
1682
1716
  PR_BODY_FILE: { optional: true },
1683
1717
  PR_LABELS: { optional: true },
1684
- WAIVER_LABEL_ACTOR: { optional: true }
1718
+ WAIVER_LABEL_ACTOR: { optional: true },
1719
+ GITHUB_REPOSITORY: { optional: true },
1720
+ GITHUB_TOKEN: { optional: true },
1721
+ GH_TOKEN: { optional: true }
1685
1722
  }
1686
1723
  },
1687
1724
  {
@@ -1760,6 +1797,8 @@ function isCheckError(value) {
1760
1797
  return v.schema === CHECK_SCHEMA_VERSION && typeof v.check === "string" && (v.severity === "error" || v.severity === "warning") && typeof v.message === "string" && typeof v.agent_recovery_prompt === "string";
1761
1798
  }
1762
1799
  function shouldSkip(spec, opts) {
1800
+ if (opts.localOnly && spec.requiresOpenPr)
1801
+ return true;
1763
1802
  if (spec.scope !== "diff")
1764
1803
  return false;
1765
1804
  if (!opts.diffOnly)
@@ -2020,7 +2059,8 @@ var CheckEntrySchema = z.object({
2020
2059
  include: z.array(z.string()).optional(),
2021
2060
  args: z.array(z.string()).optional(),
2022
2061
  timeoutMs: z.number().optional(),
2023
- env: z.record(z.string(), EnvEntrySchema).optional()
2062
+ env: z.record(z.string(), EnvEntrySchema).optional(),
2063
+ requiresOpenPr: z.boolean().optional()
2024
2064
  }).superRefine((entry2, ctx) => {
2025
2065
  if (!entry2.env)
2026
2066
  return;
@@ -2127,7 +2167,8 @@ var VinayaConfigSchema = z.object({
2127
2167
  }).optional(),
2128
2168
  checks: z.record(z.string(), CheckEntrySchema).optional(),
2129
2169
  briefSchema: BriefSchemaSchema.optional(),
2130
- managed: ManagedManifestSchema.optional()
2170
+ managed: ManagedManifestSchema.optional(),
2171
+ principals: z.array(z.string()).min(1).optional()
2131
2172
  });
2132
2173
  var GLOBAL_VINAYA_HOME = join4(homedir(), ".vinaya");
2133
2174
  var GLOBAL_CONFIG_PATH = join4(GLOBAL_VINAYA_HOME, "config.json");
@@ -2155,11 +2196,22 @@ function configPath() {
2155
2196
  function globalChecksIgnoredWarning(path) {
2156
2197
  return `${path}: "checks" registration in the global config is ignored — checks may only be registered from a repo-local vinaya.config.json.`;
2157
2198
  }
2158
- function stripGlobalChecks(config, path) {
2159
- if (path !== GLOBAL_CONFIG_PATH || !config.checks || Object.keys(config.checks).length === 0)
2199
+ function globalPrincipalsIgnoredWarning(path) {
2200
+ return `${path}: "principals" in the global config is ignored — principals may only be declared from a repo-local vinaya.config.json.`;
2201
+ }
2202
+ function stripGlobalOnlyKeys(config, path) {
2203
+ if (path !== GLOBAL_CONFIG_PATH)
2160
2204
  return config;
2161
- console.error(`⚠ ${globalChecksIgnoredWarning(path)}`);
2162
- return { ...config, checks: undefined };
2205
+ let result = config;
2206
+ if (result.checks && Object.keys(result.checks).length > 0) {
2207
+ console.error(`⚠ ${globalChecksIgnoredWarning(path)}`);
2208
+ result = { ...result, checks: undefined };
2209
+ }
2210
+ if (result.principals && result.principals.length > 0) {
2211
+ console.error(`⚠ ${globalPrincipalsIgnoredWarning(path)}`);
2212
+ result = { ...result, principals: undefined };
2213
+ }
2214
+ return result;
2163
2215
  }
2164
2216
  function loadConfigChecked() {
2165
2217
  const path = configPath();
@@ -2176,7 +2228,7 @@ function loadConfigChecked() {
2176
2228
  const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
2177
2229
  return { ok: false, path, error: detail };
2178
2230
  }
2179
- return { ok: true, config: stripGlobalChecks(parsed.data, path) };
2231
+ return { ok: true, config: stripGlobalOnlyKeys(parsed.data, path) };
2180
2232
  }
2181
2233
 
2182
2234
  // src/commands/check.ts
@@ -2301,6 +2353,7 @@ function printClassificationWarnings(configChecks) {
2301
2353
  async function checkCommand(args) {
2302
2354
  const jsonOutput = args.includes("--json");
2303
2355
  const diffOnly = args.includes("--diff-only");
2356
+ const localOnly = args.includes("--local");
2304
2357
  const requestedParallel = parseParallel(args);
2305
2358
  const allRequested = args.includes("--all");
2306
2359
  const planRequested = args.includes("--plan");
@@ -2318,7 +2371,7 @@ async function checkCommand(args) {
2318
2371
  process.exit(result.failures.length > 0 ? 1 : 0);
2319
2372
  }
2320
2373
  if (!allRequested && !requestedName) {
2321
- console.error("Usage: vinaya check <name> | --all | --plan [--json] [--diff-only] [--parallel[=n]]");
2374
+ console.error("Usage: vinaya check <name> | --all | --plan [--json] [--diff-only] [--local] [--parallel[=n]]");
2322
2375
  process.exit(2);
2323
2376
  }
2324
2377
  const { specs: customSpecs, errorOutcome, checks: configChecks } = customSpecsFromConfig();
@@ -2333,7 +2386,8 @@ async function checkCommand(args) {
2333
2386
  parallel: requestedParallel ?? defaultParallelism(),
2334
2387
  diffOnly,
2335
2388
  changedFiles: changed,
2336
- defaultTimeoutMs: 30000
2389
+ defaultTimeoutMs: 30000,
2390
+ localOnly
2337
2391
  }) : [];
2338
2392
  const allOutcomes = errorOutcome ? [...outcomes, errorOutcome] : outcomes;
2339
2393
  for (const o of allOutcomes) {
@@ -2566,8 +2620,9 @@ async function demoBreakCommand(args) {
2566
2620
  }
2567
2621
 
2568
2622
  // src/commands/doctor.ts
2623
+ import { execFileSync as execFileSync6 } from "node:child_process";
2569
2624
  import { existsSync as existsSync8, readFileSync as readFileSync6, statSync } from "node:fs";
2570
- import { join as join8 } from "node:path";
2625
+ import { isAbsolute as isAbsolute2, join as join8 } from "node:path";
2571
2626
 
2572
2627
  // src/lib/artifacts.ts
2573
2628
  import { readFileSync as readFileSync3 } from "node:fs";
@@ -2629,6 +2684,11 @@ jobs:
2629
2684
  env:
2630
2685
  GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
2631
2686
  PR_NUMBER: \${{ github.event.pull_request.number }}
2687
+ # PR_BODY is what makes test-plan/closes-n EVALUATE: neither check
2688
+ # fetches the body itself (both read \`process.env.PR_BODY\` only) —
2689
+ # without it they read "no body — nothing to check" and pass
2690
+ # vacuously regardless of the PR's real content, on every run.
2691
+ PR_BODY: \${{ github.event.pull_request.body }}
2632
2692
  BRANCH: \${{ github.head_ref }}
2633
2693
  run: npx --yes @attalabs/vinaya check --all --diff-only
2634
2694
  `;
@@ -2675,6 +2735,10 @@ jobs:
2675
2735
  # it the adapter reads "no PR yet — local dev" and exits 0, and
2676
2736
  # the gate is green regardless of review state.
2677
2737
  PR_NUMBER: \${{ github.event.pull_request.number }}
2738
+ # Same PR_BODY gap as vinaya-checks.yml — this job also runs
2739
+ # \`check --all\` (test-plan/closes-n included), so it needs the
2740
+ # same wiring or those two checks pass vacuously here too.
2741
+ PR_BODY: \${{ github.event.pull_request.body }}
2678
2742
  BRANCH: \${{ github.head_ref }}
2679
2743
  run: npx --yes @attalabs/vinaya check --all
2680
2744
  `;
@@ -2861,11 +2925,11 @@ function ownVersion() {
2861
2925
  function preCommitBody() {
2862
2926
  return `# Vinaya commit-time gate. Runs the deterministic checks over your staged
2863
2927
  # diff before the commit lands.
2864
- npx --yes @attalabs/vinaya@${ownVersion()} check --all --diff-only || exit 1`;
2928
+ npx --yes @attalabs/vinaya@${ownVersion()} check --all --diff-only --local || exit 1`;
2865
2929
  }
2866
2930
  function prePushBody() {
2867
2931
  return `# Vinaya pre-push gate. Runs branch/dispatch checks before the push leaves.
2868
- npx --yes @attalabs/vinaya@${ownVersion()} check --all || exit 1`;
2932
+ npx --yes @attalabs/vinaya@${ownVersion()} check --all --local || exit 1`;
2869
2933
  }
2870
2934
  function doctrinePointer() {
2871
2935
  const doctrineRoot = join6(packageRoot(import.meta.url), "aeg-root");
@@ -3446,6 +3510,19 @@ function labelForPath(path) {
3446
3510
  return "doctrine-pointer";
3447
3511
  return "workflows";
3448
3512
  }
3513
+ function resolveManagedBlockPath(repoRoot, opPath) {
3514
+ if (!opPath.startsWith(".git/"))
3515
+ return join8(repoRoot, opPath);
3516
+ try {
3517
+ const commonDir = execFileSync6("git", ["-C", repoRoot, "rev-parse", "--git-common-dir"], {
3518
+ encoding: "utf8"
3519
+ }).trim();
3520
+ const gitDir = isAbsolute2(commonDir) ? commonDir : join8(repoRoot, commonDir);
3521
+ return join8(gitDir, opPath.slice(".git/".length));
3522
+ } catch {
3523
+ return join8(repoRoot, opPath);
3524
+ }
3525
+ }
3449
3526
  function diagnoseInstall(repoRoot, ctx, manifest) {
3450
3527
  const findings = [];
3451
3528
  let hasDrift = false;
@@ -3479,7 +3556,7 @@ function diagnoseInstall(repoRoot, ctx, manifest) {
3479
3556
  }
3480
3557
  } else if (op.kind === "managed-block") {
3481
3558
  const check = "hooks";
3482
- const abs2 = join8(repoRoot, op.path);
3559
+ const abs2 = resolveManagedBlockPath(repoRoot, op.path);
3483
3560
  const owned = ownedBlocks.has(blockKey(op.path, op.marker));
3484
3561
  if (!existsSync8(abs2)) {
3485
3562
  findings.push(owned ? error(check, `${op.path} is missing — likely a fresh clone (raw git hooks aren't tracked by git). Run \`vinaya upgrade\` to restore it.`) : info(check, `${op.path} is not installed.`));
@@ -4030,7 +4107,7 @@ async function initProductCommand(args) {
4030
4107
  }
4031
4108
 
4032
4109
  // src/commands/issue.ts
4033
- import { execFileSync as execFileSync6 } from "node:child_process";
4110
+ import { execFileSync as execFileSync7 } from "node:child_process";
4034
4111
 
4035
4112
  // src/lib/forge-write.ts
4036
4113
  import { mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "node:fs";
@@ -4243,7 +4320,7 @@ function locateBodyOrRefuse(ghArgs, retryCommand) {
4243
4320
  function fetchForgeLabels(issueRef) {
4244
4321
  let out;
4245
4322
  try {
4246
- out = execFileSync6("gh", ["issue", "view", issueRef, "--json", "labels"], {
4323
+ out = execFileSync7("gh", ["issue", "view", issueRef, "--json", "labels"], {
4247
4324
  encoding: "utf8",
4248
4325
  stdio: ["ignore", "pipe", "pipe"]
4249
4326
  });
@@ -4271,7 +4348,7 @@ function reportPass(json, command) {
4271
4348
  function runGhWrite(ghCmd, ghArgs, bodyResult, json) {
4272
4349
  const { finalArgs, cleanup: cleanup2 } = resolveShippableArgs(ghArgs, bodyResult);
4273
4350
  try {
4274
- const out = execFileSync6("gh", [...ghCmd, ...finalArgs], { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] });
4351
+ const out = execFileSync7("gh", [...ghCmd, ...finalArgs], { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] });
4275
4352
  const url = out.trim();
4276
4353
  if (json)
4277
4354
  printJson({ validated: true, written: true, url });
@@ -4375,12 +4452,12 @@ ${registration}
4375
4452
  }
4376
4453
 
4377
4454
  // src/commands/pr.ts
4378
- import { execFileSync as execFileSync7 } from "node:child_process";
4455
+ import { execFileSync as execFileSync8 } from "node:child_process";
4379
4456
  var RETRY_CREATE2 = "vinaya pr create --validate-only …";
4380
4457
  var RETRY_EDIT2 = "vinaya pr edit <n> --validate-only …";
4381
4458
  function git2(args) {
4382
4459
  try {
4383
- return execFileSync7("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
4460
+ return execFileSync8("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
4384
4461
  } catch {
4385
4462
  return "";
4386
4463
  }
@@ -4414,7 +4491,7 @@ function reportPass2(json, command) {
4414
4491
  function runGhWrite2(ghCmd, ghArgs, bodyResult, json) {
4415
4492
  const { finalArgs, cleanup: cleanup2 } = resolveShippableArgs(ghArgs, bodyResult);
4416
4493
  try {
4417
- const out = execFileSync7("gh", [...ghCmd, ...finalArgs], { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] });
4494
+ const out = execFileSync8("gh", [...ghCmd, ...finalArgs], { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"] });
4418
4495
  const url = out.trim();
4419
4496
  if (json)
4420
4497
  printJson({ validated: true, written: true, url });
@@ -4428,7 +4505,7 @@ function runGhWrite2(ghCmd, ghArgs, bodyResult, json) {
4428
4505
  function fetchPrForgeContext(prRef) {
4429
4506
  let viewOut;
4430
4507
  try {
4431
- viewOut = execFileSync7("gh", ["pr", "view", prRef, "--json", "headRefName,files"], {
4508
+ viewOut = execFileSync8("gh", ["pr", "view", prRef, "--json", "headRefName,files"], {
4432
4509
  encoding: "utf8",
4433
4510
  stdio: ["ignore", "pipe", "pipe"]
4434
4511
  });
@@ -4524,7 +4601,7 @@ function prEditCommand(args) {
4524
4601
  }
4525
4602
 
4526
4603
  // src/commands/quickstart.ts
4527
- import { execFileSync as execFileSync8 } from "node:child_process";
4604
+ import { execFileSync as execFileSync9 } from "node:child_process";
4528
4605
  import { existsSync as existsSync14, readFileSync as readFileSync13 } from "node:fs";
4529
4606
  import { join as join15 } from "node:path";
4530
4607
 
@@ -4718,7 +4795,7 @@ function realDeps6() {
4718
4795
  };
4719
4796
  }
4720
4797
  function execGit(repoRoot, args) {
4721
- return execFileSync8("git", args, { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
4798
+ return execFileSync9("git", args, { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
4722
4799
  }
4723
4800
  function errorDetail(err) {
4724
4801
  const stderr = err.stderr;
@@ -5174,12 +5251,12 @@ async function upgradeCommand(args) {
5174
5251
  }
5175
5252
 
5176
5253
  // src/commands/waiver.ts
5177
- import { execFileSync as execFileSync9 } from "node:child_process";
5254
+ import { execFileSync as execFileSync10 } from "node:child_process";
5178
5255
  function labelFor(kind) {
5179
5256
  return kind === "docs" ? WAIVER_LABEL : WAIVER_LABEL_REVIEW;
5180
5257
  }
5181
5258
  function gh(args) {
5182
- return execFileSync9("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], env: process.env }).trim();
5259
+ return execFileSync10("gh", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], env: process.env }).trim();
5183
5260
  }
5184
5261
  function detectPrNumber() {
5185
5262
  try {
@@ -5279,8 +5356,8 @@ Reason: ${reason}`];
5279
5356
  }
5280
5357
  process.stdout.write(`Applying \`${label2}\` to PR #${prNumber} under your authenticated \`gh\` identity — vinaya never fabricates an actor.
5281
5358
  `);
5282
- execFileSync9("gh", editArgs, { stdio: "inherit", env: process.env });
5283
- execFileSync9("gh", commentArgs, { stdio: "inherit", env: process.env });
5359
+ execFileSync10("gh", editArgs, { stdio: "inherit", env: process.env });
5360
+ execFileSync10("gh", commentArgs, { stdio: "inherit", env: process.env });
5284
5361
  process.stdout.write(`
5285
5362
  ✓ Label applied and reason posted as a PR comment.
5286
5363
  `);
@@ -5337,6 +5414,10 @@ var COMMANDS = [
5337
5414
  { flag: "--all", description: "Run every registered check instead of one named check" },
5338
5415
  { flag: "--json", description: "Enveloped JSON output (schema: 1)" },
5339
5416
  { flag: "--diff-only", description: "Scope diff-declared checks to changed files" },
5417
+ {
5418
+ flag: "--local",
5419
+ description: "Skip every requiresOpenPr check (closes-n, test-plan) — set by the generated hooks, never by CI"
5420
+ },
5340
5421
  { flag: "--parallel[=n]", description: "Concurrency cap (default: cpu-derived)" },
5341
5422
  {
5342
5423
  flag: "--plan",
@@ -5345,7 +5426,8 @@ var COMMANDS = [
5345
5426
  ],
5346
5427
  details: [
5347
5428
  "Each spawned check's child process sees only a fixed baseline (`PATH`, `LANG`, `HOME`, `HTTPS_PROXY`, `HTTP_PROXY`, `NO_PROXY`, `TMPDIR`) plus whatever its `CheckSpec['env']` declaration explicitly forwards — never the full parent environment. A required (`true`) or unsatisfied `anyOf` declaration missing from the caller's environment synthesizes a `CheckError` before the check ever spawns. Declare `env` (a core check's own registration, or `vinaya.config.json`'s `checks.<name>.env` for a custom one) for any check that reads `process.env`/`Bun.env`/`Deno.env` directly — `vinaya doctor` carries the permanent diagnostic for one that doesn't.",
5348
- "`--plan` composes with `--json`. It requires zero env vars and never prints an env value — only how each one resolves (passthrough, optional, literal, or anyOf). A `FAIL_CLOSED` entry (a bare key with no namespace matching no core check) always renders inline rather than being dropped, and exits non-zero. `--plan` previews what the next minor's execution will enforce — `vinaya check` itself still runs the flat, unvalidated registry this release."
5429
+ "`--plan` composes with `--json`. It requires zero env vars and never prints an env value — only how each one resolves (passthrough, optional, literal, or anyOf). A `FAIL_CLOSED` entry (a bare key with no namespace matching no core check) always renders inline rather than being dropped, and exits non-zero. `--plan` previews what the next minor's execution will enforce — `vinaya check` itself still runs the flat, unvalidated registry this release.",
5430
+ "`--local` exists because a `requiresOpenPr` check (the core `closes-n`/`test-plan`, or a custom check declaring the same field) can only evaluate for real once a pull request exists — the generated `pre-commit`/`pre-push` hooks pass it so the first commit on a fresh task branch is never asked to satisfy a PR-body field before a PR can possibly exist. CI's `vinaya-checks.yml` omits it, so these checks always run for real once a PR is open."
5349
5431
  ],
5350
5432
  status: "shipped"
5351
5433
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@attalabs/vinaya",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Vinaya — Agentic Engineering Harness. Deterministic checks every AI coding agent must satisfy before merge.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -0,0 +1,63 @@
1
+ ---
2
+ sidebar_title: Projects
3
+ ---
4
+ # Projects in this repo
5
+
6
+ **The project registry.** Declares the projects in this repo and where each one's specs and per-project state live. The `Project` field on a task (a forge Issue) resolves against this file: `Project: vada` → the Developer reads that project's specs, the Archivist updates its per-project state.
7
+
8
+ ## What a project is (and isn't)
9
+
10
+ A project is **a `(name, folder)` pair the developer declared** — nothing more. Not derived from the folder tree, not required to match a `package.json` name, not required to be a single package. AEG does not define what a project "really" is; the developer does, by choosing a name and a home folder when registering it.
11
+
12
+ The folder is simply **the home for that project's specs and status** (`<path>/specs/`, `<path>/aeg-project/`). A project may be one package, an app, or a grouping built from several packages.
13
+
14
+ - **Identity = the registry row.** A project exists because it has a row here, not because a folder with some name exists somewhere. Nothing searches the tree; names are unique by this registry, paths are whatever the human gave.
15
+ - **Path = declared, never derived.** `--path` is a required argument to `aeg add-project`; the tool stores it.
16
+
17
+ **Presence of this file means this is a multi-project repo** — the `Project` field is required. A single-project repo has **no** `projects.md`; every task shares one project, so the field is omitted, and state lives in the root `aeg-project/`. The registry appears only when there is more than one project to disambiguate.
18
+
19
+ ## Registry
20
+
21
+ | Project | Path | Specs | Per-project state |
22
+ |----------|-----------------------|-----------------------------|--------------------------------------|
23
+ | admin | `tools/admin` | `tools/admin/specs/` | (state tracked globally for now) |
24
+ | vada | `apps/vada-ai` | `apps/vada-ai/specs/` | `apps/vada-ai/aeg-project/` |
25
+ | herald | `apps/herald-ai` | `apps/herald-ai/specs/` | `apps/herald-ai/aeg-project/` |
26
+ | aeg-core | `packages/aeg-core` | `packages/aeg-core/specs/` | (state tracked globally for now) |
27
+ | aeg-forge-state | `packages/aeg-forge-state` | `packages/aeg-forge-state/specs/` | (state tracked globally for now) |
28
+ | attalabs | `apps/attalabs` | `apps/attalabs/specs/` | (state tracked globally for now) |
29
+ | vinaya | `apps/vinaya` | `apps/vinaya/specs/` | Issue #768 (pinned) |
30
+
31
+ > **admin** — `tools/admin`: the internal Sanity-backed admin tool for managing per-product theme/library config across every AttaLabs product (not a product itself, no `.attalabs.dev` domain). Had real code and a task PR (#557, the Library-picker feature) before ever getting a registry row — added retroactively once that gap was noticed. `tools/*` carries no `-ai` suffix (internal-tooling convention, like `apps/desktop`, `apps/attalabs`).
32
+
33
+ > **vinaya** — Vinaya: the npm-distributed reference implementation of AEG, installed into any GitHub repository. One app folder, three workspaces: `apps/vinaya/cli` (the `vinaya` command — init/check/pr/issue/doctor/upgrade/eject/demo/waiver), `apps/vinaya/web` (**Vinaya Studio**, ported from the old AEG Studio app), and `apps/vinaya/sources` (the StateSource adapters — forge-backed primary, file-backed transitional). Imports `@atta/aeg-core` unchanged; the vinaya-namespace migration of shared code rides the later npm extraction, never a standalone rename. The registry row deliberately precedes the code. "Vinaya is the reference implementation of AEG": AEG stays the model's name (`aeg-root/` is untouched); Vinaya is the tool. Pāli name by elective aesthetic. `apps/vinaya` carries no `-ai` suffix (meta/infra convention).
34
+
35
+ > **attalabs** — The AttaLabs ecosystem hub app (`@atta/attalabs-web`/`-mobile`/`-mcp-server`, serving `attalabs.dev`), distinct from **Atta** (the deep-thinking AI product — Vāda+Vitakka+Sati; its dedicated app scaffold, `apps/atta-ai`, was retired — Atta remains a real, code-less concept, not a registered project). Registered here because it has real code and its own `apps/attalabs/specs/`, but had no registry row until it was added retroactively (found while sizing an unrelated task's `Project(s)` field). `apps/attalabs` carries no `-ai` suffix (meta/infra-app convention, like `apps/desktop`).
36
+
37
+ > **aeg-core** — `@atta/aeg-core` (`packages/aeg-core`): the pure, no-I/O package the AEG product runs on. Two capabilities: parse a repo's AEG artifacts (registry, tranche files) into a typed model, and `deriveTranche(tranche, forgeFacts)` → per-task derived status + the dependency/conflict graph + dispatch eligibility, mirroring `tranche-model.md` §3 exactly. Both **AEG Studio** (local, the first consumer) and the future **Portal** (hosted) read through this same substrate; they differ only in how they fetch the inputs. Registered as a project because a task declares `Project: aeg-core` — a package may be a project, per this file's framing above.
38
+
39
+ > **aeg-forge-state** — `@atta/aeg-forge-state` (`packages/aeg-forge-state`): the one sanctioned adapter that reads live GitHub forge state (Milestone + labeled Issues) and derives the typed `Tranche`/`Task` shapes `aeg-core`'s pure evaluators consume — `gh`-CLI-only, read-only, zero topology file. Listed in `.aeg/packages` as its own collision domain (distinct from `aeg-core`) since before this row, but had no registry row of its own — any task editing its `src/**` had no `Project:` whose declared path actually covered it, forcing every prior task to fall back on a `blast-radius-ack:` line instead of a real ownership match. Registered retroactively (found while planning follow-on forge-adapter test-coverage and label-vocabulary work) for the same reason `attalabs` and `admin` were: real code, its own collision domain, no row.
40
+
41
+ ## How `Project` is validated
42
+
43
+ This registry is the **authority for valid project names.** A `Project:` value is valid iff every name in it is a row above. The Planner and Developer resolve `Project:` against this file; an unmatched name (a typo like `vda`, or unregistered) makes the task malformed and the agent refuses rather than guessing — *"Project 'vda' isn't registered; did you mean 'vada', or run `aeg add-project` first?"* Can also run mechanically in `verify-docs`.
44
+
45
+ ## A task can span multiple projects — and that is normal
46
+
47
+ `Project` is **multi-valued.** A task carries as many projects as it genuinely touches: usually one (`Project: vada`), sometimes several (`Project: engine, herald`, or more). Not an exception — cross-project PRs are an expected shape. The Planner decides split-vs-combine by **verification coupling** (see `tranche-model.md` §6): provable independently → separate tasks with a `depends-on` edge; provable only as a unit → one task / branch / PR / multiple projects. The same `Ticket:` rides on all resulting tasks, so work stays atomic in Jira however it's shaped in AEG.
48
+
49
+ When a task lists multiple projects, every mechanism fans out: the Developer reads every listed project's specs; the PR is reviewed through each project's lens (more projects = more review lenses = proportionally more rigor, matching the wider blast radius); the Archivist updates every listed project's `state.md` (non-derivable operational facts; `now.md` is retired).
50
+
51
+ ## Routing vs. conflicts — two different granularities
52
+
53
+ `Project` is the **coarse routing/ownership label** — "whose specs, whose state." It is **not** the conflict unit. Conflicts happen at the **package / collision-domain level**:
54
+
55
+ - **Collision domains are packages**, listed in a rarely-changed static file, `.aeg/packages`. Known cross-cutting paths that couple tasks across package boundaries — **lockfiles, `migrations/`, codegen outputs (protobuf/GraphQL/OpenAPI), monorepo config (tsconfig/eslint/turbo)** — are declared as their own collision domains.
56
+ - Two tasks conflict if they touch the same collision domain. The canonical case: a task generalizing `@atta/engine` (a package Vāda shares) conflicts with any in-flight Vāda task touching the engine — different projects, same package, real collision. So conflict detection keys on **packages, not projects**.
57
+ - Conflicts are **declared by the Planner** (`conflicts-with` edges) and **static**. The gate is forge-answerable with zero stored state: "is a `conflicts-with` sibling's PR open?" There is **no dynamic path-overlap scanner** — that would need a live task→changed-files map, the mutable state AEG eliminates (forbidden; see `tranche-model.md` §9). When unsure two tasks collide, declare the conflict and serialize.
58
+
59
+ Don't conflate them: `Project` = whose specs/state; `conflicts-with` (via collision domains) = whose files.
60
+
61
+ ---
62
+
63
+ Project backlogs (held / future items, out of the AEG flow) live alongside each project's specs as `<path>/specs/<project>-backlog.md`. Cross-cutting / ecosystem items are cut as backlog Issues on the forge (the old `specs/ecosystem-backlog.md` was deleted).