@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,171 @@
1
+ /**
2
+ * Who each governance gate under `packages/aeg-core/bin/` is FOR.
3
+ *
4
+ * The problem this closes: this repository holds two parallel worlds — the
5
+ * gates it runs on itself (`packages/aeg-core/bin/*`) and the checks an
6
+ * adopter runs through the published CLI (`apps/cli/src/checks/bin/*`) — and
7
+ * nothing asserted a relationship between them. Membership of the shipped set
8
+ * was defined by ABSENCE from `coreCheckRegistry()`, which means a deliberate
9
+ * exclusion and a forgotten port left exactly the same trace.
10
+ *
11
+ * `reader-resolvable-prose` is the instructive case, and the reason this file
12
+ * exists. That call was made carefully and its reasoning written up well — in
13
+ * a paragraph of prose inside `registry.ts`. It is right, and it is invisible
14
+ * to tooling. A good decision and an unmade decision looked identical, so the
15
+ * next one might simply not get made.
16
+ *
17
+ * So `internal` is a thing you must SAY, with a reason, rather than something
18
+ * you can forget to notice. `gate-audience.test.ts` enumerates the bin
19
+ * directory and fails when a gate is in neither column.
20
+ *
21
+ * **This is deliberately NOT a field on `CheckSpec`.** Adopter-defined checks
22
+ * in `vinaya.config.json` produce that same shape (`lib/config.ts`), so an
23
+ * `audience` field there would push an internal-governance concept into
24
+ * adopter-facing config, where it means nothing and cannot be answered.
25
+ * The question is about THIS repo's own bins, so the declaration lives here.
26
+ *
27
+ * Adding a gate to `bin/` and nothing else is now a failing build, naming the
28
+ * file. That is the whole mechanism; deciding which column a given gate
29
+ * belongs in stays a human judgement, which is the point.
30
+ */
31
+
32
+ /** A gate that adopters run, and the `coreCheckRegistry()` name it ships
33
+ * under. `ring` mirrors `apps/cli/src/checks/registry.ts`'s `CORE_CHECK_RING`
34
+ * value for that same name — duplicated here, not imported, because
35
+ * `aeg-core` cannot import `apps/cli` (see this file's own module comment on
36
+ * the dependency-cycle constraint `coreCheckRegistry()` runs into). Kept
37
+ * from drifting by `registry-ring-parity.test.ts` (`apps/cli`), which is the
38
+ * only place both sides are in scope at once — same shape as
39
+ * `shipped-bin-audience.test.ts`'s existing `shippedAs` cross-check. This is
40
+ * the "registry-derived fact" `registry-scaffold.ts` reads to place a stub
41
+ * row for an aeg-core-bin G2 candidate that IS registry-backed; a candidate
42
+ * with no entry here gets no stub (see `registry-scaffold.ts`). */
43
+ export type ShippedGate = { shippedAs: string | string[]; ring: 0 | 1 | 2 }
44
+ /** A gate this repo runs on itself, and why it cannot or should not ship. */
45
+ export type InternalGate = { internal: string }
46
+ export type GateAudience = ShippedGate | InternalGate
47
+
48
+ /**
49
+ * Keyed by basename (no extension) of each `packages/aeg-core/bin/*.ts` that
50
+ * is a GATE — something that inspects a repo and passes or fails. Non-gate
51
+ * tooling in the same directory (forge writers, one-shot reporters) is listed
52
+ * in `NON_GATE_BINS` below rather than given a fake audience.
53
+ */
54
+ export const GATE_AUDIENCE: Record<string, GateAudience> = {
55
+ 'check-branch-topology': { shippedAs: 'branch-topology', ring: 0 },
56
+ 'check-first-push-dispatch': { shippedAs: 'first-push-dispatch', ring: 0 },
57
+ 'check-no-disk-state': { shippedAs: 'no-disk-state', ring: 0 },
58
+ 'verify-brief': { shippedAs: 'brief-shape', ring: 0 },
59
+ 'verify-coherence': { shippedAs: 'coherence', ring: 0 },
60
+ 'verify-dispatch': { shippedAs: 'dispatch-readiness', ring: 0 },
61
+ 'verify-docs': { shippedAs: ['doc-coverage', 'doc-coverage-push'], ring: 0 },
62
+ 'verify-registry': { shippedAs: 'registry-gates', ring: 0 },
63
+ 'verify-review-gate': { shippedAs: 'review-gate', ring: 1 },
64
+ 'verify-single-plan-pr': { shippedAs: 'single-plan-pr', ring: 0 },
65
+ 'verify-test-plan': { shippedAs: 'test-plan', ring: 1 },
66
+
67
+ 'check-direct-main-push': {
68
+ internal:
69
+ 'Reaches adopters through `vinaya audit --only=direct-push`, not the check registry: it is a ring-2 sweep over merge history keyed on `GITHUB_SHA`, run by the generated archivist job on push to the default branch. It has no diff to key on, so there is nothing for `vinaya check` to run it against.'
70
+ },
71
+ 'check-push-target': {
72
+ internal:
73
+ 'A pre-push hook helper: it takes a branch name as `argv[2]` and answers one `gh pr list --head <branch>` question, for a caller that already knows which ref is being pushed. Adopters reach the same question through the registered `dead-branch-push`, which resolves the branch itself when `BRANCH` is unset. Registering this one too would add a second entry answering an identical forge query with a worse interface.'
74
+ },
75
+ 'verify-task': {
76
+ internal:
77
+ "A composite that shells this repo's own `typecheck`/`lint`/`test`/`build` scripts by name. Those names are this monorepo's toolchain (bun + turbo + biome), not an adopter's; the shipped equivalent of its intent is `vinaya check --all`, which is portable by construction."
78
+ }
79
+ }
80
+
81
+ /**
82
+ * The other half, and the one the motivating case actually lives in.
83
+ *
84
+ * `reader-resolvable-prose` has no bin under `packages/aeg-core/bin/` at all —
85
+ * its logic is `src/reader-resolvable-prose.ts` and its only executable is
86
+ * `apps/cli/src/checks/bin/check-reader-resolvable-prose.ts`. So the map above
87
+ * would never have seen it, and an earlier revision of this file claimed it as
88
+ * the reason the file exists while leaving it uncovered. Review caught that;
89
+ * this is the fix, not a re-wording.
90
+ *
91
+ * Enumerating the SHIPPED check bins asks the question that case actually
92
+ * poses: here is an executable adopters could run — is it registered, and if
93
+ * not, why not? `coreCheckRegistry()` lives in `apps/cli`, which `aeg-core`
94
+ * cannot import without closing a dependency cycle, so the declaration lives
95
+ * here and `apps/cli`'s own suite does the asserting.
96
+ *
97
+ * `check-reader-resolvable-prose`/`check-retired-vocabulary` (task 7, Issue
98
+ * #56): both used to live here as `internal` — hardcoded monorepo-specific
99
+ * doctrine paths made them unreachable through an adopter's own registry.
100
+ * Both are now config-driven (`vinaya.config.json`'s `proseGates`) and
101
+ * registered in `coreCheckRegistry()` as `reader-resolvable-prose` /
102
+ * `retired-vocabulary`, so neither belongs in this map any more — this repo's
103
+ * own `shipped-bin-audience.test.ts` refuses a bin declared in BOTH places.
104
+ */
105
+ export const SHIPPED_BIN_AUDIENCE: Record<string, GateAudience> = {}
106
+
107
+ /**
108
+ * `name` -> `ring` for every check registered in `coreCheckRegistry()` whose
109
+ * bin lives under `apps/cli/src/checks/bin/` — the direct analogue of
110
+ * `ShippedGate.ring` above, for the OTHER directory. Mirrors
111
+ * `apps/cli/src/checks/registry.ts`'s `CORE_CHECK_RING` — duplicated, not
112
+ * imported, for the identical dependency-cycle reason `ShippedGate.ring`'s
113
+ * own comment gives: `aeg-core` cannot import `apps/cli`. This is the
114
+ * registry-derived fact `registry-scaffold.ts` reads to place a stub row for
115
+ * an `apps/cli/src/checks/bin/` G2 candidate; a bin whose stripped name has
116
+ * no entry here gets no stub — same no-guess discipline `classify()`
117
+ * already applies to `packages/aeg-core/bin/` candidates via
118
+ * `GATE_AUDIENCE`. Kept from drifting by `shipped-bin-audience.test.ts`
119
+ * (`apps/cli`)'s own ring-parity block, the only place both sides are in
120
+ * scope at once — same shape as this file's `ShippedGate.ring` check.
121
+ */
122
+ export const CLI_CHECK_RING: Readonly<Record<string, 0 | 1 | 2>> = {
123
+ 'brief-shape': 0,
124
+ 'pr-report-density': 0,
125
+ 'doc-coverage': 0,
126
+ coherence: 0,
127
+ 'dispatch-readiness': 0,
128
+ 'closes-n': 1,
129
+ 'single-plan-pr': 0,
130
+ 'test-plan': 1,
131
+ 'body-bare-digits': 1,
132
+ 'no-disk-state': 0,
133
+ 'registry-gates': 0,
134
+ 'review-gate': 1,
135
+ 'branch-topology': 0,
136
+ 'dead-branch-push': 0,
137
+ 'first-push-dispatch': 0,
138
+ 'doc-coverage-push': 0,
139
+ 'issue-assignment': 0,
140
+ 'evidence-fresh': 1,
141
+ 'reader-resolvable-prose': 0,
142
+ 'retired-vocabulary': 0,
143
+ 'doctrine-no-procedures': 0,
144
+ 'doctrine-portability': 0,
145
+ 'workspace-escape': 0,
146
+ 'exec-bits': 0,
147
+ 'changeset-coverage': 0,
148
+ 'quoted-command': 0,
149
+ 'main-branch-refusal': 0,
150
+ 'token-collection-wired': 0,
151
+ 'token-report': 1
152
+ }
153
+
154
+ /**
155
+ * Files in `bin/` that are not gates and therefore have no audience: forge
156
+ * writers and one-shot reporters. Listed explicitly rather than pattern-matched
157
+ * so a NEW file cannot slip through by being named unlike a gate.
158
+ */
159
+ export const NON_GATE_BINS = [
160
+ 'archive-task',
161
+ 'assign-task-issue',
162
+ 'dead-branch-audit',
163
+ 'eval-agent-compliance',
164
+ 'open-issue',
165
+ 'open-pr',
166
+ 'report-tokens'
167
+ ] as const
168
+
169
+ export function isShipped(a: GateAudience): a is ShippedGate {
170
+ return 'shippedAs' in a
171
+ }
@@ -0,0 +1,323 @@
1
+ export * from './types'
2
+ export type { StateSource } from './state-source'
3
+ export type { DoctrineContent, DoctrineSource } from './doctrine-source'
4
+ export { CLI_CHECK_RING, GATE_AUDIENCE, NON_GATE_BINS, SHIPPED_BIN_AUDIENCE, isShipped } from './gate-audience'
5
+ export type { GateAudience, InternalGate, ShippedGate } from './gate-audience'
6
+ export { ANCHOR_FIELDS, anchoredRegion, anchoredRegionBounds } from './anchored-region'
7
+ export type { AnchorField } from './anchored-region'
8
+ export { parseRegistry } from './parse-registry'
9
+ export {
10
+ CROSS_CUTTING_CANDIDATES,
11
+ deriveBuiltinCrossCuttingDefaults,
12
+ deriveWorkspacePackageDomains,
13
+ parsePnpmWorkspaceYaml,
14
+ resolveWorkspaceEntry
15
+ } from './blast-radius-domains'
16
+ export { buildConsumersOf, deriveWorkspaceMemberDirs } from './consumer-enumeration'
17
+ export type { PackageManifest } from './consumer-enumeration'
18
+ export { parseTranche } from './parse-tranche'
19
+ export { deriveTranche } from './derive-tranche'
20
+ export { parseLedger, rowFromCells } from './parse-ledger'
21
+ export { sumLedger } from './sum-ledger'
22
+ export { aggregateTaskTokenRows, parseTokenReportEntries, parseTokensLines } from './parse-token-report'
23
+ export type { TokenSourcePr } from './parse-token-report'
24
+ export { deriveReviewStatus, parseDeveloperRoundMarker, renderReviewStatus } from './review-status'
25
+ export type { ReviewStatus, ReviewStatusInput } from './review-status'
26
+ export { formatBreakdown, formatTokenReportRow, formatTokensLine } from './report-tokens'
27
+ export type { TokenReportRowInput, TokensLineInput, TranscriptSummary, UsageComponents } from './report-tokens'
28
+ // The Claude Code collection adapter (`tranche-model.md` §12 layer 2) — one
29
+ // host's way to produce a `TranscriptSummary`, exported beside the portable
30
+ // renderers it feeds, never as a requirement of the contract.
31
+ export {
32
+ isTokenCollectionWiringBroken,
33
+ resolveMeteringCapability,
34
+ summarizeTranscript
35
+ } from './claude-code-transcript'
36
+ export type { MeteringCapability, MeteringCapabilityDeps, MeteringIncapableReason } from './claude-code-transcript'
37
+ // The hardened deps every real caller of `resolveMeteringCapability` should
38
+ // build from (`#313`) — a sibling module, not part of the pure adapter above.
39
+ export { hardenedMeteringDeps, isTrustedMeteringStat } from './metering-io-guard'
40
+ export { declarationsIn, findCollisions } from './symbol-collisions'
41
+ export type { SymbolCollision, SymbolDeclaration } from './symbol-collisions'
42
+ export { isCodeFile, isDocFile, isSpecFile } from './file-classify'
43
+ export { hasStatusBlock } from './status-block'
44
+ export {
45
+ classifyDocOwnersManifest,
46
+ DOC_OWNERS_PATH,
47
+ evaluateC5,
48
+ globToRegex,
49
+ isMechanicallyNeutralDiff,
50
+ isUrlPointer,
51
+ parseDocOwners,
52
+ pointerToPath,
53
+ readDocAcks,
54
+ readDocNeutrals
55
+ } from './doc-owners'
56
+ export type { C5Result, DocAck, DocNeutral, DocOwnersBinding, DocOwnersManifestState } from './doc-owners'
57
+ export {
58
+ isPrincipal,
59
+ isWaiverLabelActorVerified,
60
+ PRINCIPAL_ALLOWLIST,
61
+ WAIVER_LABEL,
62
+ WAIVER_LABEL_REVIEW
63
+ } from './waiver-label'
64
+ export { deriveSection7, globsOverlap } from './derive-section7'
65
+ export type { Section7Match } from './derive-section7'
66
+ export { checkManifestValidity, parseNoDocRules } from './manifest-validity'
67
+ export type { NoDocRule } from './manifest-validity'
68
+ export { evaluateVocabularyCitation } from './vocabulary-citation'
69
+ export type {
70
+ VocabularyCheckResult,
71
+ VocabularyFinding,
72
+ VocabularyHit,
73
+ VocabularyPattern
74
+ } from './vocabulary-citation'
75
+ export { deriveTierFromDiff, overrideActive, readTierFromPrBody, TIER_FIELD } from './pr-tier'
76
+ export {
77
+ checkAutonomyClause,
78
+ checkBriefSections,
79
+ checkClosesN as checkBriefClosesN,
80
+ checkCommandsCarryOutput,
81
+ checkConsumerTests,
82
+ checkDefeatCases,
83
+ checkDocUpdateList,
84
+ checkForField,
85
+ checkForgeTitle,
86
+ checkNoAgentBoxes,
87
+ checkNoUnpinnedCodeClaims,
88
+ checkPlanPrNoCloses,
89
+ checkPremiseCoverage,
90
+ checkPrincipalPlaceholder,
91
+ checkProjectField,
92
+ checkStopConditions,
93
+ checkSurfaceMap,
94
+ checkTestPlan,
95
+ checkTestPlanExclusivity,
96
+ checkTierField,
97
+ checkWorktreeStep0,
98
+ AGENT_BOXES_REFUSED_SINCE_PR,
99
+ BRIEF_RULES_SINCE_PR,
100
+ COMMAND_WORDS,
101
+ COMMIT_TYPE_STYLE,
102
+ COMMIT_TYPES,
103
+ extractFencedBlocks,
104
+ headerRegion,
105
+ inferBranchFromBody,
106
+ isBriefShaped,
107
+ isGrandfatherableBriefRuleError,
108
+ isTaskBranch,
109
+ partitionBriefErrorsByRollout
110
+ } from './brief-validation'
111
+ export type { BriefSectionResult, BriefSectionsOptions, FencedBlock } from './brief-validation'
112
+ export { checkDoctrineNoProcedures } from './doctrine-no-procedures'
113
+ export type { DoctrineFile, DoctrineProcedureFinding } from './doctrine-no-procedures'
114
+ export { checkPrReportDensity, checkScopeDensity, checkSummaryDensity } from './pr-report-density'
115
+ export type { DensityResult } from './pr-report-density'
116
+ export {
117
+ checkA1,
118
+ checkA2,
119
+ checkA3,
120
+ checkClosesN,
121
+ checkD1,
122
+ checkL1,
123
+ checkL2,
124
+ checkL3,
125
+ checkL4,
126
+ checkL5,
127
+ checkR1,
128
+ checkT1,
129
+ checkT2,
130
+ checkT3,
131
+ COHERENCE_ENFORCED_FROM,
132
+ extractClosesReferences,
133
+ isGrandfathered,
134
+ R1_GRANDFATHERED_ISSUES,
135
+ scopeT2ToPlanPr
136
+ } from './coherence-checks'
137
+ export type { CheckFailure, CheckResult, ForgeIssue, TrancheFile, TaskEntry } from './coherence-checks'
138
+ export {
139
+ buildProvenanceBlock,
140
+ extractIssue,
141
+ hasProvenance,
142
+ isEligibleForProvenance,
143
+ taskRefFromBranch
144
+ } from './archive-task'
145
+ export type { MergedPrFacts } from './archive-task'
146
+ export { extractCodeReviewVerdict, extractSecurityReviewVerdict } from './verdict-extraction'
147
+ export type { VerdictExtraction } from './verdict-extraction'
148
+ export {
149
+ CHANGESET_RELEASE_BRANCH,
150
+ checkReviewGate,
151
+ DEFAULT_RELEASE_ACTOR,
152
+ isChangesetsReleasePr,
153
+ isReviewGateExemptBranch
154
+ } from './review-gate'
155
+ export type { ReviewGateComment, ReviewGateInput, ReviewGateResult, ReviewGateVerdict } from './review-gate'
156
+ export {
157
+ checkBlastRadiusScope,
158
+ checkConflictCompleteness,
159
+ checkIssueRationale,
160
+ checkIssueType,
161
+ checkNoBriefContent,
162
+ checkProjectsRegistered,
163
+ checkRationaleNamesDocs,
164
+ declaredProjects,
165
+ isTaskIssueLabelSet
166
+ } from './issue-validation'
167
+ export type { IssueSectionResult, ProjectPath, TaskIssueFacts } from './issue-validation'
168
+ export { findHeadingLine, findTable, rowToRecord } from './markdown-table'
169
+ export type { ParsedTable, TableRow } from './markdown-table'
170
+ export { deriveDiagramModel } from './diagram-model'
171
+ export type {
172
+ DiagramConfig,
173
+ DiagramEdge,
174
+ DiagramFinding,
175
+ DiagramModel,
176
+ DiagramNode,
177
+ DiagramNodeKind,
178
+ RenderState
179
+ } from './diagram-model'
180
+ export { parseEnforcementRegistry } from './registry-parse'
181
+ export type { GateRing, GateRow } from './registry-parse'
182
+ export { checkG1, checkG2, checkG3, checkG4, checkG5, checkG6 } from './registry-checks'
183
+ export type { RegistryCheckResult, RegistryCheckStatus, RegistryFinding } from './registry-checks'
184
+ export { applyScaffoldPlan, computeScaffoldPlan, PLACEHOLDER } from './registry-scaffold'
185
+ export type { ScaffoldPlan, ScaffoldSkip, ScaffoldStub } from './registry-scaffold'
186
+ export { ACTIONS, CROSSING_KEYWORDS } from './actions'
187
+ export type { Action, ActionCrossing } from './actions'
188
+ export {
189
+ DERIVABLE_STATUSES,
190
+ DERIVATION_RULES,
191
+ DERIVED_STATUSES,
192
+ deriveStatusFromModel,
193
+ FORGE_FACT_INPUTS
194
+ } from './state-machine-model'
195
+ export type { DerivationRule, ForgeFactInput } from './state-machine-model'
196
+ export { checkPremises, parsePremiseBlock } from './premise-check'
197
+ export type { PremiseAssertion, PremiseCheckResult } from './premise-check'
198
+ export { classifyLeftover } from './leftover-detection'
199
+ export type { LeftoverInput, LeftoverResult, LeftoverVerdict } from './leftover-detection'
200
+ export { checkDeadBranchPush } from './dead-branch-push-guard'
201
+ export type {
202
+ DeadBranchPushInput,
203
+ DeadBranchPushResult,
204
+ DeadBranchPushVerdict,
205
+ PrStateFact
206
+ } from './dead-branch-push-guard'
207
+ export { captureBaseline, compareToBaseline } from './baseline-capture'
208
+ export type { BaselineComparison, BaselineEntry, BaselineToolComparison } from './baseline-capture'
209
+ export { checkDoctrinePortability } from './doctrine-portability'
210
+ export type { PortabilityFinding, PortabilitySourceFile } from './doctrine-portability'
211
+ export {
212
+ checkReaderResolvableProse,
213
+ checkUndefinedVocabulary,
214
+ checkUnresolvableReferences,
215
+ classifyProseFile,
216
+ legacySlugPattern,
217
+ parseGlossaryTerms,
218
+ stripNonProse
219
+ } from './reader-resolvable-prose'
220
+ export type { ProseFileClass, ProseFinding, ProseSourceFile } from './reader-resolvable-prose'
221
+ export {
222
+ checkQuotedCommandStaleness,
223
+ evaluateCitedQuotes,
224
+ findCitedQuotes,
225
+ isValidCitedFilePath
226
+ } from './quoted-command'
227
+ export type { CitedQuote, QuotedCommandFinding, QuotedCommandSourceFile } from './quoted-command'
228
+ export {
229
+ PATTERN_EXEMPT,
230
+ RETIRED_EXEMPT_SUBSTRINGS,
231
+ RETIRED_PATTERNS,
232
+ scanRetiredVocabulary
233
+ } from './retired-vocabulary'
234
+ export type { VocabFinding, VocabSourceFile } from './retired-vocabulary'
235
+ export { checkLocalAnchorCoverage } from './local-anchor-coverage'
236
+ export type {
237
+ AnchorCoverageFinding,
238
+ AnchorCoverageScope,
239
+ AnchorSourceFile,
240
+ LocalAnchorCoverageOptions,
241
+ LocalAnchorCoverageResult
242
+ } from './local-anchor-coverage'
243
+ export { checkDispatchReadiness } from './dispatch-gate'
244
+ export type {
245
+ DispatchConflictsWithFact,
246
+ DispatchDependsOnFact,
247
+ DispatchEdgeFact,
248
+ DispatchGateInput,
249
+ DispatchIssueFact,
250
+ DispatchPriorTrancheFact,
251
+ DispatchPriorTaskFact,
252
+ DispatchResult
253
+ } from './dispatch-gate'
254
+ export { checkSinglePlanPr, trancheSlugFromTopologyPath, touchesAnyTopology } from './single-plan-pr'
255
+ export type { OpenPrFiles } from './single-plan-pr'
256
+ export { isNewDiskStateFile } from './no-disk-state'
257
+ export type { DiskStateFileStatus } from './no-disk-state'
258
+ export { checkDirectMainPush } from './direct-main-push'
259
+ export type { DirectMainPushFact, DirectMainPushResult } from './direct-main-push'
260
+ export { checkMainBranchRefusal } from './main-branch-refusal'
261
+ export type { MainBranchRefusalFinding, MainBranchRefusalReason } from './main-branch-refusal'
262
+ export { ensureLabelExists, LABEL_COLOR } from './ensure-label'
263
+ export type { LabelExistenceIo } from './ensure-label'
264
+ export { findDeadBranchPushes } from './dead-branch-push-audit'
265
+ export type { DeadBranchFact, DeadBranchPush } from './dead-branch-push-audit'
266
+ export { checkBranchTopology, taskBranchTopologyFields } from './branch-topology-gate'
267
+ export type { BranchTopologyInput, BranchTopologyResult, BranchTopologyVerdict } from './branch-topology-gate'
268
+ export { checkFirstPushDispatchGate, parseTaskBranch } from './first-push-dispatch-gate'
269
+ export type {
270
+ DispatchReadinessFact,
271
+ FirstPushDispatchGateInput,
272
+ FirstPushDispatchGateResult,
273
+ FirstPushDispatchGateVerdict
274
+ } from './first-push-dispatch-gate'
275
+ export { decideIssueAssignment } from './issue-assignment'
276
+ export type { IssueAssignmentDecision, IssueAssignmentInput } from './issue-assignment'
277
+ export { locateTestPlanSection } from './test-plan-section'
278
+ export type { TestPlanSection } from './test-plan-section'
279
+ export { evaluateTestPlanGate } from './test-plan-gate'
280
+ export type { TestPlanGateResult, TestPlanGateVerdict } from './test-plan-gate'
281
+ export {
282
+ AEG_BLOCKED_LABEL,
283
+ buildBranchName,
284
+ fetchForgeFacts,
285
+ fetchForgeTasksByLabel,
286
+ fetchOpenIssuesByLabel,
287
+ fetchTaskIssueRefs,
288
+ findTrancheSlug,
289
+ hasLabel,
290
+ trancheLabel,
291
+ trancheSlugLengthError,
292
+ trancheSlugOf,
293
+ LABEL_MAX_LENGTH,
294
+ LABEL_NAMESPACE,
295
+ LABELS,
296
+ label,
297
+ mapForgeFacts,
298
+ matchesLabel,
299
+ projectsFromBody
300
+ } from '@attalabs/aeg-forge-state'
301
+ export type { Label, LabelCategory, LabelForm, LabelKey } from '@attalabs/aeg-forge-state'
302
+ export type {
303
+ FetchForgeFactsInput,
304
+ ForgeFactsSnapshot,
305
+ PrRef,
306
+ RawTaskFacts,
307
+ TaskIssueRef,
308
+ TaskRef
309
+ } from '@attalabs/aeg-types'
310
+ export { checkAdoptable, checkMilestoneShape, releaseFieldFromBody } from './milestone-validation'
311
+ export type {
312
+ AdoptFacts,
313
+ AdoptResult,
314
+ AdoptSlugFacts,
315
+ AdoptTargetFacts,
316
+ MilestoneIntent,
317
+ MilestoneShapeResult,
318
+ ReleaseField
319
+ } from './milestone-validation'
320
+ export { findWorkspaceEscapes } from './workspace-escape'
321
+ export type { WorkspaceEscapeFinding, WorkspaceEscapeReason, WorkspaceEscapeSourceFile } from './workspace-escape'
322
+ export { parseRationaleFields, renderBrief } from './brief-render'
323
+ export type { BriefFacts, RationaleFieldKey, RenderResult, SurfaceFileFact } from './brief-render'
@@ -0,0 +1,102 @@
1
+ import { parseTaskBranch } from './first-push-dispatch-gate'
2
+
3
+ /**
4
+ * First-push Issue self-assignment (aeg-governance-hardening task 33, #401).
5
+ * Pure — no `fs`, no `gh`/`git` shell-outs. The CLI shim
6
+ * (`bin/assign-task-issue.ts`, wired into `.husky/pre-push`) gathers the
7
+ * facts — does the remote ref already exist, which Issue does the topology
8
+ * row name, who is currently assigned, who is the authenticated pusher —
9
+ * and this evaluator decides whether to assign.
10
+ *
11
+ * Produces the signal Studio's dispatch-visibility chip (task 26, #368)
12
+ * renders: `facts.assigned = assigneesCount > 0`. Assignment had never been
13
+ * mechanized anywhere — it happened only when the Principal remembered to
14
+ * assign by hand, so a genuinely in-flight task (task 28, #372: two real
15
+ * commits pushed) looked identical to an untouched one. The task branch's
16
+ * FIRST real push is the earliest genuine "in flight" evidence (assigning
17
+ * at brief-authoring time would reintroduce the false positive task 26 was
18
+ * built to avoid), so that is the moment this fires.
19
+ *
20
+ * Every guard skips rather than errors — this is a visibility nicety, never
21
+ * a gate. In particular: `assignees`/`login` arriving as `null` (a failed
22
+ * `gh` call) means "no signal", and no-signal means no action — never a
23
+ * guess, never a block.
24
+ */
25
+
26
+ export type IssueAssignmentInput = {
27
+ /** The branch being pushed. */
28
+ branch: string
29
+ /**
30
+ * Whether the remote ref already exists — from the pre-push stdin's
31
+ * remote-sha (all zeros = the ref is being created by this push, i.e.
32
+ * genuinely the first push). A force-push to an existing branch keeps a
33
+ * non-zero remote-sha and therefore never re-triggers; deleting and
34
+ * recreating the remote branch re-presents a zero sha, which the
35
+ * already-assigned no-op below absorbs.
36
+ */
37
+ remoteRefExists: boolean
38
+ /** The topology row's Issue number, or `null` when the row or its Issue cell could not be resolved. */
39
+ issue: number | null
40
+ /** Current assignee logins on the Issue, or `null` when the fetch failed. */
41
+ assignees: string[] | null
42
+ /** The authenticated `gh` login — the actual pusher — or `null` when unresolvable. Never a hardcoded fallback. */
43
+ login: string | null
44
+ }
45
+
46
+ export type IssueAssignmentDecision =
47
+ | { action: 'assign'; issue: number; login: string; reason: string }
48
+ | { action: 'skip'; reason: string }
49
+
50
+ export function decideIssueAssignment(input: IssueAssignmentInput): IssueAssignmentDecision {
51
+ const { branch, remoteRefExists, issue, assignees, login } = input
52
+
53
+ if (parseTaskBranch(branch) === null) {
54
+ return {
55
+ action: 'skip',
56
+ reason: `Branch \`${branch}\` is not a task/<tranche>/<n> branch — Issue self-assignment only applies to task branches.`
57
+ }
58
+ }
59
+
60
+ if (remoteRefExists) {
61
+ return {
62
+ action: 'skip',
63
+ reason: `Branch \`${branch}\` already exists on the remote — not its first push, nothing to assign.`
64
+ }
65
+ }
66
+
67
+ if (issue === null) {
68
+ return {
69
+ action: 'skip',
70
+ reason: `No Issue resolved for \`${branch}\` from its topology row — skipping self-assignment (the gate owns refusing an unplanned branch).`
71
+ }
72
+ }
73
+
74
+ if (assignees === null) {
75
+ return {
76
+ action: 'skip',
77
+ reason: `Could not read Issue #${issue}'s current assignees — skipping rather than risking a double-assign (fail-open, never a guess).`
78
+ }
79
+ }
80
+
81
+ if (assignees.length > 0) {
82
+ return {
83
+ action: 'skip',
84
+ reason: `Issue #${issue} is already assigned (${assignees.join(', ')}) — no-op.`
85
+ }
86
+ }
87
+
88
+ if (login === null) {
89
+ return {
90
+ action: 'skip',
91
+ reason:
92
+ 'Could not resolve the authenticated `gh` login — never assigning anyone other than the actual pusher, so skipping.'
93
+ }
94
+ }
95
+
96
+ return {
97
+ action: 'assign',
98
+ issue,
99
+ login,
100
+ reason: `First push of \`${branch}\` — assigning Issue #${issue} to @${login} (the authenticated pusher).`
101
+ }
102
+ }