archprint 0.1.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 (276) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/LICENSE +21 -0
  3. package/README.md +268 -0
  4. package/dist/cli/codeframe.d.ts +6 -0
  5. package/dist/cli/codeframe.d.ts.map +1 -0
  6. package/dist/cli/codeframe.js +22 -0
  7. package/dist/cli/codeframe.js.map +1 -0
  8. package/dist/cli/generate.d.ts +37 -0
  9. package/dist/cli/generate.d.ts.map +1 -0
  10. package/dist/cli/generate.js +259 -0
  11. package/dist/cli/generate.js.map +1 -0
  12. package/dist/cli/init.d.ts +21 -0
  13. package/dist/cli/init.d.ts.map +1 -0
  14. package/dist/cli/init.js +19 -0
  15. package/dist/cli/init.js.map +1 -0
  16. package/dist/cli/outputs-manifest.d.ts +10 -0
  17. package/dist/cli/outputs-manifest.d.ts.map +1 -0
  18. package/dist/cli/outputs-manifest.js +53 -0
  19. package/dist/cli/outputs-manifest.js.map +1 -0
  20. package/dist/cli/program.d.ts +4 -0
  21. package/dist/cli/program.d.ts.map +1 -0
  22. package/dist/cli/program.js +316 -0
  23. package/dist/cli/program.js.map +1 -0
  24. package/dist/cli/recommend.d.ts +18 -0
  25. package/dist/cli/recommend.d.ts.map +1 -0
  26. package/dist/cli/recommend.js +193 -0
  27. package/dist/cli/recommend.js.map +1 -0
  28. package/dist/cli/report.d.ts +8 -0
  29. package/dist/cli/report.d.ts.map +1 -0
  30. package/dist/cli/report.js +482 -0
  31. package/dist/cli/report.js.map +1 -0
  32. package/dist/cli/rule-guidance.d.ts +6 -0
  33. package/dist/cli/rule-guidance.d.ts.map +1 -0
  34. package/dist/cli/rule-guidance.js +22 -0
  35. package/dist/cli/rule-guidance.js.map +1 -0
  36. package/dist/cli/scan.d.ts +54 -0
  37. package/dist/cli/scan.d.ts.map +1 -0
  38. package/dist/cli/scan.js +103 -0
  39. package/dist/cli/scan.js.map +1 -0
  40. package/dist/cli/summary.d.ts +18 -0
  41. package/dist/cli/summary.d.ts.map +1 -0
  42. package/dist/cli/summary.js +59 -0
  43. package/dist/cli/summary.js.map +1 -0
  44. package/dist/cli/wiring.d.ts +42 -0
  45. package/dist/cli/wiring.d.ts.map +1 -0
  46. package/dist/cli/wiring.js +246 -0
  47. package/dist/cli/wiring.js.map +1 -0
  48. package/dist/cli.d.ts +3 -0
  49. package/dist/cli.d.ts.map +1 -0
  50. package/dist/cli.js +13 -0
  51. package/dist/cli.js.map +1 -0
  52. package/dist/data/adoption-catalog.d.ts +16 -0
  53. package/dist/data/adoption-catalog.d.ts.map +1 -0
  54. package/dist/data/adoption-catalog.js +254 -0
  55. package/dist/data/adoption-catalog.js.map +1 -0
  56. package/dist/detector/app-isolation-detector.d.ts +22 -0
  57. package/dist/detector/app-isolation-detector.d.ts.map +1 -0
  58. package/dist/detector/app-isolation-detector.js +17 -0
  59. package/dist/detector/app-isolation-detector.js.map +1 -0
  60. package/dist/detector/confidence-gate.d.ts +33 -0
  61. package/dist/detector/confidence-gate.d.ts.map +1 -0
  62. package/dist/detector/confidence-gate.js +62 -0
  63. package/dist/detector/confidence-gate.js.map +1 -0
  64. package/dist/detector/console-isolation-detector.d.ts +17 -0
  65. package/dist/detector/console-isolation-detector.d.ts.map +1 -0
  66. package/dist/detector/console-isolation-detector.js +23 -0
  67. package/dist/detector/console-isolation-detector.js.map +1 -0
  68. package/dist/detector/cycle-detector.d.ts +18 -0
  69. package/dist/detector/cycle-detector.d.ts.map +1 -0
  70. package/dist/detector/cycle-detector.js +34 -0
  71. package/dist/detector/cycle-detector.js.map +1 -0
  72. package/dist/detector/deep-relative-detector.d.ts +17 -0
  73. package/dist/detector/deep-relative-detector.d.ts.map +1 -0
  74. package/dist/detector/deep-relative-detector.js +43 -0
  75. package/dist/detector/deep-relative-detector.js.map +1 -0
  76. package/dist/detector/dependency-internals-detector.d.ts +17 -0
  77. package/dist/detector/dependency-internals-detector.d.ts.map +1 -0
  78. package/dist/detector/dependency-internals-detector.js +65 -0
  79. package/dist/detector/dependency-internals-detector.js.map +1 -0
  80. package/dist/detector/entry-purity-detector.d.ts +19 -0
  81. package/dist/detector/entry-purity-detector.d.ts.map +1 -0
  82. package/dist/detector/entry-purity-detector.js +32 -0
  83. package/dist/detector/entry-purity-detector.js.map +1 -0
  84. package/dist/detector/env-access-detector.d.ts +17 -0
  85. package/dist/detector/env-access-detector.d.ts.map +1 -0
  86. package/dist/detector/env-access-detector.js +25 -0
  87. package/dist/detector/env-access-detector.js.map +1 -0
  88. package/dist/detector/family-maturity.d.ts +3 -0
  89. package/dist/detector/family-maturity.d.ts.map +1 -0
  90. package/dist/detector/family-maturity.js +12 -0
  91. package/dist/detector/family-maturity.js.map +1 -0
  92. package/dist/detector/feature-slice-detector.d.ts +22 -0
  93. package/dist/detector/feature-slice-detector.d.ts.map +1 -0
  94. package/dist/detector/feature-slice-detector.js +17 -0
  95. package/dist/detector/feature-slice-detector.js.map +1 -0
  96. package/dist/detector/layer-detector.d.ts +37 -0
  97. package/dist/detector/layer-detector.d.ts.map +1 -0
  98. package/dist/detector/layer-detector.js +165 -0
  99. package/dist/detector/layer-detector.js.map +1 -0
  100. package/dist/detector/marker-inference.d.ts +21 -0
  101. package/dist/detector/marker-inference.d.ts.map +1 -0
  102. package/dist/detector/marker-inference.js +236 -0
  103. package/dist/detector/marker-inference.js.map +1 -0
  104. package/dist/detector/orphan-detector.d.ts +12 -0
  105. package/dist/detector/orphan-detector.d.ts.map +1 -0
  106. package/dist/detector/orphan-detector.js +24 -0
  107. package/dist/detector/orphan-detector.js.map +1 -0
  108. package/dist/detector/pattern-detector.d.ts +48 -0
  109. package/dist/detector/pattern-detector.d.ts.map +1 -0
  110. package/dist/detector/pattern-detector.js +139 -0
  111. package/dist/detector/pattern-detector.js.map +1 -0
  112. package/dist/detector/phantom-dependency-detector.d.ts +18 -0
  113. package/dist/detector/phantom-dependency-detector.d.ts.map +1 -0
  114. package/dist/detector/phantom-dependency-detector.js +91 -0
  115. package/dist/detector/phantom-dependency-detector.js.map +1 -0
  116. package/dist/detector/public-api-detector.d.ts +23 -0
  117. package/dist/detector/public-api-detector.d.ts.map +1 -0
  118. package/dist/detector/public-api-detector.js +87 -0
  119. package/dist/detector/public-api-detector.js.map +1 -0
  120. package/dist/detector/reachability.d.ts +13 -0
  121. package/dist/detector/reachability.d.ts.map +1 -0
  122. package/dist/detector/reachability.js +64 -0
  123. package/dist/detector/reachability.js.map +1 -0
  124. package/dist/detector/role-layering-detector.d.ts +28 -0
  125. package/dist/detector/role-layering-detector.d.ts.map +1 -0
  126. package/dist/detector/role-layering-detector.js +75 -0
  127. package/dist/detector/role-layering-detector.js.map +1 -0
  128. package/dist/detector/self-consistency.d.ts +7 -0
  129. package/dist/detector/self-consistency.d.ts.map +1 -0
  130. package/dist/detector/self-consistency.js +65 -0
  131. package/dist/detector/self-consistency.js.map +1 -0
  132. package/dist/detector/server-client-detector.d.ts +19 -0
  133. package/dist/detector/server-client-detector.d.ts.map +1 -0
  134. package/dist/detector/server-client-detector.js +51 -0
  135. package/dist/detector/server-client-detector.js.map +1 -0
  136. package/dist/detector/sibling-isolation.d.ts +23 -0
  137. package/dist/detector/sibling-isolation.d.ts.map +1 -0
  138. package/dist/detector/sibling-isolation.js +76 -0
  139. package/dist/detector/sibling-isolation.js.map +1 -0
  140. package/dist/detector/stories-isolation-detector.d.ts +19 -0
  141. package/dist/detector/stories-isolation-detector.d.ts.map +1 -0
  142. package/dist/detector/stories-isolation-detector.js +31 -0
  143. package/dist/detector/stories-isolation-detector.js.map +1 -0
  144. package/dist/detector/test-isolation-detector.d.ts +18 -0
  145. package/dist/detector/test-isolation-detector.d.ts.map +1 -0
  146. package/dist/detector/test-isolation-detector.js +34 -0
  147. package/dist/detector/test-isolation-detector.js.map +1 -0
  148. package/dist/detector/ui-data-isolation-detector.d.ts +19 -0
  149. package/dist/detector/ui-data-isolation-detector.d.ts.map +1 -0
  150. package/dist/detector/ui-data-isolation-detector.js +41 -0
  151. package/dist/detector/ui-data-isolation-detector.js.map +1 -0
  152. package/dist/detector/workspace-package-detector.d.ts +19 -0
  153. package/dist/detector/workspace-package-detector.d.ts.map +1 -0
  154. package/dist/detector/workspace-package-detector.js +65 -0
  155. package/dist/detector/workspace-package-detector.js.map +1 -0
  156. package/dist/generator/app-isolation-emitters.d.ts +19 -0
  157. package/dist/generator/app-isolation-emitters.d.ts.map +1 -0
  158. package/dist/generator/app-isolation-emitters.js +19 -0
  159. package/dist/generator/app-isolation-emitters.js.map +1 -0
  160. package/dist/generator/console-isolation-emitters.d.ts +8 -0
  161. package/dist/generator/console-isolation-emitters.d.ts.map +1 -0
  162. package/dist/generator/console-isolation-emitters.js +10 -0
  163. package/dist/generator/console-isolation-emitters.js.map +1 -0
  164. package/dist/generator/deep-relative-emitters.d.ts +14 -0
  165. package/dist/generator/deep-relative-emitters.d.ts.map +1 -0
  166. package/dist/generator/deep-relative-emitters.js +20 -0
  167. package/dist/generator/deep-relative-emitters.js.map +1 -0
  168. package/dist/generator/dependency-internals-emitters.d.ts +17 -0
  169. package/dist/generator/dependency-internals-emitters.d.ts.map +1 -0
  170. package/dist/generator/dependency-internals-emitters.js +20 -0
  171. package/dist/generator/dependency-internals-emitters.js.map +1 -0
  172. package/dist/generator/entry-purity-emitters.d.ts +17 -0
  173. package/dist/generator/entry-purity-emitters.d.ts.map +1 -0
  174. package/dist/generator/entry-purity-emitters.js +24 -0
  175. package/dist/generator/entry-purity-emitters.js.map +1 -0
  176. package/dist/generator/env-access-emitters.d.ts +4 -0
  177. package/dist/generator/env-access-emitters.d.ts.map +1 -0
  178. package/dist/generator/env-access-emitters.js +19 -0
  179. package/dist/generator/env-access-emitters.js.map +1 -0
  180. package/dist/generator/eslint-plugin-emitter.d.ts +14 -0
  181. package/dist/generator/eslint-plugin-emitter.d.ts.map +1 -0
  182. package/dist/generator/eslint-plugin-emitter.js +57 -0
  183. package/dist/generator/eslint-plugin-emitter.js.map +1 -0
  184. package/dist/generator/feature-slice-emitters.d.ts +19 -0
  185. package/dist/generator/feature-slice-emitters.d.ts.map +1 -0
  186. package/dist/generator/feature-slice-emitters.js +19 -0
  187. package/dist/generator/feature-slice-emitters.js.map +1 -0
  188. package/dist/generator/graph-emitters.d.ts +11 -0
  189. package/dist/generator/graph-emitters.d.ts.map +1 -0
  190. package/dist/generator/graph-emitters.js +56 -0
  191. package/dist/generator/graph-emitters.js.map +1 -0
  192. package/dist/generator/layer-emitters.d.ts +39 -0
  193. package/dist/generator/layer-emitters.d.ts.map +1 -0
  194. package/dist/generator/layer-emitters.js +43 -0
  195. package/dist/generator/layer-emitters.js.map +1 -0
  196. package/dist/generator/phantom-dependency-emitters.d.ts +17 -0
  197. package/dist/generator/phantom-dependency-emitters.d.ts.map +1 -0
  198. package/dist/generator/phantom-dependency-emitters.js +19 -0
  199. package/dist/generator/phantom-dependency-emitters.js.map +1 -0
  200. package/dist/generator/public-api-emitters.d.ts +19 -0
  201. package/dist/generator/public-api-emitters.d.ts.map +1 -0
  202. package/dist/generator/public-api-emitters.js +19 -0
  203. package/dist/generator/public-api-emitters.js.map +1 -0
  204. package/dist/generator/role-layering-emitters.d.ts +18 -0
  205. package/dist/generator/role-layering-emitters.d.ts.map +1 -0
  206. package/dist/generator/role-layering-emitters.js +19 -0
  207. package/dist/generator/role-layering-emitters.js.map +1 -0
  208. package/dist/generator/rule-generator.d.ts +14 -0
  209. package/dist/generator/rule-generator.d.ts.map +1 -0
  210. package/dist/generator/rule-generator.js +174 -0
  211. package/dist/generator/rule-generator.js.map +1 -0
  212. package/dist/generator/server-client-emitters.d.ts +17 -0
  213. package/dist/generator/server-client-emitters.d.ts.map +1 -0
  214. package/dist/generator/server-client-emitters.js +18 -0
  215. package/dist/generator/server-client-emitters.js.map +1 -0
  216. package/dist/generator/stories-isolation-emitters.d.ts +17 -0
  217. package/dist/generator/stories-isolation-emitters.d.ts.map +1 -0
  218. package/dist/generator/stories-isolation-emitters.js +19 -0
  219. package/dist/generator/stories-isolation-emitters.js.map +1 -0
  220. package/dist/generator/test-isolation-emitters.d.ts +17 -0
  221. package/dist/generator/test-isolation-emitters.d.ts.map +1 -0
  222. package/dist/generator/test-isolation-emitters.js +19 -0
  223. package/dist/generator/test-isolation-emitters.js.map +1 -0
  224. package/dist/generator/ui-data-isolation-emitters.d.ts +17 -0
  225. package/dist/generator/ui-data-isolation-emitters.d.ts.map +1 -0
  226. package/dist/generator/ui-data-isolation-emitters.js +25 -0
  227. package/dist/generator/ui-data-isolation-emitters.js.map +1 -0
  228. package/dist/generator/workspace-package-emitters.d.ts +4 -0
  229. package/dist/generator/workspace-package-emitters.d.ts.map +1 -0
  230. package/dist/generator/workspace-package-emitters.js +24 -0
  231. package/dist/generator/workspace-package-emitters.js.map +1 -0
  232. package/dist/index.d.ts +52 -0
  233. package/dist/index.d.ts.map +1 -0
  234. package/dist/index.js +52 -0
  235. package/dist/index.js.map +1 -0
  236. package/dist/scanner/app-dirs.d.ts +2 -0
  237. package/dist/scanner/app-dirs.d.ts.map +1 -0
  238. package/dist/scanner/app-dirs.js +58 -0
  239. package/dist/scanner/app-dirs.js.map +1 -0
  240. package/dist/scanner/barrel-resolver.d.ts +4 -0
  241. package/dist/scanner/barrel-resolver.d.ts.map +1 -0
  242. package/dist/scanner/barrel-resolver.js +40 -0
  243. package/dist/scanner/barrel-resolver.js.map +1 -0
  244. package/dist/scanner/file-walker.d.ts +21 -0
  245. package/dist/scanner/file-walker.d.ts.map +1 -0
  246. package/dist/scanner/file-walker.js +279 -0
  247. package/dist/scanner/file-walker.js.map +1 -0
  248. package/dist/scanner/ignore-filter.d.ts +3 -0
  249. package/dist/scanner/ignore-filter.d.ts.map +1 -0
  250. package/dist/scanner/ignore-filter.js +26 -0
  251. package/dist/scanner/ignore-filter.js.map +1 -0
  252. package/dist/scanner/import-graph.d.ts +13 -0
  253. package/dist/scanner/import-graph.d.ts.map +1 -0
  254. package/dist/scanner/import-graph.js +103 -0
  255. package/dist/scanner/import-graph.js.map +1 -0
  256. package/dist/scanner/resolve-import.d.ts +6 -0
  257. package/dist/scanner/resolve-import.d.ts.map +1 -0
  258. package/dist/scanner/resolve-import.js +42 -0
  259. package/dist/scanner/resolve-import.js.map +1 -0
  260. package/dist/scanner/role-classifier.d.ts +12 -0
  261. package/dist/scanner/role-classifier.d.ts.map +1 -0
  262. package/dist/scanner/role-classifier.js +158 -0
  263. package/dist/scanner/role-classifier.js.map +1 -0
  264. package/dist/scanner/usage-scanner.d.ts +12 -0
  265. package/dist/scanner/usage-scanner.d.ts.map +1 -0
  266. package/dist/scanner/usage-scanner.js +31 -0
  267. package/dist/scanner/usage-scanner.js.map +1 -0
  268. package/dist/scanner/workspace-packages.d.ts +3 -0
  269. package/dist/scanner/workspace-packages.d.ts.map +1 -0
  270. package/dist/scanner/workspace-packages.js +96 -0
  271. package/dist/scanner/workspace-packages.js.map +1 -0
  272. package/dist/scanner/workspace-resolver.d.ts +2 -0
  273. package/dist/scanner/workspace-resolver.d.ts.map +1 -0
  274. package/dist/scanner/workspace-resolver.js +57 -0
  275. package/dist/scanner/workspace-resolver.js.map +1 -0
  276. package/package.json +100 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ # archprint
