@attalabs/vinaya 0.22.0 → 0.24.0

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 (194) hide show
  1. package/README.md +14 -3
  2. package/aeg-root/contracts/archivist-tranche-archivist.md +2 -2
  3. package/aeg-root/contracts/brief-developer.md +1 -0
  4. package/aeg-root/contracts/developer-reviewer.md +3 -3
  5. package/aeg-root/contracts/reviewer-archivist.md +3 -3
  6. package/aeg-root/enforcement.md +26 -20
  7. package/aeg-root/process.md +8 -4
  8. package/aeg-root/roles/archivist.md +2 -2
  9. package/aeg-root/roles/brief-author.md +9 -3
  10. package/aeg-root/roles/developer.md +63 -31
  11. package/aeg-root/roles/planner.md +1 -1
  12. package/aeg-root/roles/principal.md +14 -0
  13. package/aeg-root/roles/reviewer.md +32 -15
  14. package/aeg-root/roles/security.md +15 -5
  15. package/aeg-root/skills/aeg/SKILL.md +9 -5
  16. package/aeg-root/skills/aeg-roles/SKILL.md +2 -2
  17. package/aeg-root/skills/brief-authoring/SKILL.md +39 -27
  18. package/aeg-root/state-machine.md +9 -11
  19. package/aeg-root/templates/brief-template.md +6 -6
  20. package/aeg-root/templates/pr-report-template.md +10 -7
  21. package/aeg-root/tranche-model.md +6 -2
  22. package/dist/checks/bin/check-body-bare-digits.js +559 -140
  23. package/dist/checks/bin/check-branch-topology.js +630 -145
  24. package/dist/checks/bin/check-brief-shape.js +600 -143
  25. package/dist/checks/bin/check-changeset-coverage.js +942 -162
  26. package/dist/checks/bin/check-closes-n.js +630 -145
  27. package/dist/checks/bin/check-coherence.js +641 -148
  28. package/dist/checks/bin/check-dead-branch-push.js +559 -140
  29. package/dist/checks/bin/check-dispatch-readiness.js +639 -146
  30. package/dist/checks/bin/check-doc-coverage-push.js +696 -219
  31. package/dist/checks/bin/check-doc-coverage.js +696 -219
  32. package/dist/checks/bin/check-doctrine-no-procedures.js +4800 -0
  33. package/dist/checks/bin/check-doctrine-portability.js +691 -147
  34. package/dist/checks/bin/check-evidence-fresh.js +1048 -222
  35. package/dist/checks/bin/check-exec-bits.js +4937 -0
  36. package/dist/checks/bin/check-first-push-dispatch.js +630 -145
  37. package/dist/checks/bin/check-issue-assignment.js +630 -145
  38. package/dist/checks/bin/check-main-branch-refusal.js +565 -141
  39. package/dist/checks/bin/check-no-disk-state.js +559 -140
  40. package/dist/checks/bin/check-pr-report-density.js +4467 -0
  41. package/dist/checks/bin/check-quoted-command.js +656 -164
  42. package/dist/checks/bin/check-reader-resolvable-prose.js +652 -163
  43. package/dist/checks/bin/check-registry-gates.js +602 -145
  44. package/dist/checks/bin/check-retired-vocabulary.js +652 -163
  45. package/dist/checks/bin/check-review-gate.js +677 -142
  46. package/dist/checks/bin/check-single-plan-pr.js +559 -140
  47. package/dist/checks/bin/check-test-plan.js +560 -141
  48. package/dist/checks/bin/check-token-collection-wired.js +559 -140
  49. package/dist/checks/bin/check-token-report.js +559 -140
  50. package/dist/checks/bin/check-workspace-escape.js +992 -146
  51. package/dist/index.js +8884 -5627
  52. package/package.json +1 -1
  53. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/actions.ts +173 -0
  54. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/anchored-region.ts +118 -0
  55. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/archive-task.ts +206 -0
  56. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/baseline-capture.ts +65 -0
  57. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/blast-radius-domains.ts +198 -0
  58. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/branch-topology-gate.ts +85 -0
  59. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/brief-render.ts +462 -0
  60. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/brief-validation.ts +912 -0
  61. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/claude-code-transcript.ts +460 -0
  62. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/coherence-checks.ts +806 -0
  63. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/consumer-enumeration.ts +75 -0
  64. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/dead-branch-push-audit.ts +55 -0
  65. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/dead-branch-push-guard.ts +77 -0
  66. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/derive-section7.ts +66 -0
  67. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/derive-tranche.ts +94 -0
  68. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/diagram-model.ts +372 -0
  69. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/direct-main-push.ts +39 -0
  70. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/dispatch-gate.ts +268 -0
  71. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doc-owners.ts +314 -0
  72. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/build-doc-nav.ts +38 -0
  73. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/docs-coherence.ts +136 -0
  74. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/index.ts +29 -0
  75. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/legacy-anchors.ts +48 -0
  76. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/nav-helpers.ts +17 -0
  77. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/node-route.ts +134 -0
  78. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/parse-doc.ts +43 -0
  79. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/published-prose.ts +306 -0
  80. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/surfaced-manifest.ts +89 -0
  81. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/types.ts +33 -0
  82. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doctrine-no-procedures.ts +94 -0
  83. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doctrine-portability.ts +293 -0
  84. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doctrine-source.ts +23 -0
  85. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/ensure-label.ts +28 -0
  86. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/file-classify.ts +51 -0
  87. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/first-push-dispatch-gate.ts +80 -0
  88. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/gate-audience.ts +171 -0
  89. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/index.ts +323 -0
  90. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/issue-assignment.ts +102 -0
  91. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/issue-validation.ts +746 -0
  92. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/leftover-detection.ts +68 -0
  93. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/local-anchor-coverage.ts +245 -0
  94. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/main-branch-refusal.ts +64 -0
  95. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/manifest-validity.ts +81 -0
  96. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/markdown-table.ts +76 -0
  97. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/metering-io-guard.ts +119 -0
  98. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/milestone-validation.ts +240 -0
  99. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/no-disk-state.ts +50 -0
  100. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-ledger.ts +77 -0
  101. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-registry.ts +105 -0
  102. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-token-report.ts +177 -0
  103. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-tranche.ts +183 -0
  104. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/pr-report-density.ts +116 -0
  105. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/pr-tier.ts +81 -0
  106. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/premise-check.ts +160 -0
  107. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/quoted-command.ts +275 -0
  108. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/reader-resolvable-prose.ts +320 -0
  109. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/registry-checks.ts +255 -0
  110. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/registry-parse.ts +164 -0
  111. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/registry-scaffold.ts +216 -0
  112. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/report-tokens.ts +261 -0
  113. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/retired-vocabulary.ts +163 -0
  114. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/review-gate.ts +378 -0
  115. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/review-status.ts +200 -0
  116. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/single-plan-pr.ts +73 -0
  117. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/state-machine-model.ts +269 -0
  118. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/state-source.ts +14 -0
  119. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/status-block.ts +8 -0
  120. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/sum-ledger.ts +26 -0
  121. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/symbol-collisions.ts +78 -0
  122. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/test-plan-gate.ts +117 -0
  123. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/test-plan-section.ts +65 -0
  124. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/types.ts +174 -0
  125. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/verdict-extraction.ts +199 -0
  126. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/vocabulary-citation.ts +114 -0
  127. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/waiver-label.ts +52 -0
  128. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/workspace-escape.ts +172 -0
  129. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  130. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  131. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  132. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  133. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  134. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  135. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  136. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  137. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  138. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  139. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  140. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  141. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  142. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
  143. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  144. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  145. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  146. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  147. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  148. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  149. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  150. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  151. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  152. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  153. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  154. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  155. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  156. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  157. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  158. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  159. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  160. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  161. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  162. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  163. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  164. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  165. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  166. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  167. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/[root-of-the-server]__02a-3g8._.js +1 -1
  168. 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
  169. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0053k9k._.js +1 -1
  170. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0112h-k._.js +1 -1
  171. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0o771t1._.js +1 -1
  172. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0puovz5._.js +1 -1
  173. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1hs0dcu._.js +1 -1
  174. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__069-_41._.js → [root-of-the-server]__1pndh3_._.js} +1 -1
  175. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1wc4-ip._.js +1 -1
  176. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_03x_w6q._.js +1 -1
  177. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0gvm3og._.js +1 -1
  178. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0lwxg63._.js +1 -1
  179. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1fqw88f._.js +2 -2
  180. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1n0cnq-._.js +1 -1
  181. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/apps_vinaya-studio_web_src_app_studio_projects_[name]_tranches_[slug]_05ufo8i._.js +3 -3
  182. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  183. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  184. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  185. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  186. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  187. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{37hnluhzj9bei.js → 0zebsmmk1bvnb.js} +1 -1
  188. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{267o-tsrnuwnv.js → 1uogpj5w2n5ju.js} +1 -1
  189. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1hufpuody6vjv.js → 3m1kgax7j2vgs.js} +1 -1
  190. package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
  191. /package/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/{aeg-core → vinaya/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/aeg-core}/bin/verify-coherence.ts +0 -0
  192. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → QY2GhiI47-765D9DHGy1I}/_buildManifest.js +0 -0
  193. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → QY2GhiI47-765D9DHGy1I}/_clientMiddlewareManifest.js +0 -0
  194. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → QY2GhiI47-765D9DHGy1I}/_ssgManifest.js +0 -0
