@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,261 @@
1
+ /**
2
+ * report-tokens — the **portable** half of the token-report contract
3
+ * (`aeg-root/tranche-model.md` §12, layers 1 and 3). No value this file reads,
4
+ * computes or emits is host-specific: it defines the summary shape every
5
+ * collection adapter must produce, and renders that shape into the frozen
6
+ * `Tokens: …` grammar `parse-token-report.ts` reads back. A host is named
7
+ * below only to point at the one shipped adapter and to record a retraction —
8
+ * never to branch on, and never in rendered output.
9
+ *
10
+ * `TranscriptSummary` **is the seam.** A host adapter's whole job is to
11
+ * produce one — from a session transcript, an API usage response, a meter the
12
+ * harness exposes, or figures an operator supplies by hand. AEG ships exactly
13
+ * one such adapter, for Claude Code, in `claude-code-transcript.ts`; a repo
14
+ * on another harness writes its own and reuses everything here unchanged.
15
+ * Adding host knowledge to this file would silently re-couple the portable
16
+ * layers to one vendor — that coupling is the defect the split exists to
17
+ * prevent, and this file is where it would reappear first.
18
+ *
19
+ * Historical note (misc-hardening-v1 task 1, #675): this module retracted
20
+ * §12's earlier claim that a role reports exact tokens "from `/cost`" — an
21
+ * operator-typed slash command no unattended agent session can invoke, in
22
+ * that host or any other. §12 records the retraction; the fix was to collect
23
+ * from a real per-turn source instead.
24
+ *
25
+ * Pure — no `fs`, no `process.env`. The CLI shim (`bin/report-tokens.ts`)
26
+ * does the I/O; these functions take values and produce the report.
27
+ */
28
+
29
+ /**
30
+ * The four usage figures a role reports, in host-neutral terms. Every
31
+ * collection adapter maps its host's own field names onto these, so no field
32
+ * name from a host's own API or transcript format appears downstream of the
33
+ * adapter — including in this file.
34
+ */
35
+ export type UsageComponents = {
36
+ inputTokens: number
37
+ outputTokens: number
38
+ cacheCreationInputTokens: number
39
+ cacheReadInputTokens: number
40
+ }
41
+
42
+ /**
43
+ * **The adapter seam.** What a host's collection step must produce for one
44
+ * role-turn, and the only thing the portable layers below consume. Named for
45
+ * the shipped adapter's source (a session transcript), but the shape is not
46
+ * transcript-specific: an adapter that reads an API usage response or takes
47
+ * operator-supplied figures produces this same object.
48
+ */
49
+ export type TranscriptSummary = {
50
+ components: UsageComponents
51
+ /** The model id for the turn, or `null` when the host does not report one. */
52
+ model: string | null
53
+ /**
54
+ * How many distinct usage records were summed. Callers use `0` as the
55
+ * "nothing usable was collected" signal — a real turn always has at least
56
+ * one — rather than letting a zeroed summary format as a plausible `0/0/—`.
57
+ */
58
+ messageCount: number
59
+ }
60
+
61
+ export type TokensLineInput = {
62
+ phase: string
63
+ role: string
64
+ /**
65
+ * `null` only when the role is operator-metered — its host exposes no
66
+ * usage to the agent at all — which produces the all-`—` numbers segment
67
+ * the parser already tolerates. Never a convenience escape for a
68
+ * self-metering role whose collection step merely failed: that case is an
69
+ * error to report, not a blank to emit (`aeg-root/tranche-model.md` §12).
70
+ */
71
+ summary: TranscriptSummary | null
72
+ /** Overrides the model the adapter derived, if given. */
73
+ modelOverride?: string
74
+ }
75
+
76
+ /**
77
+ * Format the one-line `Tokens: <phase> — <role> — <model> — <in>/<out>/<cost>`
78
+ * report that `parse-token-report.ts`'s `parseTokensLines` consumes. The
79
+ * grammar is frozen (three parsers depend on it) — this function must never
80
+ * change its shape, only what feeds it.
81
+ *
82
+ * `Tokens in` is the full input-side total (`inputTokens +
83
+ * cacheCreationInputTokens + cacheReadInputTokens`) — genuinely every
84
+ * token that went in, not a partial figure. Cache reads can outweigh fresh
85
+ * input by two orders of magnitude on a long session, so that total is
86
+ * never the whole story on its own; `formatBreakdown` reports the four
87
+ * components separately for anyone reading the reporter's own output,
88
+ * rather than letting the single blended cell stand as if it were.
89
+ *
90
+ * Cost is always reported as `—`: no maintained, accurate $/token pricing
91
+ * table for current models lives in this package (the one in
92
+ * `@atta/adapter-langgraph` is a different product's provider-pricing
93
+ * table, out of this task's surface, and does not cover these model ids) —
94
+ * an unverified guess baked into a PR's permanent history is worse than an
95
+ * honest unknown.
96
+ */
97
+ /**
98
+ * An embedded newline could forge a synthetic row/line neither writer ever
99
+ * intended, regardless of which grammar's own delimiter is in play — shared
100
+ * by both sanitizers below.
101
+ */
102
+ function stripNewlines(value: string): string {
103
+ return value.replace(/\r?\n/g, ' ')
104
+ }
105
+
106
+ /**
107
+ * Table-row sanitizer: escapes `|` (the cell delimiter `splitTableRow`
108
+ * already expects `\|` for on read — the same escape convention, applied on
109
+ * write) on top of `stripNewlines`. Used by `formatTokenReportRow` for every
110
+ * free-text cell — `phase`, `role`, the derived `model`, and `date` —
111
+ * because any of them can arrive from an untrusted CLI flag or a git branch
112
+ * name, both of which can legally contain `|` and newlines (found live: a
113
+ * crafted branch name produced a row whose columns silently shifted past
114
+ * `parseTokenReportEntries`, discarding real measured usage with no error).
115
+ */
116
+ function sanitizeForTableCell(value: string): string {
117
+ return stripNewlines(value).replace(/\|/g, '\\|')
118
+ }
119
+
120
+ /**
121
+ * `Tokens: …` line sanitizer. `parse-token-report.ts`'s `SEGMENT_SEP`
122
+ * (`/\s+[—–-]\s+/`) is a whitespace-flanked dash — and a field-local fix
123
+ * (removing the flanking whitespace around a dash the field itself
124
+ * contains) is provably incomplete: the JOIN ITSELF contributes whitespace
125
+ * on both sides of every field (the literal `" — "` between segments), so a
126
+ * field merely ENDING or STARTING with a dash reconstructs the exact same
127
+ * pattern from the boundary alone — `"9 -"` joined with the next field's
128
+ * leading `" — "` reads back as `"...9 - — ..."`, a real `SEGMENT_SEP`
129
+ * match, with no dash-adjacent whitespace inside the field at all to strip
130
+ * (found live: `--phase "9 -" --role "Dev -"` silently dropped the real
131
+ * `tokensIn`). No amount of field-local whitespace-stripping closes this —
132
+ * the hazard is the CHARACTER, reachable from any position once anything
133
+ * joins around it, not a particular spacing.
134
+ *
135
+ * The fix that actually closes it: remove the character itself from the
136
+ * field's vocabulary, but ONLY where it's actually reachable by
137
+ * `SEGMENT_SEP` — a dash counts as reachable on a given side when that
138
+ * side is whitespace, OR is the very edge of the field (every field in
139
+ * this grammar sits directly against a `" — "` join or the `"Tokens: "`
140
+ * prefix, so an edge is guaranteed adjacent to boundary whitespace once
141
+ * concatenated). A dash is substituted only when BOTH sides are reachable
142
+ * — matching exactly what `SEGMENT_SEP` itself requires (whitespace on
143
+ * both sides) once the guaranteed boundary whitespace is accounted for.
144
+ * An ordinary hyphenated identifier like `"claude-sonnet-5"` has
145
+ * non-whitespace, non-edge neighbors on both sides of every hyphen and is
146
+ * left completely untouched — a whole-field substitution (an earlier draft
147
+ * of this fix) needlessly mangled every such identifier, which is why this
148
+ * checks each dash's actual neighbors instead of blanket-replacing the
149
+ * class. This is also less lossy than the whitespace-stripping this
150
+ * replaces: two phase labels that only differed in spacing around a
151
+ * hazardous hyphen (`"9-fix"` — never hazardous, untouched — vs.
152
+ * `"9 - fix"` — hazardous, substituted) used to sanitize to byte-identical
153
+ * output; substitution preserves spacing, changing only the one character
154
+ * that must never survive verbatim in a hazardous position.
155
+ */
156
+ const DASH_LOOKALIKES: Record<string, string> = {
157
+ '-': '‑', // U+2011 NON-BREAKING HYPHEN
158
+ '–': '‒', // U+2012 FIGURE DASH
159
+ '—': '―' // U+2015 HORIZONTAL BAR
160
+ }
161
+ const DASH_CHARS = new Set(Object.keys(DASH_LOOKALIKES))
162
+
163
+ /**
164
+ * `formatTokenReportRow`'s own table cells escape `|` as `\|` because
165
+ * `splitTableRow` reads that convention back on the other side (`#313`).
166
+ * This line has no such reader — nothing unescapes a backslash out of it —
167
+ * so a literal `\|` here would just be a backslash followed by a visible
168
+ * bar, one more hazardous character rather than fewer. A same-glyph
169
+ * lookalike, matching `DASH_LOOKALIKES`'s own approach, neutralizes the
170
+ * delimiter unconditionally (unlike a dash, no ordinary model id legitimately
171
+ * contains a pipe, so there is no adjacency case to preserve): an
172
+ * attacker-controlled `model` field (`#313`'s live reproduction: a transcript
173
+ * whose `message.model` read `attacker | evil-injected-cell | extra`) must
174
+ * not survive with a real `|` if this line is later quoted verbatim inside
175
+ * an actual markdown table cell by anything downstream — `pr report --write`
176
+ * already escapes correctly at its own render step (`formatTokenReportRow`),
177
+ * but this line, not that one, is what `#313`'s reproduction actually
178
+ * printed, and it carries no such guarantee of its own once it leaves here.
179
+ */
180
+ const PIPE_LOOKALIKE = '|' // U+FF5C FULLWIDTH VERTICAL LINE
181
+
182
+ function sanitizeForTokensLine(value: string): string {
183
+ const stripped = stripNewlines(value)
184
+ let result = ''
185
+ for (let i = 0; i < stripped.length; i++) {
186
+ const ch = stripped[i] as string
187
+ if (ch === '|') {
188
+ result += PIPE_LOOKALIKE
189
+ continue
190
+ }
191
+ if (DASH_CHARS.has(ch)) {
192
+ const leftReachable = i === 0 || /\s/.test(stripped[i - 1] as string)
193
+ const rightReachable = i === stripped.length - 1 || /\s/.test(stripped[i + 1] as string)
194
+ if (leftReachable && rightReachable) {
195
+ result += DASH_LOOKALIKES[ch]
196
+ continue
197
+ }
198
+ }
199
+ result += ch
200
+ }
201
+ return result
202
+ }
203
+
204
+ /** Shared by `formatTokensLine` and `formatTokenReportRow` — one place that turns a summary into the model/tokensIn/tokensOut cells both grammars report, so the two shapes can never drift on the arithmetic. `model` is `null` rather than the `—` placeholder when neither `modelOverride` nor the summary supplies one — that placeholder is a sentinel THIS module emits, never untrusted content, and must never be run through either grammar's sanitizer (which would rewrite its `—` into something that no longer reads as "unknown"). Each caller substitutes the literal `—` for `null` itself, after sanitizing everything that came from `input`. */
205
+ function renderCells(input: TokensLineInput): { model: string | null; tokensIn: string; tokensOut: string } {
206
+ const model = input.modelOverride ?? input.summary?.model ?? null
207
+ if (!input.summary) return { model, tokensIn: '—', tokensOut: '—' }
208
+ const { inputTokens, cacheCreationInputTokens, cacheReadInputTokens, outputTokens } = input.summary.components
209
+ const tokensIn = inputTokens + cacheCreationInputTokens + cacheReadInputTokens
210
+ return { model, tokensIn: String(tokensIn), tokensOut: String(outputTokens) }
211
+ }
212
+
213
+ export function formatTokensLine(input: TokensLineInput): string {
214
+ const { model, tokensIn, tokensOut } = renderCells(input)
215
+ const phase = sanitizeForTokensLine(input.phase)
216
+ const role = sanitizeForTokensLine(input.role)
217
+ const safeModel = model === null ? '—' : sanitizeForTokensLine(model)
218
+ if (!input.summary) {
219
+ return `Tokens: ${phase} — ${role} — ${safeModel} — —`
220
+ }
221
+ return `Tokens: ${phase} — ${role} — ${safeModel} — ${tokensIn}/${tokensOut}/—`
222
+ }
223
+
224
+ export type TokenReportRowInput = TokensLineInput & {
225
+ /** `YYYY-MM-DD`. Caller-supplied, never derived here — this file stays `Date.now()`-free per its purity charter above. */
226
+ date: string
227
+ }
228
+
229
+ /**
230
+ * The same cells `formatTokensLine` reports, rendered as one `| Phase | Role
231
+ * | Agent/Model | Tokens in | Tokens out | Cost | Date |` markdown-table row
232
+ * — the shape `aeg-root/roles/developer.md`'s "Token report" heading and
233
+ * `parse-token-report.ts`'s `parseTokenReportEntries` (table form) both
234
+ * already expect. `Cost` is always `—`, same reasoning as `formatTokensLine`'s
235
+ * own Cost cell: no maintained per-model pricing table exists in this package.
236
+ */
237
+ export function formatTokenReportRow(input: TokenReportRowInput): string {
238
+ const { model, tokensIn, tokensOut } = renderCells(input)
239
+ const phase = sanitizeForTableCell(input.phase)
240
+ const role = sanitizeForTableCell(input.role)
241
+ const safeModel = model === null ? '—' : sanitizeForTableCell(model)
242
+ const date = sanitizeForTableCell(input.date)
243
+ return `| ${phase} | ${role} | ${safeModel} | ${tokensIn} | ${tokensOut} | — | ${date} |`
244
+ }
245
+
246
+ /**
247
+ * Human-readable component breakdown for the reporter's own console output
248
+ * — never folded silently into the single `Tokens in` cell. Not parsed by
249
+ * anything; informational only.
250
+ */
251
+ export function formatBreakdown(summary: TranscriptSummary): string {
252
+ const { inputTokens, cacheCreationInputTokens, cacheReadInputTokens, outputTokens } = summary.components
253
+ return [
254
+ ` fresh input tokens: ${inputTokens}`,
255
+ ` cache creation tokens: ${cacheCreationInputTokens}`,
256
+ ` cache read tokens: ${cacheReadInputTokens}`,
257
+ ` output tokens: ${outputTokens}`,
258
+ ` model: ${summary.model ?? '—'}`,
259
+ ` messages summed: ${summary.messageCount}`
260
+ ].join('\n')
261
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Retired vocabulary — mechanisms genuinely gone from AEG's own methodology
3
+ * (the decision log, the lock, the `team-leader` role, the `D-###` decision
4
+ * id, …) with no legitimate positive occurrence anywhere but the exempt
5
+ * lists below. This is `retired-vocabulary.test.ts`'s `RETIRED` array and
6
+ * the non-citation members of its `RETIRED_IN_PRODUCT` array (both target
7
+ * the same retired-mechanism concern; the split between the two vitest
8
+ * arrays there is organic history, not a semantic boundary) — the file this
9
+ * module was extracted from names the incident: five review rounds each
10
+ * declared the decision-log claim swept, and each time it resurfaced a few
11
+ * lines from where the previous pass fixed it. A rule enforced by attention,
12
+ * not a gate.
13
+ *
14
+ * Deliberately NOT `RETIRED_IN_PRODUCT`'s forge-number / tranche-slug /
15
+ * legacy-slug patterns: those ban a LIVE, unexplained citation, not a
16
+ * retired concept, and are already this package's own
17
+ * `checkUnresolvableReferences` (`reader-resolvable-prose.ts`) — shipping
18
+ * both under two check names would double-report the identical match. This
19
+ * module is deliberately the narrower half of that file's original scan.
20
+ *
21
+ * Content copied verbatim from `retired-vocabulary.test.ts`'s `RETIRED`
22
+ * (minus its citation members), `EXEMPT`, and the non-citation entries of
23
+ * `PATTERN_EXEMPT`, never re-derived — this module's whole purpose is
24
+ * giving that list a CheckSpec adapter, not re-litigating it. Every pattern
25
+ * here is POSIX-ERE-safe (no `\d`/`\w`/`\s`, no lookaheads) by the same
26
+ * discipline `reader-resolvable-prose.ts` documents, so `grep -E` (the
27
+ * vitest suite) and native `RegExp` (this evaluator) agree on every match.
28
+ *
29
+ * Zero I/O: every input (file paths + contents) is read by the adapter and
30
+ * passed in, same charter as `reader-resolvable-prose.ts`.
31
+ */
32
+
33
+ export type VocabSourceFile = { path: string; content: string }
34
+
35
+ export type VocabFinding = {
36
+ file: string
37
+ line: number
38
+ pattern: string
39
+ message: string
40
+ }
41
+
42
+ /** The retired mechanisms this scan bans as claims of current-ness — see the module header for what is deliberately NOT here. */
43
+ export const RETIRED_PATTERNS: readonly string[] = [
44
+ // the deleted decision-id format
45
+ String.raw`\bD-[0-9]{3}\b`,
46
+ 'D-(###|NNN|nnn|xxx)',
47
+ // the retired record as a live, writable, required artifact, every
48
+ // inflection but never "decision logic"
49
+ 'decision (entry|logged)',
50
+ 'decision-log entry',
51
+ 'decision log entry',
52
+ 'decision log(s|ged|ging)?([^a-z]|$)',
53
+ 'decision-log',
54
+ 'decision entr',
55
+ 'decisions? (are |is )?logged',
56
+ 'log entries',
57
+ String.raw`decisions\.md`,
58
+ 'decisions-legacy',
59
+ 'CONTRADICTION',
60
+ 'assumes Tier 3',
61
+ // the lock
62
+ 'Lock: ?YES',
63
+ 'Lock: ?NO',
64
+ 'lock approvals?',
65
+ 'approves locks',
66
+ 'Conforms to lock',
67
+ 'Challenges lock',
68
+ // checks deleted with the machinery
69
+ 'checkDecisionNumbersFresh',
70
+ // the role that no longer exists
71
+ String.raw`roles/team-leader\.md`,
72
+ // the wreckage a citation strip leaves when it deletes the contents of a
73
+ // parenthetical and not the punctuation around it — the shape is banned,
74
+ // not one instance of it. `()` is deliberately absent: it is an ordinary
75
+ // function call, and banning it would make any TypeScript source unusable.
76
+ String.raw`\(,`,
77
+ String.raw`,\)`,
78
+ String.raw`\(\.\)`
79
+ ]
80
+
81
+ /** Paths where a retired-vocabulary mention is legitimate: frozen archives and historical records. Substring match, same as the vitest suite's own `EXEMPT`. */
82
+ export const RETIRED_EXEMPT_SUBSTRINGS: readonly string[] = [
83
+ 'docs/decisions-legacy.md',
84
+ 'apps/herald-ai/docs/',
85
+ 'apps/vada-ai/docs/',
86
+ 'aeg-root/tranches/completed/',
87
+ 'packages/aeg-core/src/docs/published-prose',
88
+ 'packages/aeg-core/src/retired-vocabulary.test.ts',
89
+ 'packages/aeg-core/src/retired-vocabulary.ts',
90
+ '/fixtures/',
91
+ '/node_modules/',
92
+ '/.next/',
93
+ '/.turbo/'
94
+ ]
95
+
96
+ /**
97
+ * Per-pattern exemptions on top of `RETIRED_EXEMPT_SUBSTRINGS`, for the
98
+ * handful of patterns with a real, load-bearing false-positive shape (a
99
+ * live doc that legitimately discusses the retired concept by name, or a
100
+ * same-shaped live feature). Copied verbatim from `retired-vocabulary.test.ts`'s
101
+ * `PATTERN_EXEMPT` — including entries for `apps/herald-ai`/`apps/vada-ai`
102
+ * paths this repo does not carry, since a nonexistent path costs nothing to
103
+ * keep and this module's whole point is not re-deriving the list.
104
+ */
105
+ export const PATTERN_EXEMPT: Readonly<Record<string, readonly string[]>> = {
106
+ [String.raw`\bD-[0-9]{3}\b`]: ['./CLAUDE.md'],
107
+ [String.raw`decisions\.md`]: [
108
+ 'docs-index.md',
109
+ 'apps/vada-ai/specs/legacy/README.md',
110
+ 'apps/vada-ai/specs/vada-product-spec.md',
111
+ 'apps/vada-ai/web/CLAUDE.md',
112
+ 'apps/vada-ai/CLAUDE.md'
113
+ ],
114
+ 'decisions-legacy': [
115
+ 'packages/aeg-core/src/file-classify.ts',
116
+ 'packages/aeg-core/src/file-classify.test.ts',
117
+ 'packages/aeg-core/src/pr-tier.test.ts',
118
+ 'docs-index.md',
119
+ '.claude/skills/database/SKILL.md',
120
+ '.claude/skills/vada-architecture/SKILL.md',
121
+ 'apps/vada-ai/specs/vada-byok-principles.md',
122
+ 'apps/vada-ai/web/CLAUDE.md',
123
+ 'apps/vada-ai/CLAUDE.md'
124
+ ],
125
+ CONTRADICTION: ['apps/vada-ai/specs/vada-reviewers-spec.md']
126
+ }
127
+
128
+ function lineNumbers(content: string, pattern: RegExp): number[] {
129
+ const out: number[] = []
130
+ const lines = content.split('\n')
131
+ for (let i = 0; i < lines.length; i++) {
132
+ if (pattern.test(lines[i] ?? '')) out.push(i + 1)
133
+ }
134
+ return out
135
+ }
136
+
137
+ /**
138
+ * Sweeps `files` for `RETIRED_PATTERNS`, skipping any file whose path
139
+ * contains a `RETIRED_EXEMPT_SUBSTRINGS` entry, or that pattern's own
140
+ * `PATTERN_EXEMPT` entry. Matching is per-line, case-sensitive — the same
141
+ * semantics `grep -E` (with no `-i`) gives the vitest suite this was
142
+ * extracted from.
143
+ */
144
+ export function scanRetiredVocabulary(files: readonly VocabSourceFile[]): VocabFinding[] {
145
+ const findings: VocabFinding[] = []
146
+ for (const file of files) {
147
+ if (RETIRED_EXEMPT_SUBSTRINGS.some((e) => file.path.includes(e))) continue
148
+ for (const pattern of RETIRED_PATTERNS) {
149
+ const patternExempt = PATTERN_EXEMPT[pattern] ?? []
150
+ if (patternExempt.some((e) => file.path.includes(e))) continue
151
+ const re = new RegExp(pattern)
152
+ for (const line of lineNumbers(file.content, re)) {
153
+ findings.push({
154
+ file: file.path,
155
+ line,
156
+ pattern,
157
+ message: `claims a retired AEG mechanism is still live (matches /${pattern}/)`
158
+ })
159
+ }
160
+ }
161
+ }
162
+ return findings
163
+ }