@attalabs/vinaya 0.22.0 → 0.23.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 (188) hide show
  1. package/README.md +13 -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 +552 -139
  23. package/dist/checks/bin/check-branch-topology.js +603 -144
  24. package/dist/checks/bin/check-brief-shape.js +593 -142
  25. package/dist/checks/bin/check-changeset-coverage.js +935 -161
  26. package/dist/checks/bin/check-closes-n.js +603 -144
  27. package/dist/checks/bin/check-coherence.js +614 -147
  28. package/dist/checks/bin/check-dead-branch-push.js +552 -139
  29. package/dist/checks/bin/check-dispatch-readiness.js +612 -145
  30. package/dist/checks/bin/check-doc-coverage-push.js +689 -218
  31. package/dist/checks/bin/check-doc-coverage.js +689 -218
  32. package/dist/checks/bin/check-doctrine-no-procedures.js +4794 -0
  33. package/dist/checks/bin/check-doctrine-portability.js +684 -146
  34. package/dist/checks/bin/check-evidence-fresh.js +994 -174
  35. package/dist/checks/bin/check-exec-bits.js +4931 -0
  36. package/dist/checks/bin/check-first-push-dispatch.js +603 -144
  37. package/dist/checks/bin/check-issue-assignment.js +603 -144
  38. package/dist/checks/bin/check-main-branch-refusal.js +552 -139
  39. package/dist/checks/bin/check-no-disk-state.js +552 -139
  40. package/dist/checks/bin/check-pr-report-density.js +4461 -0
  41. package/dist/checks/bin/check-quoted-command.js +621 -155
  42. package/dist/checks/bin/check-reader-resolvable-prose.js +617 -154
  43. package/dist/checks/bin/check-registry-gates.js +595 -144
  44. package/dist/checks/bin/check-retired-vocabulary.js +617 -154
  45. package/dist/checks/bin/check-review-gate.js +670 -141
  46. package/dist/checks/bin/check-single-plan-pr.js +552 -139
  47. package/dist/checks/bin/check-test-plan.js +553 -140
  48. package/dist/checks/bin/check-token-collection-wired.js +552 -139
  49. package/dist/checks/bin/check-token-report.js +552 -139
  50. package/dist/checks/bin/check-workspace-escape.js +985 -145
  51. package/dist/index.js +8500 -5518
  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 +113 -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 +192 -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-validation.ts +511 -0
  60. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/claude-code-transcript.ts +460 -0
  61. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/coherence-checks.ts +787 -0
  62. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/dead-branch-push-audit.ts +55 -0
  63. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/dead-branch-push-guard.ts +77 -0
  64. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/derive-section7.ts +66 -0
  65. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/derive-tranche.ts +94 -0
  66. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/diagram-model.ts +372 -0
  67. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/direct-main-push.ts +39 -0
  68. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/dispatch-gate.ts +224 -0
  69. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doc-owners.ts +314 -0
  70. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/build-doc-nav.ts +38 -0
  71. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/docs-coherence.ts +136 -0
  72. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/index.ts +29 -0
  73. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/legacy-anchors.ts +48 -0
  74. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/nav-helpers.ts +17 -0
  75. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/node-route.ts +134 -0
  76. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/parse-doc.ts +43 -0
  77. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/published-prose.ts +306 -0
  78. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/surfaced-manifest.ts +89 -0
  79. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/docs/types.ts +33 -0
  80. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doctrine-portability.ts +170 -0
  81. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/doctrine-source.ts +23 -0
  82. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/ensure-label.ts +28 -0
  83. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/file-classify.ts +51 -0
  84. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/first-push-dispatch-gate.ts +80 -0
  85. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/gate-audience.ts +168 -0
  86. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/index.ts +301 -0
  87. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/issue-assignment.ts +102 -0
  88. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/issue-validation.ts +746 -0
  89. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/leftover-detection.ts +68 -0
  90. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/local-anchor-coverage.ts +245 -0
  91. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/main-branch-refusal.ts +64 -0
  92. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/manifest-validity.ts +81 -0
  93. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/markdown-table.ts +76 -0
  94. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/metering-io-guard.ts +119 -0
  95. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/milestone-validation.ts +240 -0
  96. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/no-disk-state.ts +50 -0
  97. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-ledger.ts +77 -0
  98. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-registry.ts +105 -0
  99. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-token-report.ts +165 -0
  100. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/parse-tranche.ts +183 -0
  101. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/pr-tier.ts +81 -0
  102. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/premise-check.ts +125 -0
  103. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/quoted-command.ts +275 -0
  104. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/reader-resolvable-prose.ts +320 -0
  105. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/registry-checks.ts +255 -0
  106. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/registry-parse.ts +164 -0
  107. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/registry-scaffold.ts +216 -0
  108. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/report-tokens.ts +261 -0
  109. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/retired-vocabulary.ts +163 -0
  110. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/review-gate.ts +269 -0
  111. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/single-plan-pr.ts +73 -0
  112. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/state-machine-model.ts +269 -0
  113. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/state-source.ts +14 -0
  114. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/status-block.ts +8 -0
  115. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/sum-ledger.ts +26 -0
  116. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/symbol-collisions.ts +78 -0
  117. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/test-plan-gate.ts +104 -0
  118. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/test-plan-section.ts +65 -0
  119. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/types.ts +174 -0
  120. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/verdict-extraction.ts +140 -0
  121. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/vocabulary-citation.ts +114 -0
  122. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/waiver-label.ts +52 -0
  123. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/workspace-escape.ts +172 -0
  124. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  125. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  126. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  127. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  128. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  129. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  130. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  131. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  132. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  133. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  134. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  135. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  136. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  137. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
  138. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  139. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  140. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  141. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  142. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  143. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  144. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  145. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  146. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  147. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  148. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  149. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  150. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  151. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  152. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  153. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  154. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  155. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  156. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  157. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  158. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  159. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  160. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  161. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  162. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/[root-of-the-server]__02a-3g8._.js +1 -1
  163. 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
  164. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0053k9k._.js +1 -1
  165. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0112h-k._.js +1 -1
  166. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0o771t1._.js +1 -1
  167. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0puovz5._.js +1 -1
  168. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1hs0dcu._.js +1 -1
  169. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__069-_41._.js → [root-of-the-server]__1mcitst._.js} +1 -1
  170. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1wc4-ip._.js +1 -1
  171. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_03x_w6q._.js +1 -1
  172. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0gvm3og._.js +1 -1
  173. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0lwxg63._.js +1 -1
  174. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1fqw88f._.js +1 -1
  175. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1n0cnq-._.js +1 -1
  176. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  177. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  178. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  179. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  180. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  181. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{37hnluhzj9bei.js → 0jer_drkl1e4r.js} +4 -4
  182. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{267o-tsrnuwnv.js → 20x-t0a0v129z.js} +1 -1
  183. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1hufpuody6vjv.js → 2ou_81ahqw31e.js} +1 -1
  184. package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
  185. /package/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/{aeg-core → vinaya/studio-standalone/_node_modules/@attalabs/aeg-core}/bin/verify-coherence.ts +0 -0
  186. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → 5xy_GA7G5KRuE3HDumIiw}/_buildManifest.js +0 -0
  187. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → 5xy_GA7G5KRuE3HDumIiw}/_clientMiddlewareManifest.js +0 -0
  188. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{Fmyc42qzLwFcWWHLLNy-G → 5xy_GA7G5KRuE3HDumIiw}/_ssgManifest.js +0 -0
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Typed model for AEG artifacts. See `aeg-root/tranche-model.md` for the
3
+ * canonical specification (§3 status table, §4 thin-file template).
4
+ *
5
+ * This module is pure: no I/O. The parser produces these shapes from file
6
+ * contents; `deriveTranche` consumes them plus a `ForgeFacts` snapshot.
7
+ */
8
+
9
+ import type { ForgeFacts, Tranche, Lifecycle, Task } from '@attalabs/aeg-types'
10
+
11
+ // ---------- Registry (projects.md) ----------
12
+
13
+ export type Project = {
14
+ /** Registry-unique name. Authority for `Project:` validation. */
15
+ name: string
16
+ /** Declared home folder (e.g. `apps/<product>`). Never derived. */
17
+ path: string
18
+ /** Specs folder for the project. */
19
+ specsPath: string
20
+ /**
21
+ * Per-project state folder, or `null` when the cell holds prose like
22
+ * "state tracked globally for now" — i.e. state isn't tracked under this
23
+ * project's path.
24
+ */
25
+ statePath: string | null
26
+ }
27
+
28
+ export type Registry = Project[]
29
+
30
+ // ---------- Tranche file (tranches/<name>.md) ----------
31
+
32
+ /**
33
+ * `Lifecycle`/`Task`/`Tranche` live in `@attalabs/aeg-types` (task
34
+ * aeg-forge-state-v1 3a) — re-exported here since every existing call site
35
+ * across the repo imports them from `@attalabs/aeg-core`. Moved out so
36
+ * `@attalabs/aeg-forge-state` can depend on these shapes without creating a
37
+ * package cycle with `aeg-core` (which in turn depends on
38
+ * `aeg-forge-state`'s derivation function).
39
+ */
40
+ export type { Lifecycle, Task, Tranche }
41
+
42
+ // ---------- Forge facts ----------
43
+
44
+ /**
45
+ * `ForgeFacts` lives in `@attalabs/aeg-types` (aeg-core-purity fix, #521) —
46
+ * re-exported here for the same reason as `Lifecycle`/`Task`/`Tranche`:
47
+ * every existing call site imports it from `@attalabs/aeg-core`, and it moved
48
+ * out so the I/O-performing fetchers that produce it (now in
49
+ * `@attalabs/aeg-forge-state`) don't need to depend on `aeg-core`.
50
+ */
51
+ export type { ForgeFacts }
52
+
53
+ // ---------- Derivation output ----------
54
+
55
+ /**
56
+ * The statuses derivation can conclude. The *rules* that produce them are not
57
+ * written here or in `derive-tranche.ts` — they live as an ordered,
58
+ * pure-data list in `state-machine-model.ts` (the `actions.ts`
59
+ * discipline), which `deriveStatus` executes and the docs render from the
60
+ * same list.
61
+ *
62
+ * `backlog` remains a member because it is a project-level concept consumers
63
+ * still render; derivation never emits it inside a tranche.
64
+ *
65
+ * `dropped` and `incoherent` are the two **honest terminal** statuses.
66
+ * A closed Issue with no merged PR must never resolve to `todo` (which implies
67
+ * not-started). `dropped` = closed `NOT_PLANNED` (legitimately abandoned);
68
+ * `incoherent` = closed `COMPLETED` but with no merged-PR link (genuinely done
69
+ * yet unprovable, or a broken close). Both are surfaced, never hidden.
70
+ */
71
+ export type DerivedStatus =
72
+ | 'backlog'
73
+ | 'todo'
74
+ | 'in-flight'
75
+ | 'in-review'
76
+ | 'changes-requested'
77
+ | 'merged'
78
+ | 'blocked'
79
+ | 'dropped'
80
+ | 'incoherent'
81
+
82
+ export type DispatchBlockers = {
83
+ /** Depends-on edges whose target is not yet `merged`. */
84
+ dependsOnNotMerged: string[]
85
+ /**
86
+ * Conflicts-with edges whose target is currently occupying its collision
87
+ * domain — derived status `in-flight`, `in-review`, or `changes-requested`
88
+ * (§8 conflict gate).
89
+ */
90
+ conflictsWithOpenOrInFlight: string[]
91
+ }
92
+
93
+ export type DerivedTask = {
94
+ task: Task
95
+ status: DerivedStatus
96
+ /**
97
+ * `true` when the §8 gates allow dispatching this task: all `dependsOn`
98
+ * targets are `merged`, and no `conflictsWith` target is open/in-flight.
99
+ * Dispatchability is independent of this task's own current status — a
100
+ * task already `in-review` is still "dispatchable" by this definition (the
101
+ * gates would have allowed it to start).
102
+ */
103
+ dispatchable: boolean
104
+ blockers: DispatchBlockers
105
+ }
106
+
107
+ export type UnknownEdge = {
108
+ from: string
109
+ to: string
110
+ kind: 'depends-on' | 'conflicts-with'
111
+ }
112
+
113
+ export type DerivedTranche = {
114
+ tranche: Tranche
115
+ tasks: DerivedTask[]
116
+ /**
117
+ * Edges that reference task ids not present in this tranche's topology
118
+ * table. Surface for diagnostics; does not throw, since real files
119
+ * occasionally name dropped/prose-only ids (e.g. a `3a` that survives only
120
+ * in a tranche file's narrative).
121
+ */
122
+ unknownEdges: UnknownEdge[]
123
+ }
124
+
125
+ // ---------- Token ledger (tranches/<name>.tokens.md) ----------
126
+
127
+ /**
128
+ * One row of the append-only per-tranche token/cost ledger. See
129
+ * `aeg-root/tranche-model.md` §12 for the canonical format. Each role
130
+ * appends one row at the end of its turn; re-entry appends another row.
131
+ * The tranche total is `sum(rows)`, derived at read time — never stored.
132
+ *
133
+ * The two capture sources surface here as `null` cells: a row written by a
134
+ * claude.ai role (Planner / Brief Author / Reviewer / Security) before the
135
+ * Principal has filled in its usage figure has `tokensIn`/`tokensOut`/`cost`
136
+ * as `null`; a terminal-role row (Developer / Archivist with `/cost`) has
137
+ * exact numbers.
138
+ */
139
+ export type LedgerRow = {
140
+ /** Free-text phase identifier. Convention: `<task-id>: <phase>` per task
141
+ * (e.g. `9: develop`), or a bare phase for tranche-wide work (e.g.
142
+ * `planning`). The parser does not enforce the convention — the Phase
143
+ * cell is opaque so the table can be re-pivoted later. */
144
+ phase: string
145
+ role: string
146
+ /** Free-text — the role's agent + model (e.g. `claude-opus-4-7 (CC)`,
147
+ * `claude-opus-4-7 (chat)`). The PRICING table dependency is on the
148
+ * model name, not on the parser. */
149
+ agentModel: string
150
+ /** `null` when the cell was `—` / `-` / empty (unknown — claude.ai
151
+ * row not yet filled). */
152
+ tokensIn: number | null
153
+ tokensOut: number | null
154
+ /** Cost in USD as a number. `null` for unknown. Cost depends on the
155
+ * adapter's PRICING table — when the model is missing from it, this is
156
+ * `0` from the source row, not `null` (a known limitation, not a parser
157
+ * bug). */
158
+ cost: number | null
159
+ /** Raw date cell — typically `YYYY-MM-DD`. Captured as a string so the
160
+ * model docs can evolve the format without breaking the parser. */
161
+ date: string
162
+ }
163
+
164
+ /**
165
+ * `sumLedger(rows)` output — pure aggregate over the rows in source order.
166
+ * Null cells contribute zero; the row count is preserved so a UI can show
167
+ * "N entries totalling …".
168
+ */
169
+ export type LedgerTotals = {
170
+ tokensIn: number
171
+ tokensOut: number
172
+ cost: number
173
+ rows: number
174
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Shared code-reviewer / security-review verdict extraction (aeg-review-gate-v1
3
+ * task 1, #474). Originally private to `archive-task.ts`'s post-merge
4
+ * provenance assembly (advisory-only, DANGLING on a missing/unclear verdict).
5
+ * Extracted so the pre-merge `review-gate.ts` blocking check calls the
6
+ * IDENTICAL regex/parsing logic — a future drift between two copies of the
7
+ * same pattern would silently reopen the gap this task exists to close (one
8
+ * implementation per fact, §11 constraint). Pure — no `fs`, no `fetch`.
9
+ *
10
+ * Picks the MOST RECENT comment carrying a clear value, not the first comment
11
+ * merely matching the pattern (aeg-review-gate-v1 task 1 fix — the original
12
+ * single-comment `.find()` broke on real multi-comment PRs: a REQUEST_CHANGES
13
+ * verdict followed by fixes and a later clean APPROVE).
14
+ *
15
+ * Line-anchored `VERDICT:` marker only — NOT a bare word search anywhere in
16
+ * the comment (aeg-review-gate-v1 task 1 follow-up, security-review FAIL
17
+ * finding, confirmed by direct execution: the old bare `\b(PASS|FAIL)\b`
18
+ * pattern matched the post-merge Archivist's own auto-generated DANGLING
19
+ * placeholder text — "no security-review pass was run before merge —
20
+ * DANGLING, see below" — as a clean PASS, since it contains the standalone
21
+ * word "pass". Same risk in reverse for "APPROVE" inside ordinary prose,
22
+ * e.g. "I do NOT approve of that design"). `^\s*VERDICT:\s*<value>\b`,
23
+ * multiline, is the real subagent contract (`roles/reviewer.md`/
24
+ * `roles/security.md`'s own VERDICT block, which `.claude/agents/
25
+ * code-reviewer.md`/`security-reviewer.md` require verbatim) — tightening
26
+ * to it closes the gap without inventing a new convention.
27
+ *
28
+ * The anchor tolerates a leading markdown EMPHASIS run — one to three `*` or
29
+ * `_`, immediately abutting the token — so `**VERDICT: APPROVE**` and
30
+ * `_VERDICT: APPROVE_` match (PR #636: the reviewer subagent emitted the
31
+ * bolded form and the gate read the PR as carrying no code-review verdict at
32
+ * all). The spec still mandates the bare line; this only stops an agent's
33
+ * markdown drift from silently DANGLING a real verdict.
34
+ *
35
+ * Emphasis ONLY — the following are deliberately NOT tolerated, because each
36
+ * is a way for prose to *mention* a verdict rather than *cast* one, and this
37
+ * gate blocks merges (#639 review, findings 1/3/5):
38
+ * `> VERDICT: APPROVE` blockquote — GitHub's quote-reply syntax. A
39
+ * Developer quoting the reviewer's earlier text
40
+ * would otherwise become the PR's own verdict, and
41
+ * most-recent-hit-wins means the quote beats a live
42
+ * REQUEST CHANGES. Fail-open and silent.
43
+ * `* VERDICT: APPROVE` list item — the space is what distinguishes it
44
+ * from emphasis; a bullet is prose context. `-` and
45
+ * `1.` never matched, so `*` must not either.
46
+ * `# VERDICT: APPROVE` heading — same mention-not-cast reasoning.
47
+ * `` `VERDICT: APPROVE` `` code span — decided, not overlooked. A
48
+ * backticked marker is exactly how the role docs
49
+ * and this comment WRITE about the contract, so
50
+ * tolerating it would match prose describing the
51
+ * rule. If a real agent ever emits the backticked
52
+ * form, fix the agent: it fails loud (DANGLING),
53
+ * which is the safe direction.
54
+ *
55
+ * The value-side boundary is `(?![A-Za-z0-9])`, not `\b`: `_` is a word
56
+ * character, so `\b` after the captured value would reject the closing `_` of
57
+ * `_VERDICT: APPROVE_` while accepting the `*` of the bolded form — the class
58
+ * and the claim would disagree. It still rejects `APPROVED`/`PASSED`.
59
+ *
60
+ * What is NOT loosened is the requirement that a literal `VERDICT:` token be
61
+ * present — ordinary prose and the Archivist's own DANGLING placeholder still
62
+ * miss.
63
+ *
64
+ * No separate "marker present but value unclear" branch: a generic
65
+ * `VERDICT:`-prefix-only marker would itself cross-contaminate the two
66
+ * extractors (a security reviewer's own `VERDICT: PASS` line would loosely
67
+ * match the code-review marker too, misreporting "code-reviewer comment
68
+ * found but unclear" for a comment that was never a code review at all).
69
+ * Anything that doesn't match the exact value pattern reads as "missing",
70
+ * identical to no comment existing — which is also what the DANGLING
71
+ * placeholder case above requires.
72
+ *
73
+ * Reviewed-commit binding (#73, a duplicate of #71 closes this one). The
74
+ * winning comment (the one whose `VERDICT:` line matched) is also searched
75
+ * for a `Judged head: <sha>` line — the exact phrase reviewers on
76
+ * atta-labs/attalabs#664 were already hand-typing before this existed as a
77
+ * mechanism, formalized rather than invented. Same anchor discipline as
78
+ * `VERDICT:` itself: line-start, optional leading emphasis run, no
79
+ * blockquote/list-item/heading/code-span tolerance — a sha mentioned in
80
+ * ordinary prose ("see 8365ca57 for context") is a mention, not a binding,
81
+ * and must not parse as one. Accepts both the abbreviated (7-char) and full
82
+ * (40-char) hex forms; `review-gate.ts` does the prefix comparison against
83
+ * the PR's actual head. Searched only within the SAME comment body that
84
+ * produced the winning verdict — a sha mentioned in a different comment is
85
+ * not this verdict's binding.
86
+ */
87
+
88
+ const HEAD_SHA_PATTERN = /^[ \t]*(?:\*{1,3}|_{1,3})?Judged head:\s*([0-9a-f]{7,40})(?![A-Za-z0-9])/im
89
+
90
+ function extractHeadSha(comment: string): string | null {
91
+ const m = comment.match(HEAD_SHA_PATTERN)
92
+ return m ? (m[1] as string).toLowerCase() : null
93
+ }
94
+
95
+ /**
96
+ * `headSha` is `null` in two distinct situations that both mean "cannot
97
+ * confirm this verdict covers the current head": no verdict comment matched
98
+ * at all (`danglingNote` is also set), or a verdict comment matched but
99
+ * carried no `Judged head:` line (`danglingNote` is `null` — the verdict
100
+ * itself is real, only the binding is missing). `review-gate.ts` treats both
101
+ * as unbound; only their `danglingNote`/`value` differ.
102
+ */
103
+ export type VerdictExtraction = { value: string; headSha: string | null; danglingNote: string | null }
104
+
105
+ function extractVerdict(comments: string[], valuePattern: RegExp, missingLabel: string): VerdictExtraction {
106
+ const clearHits = comments.filter((c) => valuePattern.test(c))
107
+ if (clearHits.length > 0) {
108
+ const latest = clearHits[clearHits.length - 1] as string
109
+ const m = latest.match(valuePattern) as RegExpMatchArray
110
+ return {
111
+ value: (m[1] as string).toUpperCase().replace(/[_-]/g, ' '),
112
+ headSha: extractHeadSha(latest),
113
+ danglingNote: null
114
+ }
115
+ }
116
+
117
+ return {
118
+ value: `no ${missingLabel} pass was run before merge — DANGLING, see below`,
119
+ headSha: null,
120
+ danglingNote: `no ${missingLabel} verdict comment found on this PR`
121
+ }
122
+ }
123
+
124
+ /** `value` is `APPROVE`, `REQUEST CHANGES`, `LGTM`, or a DANGLING placeholder string. */
125
+ export function extractCodeReviewVerdict(comments: string[]): VerdictExtraction {
126
+ return extractVerdict(
127
+ comments,
128
+ /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(APPROVE|REQUEST[ _-]?CHANGES|LGTM)(?![A-Za-z0-9])/im,
129
+ 'code-reviewer'
130
+ )
131
+ }
132
+
133
+ /** `value` is `PASS`, `FAIL`, or a DANGLING placeholder string. */
134
+ export function extractSecurityReviewVerdict(comments: string[]): VerdictExtraction {
135
+ return extractVerdict(
136
+ comments,
137
+ /^[ \t]*(?:\*{1,3}|_{1,3})?VERDICT:\s*(PASS|FAIL)(?![A-Za-z0-9])/im,
138
+ 'security-review'
139
+ )
140
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * vocabulary-citation.ts — a generic, regex-parameterized citation checker.
3
+ *
4
+ * `retired-vocabulary.test.ts` hand-rolls this exact sweep shape (pattern +
5
+ * scope + exemptions + a non-vacuity self-test) for this repo's own internal
6
+ * use, but as a `vitest` file no adopter can install it — and it hardcodes
7
+ * every pattern, scope and exemption as a repo-specific literal. This module
8
+ * generalizes the sweep into a reusable primitive: every pattern, its scope,
9
+ * its exemptions, and the sample that proves it is not vacuous arrive as
10
+ * caller-supplied input. It contains no product name, host-tool path, or
11
+ * any other repo-specific literal — that knowledge belongs to whoever
12
+ * configures an instance (a Vinaya check bin is one such consumer).
13
+ *
14
+ * Pure — no `fs`, no child-process I/O. The caller injects `grepFn`/
15
+ * `matchFn`, mirroring `evaluateC5`'s `fileExists` injection in
16
+ * `doc-owners.ts`: this file decides what counts as a finding, the I/O shim
17
+ * decides how to look.
18
+ */
19
+
20
+ export type VocabularyPattern = {
21
+ /** Short, stable id for reporting — never the raw regex, which can be long. */
22
+ id: string
23
+ /**
24
+ * POSIX ERE, `grep -E` compatible. Never `\d`/`\w`/`\s` — GNU grep reads
25
+ * those as literal characters, not shorthand classes, so a pattern relying
26
+ * on them can pass locally (a permissive grep) and go silently vacuous on
27
+ * CI (GNU grep). Use `[0-9]` etc. instead. `\b` is safe: both GNU and BSD
28
+ * `grep -E` support it as an extension.
29
+ */
30
+ pattern: string
31
+ /** Repo-root-relative paths/dirs this pattern is checked against. */
32
+ scope: string[]
33
+ /** Path substrings exempted for this pattern only, beyond `globalExempt`. */
34
+ exempt?: string[]
35
+ /**
36
+ * Substrings that, when present on the matched LINE, make the hit legitimate.
37
+ * Distinct from `exempt`, which filters by path: a name can be leakage in one
38
+ * sentence and the product's own identifier in the next, within the same file.
39
+ * An npm scope or a domain that merely CONTAINS a product name is the product
40
+ * naming itself, not an unexplained reference an adopter cannot resolve —
41
+ * exempting the whole file would blind the pattern to real leakage beside it.
42
+ *
43
+ * Granularity is the LINE, which is the accepted limit: a genuine leak that
44
+ * shares a line with a legitimate citation escapes. Prose puts them on
45
+ * separate lines in practice, and the alternative — exempting only the
46
+ * matched span — would need column offsets `grep -n` does not report.
47
+ */
48
+ exemptContent?: string[]
49
+ /** A string this pattern MUST match — proves the pattern is not vacuous. */
50
+ sample: string
51
+ /**
52
+ * When true, a hit on a line that is a markdown table row (starts with
53
+ * `|`, ignoring leading whitespace) is not a finding. A registry table's
54
+ * "implementation" column citing a harness path is verified by a gate
55
+ * (e.g. `verify-registry.ts`'s G1/G2), not a claim stated as normative
56
+ * prose — banning the shape would flag the very registry a widened
57
+ * pattern exists to protect.
58
+ */
59
+ exemptTableRow?: boolean
60
+ }
61
+
62
+ export type VocabularyHit = { file: string; line: number; content: string }
63
+
64
+ export type VocabularyFinding = VocabularyHit & { patternId: string }
65
+
66
+ export type VocabularyCheckResult = {
67
+ findings: VocabularyFinding[]
68
+ /**
69
+ * Patterns whose sample failed to self-match — a config bug, reported
70
+ * loud instead of silently passing. A pattern that matches nothing passes
71
+ * every scope silently, which is not hypothetical: this is the exact
72
+ * failure mode `retired-vocabulary.test.ts`'s own non-vacuity suite exists
73
+ * to catch (`\bD-(###|NNN)\b` shipped green over eleven live sites because
74
+ * `\b` cannot anchor after `#`).
75
+ */
76
+ vacuousPatterns: string[]
77
+ }
78
+
79
+ const TABLE_ROW = /^\s*\|/
80
+
81
+ /**
82
+ * Evaluates every pattern against its own scope, filtering exempted paths
83
+ * and (optionally) table-row citations, and reports which patterns proved
84
+ * vacuous against their own sample instead of silently returning clean.
85
+ */
86
+ export function evaluateVocabularyCitation(
87
+ patterns: VocabularyPattern[],
88
+ grepFn: (pattern: string, scope: string[]) => VocabularyHit[],
89
+ matchFn: (pattern: string, sample: string) => boolean,
90
+ globalExempt: string[] = []
91
+ ): VocabularyCheckResult {
92
+ const findings: VocabularyFinding[] = []
93
+ const vacuousPatterns: string[] = []
94
+
95
+ for (const p of patterns) {
96
+ if (!matchFn(p.pattern, p.sample)) {
97
+ vacuousPatterns.push(p.id)
98
+ continue
99
+ }
100
+
101
+ const exempt = [...globalExempt, ...(p.exempt ?? [])]
102
+ const exemptContent = p.exemptContent ?? []
103
+ const hits = grepFn(p.pattern, p.scope).filter((h) => {
104
+ if (exempt.some((e) => h.file.includes(e))) return false
105
+ if (exemptContent.some((e) => h.content.includes(e))) return false
106
+ if (p.exemptTableRow && TABLE_ROW.test(h.content)) return false
107
+ return true
108
+ })
109
+
110
+ for (const h of hits) findings.push({ ...h, patternId: p.id })
111
+ }
112
+
113
+ return { findings, vacuousPatterns }
114
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Waiver-label actor verification. A doc-coverage waiver is honored
3
+ * ONLY when the `vinaya/waiver:docs` PR label is present AND the actor of that
4
+ * label's most recent labeling timeline event is a configured principal.
5
+ * Label presence alone is never sufficient — that is exactly the
6
+ * agent-emittable-string hole closes (supersedes's
7
+ * `Doc-waiver:` grammar). Pure — no env reads here; I/O stays in the CLI
8
+ * shim, same discipline as `pr-tier.ts`.
9
+ *
10
+ * `isWaiverLabelActorVerified` is parameterized by `label` (aeg-review-gate-v1
11
+ * task 1, #474) so a second waiver label (`vinaya/waiver:review`) can reuse the exact
12
+ * same actor-verification predicate rather than a copy-pasted duplicate that
13
+ * could drift out of sync.
14
+ *
15
+ * The label strings themselves are read from the code-owned vocabulary in
16
+ * `@attalabs/aeg-forge-state`'s `labels.ts`, never written as literals here — a
17
+ * namespace change lands in one file (discipline).
18
+ */
19
+
20
+ import { label as labelFor } from '@attalabs/aeg-forge-state'
21
+
22
+ export const WAIVER_LABEL = labelFor('waiver-docs')
23
+ export const WAIVER_LABEL_REVIEW = labelFor('waiver-review')
24
+ export const PRINCIPAL_ALLOWLIST = ['daniboomerang']
25
+
26
+ /**
27
+ * Membership test for a principal allowlist. **Case-insensitive, because
28
+ * GitHub logins are** — `Alice` and `alice` address the same account, and the
29
+ * forge may return either casing depending on the API surface. An exact-match
30
+ * test fails closed, but it fails closed *invisibly*: an adopter who wrote
31
+ * `"principals": ["Alice"]` would watch Alice's genuine verdicts land as
32
+ * DANGLING with no stated reason — precisely the baffling symptom the
33
+ * `principals` field exists to remove (review finding, PR #862).
34
+ *
35
+ * Shared by every allowlist consumer so the waiver check and the review gate
36
+ * can never disagree about whether a given login is a principal.
37
+ */
38
+ export function isPrincipal(login: string | null, principalAllowlist: string[]): boolean {
39
+ if (login === null) return false
40
+ const normalized = login.toLowerCase()
41
+ return principalAllowlist.some((p) => p.toLowerCase() === normalized)
42
+ }
43
+
44
+ export function isWaiverLabelActorVerified(opts: {
45
+ label: string
46
+ labels: string[]
47
+ labelActor: string | null
48
+ principalAllowlist: string[]
49
+ }): boolean {
50
+ if (!opts.labels.includes(opts.label)) return false
51
+ return isPrincipal(opts.labelActor, opts.principalAllowlist)
52
+ }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Workspace-escape detection (task 17, Issue #78). Neither the module
3
+ * dependency graph nor a literal-path grep sees a filesystem reference built
4
+ * at runtime from a relative string — measured 2026-08-16 during attalabs'
5
+ * adoption of the published CLI: deleting the vendored CLI workspace looked
6
+ * safe by both instruments (nothing depended on it; no source file
7
+ * referenced it by name) and broke a sibling workspace's test suite anyway,
8
+ * via `readFileSync(fileURLToPath(new URL('../../cli/src/index.ts',
9
+ * import.meta.url)))` — not a dependency edge, not an import, and a
10
+ * relative specifier with no literal `apps/vinaya/cli` substring for the
11
+ * grep to hit.
12
+ *
13
+ * This covers the three idioms Issue #78 names: `readFileSync`/`readFile`
14
+ * with a literal relative-path first argument, and `new URL('…',
15
+ * import.meta.url)`. It does NOT follow `import`/`require` module
16
+ * specifiers — the dependency graph already owns those, and duplicating it
17
+ * here would be noise, not signal.
18
+ *
19
+ * Deliberately static-string-only: a literal containing `${…}` (a template
20
+ * with interpolation) is a dynamically computed path and stays out of
21
+ * scope, same as any non-literal argument — resolving those needs data-flow
22
+ * analysis, a different and much larger job than this file (Issue #78's own
23
+ * stated boundary).
24
+ *
25
+ * Zero I/O: every input (file paths + contents, `knownPaths`) is read by the
26
+ * adapter and passed in, same discipline as `doctrine-portability.ts` and
27
+ * `reader-resolvable-prose.ts`.
28
+ */
29
+
30
+ export type WorkspaceEscapeSourceFile = { path: string; content: string }
31
+
32
+ export type WorkspaceEscapeReason = 'escapes-workspace-package' | 'path-not-found'
33
+
34
+ export type WorkspaceEscapeFinding = {
35
+ file: string
36
+ line: number
37
+ reference: string
38
+ resolved: string
39
+ reason: WorkspaceEscapeReason
40
+ }
41
+
42
+ /** `apps/*`/`packages/*` — this repo's own `package.json` `workspaces` globs. A caller with a different layout passes its own. */
43
+ const DEFAULT_WORKSPACE_DIRS: readonly string[] = ['apps', 'packages']
44
+
45
+ /**
46
+ * `readFileSync('./x')`/`readFile('../x')` — literal first argument,
47
+ * required to start `./` or `../` (the only shape found anywhere in this
48
+ * repo's own corpus; a bare non-relative literal like `'utf8'` is always
49
+ * the SECOND argument to these calls, never the first, so requiring the
50
+ * relative prefix costs no real coverage while ruling out an accidental
51
+ * match on an unrelated literal).
52
+ */
53
+ const READ_CALL = /\b(?:readFileSync|readFile)\s*\(\s*(['"`])((?:\.\.?\/)[^'"`]*)\1/g
54
+
55
+ /** `new URL('…', import.meta.url)` — the second argument must be the literal expression, not a variable holding it. */
56
+ const NEW_URL_IMPORT_META_URL = /\bnew\s+URL\s*\(\s*(['"`])([^'"`]*)\1\s*,\s*import\.meta\.url\s*\)/g
57
+
58
+ function lineAtOffset(content: string, index: number): number {
59
+ let line = 1
60
+ for (let i = 0; i < index; i++) {
61
+ if (content.charCodeAt(i) === 10) line++
62
+ }
63
+ return line
64
+ }
65
+
66
+ /**
67
+ * Repo-relative POSIX join + `.`/`..` normalization. No `node:path` — every
68
+ * input here is already POSIX (repo-relative, forward-slash), so there is
69
+ * no platform-separator handling to delegate, and this package's other pure
70
+ * scanners (`doctrine-portability.ts`, `reader-resolvable-prose.ts`) carry
71
+ * no filesystem-module dependency either.
72
+ */
73
+ function resolveRelative(fromDir: string, ref: string): string {
74
+ const stack = fromDir === '' ? [] : fromDir.split('/')
75
+ for (const segment of ref.split('/')) {
76
+ if (segment === '' || segment === '.') continue
77
+ if (segment === '..') stack.pop()
78
+ else stack.push(segment)
79
+ }
80
+ return stack.join('/')
81
+ }
82
+
83
+ function dirOf(path: string): string {
84
+ const idx = path.lastIndexOf('/')
85
+ return idx === -1 ? '' : path.slice(0, idx)
86
+ }
87
+
88
+ /** The workspace package a repo-relative path belongs to — its first two segments when it sits under one of `workspaceDirs` (`apps/<name>`, `packages/<name>`); `null` for a repo-root file, `aeg-root/**`, or anything that normalizes above the repo root entirely. */
89
+ function workspacePackageOf(path: string, workspaceDirs: readonly string[]): string | null {
90
+ const [first, second] = path.split('/')
91
+ if (first === undefined || second === undefined || !workspaceDirs.includes(first)) return null
92
+ return `${first}/${second}`
93
+ }
94
+
95
+ /**
96
+ * Blanks out `/* *\/` and `//` comments, preserving every newline so
97
+ * reported line numbers stay accurate against the original file — same
98
+ * technique as `reader-resolvable-prose.ts`'s `stripNonProse` for `.ts`/
99
+ * `.tsx`. Without this, a module comment merely describing a call shape in
100
+ * prose (this very file's own header does) reads as a real call site.
101
+ */
102
+ function stripComments(content: string): string {
103
+ return content
104
+ .replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, ''))
105
+ .replace(/(^|[^:])\/\/[^\n]*/g, (m) => m.replace(/[^\n]/g, ''))
106
+ }
107
+
108
+ type ExtractedReference = { line: number; reference: string; resolved: string }
109
+
110
+ /**
111
+ * Both idioms scanned by raw pattern, not a parser — same syntactic
112
+ * discipline as `symbol-collisions.ts`'s declaration scan. KNOWN BLIND SPOT,
113
+ * stated rather than papered over: a match inside a STRING (as opposed to a
114
+ * comment, stripped above) reads as code — e.g. this module's own test
115
+ * fixtures, which embed example call shapes as string literals for the
116
+ * regex to parse, are themselves indistinguishable from real call sites by
117
+ * a text scan. Resolving that needs a real parser, the same larger job
118
+ * `symbol-collisions.ts` declines for the identical reason.
119
+ */
120
+ function extractReferences(path: string, content: string): ExtractedReference[] {
121
+ const dir = dirOf(path)
122
+ const scrubbed = stripComments(content)
123
+ const out: ExtractedReference[] = []
124
+ for (const pattern of [READ_CALL, NEW_URL_IMPORT_META_URL]) {
125
+ pattern.lastIndex = 0
126
+ let m: RegExpExecArray | null = pattern.exec(scrubbed)
127
+ while (m !== null) {
128
+ const literal = m[2] as string
129
+ if (!literal.includes('${')) {
130
+ out.push({ line: lineAtOffset(scrubbed, m.index), reference: literal, resolved: resolveRelative(dir, literal) })
131
+ }
132
+ m = pattern.exec(scrubbed)
133
+ }
134
+ }
135
+ return out
136
+ }
137
+
138
+ /**
139
+ * Static filesystem references that either resolve outside the citing
140
+ * file's own workspace package, or to a repo path that does not exist at
141
+ * all. `knownPaths` is every repo-relative path that exists on disk,
142
+ * collected by the adapter — this module never touches the filesystem.
143
+ * `workspaceDirs` defaults to this repo's own `apps`/`packages` layout; a
144
+ * file outside every workspace dir is skipped entirely (there is no "own
145
+ * package" for it to escape).
146
+ */
147
+ export function findWorkspaceEscapes(
148
+ files: readonly WorkspaceEscapeSourceFile[],
149
+ knownPaths: ReadonlySet<string>,
150
+ workspaceDirs: readonly string[] = DEFAULT_WORKSPACE_DIRS
151
+ ): WorkspaceEscapeFinding[] {
152
+ const findings: WorkspaceEscapeFinding[] = []
153
+ for (const file of files) {
154
+ const own = workspacePackageOf(file.path, workspaceDirs)
155
+ if (own === null) continue
156
+ for (const ref of extractReferences(file.path, file.content)) {
157
+ const target = workspacePackageOf(ref.resolved, workspaceDirs)
158
+ const escapes = target !== own
159
+ const missing = !knownPaths.has(ref.resolved)
160
+ if (escapes || missing) {
161
+ findings.push({
162
+ file: file.path,
163
+ line: ref.line,
164
+ reference: ref.reference,
165
+ resolved: ref.resolved,
166
+ reason: escapes ? 'escapes-workspace-package' : 'path-not-found'
167
+ })
168
+ }
169
+ }
170
+ }
171
+ return findings
172
+ }
@@ -1 +1 @@
1
- Fmyc42qzLwFcWWHLLNy-G
1
+ 5xy_GA7G5KRuE3HDumIiw
@@ -7,9 +7,9 @@
7
7
  "static/chunks/0cz1d0mv5g_q7.js"
8
8
  ],
9
9
  "lowPriorityFiles": [
10
- "static/Fmyc42qzLwFcWWHLLNy-G/_buildManifest.js",
11
- "static/Fmyc42qzLwFcWWHLLNy-G/_ssgManifest.js",
12
- "static/Fmyc42qzLwFcWWHLLNy-G/_clientMiddlewareManifest.js"
10
+ "static/5xy_GA7G5KRuE3HDumIiw/_buildManifest.js",
11
+ "static/5xy_GA7G5KRuE3HDumIiw/_ssgManifest.js",
12
+ "static/5xy_GA7G5KRuE3HDumIiw/_clientMiddlewareManifest.js"
13
13
  ],
14
14
  "rootMainFiles": [
15
15
  "static/chunks/1hklle-my0ly_.js",