@@ -0,0 +1,75 @@
1
+ /**
2
+ * `checkConsumerTests`'s consumer enumeration (`brief-validation.ts`, task 10,
3
+ * Issue #385; corrected by the round-2 ruling item 3) — every workspace
4
+ * member whose own `package.json` depends on `@attalabs/<pkg>`, for "every
5
+ * workspace package depending on `@attalabs/<pkg>`" to mean what it says.
6
+ *
7
+ * The first cut reused `deriveWorkspacePackageDomains` (`blast-radius-domains.ts`),
8
+ * which deliberately collapses to `packages/*` members only — correct for
9
+ * that module's own collision-domain purpose, wrong here: it silently
10
+ * exempted `apps/cli`, a real `@attalabs/aeg-core` consumer, from a rule
11
+ * that promises "every workspace package" (found live reviewing this PR's
12
+ * own diff). This module resolves every workspace glob instead —
13
+ * `apps/*` and `packages/*` alike — via `resolveWorkspaceEntry`, the same
14
+ * glob grammar `deriveWorkspacePackageDomains` uses, reused rather than
15
+ * re-derived a second time.
16
+ *
17
+ * Zero I/O: `listDirs`/`readManifest` are injected by each of the two real
18
+ * entry points — `packages/aeg-core/bin/verify-brief.ts` (authoring-time,
19
+ * pre-dispatch) and `apps/cli`'s `check-brief-shape.ts` (CI) — so both share
20
+ * ONE enumeration rather than two independently-maintained copies that can
21
+ * silently diverge (round-2 ruling item 4).
22
+ */
23
+
24
+ import { resolveWorkspaceEntry } from './blast-radius-domains'
25
+
26
+ export type PackageManifest = { dependencies?: Record<string, string>; devDependencies?: Record<string, string> }
27
+
28
+ /**
29
+ * Every workspace member directory (`apps/cli`, `packages/aeg-core`, …),
30
+ * resolved from `workspaces` (positive entries minus any `!`-negated ones,
31
+ * same semantics as `deriveWorkspacePackageDomains`) — but NOT collapsed to
32
+ * `packages/*` only, since a real consumer can live anywhere in the
33
+ * workspace.
34
+ */
35
+ export function deriveWorkspaceMemberDirs(workspaces: string[], listDirs: (dir: string) => string[]): string[] {
36
+ const positive = workspaces.filter((entry) => !entry.startsWith('!'))
37
+ const negative = workspaces.filter((entry) => entry.startsWith('!')).map((entry) => entry.slice(1))
38
+
39
+ const excluded = new Set<string>()
40
+ for (const entry of negative) {
41
+ for (const member of resolveWorkspaceEntry(entry, listDirs)) excluded.add(member)
42
+ }
43
+
44
+ const members = new Set<string>()
45
+ for (const entry of positive) {
46
+ for (const member of resolveWorkspaceEntry(entry, listDirs)) {
47
+ if (!excluded.has(member)) members.add(member)
48
+ }
49
+ }
50
+ return [...members].sort()
51
+ }
52
+
53
+ /**
54
+ * Builds `checkConsumerTests`'s `consumersOf` callback: for a package `pkg`
55
+ * (as named in a brief's `§4`, e.g. `packages/aeg-core/` → `'aeg-core'`),
56
+ * every workspace member directory (self excluded) whose own manifest lists
57
+ * `@attalabs/<pkg>` in `dependencies` or `devDependencies`.
58
+ */
59
+ export function buildConsumersOf(
60
+ workspaces: string[],
61
+ listDirs: (dir: string) => string[],
62
+ readManifest: (dir: string) => PackageManifest | null
63
+ ): (pkg: string) => string[] {
64
+ const members = deriveWorkspaceMemberDirs(workspaces, listDirs)
65
+
66
+ return (pkg: string): string[] => {
67
+ const target = `@attalabs/${pkg}`
68
+ return members.filter((dir) => {
69
+ if (dir === `packages/${pkg}`) return false
70
+ const manifest = readManifest(dir)
71
+ if (!manifest) return false
72
+ return target in (manifest.dependencies ?? {}) || target in (manifest.devDependencies ?? {})
73
+ })
74
+ }
75
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Dead-branch-push AUDIT (aeg-governance-hardening task 24, #364, Part 4 —
3
+ * item 2). Distinct from the ring-0 `dead-branch-push-guard.ts` (task 18):
4
+ * that predicate answers "should THIS push be allowed right now?" at push
5
+ * time; this one answers "did commits land on a branch AFTER its PR already
6
+ * resolved?" retrospectively, across every remote `task/*` branch — the
7
+ * ring-2 backstop for the class the ring-0 hook is fail-open/hook-dependent
8
+ * against (a writer without the hook installed, or a hook bypass). Pure —
9
+ * no `fs`, no `gh`/`git` shell-outs. The CLI shim (`bin/dead-branch-audit.ts`,
10
+ * joining task 23's `daily-drift` job) gathers each branch's most recent
11
+ * PR (any state) plus its resolution/latest-commit timestamps and passes
12
+ * them in here.
13
+ *
14
+ * Exists to catch the "six topology rows landed on a merged plan PR's
15
+ * branch" incident class (2026-07-03) server-side, within a day, instead of
16
+ * never — never-red discipline: `daily-drift` (the CI job that calls this)
17
+ * must never fail red.
18
+ */
19
+
20
+ export type DeadBranchFact = {
21
+ branch: string
22
+ prNumber: number
23
+ prState: 'MERGED' | 'CLOSED'
24
+ /** ISO timestamp the branch's PR was merged (MERGED) or closed (CLOSED). */
25
+ resolvedAt: string
26
+ /** ISO commit date of the branch's current remote tip. */
27
+ latestCommitAt: string
28
+ }
29
+
30
+ export type DeadBranchPush = {
31
+ branch: string
32
+ prNumber: number
33
+ prState: 'MERGED' | 'CLOSED'
34
+ resolvedAt: string
35
+ latestCommitAt: string
36
+ }
37
+
38
+ /**
39
+ * Flags a branch whose current tip commit is strictly newer than its own
40
+ * PR's resolution time — i.e. commits kept landing on a branch after the
41
+ * forge already considered its work done. A branch whose tip predates (or
42
+ * exactly matches) its PR's resolution is normal: the branch simply hasn't
43
+ * been deleted yet, which is not itself a violation.
44
+ */
45
+ export function findDeadBranchPushes(facts: DeadBranchFact[]): DeadBranchPush[] {
46
+ return facts
47
+ .filter((f) => new Date(f.latestCommitAt).getTime() > new Date(f.resolvedAt).getTime())
48
+ .map(({ branch, prNumber, prState, resolvedAt, latestCommitAt }) => ({
49
+ branch,
50
+ prNumber,
51
+ prState,
52
+ resolvedAt,
53
+ latestCommitAt
54
+ }))
55
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Dead-branch push guard (aeg-governance-hardening task 18, #335). Pure — no
3
+ * `fs`, no `gh`/`git` shell-outs. The CLI shim (`bin/check-push-target.ts`)
4
+ * makes the one batched forge call (`gh pr list --head <branch> --state all
5
+ * --json number,state --limit 1`), maps its result to a `PrStateFact`, and
6
+ * passes it in here.
7
+ *
8
+ * Exists to answer, deterministically, "is it safe to push more commits to
9
+ * this `task/<tranche>/<n>` branch?" — closing the live-fire incident
10
+ * class where stale local work landed on a branch whose PR had already
11
+ * merged, silently reopening/confusing forge state with no push-time signal.
12
+ *
13
+ * `UNKNOWN` (forge unreachable — network flake, auth issue, rate limit) maps
14
+ * to `allow`, the same as `NONE` (no PR exists yet — a brand-new branch
15
+ * legitimately has none). This is a deliberate fail-open choice: a guard
16
+ * that can block *all* pushes on a transient forge-reachability issue is
17
+ * worse than the bug it fixes. See `aeg-root/enforcement.md`.
18
+ */
19
+
20
+ export type PrStateFact = 'MERGED' | 'CLOSED' | 'OPEN' | 'NONE' | 'UNKNOWN'
21
+
22
+ export type DeadBranchPushInput = {
23
+ /** The `task/<tranche>/<n>` branch being pushed. */
24
+ branch: string
25
+ /** State of the branch's most recent PR (any state), or the fact that none/forge-unreachable applies. */
26
+ prState: PrStateFact
27
+ /** The PR number that `prState` refers to, or `null` when no PR exists / could not be determined. */
28
+ prNumber: number | null
29
+ }
30
+
31
+ export type DeadBranchPushVerdict = 'allow' | 'refuse'
32
+
33
+ export type DeadBranchPushResult = { verdict: DeadBranchPushVerdict; reason: string }
34
+
35
+ /**
36
+ * `allow` — `OPEN`/`NONE`/`UNKNOWN`: an in-flight PR, no PR yet, or the
37
+ * forge couldn't be reached — all legitimate reasons to push.
38
+ * `refuse` — `MERGED`/`CLOSED`: the branch's most recent PR is already
39
+ * resolved. Pushing here would silently resurrect resolved forge
40
+ * state instead of starting a fresh branch.
41
+ */
42
+ export function checkDeadBranchPush(input: DeadBranchPushInput): DeadBranchPushResult {
43
+ const { branch, prState, prNumber } = input
44
+
45
+ if (prState === 'MERGED') {
46
+ return {
47
+ verdict: 'refuse',
48
+ reason: `Branch \`${branch}\` already has a MERGED PR (#${prNumber}) — this branch's work is done. Push a new task branch instead of pushing onto an already-resolved one.`
49
+ }
50
+ }
51
+
52
+ if (prState === 'CLOSED') {
53
+ return {
54
+ verdict: 'refuse',
55
+ reason: `Branch \`${branch}\` has a CLOSED PR (#${prNumber}) that was never merged — pushing here would resurrect abandoned/rejected work. Open a new task branch instead.`
56
+ }
57
+ }
58
+
59
+ if (prState === 'OPEN') {
60
+ return {
61
+ verdict: 'allow',
62
+ reason: `Branch \`${branch}\` has an OPEN PR (#${prNumber}) — pushing more commits to it is normal.`
63
+ }
64
+ }
65
+
66
+ if (prState === 'NONE') {
67
+ return {
68
+ verdict: 'allow',
69
+ reason: `Branch \`${branch}\` has no PR yet — a brand-new task branch legitimately has none.`
70
+ }
71
+ }
72
+
73
+ return {
74
+ verdict: 'allow',
75
+ reason: `Could not determine \`${branch}\`'s PR state (forge unreachable) — failing open rather than blocking the push on a transient issue.`
76
+ }
77
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Derive the floor of a task's §7 doc-update list by matching its intended
3
+ * surface globs against `.vinaya/doc-owners` bindings (state-machine.md
4
+ * Section 15). Pure — parses doc-owners content via `parseDocOwners` (no
5
+ * second parser) and does segment-wise glob overlap between each intended
6
+ * surface and each binding's code glob. A Planner/Brief-Author aid only —
7
+ * not wired into any CI gate.
8
+ */
9
+
10
+ import { parseDocOwners } from './doc-owners'
11
+
12
+ export type Section7Match = { surface: string; glob: string; pointer: string; lineNum: number }
13
+
14
+ export function deriveSection7(
15
+ intendedSurfaces: string[],
16
+ docOwnersContent: string
17
+ ): { pointers: string[]; matches: Section7Match[]; errors: string[] } {
18
+ const { bindings, errors } = parseDocOwners(docOwnersContent)
19
+ const matches: Section7Match[] = []
20
+ for (const surface of intendedSurfaces) {
21
+ for (const binding of bindings) {
22
+ if (globsOverlap(binding.glob, surface)) {
23
+ matches.push({ surface, glob: binding.glob, pointer: binding.pointer, lineNum: binding.lineNum })
24
+ }
25
+ }
26
+ }
27
+
28
+ const pointers: string[] = []
29
+ for (const m of matches) {
30
+ if (!pointers.includes(m.pointer)) pointers.push(m.pointer)
31
+ }
32
+
33
+ return { pointers, matches, errors }
34
+ }
35
+
36
+ /**
37
+ * True if two doc-owners-grammar globs can match at least one common path,
38
+ * compared segment-by-segment (split on `/`). `**` absorbs zero or more
39
+ * remaining segments on either side — checked at each step, even once one
40
+ * side has run out of segments, so a trailing `**` still absorbs. `*`
41
+ * matches exactly one segment regardless of the other side's literal/`*`.
42
+ * Two literal segments must be byte-equal. If one side runs out of segments
43
+ * before the other and the next pending segment on either side isn't `**`,
44
+ * there is no overlap.
45
+ */
46
+ export function globsOverlap(a: string, b: string): boolean {
47
+ const segsA = a.split('/')
48
+ const segsB = b.split('/')
49
+ let i = 0
50
+ let j = 0
51
+ while (i < segsA.length || j < segsB.length) {
52
+ const sa = i < segsA.length ? segsA[i] : undefined
53
+ const sb = j < segsB.length ? segsB[j] : undefined
54
+ if (sa === '**' || sb === '**') return true
55
+ if (sa === undefined || sb === undefined) return false
56
+ if (sa === '*' || sb === '*') {
57
+ i++
58
+ j++
59
+ continue
60
+ }
61
+ if (sa !== sb) return false
62
+ i++
63
+ j++
64
+ }
65
+ return true
66
+ }
@@ -0,0 +1,94 @@
1
+ import { deriveStatusFromModel } from './state-machine-model'
2
+ import type { DerivedTranche, DerivedStatus, DerivedTask, ForgeFacts, Tranche, Task, UnknownEdge } from './types'
3
+
4
+ /**
5
+ * Compute each task's derived status, the resolved depends-on / conflicts-with
6
+ * graph, and dispatch-eligibility per `tranche-model.md` §3 (status table)
7
+ * and §8 (dispatch gates).
8
+ *
9
+ * The function is pure: `tranche` + `forgeFacts` in, `DerivedTranche` out.
10
+ * Edges referencing task ids not present in this tranche's table are
11
+ * reported via `unknownEdges` rather than throwing — see §4 traps in the brief
12
+ * (real files reference dropped/prose-only ids in narrative).
13
+ */
14
+ export function deriveTranche(tranche: Tranche, forgeFacts: Map<string, ForgeFacts>): DerivedTranche {
15
+ const byId = new Map<string, Task>()
16
+ for (const task of tranche.tasks) byId.set(task.id, task)
17
+
18
+ // First pass: derive each task's own status.
19
+ const statuses = new Map<string, DerivedStatus>()
20
+ for (const task of tranche.tasks) {
21
+ statuses.set(task.id, deriveStatus(forgeFacts.get(task.id)))
22
+ }
23
+
24
+ // Second pass: dispatch eligibility, which references siblings' derived
25
+ // statuses (so it must come after the first pass).
26
+ const unknownEdges: UnknownEdge[] = []
27
+ const derived: DerivedTask[] = []
28
+ for (const task of tranche.tasks) {
29
+ const status = statuses.get(task.id) ?? 'todo'
30
+ const dependsOnNotMerged: string[] = []
31
+ for (const dep of task.dependsOn) {
32
+ if (!byId.has(dep)) {
33
+ unknownEdges.push({ from: task.id, to: dep, kind: 'depends-on' })
34
+ continue
35
+ }
36
+ if (statuses.get(dep) !== 'merged') dependsOnNotMerged.push(dep)
37
+ }
38
+ const conflictsWithOpenOrInFlight: string[] = []
39
+ for (const sib of task.conflictsWith) {
40
+ if (!byId.has(sib)) {
41
+ unknownEdges.push({ from: task.id, to: sib, kind: 'conflicts-with' })
42
+ continue
43
+ }
44
+ const sibStatus = statuses.get(sib)
45
+ if (sibStatus === 'in-flight' || sibStatus === 'in-review' || sibStatus === 'changes-requested') {
46
+ conflictsWithOpenOrInFlight.push(sib)
47
+ }
48
+ }
49
+ derived.push({
50
+ task,
51
+ status,
52
+ dispatchable: dependsOnNotMerged.length === 0 && conflictsWithOpenOrInFlight.length === 0,
53
+ blockers: { dependsOnNotMerged, conflictsWithOpenOrInFlight }
54
+ })
55
+ }
56
+
57
+ return { tranche, tasks: derived, unknownEdges }
58
+ }
59
+
60
+ /**
61
+ * Map a single task's forge facts to its derived status. Mirrors the §3 table
62
+ *: `blocked` wins over everything else, then `merged`, then
63
+ * `changes-requested`, then `in-review`, then `in-flight`, then `todo` for the
64
+ * open-issue cases (assigned or not — both are `todo` inside a tranche per
65
+ *), then the honest terminal cases for a closed-without-merge Issue.
66
+ *
67
+ * Reopened-after-merge exception: a reopened Issue can carry a stale
68
+ * `prState: 'merged'` fact from a closing PR that predates the reopen —
69
+ * `issueState` and `prState` are independently sourced, and GitHub does not
70
+ * retract a PR's merge history just because the Issue reopened. Without this
71
+ * check, such a task would permanently display `merged` even though the work
72
+ * it names was never actually done. Checked before the `prState === 'merged'`
73
+ * branch below so a currently-open Issue always wins over a historical merge.
74
+ *
75
+ * Honest terminal derivation: a closed Issue with no merged PR must
76
+ * never resolve to `todo` (which would imply not-started — a lie). It reads
77
+ * GitHub's native `stateReason`: closed `NOT_PLANNED` → `dropped` (legitimately
78
+ * abandoned); anything else (closed `COMPLETED` or no reason recorded) →
79
+ * `incoherent` (genuinely done but unprovable, or a broken close that the
80
+ * `Closes #N` law was built to surface).
81
+ *
82
+ * A missing `forgeFacts` entry → `todo`. Tranche tasks are committed work;
83
+ * `backlog` is a project-level concept only and is never emitted here.
84
+ *
85
+ * The rules themselves are no longer written here. They live as an ordered,
86
+ * pure-data list in `state-machine-model.ts` (the `actions.ts`
87
+ * discipline): one list, executed by this function and rendered by the docs,
88
+ * so the two cannot drift. Each rule carries the prose and the reason its
89
+ * position matters — change the order there, and this function's behavior
90
+ * changes with it.
91
+ */
92
+ function deriveStatus(facts: ForgeFacts | undefined): DerivedStatus {
93
+ return deriveStatusFromModel(facts)
94
+ }