2
+
3
+ Release notes are generated by [Changesets](https://github.com/changesets/changesets) on each release and
4
+ appear here starting with the first published version.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oluwatomiwa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,268 @@
1
+ # Archprint
2
+
3
+ **Mine the architecture rules your repo already enforces, with the evidence attached.**
4
+
5
+ Archprint scans a TypeScript repository's real import graph, finds the architectural boundaries the code
6
+ already respects, and turns the ones that pass a statistical confidence gate into deterministic, ready to
7
+ install lint rules. Every rule ships with the evidence behind it: how many files conform, how many break it,
8
+ and how confident the inference is.
9
+
10
+ Your `CLAUDE.md` is guidance. Your lint rules are enforcement. Archprint closes the gap by generating the
11
+ enforcement from patterns your codebase already demonstrates, so you adopt rules you can trust instead of
12
+ authoring them by hand.
13
+
14
+ **What auto-enforces vs. what you review.** Archprint is honest about which of its inferences it will stand
15
+ behind unattended. An adversarial correctness audit (three rounds over four real repositories) found that the
16
+ _mechanical_ families, ones grounded in unambiguous signals (no cycles, production must not import tests, no
17
+ `console` in library code, no undeclared dependencies, deep-relative import style, public-API barrels, no
18
+ reaching into a dependency's internals, and the DB/UI-in-server-entry rule), had zero false positives every
19
+ round. So those auto-generate as enforcement. The _structural-inference_ families (layer and role boundaries,
20
+ UI/data separation, entry purity, server/client, feature-slice and app isolation) infer a "layer" or "role"
21
+ from paths, which can be wrong, so Archprint holds them for human review by default rather than silently
22
+ enforcing them. Nothing whose inferred layer or role could be wrong is written as enforcement without you
23
+ opting in.
24
+
25
+ > Status: `0.1.0`, pre-stable (0.x may break between minor versions). Not yet published to npm. Production-ready
26
+ > today: the insight commands (`scan`, `recommend`) and the auto-enforcement of the mechanical families above.
27
+ > The structural families are review-only while they are hardened.
28
+
29
+ ## What makes it different
30
+
31
+ Established TypeScript tools (dependency-cruiser, eslint-plugin-boundaries, Nx, Sheriff, ts-arch) all
32
+ **enforce** architecture rules you write by hand. Archprint **infers** them from the actual import graph and
33
+ **gates each one on statistical evidence** before proposing it. Across the TypeScript ecosystem, no other tool
34
+ does either (see the comparison below). It then emits into those existing tools' formats, so it complements
35
+ your stack rather than replacing it.
36
+
37
+ ## Install
38
+
39
+ Not yet on npm. For now, build from source:
40
+
41
+ ```bash
42
+ git clone https://github.com/Tommkruix/archprint
43
+ cd archprint
44
+ npm ci
45
+ npm run build
46
+ node dist/cli.js scan <path-to-your-app>
47
+ ```
48
+
49
+ Once published, it will install as a normal dev dependency:
50
+
51
+ ```bash
52
+ npm install --save-dev archprint
53
+ npx archprint scan .
54
+ ```
55
+
56
+ Requires Node >= 20. Point Archprint at an app directory that has a `tsconfig.json` (for a monorepo, a
57
+ package such as `apps/web`; a monorepo root is fine too, Archprint discovers the app directories).
58
+
59
+ ## Quick start
60
+
61
+ ```bash
62
+ # One-shot setup: detect the stack, enforce the rules your code already follows,
63
+ # and record what to adopt next in archprint.json
64
+ archprint init apps/web
65
+
66
+ # See the rules your repo already follows, with the evidence
67
+ archprint scan apps/web
68
+
69
+ # Write the auto-trusted (mechanical) rules to disk (rule files + tool configs).
70
+ # Structural-inference rules are held for review; add --include-structural to emit them too.
71
+ archprint generate apps/web --out archprint-rules
72
+
73
+ # Inspect the gate evidence behind one rule
74
+ archprint explain AP-002 apps/web
75
+
76
+ # Generate a single rule by id after reviewing it (including a SUGGEST rule)
77
+ archprint generate apps/web --rule AP-001
78
+
79
+ # Recommend a rule set from the evidence and the detected stack (fresh repos too)
80
+ archprint recommend apps/web
81
+
82
+ # Reference the generated rules from the enforcement tools your repo uses (managed, reversible)
83
+ archprint wire
84
+
85
+ # Remove archprint's files and any wired references (clean uninstall)
86
+ archprint eject
87
+ ```
88
+
89
+ Re-running `generate` (or `init`) refreshes the files in `archprint-rules/` and removes any rule the
90
+ evidence no longer supports, so the output never drifts from the current codebase. `wire` detects the
91
+ enforcement tools your repo already uses (a flat eslint config, a `.dependency-cruiser.json`) and inserts a
92
+ single managed reference into each, one that survives those regenerations; `eject` removes archprint's files
93
+ and every wired reference, restoring each config exactly. For a tool config it cannot safely edit (a JS
94
+ dependency-cruiser config, say), it prints the exact snippet to paste. The flagship forbidden-import rules
95
+ (AP-) ship as a generated local eslint plugin that the eslint reference activates, so wiring the eslint config
96
+ enforces them too, no extra install.
97
+
98
+ `recommend` sorts every rule family into three tiers: rules your code already
99
+ follows (enforce now), rules with thin evidence (review and adopt), and rules that
100
+ comparable repos commonly follow but yours does not yet (adopt from day one). Each
101
+ recommendation carries the evidence behind it: the share of comparable repos (your
102
+ detected stack, else overall) that already enforce that rule, mined from a census of
103
+ tens of thousands of public TypeScript repositories. The "adopt from day one" tier is
104
+ driven by that census rather than hand-picked defaults, so on a fresh repo, where
105
+ there is little code to infer from, it still gives you a stack-aware baseline backed
106
+ by what the ecosystem actually does.
107
+
108
+ ## Example
109
+
110
+ A real scan of [inbox-zero](https://github.com/elie222/inbox-zero) (`apps/web`, 2,232 TypeScript files),
111
+ trimmed:
112
+
113
+ ```
114
+ Archprint v0.1.0
115
+ Scanned 2,232 TypeScript files
116
+ Workspace aliases: 18 resolved
117
+
118
+ GENERATED RULES
119
+ AP-002 no-ui-layer-in-server-entry confidence 97%
120
+ Evidence: 216/217 role files conform (99.5% observed)
121
+ Exceptions: 1
122
+
123
+ LAYER BOUNDARIES (review before enforcing)
124
+ utils !-> app layer boundary confidence 99%
125
+ Evidence: 650/653 utils files conform (99.5%); 451 app file(s) depend on utils
126
+ hooks !-> app layer boundary confidence 94%
127
+ Evidence: 65/65 hooks files conform (100%); 121 app file(s) depend on hooks
128
+ ```
129
+
130
+ `AP-002` is a mechanical family, so it auto-generates as enforcement. The layer boundaries are inferred, so
131
+ they are shown for review, not written as enforcement unless you pass `--include-structural`.
132
+
133
+ Every number is measured from the import graph, not estimated.
134
+
135
+ ## What Archprint detects
136
+
137
+ Ships as: **Auto** = auto-generated as enforcement (mechanical families, 0 false positives across the
138
+ correctness audit). **Review** = held for human review by default; emit with `--include-structural` (the
139
+ inferred layer/role can be wrong, so it is not enforced silently). **Report** = surfaced only, never enforced.
140
+
141
+ | Detector | Rule it can infer | Ships as |
142
+ | -------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -------- |
143
+ | Forbidden imports (marker based) | A role (route handler, server entry) must not import a target (the DB client, the UI layer) | Auto |
144
+ | Circular dependencies | The module graph should stay acyclic (gated on how cycle free it already is) | Auto |
145
+ | Test isolation | Production (non-test) code must not import test or spec files | Auto |
146
+ | Dependency hygiene | Import third-party packages by their public entry, not a dependency's `src`/`internal` internals | Auto |
147
+ | Dependency declaration | Every imported third-party package must be declared in `package.json` (no phantom/transitive deps) | Auto |
148
+ | Import style | Prefer workspace aliases over deep relative imports (`../../../`) | Auto |
149
+ | Console isolation | Library (non-CLI) code must not call `console.*` | Auto |
150
+ | Public API (barrel) boundaries | Files outside a feature or package must import it through its `index` barrel, not deep import its internals | Auto |
151
+ | Layer boundaries | Files in one layer must not import another, inferred from the dominant dependency direction | Review |
152
+ | Role layering | Semantic tiers keep their direction (a REPOSITORY must not import a SERVICE, a SERVICE must not import a CONTROLLER) | Review |
153
+ | Entry purity | Framework entries (pages, routes, layouts) must not be imported by other first-party code | Review |
154
+ | UI / data separation | Reusable UI components must not import the DB/data layer directly | Review |
155
+ | Server / client boundary | A Next.js `"use client"` module must not import a `server-only` module | Review |
156
+ | Feature-slice isolation | Sibling slices under a `features`/`modules`/`slices`/`domains` container must not import each other | Review |
157
+ | App isolation | Sibling apps under an `apps`/`services` container must not import each other directly | Review |
158
+ | Env access | Read `process.env` only in the config/env layer | Review |
159
+ | Workspace package API | Import a monorepo workspace package by its name, not a deep path into its source | Review |
160
+ | Stories isolation | Storybook `.stories` files must not be imported by other code | Review |
161
+ | Orphan modules | Files nothing imports and that are not framework entries (dead code candidates) | Report |
162
+ | Transitive reachability | A layer boundary that a plain import rule passes but that leaks through an intermediary layer | Report |
163
+
164
+ ## The confidence gate
165
+
166
+ Archprint never proposes a rule as enforceable on a thin sample. Each candidate is scored with a **Wilson
167
+ score lower bound** on its true conformance rate, which fuses the observed ratio and the sample size into one
168
+ number, so 5 of 5 clean files is not treated as evidence of a 90% rule but 40 of 40 is.
169
+
170
+ - **AUTO** (enforceable): the 95% lower bound on conformance is at least 90%, with at most 3 exceptions and a
171
+ confidently classified role.
172
+ - **SUGGEST** (provisional): the pattern looks like a rule (at least 80% observed) but the sample is too thin
173
+ to be confident. Surfaced for review, not auto generated.
174
+ - **REJECT**: not enough signal.
175
+
176
+ The statistical gate is necessary but not sufficient: a rule can be statistically clean yet semantically wrong
177
+ if the inferred "layer" or "role" is not real. So a second, evidence-based gate sits on top of it: only the
178
+ _mechanical_ families (see the "Ships as: Auto" rows above), which an adversarial correctness audit found had
179
+ zero false positives across three rounds and four repositories, auto-generate as enforcement. The
180
+ _structural-inference_ families are capped at review regardless of their statistical score until they earn the
181
+ same clean record. The bias is deliberate and conservative: one wrong enforced rule hurts credibility more than
182
+ zero rules.
183
+
184
+ ## Output formats
185
+
186
+ `archprint generate` writes into the formats your existing tools already read:
187
+
188
+ - **dependency-cruiser** `forbidden` rulesets: by default the mechanical boundaries (public-API deep-import,
189
+ test-isolation, dependency-internals); the structural ones (layer, role-layering, feature-slice,
190
+ app-isolation, entry-purity) are written only with `--include-structural`, after you review them
191
+ - **eslint-plugin-boundaries** element-types config, and **ESLint core** rules (`no-restricted-imports`) for
192
+ import-style boundaries
193
+ - **ESLint rule files** for marker based patterns: a rule card (`.md`), the rule (`.ts`), and a passing and a
194
+ failing fixture
195
+ - **Mermaid** and **Graphviz DOT** of the layer dependency graph, so the inferred architecture is visible and
196
+ its violations are marked
197
+
198
+ ## How it compares
199
+
200
+ Verified against each tool's documentation (TypeScript ecosystem). The two columns that matter are the ones no
201
+ other TypeScript tool fills:
202
+
203
+ | Tool | Enforces arch rules | Auto-infers from the import graph | Attaches statistical evidence |
204
+ | ----------------------------- | :-----------------: | :-------------------------------: | :---------------------------: |
205
+ | **Archprint** | yes | **yes** | **yes** |
206
+ | dependency-cruiser | yes | no | no |
207
+ | eslint-plugin-boundaries | yes | no | no |
208
+ | @nx/enforce-module-boundaries | yes | no | no |
209
+ | Sheriff | yes | no | no |
210
+ | ts-arch | yes | no | no |
211
+ | madge / knip | analysis only | no | no |
212
+
213
+ Honest caveat: in other ecosystems, [Tach](https://github.com/gauge-sh/tach) (Python) and ArchLint (Java) do
214
+ auto-infer module boundaries, so Archprint's specific niche is auto-inference **plus statistical evidence
215
+ gating in the TypeScript ecosystem**. Archprint also overlaps in detection with dependency-cruiser (cycles,
216
+ orphans, reachability) and knip (dead code); rather than compete, it emits into those tools' formats.
217
+
218
+ ## Commands
219
+
220
+ | Command | What it does |
221
+ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
222
+ | `archprint init [path]` | Zero-config setup: detect the stack, enforce the rules the code already follows, and write an `archprint.json` with the adopt tiers. `--include-structural`, `--out <dir>`, `--fast`, `--force`. |
223
+ | `archprint scan [path]` | Report the rules the repo already follows, with evidence. `--deep` resolves through barrels and aliases. |
224
+ | `archprint generate [path]` | Write the auto-trusted mechanical rules + tool configs; structural rules held for review. `--rule <id>` emits one reviewed rule (including a SUGGEST rule). `--include-structural`, `--out <dir>`, `--fast`. |
225
+ | `archprint explain <id> [path]` | Show the gate breakdown for one rule, with a codeframe per exception plus how-to-fix, when-not-to-use, and how-to-enforce. |
226
+ | `archprint recommend [path]` | Recommend a rule set from the repo's evidence and detected stack (works on a fresh repo too). |
227
+ | `archprint wire` | Reference the generated rules from the enforcement tools your repo uses (flat eslint config, `.dependency-cruiser.json`) via a managed, reversible reference. `--out <dir>`, `--dry-run`. |
228
+ | `archprint eject` | Remove archprint's generated files, its manifests, and any wired references. `--out <dir>`, `--dry-run`. |
229
+
230
+ ## Documentation
231
+
232
+ Full docs live in [`docs/`](./docs/): [getting started](./docs/getting-started.md),
233
+ [concepts](./docs/concepts.md) (the confidence gate, mechanical vs. structural, fast vs. deep, the
234
+ generate/wire/eject lifecycle), and the [rule-family reference](./docs/rules.md) (what each rule detects, how it
235
+ ships, and when not to use it).
236
+
237
+ ## Fast and deep modes
238
+
239
+ `scan` defaults to a **fast** specifier level pass (no type checker). `generate` defaults to a
240
+ **deep** pass that resolves through barrels and workspace aliases, since generation is the commitment point.
241
+ Structural analysis (cycles, orphans, reachability, public-API) always uses the fast graph: it is faithful to
242
+ deep resolution for those, and public-API detection in fact requires it (deep resolution would resolve through
243
+ a barrel and erase the barrel-versus-deep signal).
244
+
245
+ ## Determinism
246
+
247
+ Same repo plus same version produces the same output. Analysis is pure and sorted; there is no randomness.
248
+
249
+ ## Status and roadmap
250
+
251
+ `0.1.0`, pre-stable. The engine (twenty detectors, five output formats, the confidence gate) is in place and
252
+ tested, and an adversarial correctness audit (three rounds, four real repositories) drove the false-positive
253
+ rate on auto-generated rules to zero for the mechanical families, which is why those auto-enforce while the
254
+ structural-inference families are held for review.
255
+
256
+ Production-ready today: `scan` and `recommend` (insight), and auto-enforcement of the mechanical families.
257
+ Still ahead: hardening the structural families toward auto-enforcement (a real per-file role-confidence measure,
258
+ layer-cohesion, role-classifier ordering), a self-consistency check at generate time, a pinned regression
259
+ corpus, an `init` scaffolder for fresh repos, and broader framework role coverage.
260
+
261
+ ## Contributing
262
+
263
+ See [CONTRIBUTING.md](CONTRIBUTING.md). The project lints, type checks, and tests itself; every change keeps
264
+ coverage above its thresholds and ships a changeset.
265
+
266
+ ## License
267
+
268
+ [MIT](LICENSE)
@@ -0,0 +1,6 @@
1
+ export interface CodeFrame {
2
+ line: number;
3
+ text: string;
4
+ }
5
+ export declare function locateImport(appDir: string, relativeFile: string, specifier: string): CodeFrame | null;
6
+ //# sourceMappingURL=codeframe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codeframe.d.ts","sourceRoot":"","sources":["../../src/cli/codeframe.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,GAChB,SAAS,GAAG,IAAI,CAiBlB"}
@@ -0,0 +1,22 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import * as path from 'node:path';
3
+ export function locateImport(appDir, relativeFile, specifier) {
4
+ let source;
5
+ try {
6
+ source = readFileSync(path.join(appDir, relativeFile), 'utf8');
7
+ }
8
+ catch {
9
+ return null;
10
+ }
11
+ const single = `'${specifier}'`;
12
+ const double = `"${specifier}"`;
13
+ const lines = source.split(/\r?\n/);
14
+ for (let index = 0; index < lines.length; index++) {
15
+ const text = lines[index];
16
+ if (text.includes(single) || text.includes(double)) {
17
+ return { line: index + 1, text: text.trim() };
18
+ }
19
+ }
20
+ return null;
21
+ }
22
+ //# sourceMappingURL=codeframe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codeframe.js","sourceRoot":"","sources":["../../src/cli/codeframe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAOlC,MAAM,UAAU,YAAY,CAC1B,MAAc,EACd,YAAoB,EACpB,SAAiB;IAEjB,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,SAAS,GAAG,CAAC;IAChC,MAAM,MAAM,GAAG,IAAI,SAAS,GAAG,CAAC;IAChC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACpC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QAClD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACnD,OAAO,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QAChD,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { GenerationStatus } from '../detector/confidence-gate.js';
2
+ import type { ScannedPattern, ScanResult } from './scan.js';
3
+ export declare function writeRules(scan: ScanResult, outDir: string, statuses?: readonly GenerationStatus[]): string[];
4
+ export declare function emitOne(pattern: ScannedPattern, appDir: string, outDir: string): string;
5
+ export declare function writeLayerConfig(scan: ScanResult, outDir: string, statuses?: readonly GenerationStatus[]): string[];
6
+ export declare function writePublicApiConfig(scan: ScanResult, outDir: string, statuses?: readonly GenerationStatus[]): string[];
7
+ export declare function writeFeatureSliceConfig(scan: ScanResult, outDir: string, statuses?: readonly GenerationStatus[]): string[];
8
+ export declare function writeAppIsolationConfig(scan: ScanResult, outDir: string, statuses?: readonly GenerationStatus[]): string[];
9
+ export declare function writeTestIsolationConfig(scan: ScanResult, outDir: string): string[];
10
+ export declare function writeRoleLayeringConfig(scan: ScanResult, outDir: string, statuses?: readonly GenerationStatus[]): string[];
11
+ export declare function writeDeepRelativeConfig(scan: ScanResult, outDir: string): string[];
12
+ export declare function writeConsoleIsolationConfig(scan: ScanResult, outDir: string): string[];
13
+ export declare function writeServerClientConfig(scan: ScanResult, outDir: string): string[];
14
+ export declare function writeUiDataConfig(scan: ScanResult, outDir: string): string[];
15
+ export declare function writeStoriesIsolationConfig(scan: ScanResult, outDir: string): string[];
16
+ export declare function writeWorkspacePackageConfig(scan: ScanResult, outDir: string): string[];
17
+ export declare function writeEnvAccessConfig(scan: ScanResult, outDir: string): string[];
18
+ export declare function writePhantomDependencyConfig(scan: ScanResult, outDir: string): string[];
19
+ export declare function writeEntryPurityConfig(scan: ScanResult, outDir: string): string[];
20
+ export declare function writeDependencyInternalsConfig(scan: ScanResult, outDir: string): string[];
21
+ export declare function writeEslintPlugin(scan: ScanResult, outDir: string): string[];
22
+ export declare function writeGraph(scan: ScanResult, outDir: string): string[];
23
+ export interface WrittenConfig {
24
+ files: string[];
25
+ label: string | null;
26
+ }
27
+ export declare function writeEnforcementConfigs(scan: ScanResult, outDir: string, options?: {
28
+ structural?: boolean;
29
+ }): WrittenConfig[];
30
+ export declare function regenerateConfigs(scan: ScanResult, outDir: string, options: {
31
+ structural?: boolean;
32
+ version: string;
33
+ }): {
34
+ configs: WrittenConfig[];
35
+ removed: string[];
36
+ };
37
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/cli/generate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,259 @@
1
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { toDependencyCruiser, toEslintBoundaries } from '../generator/layer-emitters.js';
4
+ import { toDependencyCruiserPublicApi } from '../generator/public-api-emitters.js';
5
+ import { toDependencyCruiserFeatureSlice } from '../generator/feature-slice-emitters.js';
6
+ import { toDependencyCruiserTestIsolation } from '../generator/test-isolation-emitters.js';
7
+ import { toDependencyCruiserAppIsolation } from '../generator/app-isolation-emitters.js';
8
+ import { toDependencyCruiserDependencyInternals } from '../generator/dependency-internals-emitters.js';
9
+ import { toDependencyCruiserRoleLayering } from '../generator/role-layering-emitters.js';
10
+ import { toDependencyCruiserEntryPurity } from '../generator/entry-purity-emitters.js';
11
+ import { toDependencyCruiserPhantomDependencies } from '../generator/phantom-dependency-emitters.js';
12
+ import { toEslintDeepRelative } from '../generator/deep-relative-emitters.js';
13
+ import { toEslintConsoleIsolation } from '../generator/console-isolation-emitters.js';
14
+ import { toEslintEnvAccess } from '../generator/env-access-emitters.js';
15
+ import { toEslintWorkspacePackageApi } from '../generator/workspace-package-emitters.js';
16
+ import { toDependencyCruiserStoriesIsolation } from '../generator/stories-isolation-emitters.js';
17
+ import { toDependencyCruiserUiData } from '../generator/ui-data-isolation-emitters.js';
18
+ import { toDependencyCruiserServerClient } from '../generator/server-client-emitters.js';
19
+ import { toGraphviz, toMermaid } from '../generator/graph-emitters.js';
20
+ import { emitRuleArtifacts } from '../generator/rule-generator.js';
21
+ import { buildForbiddenImportSpecs, renderEslintPluginSource, } from '../generator/eslint-plugin-emitter.js';
22
+ import { cleanPreviousOutputs, removeIfEmpty, writeOutputsManifest } from './outputs-manifest.js';
23
+ import { hasDependencyCruiserBlocks, hasEslintOutputs, writeDependencyCruiserAggregate, writeEslintAggregator, } from './wiring.js';
24
+ export function writeRules(scan, outDir, statuses = ['AUTO']) {
25
+ const written = [];
26
+ for (const pattern of scan.patterns) {
27
+ if (!statuses.includes(pattern.result.gate.status))
28
+ continue;
29
+ written.push(emitOne(pattern, scan.appDir, outDir));
30
+ }
31
+ return written;
32
+ }
33
+ export function emitOne(pattern, appDir, outDir) {
34
+ return emitRuleArtifacts(pattern.config, pattern.result, outDir, `archprint scan ${appDir}`);
35
+ }
36
+ export function writeLayerConfig(scan, outDir, statuses = ['AUTO']) {
37
+ const dependencyCruiser = toDependencyCruiser(scan.layerBoundaries, statuses);
38
+ if (dependencyCruiser.forbidden.length === 0)
39
+ return [];
40
+ mkdirSync(outDir, { recursive: true });
41
+ const write = (name, config) => {
42
+ const file = path.join(outDir, name);
43
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
44
+ return file;
45
+ };
46
+ return [
47
+ write('dependency-cruiser.archprint.json', dependencyCruiser),
48
+ write('eslint-boundaries.archprint.json', toEslintBoundaries(scan.layerBoundaries, statuses)),
49
+ ];
50
+ }
51
+ export function writePublicApiConfig(scan, outDir, statuses = ['AUTO']) {
52
+ const config = toDependencyCruiserPublicApi(scan.publicApi.groups, statuses);
53
+ if (config.forbidden.length === 0)
54
+ return [];
55
+ mkdirSync(outDir, { recursive: true });
56
+ const file = path.join(outDir, 'dependency-cruiser.public-api.archprint.json');
57
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
58
+ return [file];
59
+ }
60
+ export function writeFeatureSliceConfig(scan, outDir, statuses = ['AUTO']) {
61
+ const config = toDependencyCruiserFeatureSlice(scan.featureSlices.groups, statuses);
62
+ if (config.forbidden.length === 0)
63
+ return [];
64
+ mkdirSync(outDir, { recursive: true });
65
+ const file = path.join(outDir, 'dependency-cruiser.feature-slice.archprint.json');
66
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
67
+ return [file];
68
+ }
69
+ export function writeAppIsolationConfig(scan, outDir, statuses = ['AUTO']) {
70
+ const config = toDependencyCruiserAppIsolation(scan.appIsolation.groups, statuses);
71
+ if (config.forbidden.length === 0)
72
+ return [];
73
+ mkdirSync(outDir, { recursive: true });
74
+ const file = path.join(outDir, 'dependency-cruiser.app-isolation.archprint.json');
75
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
76
+ return [file];
77
+ }
78
+ export function writeTestIsolationConfig(scan, outDir) {
79
+ const config = toDependencyCruiserTestIsolation(scan.testIsolation);
80
+ if (config.forbidden.length === 0)
81
+ return [];
82
+ mkdirSync(outDir, { recursive: true });
83
+ const file = path.join(outDir, 'dependency-cruiser.test-isolation.archprint.json');
84
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
85
+ return [file];
86
+ }
87
+ export function writeRoleLayeringConfig(scan, outDir, statuses = ['AUTO']) {
88
+ const config = toDependencyCruiserRoleLayering(scan.roleLayering.boundaries, statuses);
89
+ if (config.forbidden.length === 0)
90
+ return [];
91
+ mkdirSync(outDir, { recursive: true });
92
+ const file = path.join(outDir, 'dependency-cruiser.role-layering.archprint.json');
93
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
94
+ return [file];
95
+ }
96
+ export function writeDeepRelativeConfig(scan, outDir) {
97
+ const config = toEslintDeepRelative(scan.deepRelative);
98
+ if (config === null)
99
+ return [];
100
+ mkdirSync(outDir, { recursive: true });
101
+ const file = path.join(outDir, 'eslint.deep-relative.archprint.json');
102
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
103
+ return [file];
104
+ }
105
+ export function writeConsoleIsolationConfig(scan, outDir) {
106
+ const config = toEslintConsoleIsolation(scan.consoleIsolation);
107
+ if (config === null)
108
+ return [];
109
+ mkdirSync(outDir, { recursive: true });
110
+ const file = path.join(outDir, 'eslint.console-isolation.archprint.json');
111
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
112
+ return [file];
113
+ }
114
+ export function writeServerClientConfig(scan, outDir) {
115
+ const config = toDependencyCruiserServerClient(scan.serverClient);
116
+ if (config.forbidden.length === 0)
117
+ return [];
118
+ mkdirSync(outDir, { recursive: true });
119
+ const file = path.join(outDir, 'dependency-cruiser.server-client.archprint.json');
120
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
121
+ return [file];
122
+ }
123
+ export function writeUiDataConfig(scan, outDir) {
124
+ const config = toDependencyCruiserUiData(scan.uiDataIsolation);
125
+ if (config.forbidden.length === 0)
126
+ return [];
127
+ mkdirSync(outDir, { recursive: true });
128
+ const file = path.join(outDir, 'dependency-cruiser.ui-data.archprint.json');
129
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
130
+ return [file];
131
+ }
132
+ export function writeStoriesIsolationConfig(scan, outDir) {
133
+ const config = toDependencyCruiserStoriesIsolation(scan.storiesIsolation);
134
+ if (config.forbidden.length === 0)
135
+ return [];
136
+ mkdirSync(outDir, { recursive: true });
137
+ const file = path.join(outDir, 'dependency-cruiser.stories-isolation.archprint.json');
138
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
139
+ return [file];
140
+ }
141
+ export function writeWorkspacePackageConfig(scan, outDir) {
142
+ const config = toEslintWorkspacePackageApi(scan.workspacePackageApi);
143
+ if (config === null)
144
+ return [];
145
+ mkdirSync(outDir, { recursive: true });
146
+ const file = path.join(outDir, 'eslint.workspace-package.archprint.json');
147
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
148
+ return [file];
149
+ }
150
+ export function writeEnvAccessConfig(scan, outDir) {
151
+ const config = toEslintEnvAccess(scan.envAccess);
152
+ if (config === null)
153
+ return [];
154
+ mkdirSync(outDir, { recursive: true });
155
+ const file = path.join(outDir, 'eslint.env-access.archprint.json');
156
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
157
+ return [file];
158
+ }
159
+ export function writePhantomDependencyConfig(scan, outDir) {
160
+ const config = toDependencyCruiserPhantomDependencies(scan.phantomDependencies);
161
+ if (config.forbidden.length === 0)
162
+ return [];
163
+ mkdirSync(outDir, { recursive: true });
164
+ const file = path.join(outDir, 'dependency-cruiser.phantom-deps.archprint.json');
165
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
166
+ return [file];
167
+ }
168
+ export function writeEntryPurityConfig(scan, outDir) {
169
+ const config = toDependencyCruiserEntryPurity(scan.entryPurity);
170
+ if (config.forbidden.length === 0)
171
+ return [];
172
+ mkdirSync(outDir, { recursive: true });
173
+ const file = path.join(outDir, 'dependency-cruiser.entry-purity.archprint.json');
174
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
175
+ return [file];
176
+ }
177
+ export function writeDependencyInternalsConfig(scan, outDir) {
178
+ const config = toDependencyCruiserDependencyInternals(scan.dependencyInternals);
179
+ if (config.forbidden.length === 0)
180
+ return [];
181
+ mkdirSync(outDir, { recursive: true });
182
+ const file = path.join(outDir, 'dependency-cruiser.dependency-internals.archprint.json');
183
+ writeFileSync(file, `${JSON.stringify(config, null, 2)}\n`);
184
+ return [file];
185
+ }
186
+ export function writeEslintPlugin(scan, outDir) {
187
+ const specs = buildForbiddenImportSpecs(scan.patterns);
188
+ if (specs.length === 0)
189
+ return [];
190
+ mkdirSync(outDir, { recursive: true });
191
+ const file = path.join(outDir, 'eslint-plugin.archprint.mjs');
192
+ writeFileSync(file, renderEslintPluginSource(specs));
193
+ return [file];
194
+ }
195
+ export function writeGraph(scan, outDir) {
196
+ if (scan.layerBoundaries.length === 0)
197
+ return [];
198
+ mkdirSync(outDir, { recursive: true });
199
+ const write = (name, content) => {
200
+ const file = path.join(outDir, name);
201
+ writeFileSync(file, `${content}\n`);
202
+ return file;
203
+ };
204
+ return [
205
+ write('layer-graph.archprint.mmd', toMermaid(scan.layerBoundaries)),
206
+ write('layer-graph.archprint.dot', toGraphviz(scan.layerBoundaries)),
207
+ ];
208
+ }
209
+ const countAuto = (items) => items.filter((item) => item.gate.status === 'AUTO').length;
210
+ export function writeEnforcementConfigs(scan, outDir, options = {}) {
211
+ const structural = options.structural ?? false;
212
+ const configs = [];
213
+ const add = (files, label) => {
214
+ if (files.length > 0)
215
+ configs.push({ files, label });
216
+ };
217
+ add(writeRules(scan, outDir, ['AUTO']), null);
218
+ add(writeEslintPlugin(scan, outDir), 'forbidden-import rules as a loadable eslint plugin');
219
+ if (structural) {
220
+ add(writeLayerConfig(scan, outDir, ['AUTO']), `${countAuto(scan.layerBoundaries)} layer boundaries: dependency-cruiser and eslint-plugin-boundaries`);
221
+ add(writeRoleLayeringConfig(scan, outDir, ['AUTO']), `${countAuto(scan.roleLayering.boundaries)} role-layering boundaries: dependency-cruiser rules`);
222
+ }
223
+ add(writePublicApiConfig(scan, outDir, ['AUTO']), `${countAuto(scan.publicApi.groups)} public API boundaries: dependency-cruiser deep-import rules`);
224
+ if (structural) {
225
+ add(writeFeatureSliceConfig(scan, outDir, ['AUTO']), `${countAuto(scan.featureSlices.groups)} feature-slice boundaries: dependency-cruiser cross-slice rules`);
226
+ add(writeAppIsolationConfig(scan, outDir, ['AUTO']), `${countAuto(scan.appIsolation.groups)} app boundaries: dependency-cruiser cross-app rules`);
227
+ }
228
+ add(writeTestIsolationConfig(scan, outDir), 'test isolation: dependency-cruiser not-to-test rule');
229
+ add(writeDependencyInternalsConfig(scan, outDir), 'dependency hygiene: dependency-cruiser no-internals rule');
230
+ if (structural)
231
+ add(writeEntryPurityConfig(scan, outDir), 'entry purity: dependency-cruiser no-import-entry rule');
232
+ add(writePhantomDependencyConfig(scan, outDir), 'dependency declaration: dependency-cruiser no-phantom-deps rule');
233
+ add(writeDeepRelativeConfig(scan, outDir), 'import style: eslint no-restricted-imports rule');
234
+ add(writeConsoleIsolationConfig(scan, outDir), 'console isolation: eslint no-console rule');
235
+ if (structural) {
236
+ add(writeEnvAccessConfig(scan, outDir), 'env access: eslint no-restricted-properties rule');
237
+ add(writeWorkspacePackageConfig(scan, outDir), 'workspace package API: eslint no-restricted-imports rule');
238
+ add(writeStoriesIsolationConfig(scan, outDir), 'stories isolation: dependency-cruiser no-import-stories rule');
239
+ add(writeUiDataConfig(scan, outDir), 'UI / data separation: dependency-cruiser no-ui-to-data rule');
240
+ add(writeServerClientConfig(scan, outDir), 'server / client boundary: dependency-cruiser no-server-only-in-client rule');
241
+ }
242
+ add(writeGraph(scan, outDir), 'layer dependency graph: Mermaid and Graphviz DOT');
243
+ return configs;
244
+ }
245
+ export function regenerateConfigs(scan, outDir, options) {
246
+ const removed = cleanPreviousOutputs(outDir);
247
+ const configs = writeEnforcementConfigs(scan, outDir, { structural: options.structural });
248
+ const allPaths = configs.flatMap((config) => config.files);
249
+ if (hasEslintOutputs(allPaths))
250
+ allPaths.push(writeEslintAggregator(outDir));
251
+ if (hasDependencyCruiserBlocks(allPaths))
252
+ allPaths.push(writeDependencyCruiserAggregate(outDir));
253
+ if (allPaths.length > 0)
254
+ writeOutputsManifest(outDir, allPaths, options.version);
255
+ else
256
+ removeIfEmpty(outDir);
257
+ return { configs, removed };
258
+ }
259
+ //# sourceMappingURL=generate.js.map