archcheck 0.2.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 (251) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +536 -0
  3. package/dist/archcheck.d.ts +99 -0
  4. package/dist/archcheck.d.ts.map +1 -0
  5. package/dist/archcheck.js +339 -0
  6. package/dist/archcheck.js.map +1 -0
  7. package/dist/baseline/index.d.ts +29 -0
  8. package/dist/baseline/index.d.ts.map +1 -0
  9. package/dist/baseline/index.js +116 -0
  10. package/dist/baseline/index.js.map +1 -0
  11. package/dist/cli/args.d.ts +36 -0
  12. package/dist/cli/args.d.ts.map +1 -0
  13. package/dist/cli/args.js +233 -0
  14. package/dist/cli/args.js.map +1 -0
  15. package/dist/cli/index.d.ts +4 -0
  16. package/dist/cli/index.d.ts.map +1 -0
  17. package/dist/cli/index.js +219 -0
  18. package/dist/cli/index.js.map +1 -0
  19. package/dist/cli/version.d.ts +2 -0
  20. package/dist/cli/version.d.ts.map +1 -0
  21. package/dist/cli/version.js +29 -0
  22. package/dist/cli/version.js.map +1 -0
  23. package/dist/contracts/banned-syntax.d.ts +48 -0
  24. package/dist/contracts/banned-syntax.d.ts.map +1 -0
  25. package/dist/contracts/banned-syntax.js +201 -0
  26. package/dist/contracts/banned-syntax.js.map +1 -0
  27. package/dist/contracts/declared-field.d.ts +39 -0
  28. package/dist/contracts/declared-field.d.ts.map +1 -0
  29. package/dist/contracts/declared-field.js +142 -0
  30. package/dist/contracts/declared-field.js.map +1 -0
  31. package/dist/contracts/encapsulation.d.ts +28 -0
  32. package/dist/contracts/encapsulation.d.ts.map +1 -0
  33. package/dist/contracts/encapsulation.js +130 -0
  34. package/dist/contracts/encapsulation.js.map +1 -0
  35. package/dist/contracts/index.d.ts +10 -0
  36. package/dist/contracts/index.d.ts.map +1 -0
  37. package/dist/contracts/index.js +9 -0
  38. package/dist/contracts/index.js.map +1 -0
  39. package/dist/contracts/judgement.d.ts +21 -0
  40. package/dist/contracts/judgement.d.ts.map +1 -0
  41. package/dist/contracts/judgement.js +109 -0
  42. package/dist/contracts/judgement.js.map +1 -0
  43. package/dist/contracts/layers.d.ts +75 -0
  44. package/dist/contracts/layers.d.ts.map +1 -0
  45. package/dist/contracts/layers.js +244 -0
  46. package/dist/contracts/layers.js.map +1 -0
  47. package/dist/contracts/naming.d.ts +21 -0
  48. package/dist/contracts/naming.d.ts.map +1 -0
  49. package/dist/contracts/naming.js +120 -0
  50. package/dist/contracts/naming.js.map +1 -0
  51. package/dist/contracts/registry.d.ts +23 -0
  52. package/dist/contracts/registry.d.ts.map +1 -0
  53. package/dist/contracts/registry.js +48 -0
  54. package/dist/contracts/registry.js.map +1 -0
  55. package/dist/contracts/required-file.d.ts +29 -0
  56. package/dist/contracts/required-file.d.ts.map +1 -0
  57. package/dist/contracts/required-file.js +131 -0
  58. package/dist/contracts/required-file.js.map +1 -0
  59. package/dist/contracts/types.d.ts +61 -0
  60. package/dist/contracts/types.d.ts.map +1 -0
  61. package/dist/contracts/types.js +2 -0
  62. package/dist/contracts/types.js.map +1 -0
  63. package/dist/coverage/index.d.ts +55 -0
  64. package/dist/coverage/index.d.ts.map +1 -0
  65. package/dist/coverage/index.js +68 -0
  66. package/dist/coverage/index.js.map +1 -0
  67. package/dist/emit/collect.d.ts +20 -0
  68. package/dist/emit/collect.d.ts.map +1 -0
  69. package/dist/emit/collect.js +17 -0
  70. package/dist/emit/collect.js.map +1 -0
  71. package/dist/emit/header.d.ts +21 -0
  72. package/dist/emit/header.d.ts.map +1 -0
  73. package/dist/emit/header.js +20 -0
  74. package/dist/emit/header.js.map +1 -0
  75. package/dist/emit/index.d.ts +57 -0
  76. package/dist/emit/index.d.ts.map +1 -0
  77. package/dist/emit/index.js +128 -0
  78. package/dist/emit/index.js.map +1 -0
  79. package/dist/emit/merge.d.ts +23 -0
  80. package/dist/emit/merge.d.ts.map +1 -0
  81. package/dist/emit/merge.js +71 -0
  82. package/dist/emit/merge.js.map +1 -0
  83. package/dist/emit/write.d.ts +20 -0
  84. package/dist/emit/write.d.ts.map +1 -0
  85. package/dist/emit/write.js +61 -0
  86. package/dist/emit/write.js.map +1 -0
  87. package/dist/exceptions/index.d.ts +46 -0
  88. package/dist/exceptions/index.d.ts.map +1 -0
  89. package/dist/exceptions/index.js +117 -0
  90. package/dist/exceptions/index.js.map +1 -0
  91. package/dist/fs/index.d.ts +12 -0
  92. package/dist/fs/index.d.ts.map +1 -0
  93. package/dist/fs/index.js +78 -0
  94. package/dist/fs/index.js.map +1 -0
  95. package/dist/graph/cache.d.ts +12 -0
  96. package/dist/graph/cache.d.ts.map +1 -0
  97. package/dist/graph/cache.js +76 -0
  98. package/dist/graph/cache.js.map +1 -0
  99. package/dist/graph/edges.d.ts +16 -0
  100. package/dist/graph/edges.d.ts.map +1 -0
  101. package/dist/graph/edges.js +161 -0
  102. package/dist/graph/edges.js.map +1 -0
  103. package/dist/graph/index.d.ts +15 -0
  104. package/dist/graph/index.d.ts.map +1 -0
  105. package/dist/graph/index.js +90 -0
  106. package/dist/graph/index.js.map +1 -0
  107. package/dist/graph/match.d.ts +12 -0
  108. package/dist/graph/match.d.ts.map +1 -0
  109. package/dist/graph/match.js +35 -0
  110. package/dist/graph/match.js.map +1 -0
  111. package/dist/graph/program.d.ts +12 -0
  112. package/dist/graph/program.d.ts.map +1 -0
  113. package/dist/graph/program.js +54 -0
  114. package/dist/graph/program.js.map +1 -0
  115. package/dist/graph/types.d.ts +55 -0
  116. package/dist/graph/types.d.ts.map +1 -0
  117. package/dist/graph/types.js +2 -0
  118. package/dist/graph/types.js.map +1 -0
  119. package/dist/index.d.ts +21 -0
  120. package/dist/index.d.ts.map +1 -0
  121. package/dist/index.js +20 -0
  122. package/dist/index.js.map +1 -0
  123. package/dist/init/index.d.ts +23 -0
  124. package/dist/init/index.d.ts.map +1 -0
  125. package/dist/init/index.js +218 -0
  126. package/dist/init/index.js.map +1 -0
  127. package/dist/normalize/identity.d.ts +4 -0
  128. package/dist/normalize/identity.d.ts.map +1 -0
  129. package/dist/normalize/identity.js +33 -0
  130. package/dist/normalize/identity.js.map +1 -0
  131. package/dist/normalize/index.d.ts +3 -0
  132. package/dist/normalize/index.d.ts.map +1 -0
  133. package/dist/normalize/index.js +3 -0
  134. package/dist/normalize/index.js.map +1 -0
  135. package/dist/normalize/normalize.d.ts +34 -0
  136. package/dist/normalize/normalize.d.ts.map +1 -0
  137. package/dist/normalize/normalize.js +91 -0
  138. package/dist/normalize/normalize.js.map +1 -0
  139. package/dist/probes/graph.d.ts +19 -0
  140. package/dist/probes/graph.d.ts.map +1 -0
  141. package/dist/probes/graph.js +181 -0
  142. package/dist/probes/graph.js.map +1 -0
  143. package/dist/probes/index.d.ts +5 -0
  144. package/dist/probes/index.d.ts.map +1 -0
  145. package/dist/probes/index.js +5 -0
  146. package/dist/probes/index.js.map +1 -0
  147. package/dist/probes/lint.d.ts +17 -0
  148. package/dist/probes/lint.d.ts.map +1 -0
  149. package/dist/probes/lint.js +121 -0
  150. package/dist/probes/lint.js.map +1 -0
  151. package/dist/probes/model/anthropic.d.ts +47 -0
  152. package/dist/probes/model/anthropic.d.ts.map +1 -0
  153. package/dist/probes/model/anthropic.js +100 -0
  154. package/dist/probes/model/anthropic.js.map +1 -0
  155. package/dist/probes/model/budget.d.ts +22 -0
  156. package/dist/probes/model/budget.d.ts.map +1 -0
  157. package/dist/probes/model/budget.js +27 -0
  158. package/dist/probes/model/budget.js.map +1 -0
  159. package/dist/probes/model/cache.d.ts +13 -0
  160. package/dist/probes/model/cache.d.ts.map +1 -0
  161. package/dist/probes/model/cache.js +74 -0
  162. package/dist/probes/model/cache.js.map +1 -0
  163. package/dist/probes/model/index.d.ts +20 -0
  164. package/dist/probes/model/index.d.ts.map +1 -0
  165. package/dist/probes/model/index.js +160 -0
  166. package/dist/probes/model/index.js.map +1 -0
  167. package/dist/probes/model/prompt.d.ts +16 -0
  168. package/dist/probes/model/prompt.d.ts.map +1 -0
  169. package/dist/probes/model/prompt.js +57 -0
  170. package/dist/probes/model/prompt.js.map +1 -0
  171. package/dist/probes/model/provider.d.ts +88 -0
  172. package/dist/probes/model/provider.d.ts.map +1 -0
  173. package/dist/probes/model/provider.js +48 -0
  174. package/dist/probes/model/provider.js.map +1 -0
  175. package/dist/probes/structure.d.ts +3 -0
  176. package/dist/probes/structure.d.ts.map +1 -0
  177. package/dist/probes/structure.js +95 -0
  178. package/dist/probes/structure.js.map +1 -0
  179. package/dist/probes/types.d.ts +46 -0
  180. package/dist/probes/types.d.ts.map +1 -0
  181. package/dist/probes/types.js +15 -0
  182. package/dist/probes/types.js.map +1 -0
  183. package/dist/registry/define.d.ts +40 -0
  184. package/dist/registry/define.d.ts.map +1 -0
  185. package/dist/registry/define.js +4 -0
  186. package/dist/registry/define.js.map +1 -0
  187. package/dist/registry/ids.d.ts +19 -0
  188. package/dist/registry/ids.d.ts.map +1 -0
  189. package/dist/registry/ids.js +86 -0
  190. package/dist/registry/ids.js.map +1 -0
  191. package/dist/registry/index.d.ts +45 -0
  192. package/dist/registry/index.d.ts.map +1 -0
  193. package/dist/registry/index.js +71 -0
  194. package/dist/registry/index.js.map +1 -0
  195. package/dist/registry/load.d.ts +14 -0
  196. package/dist/registry/load.d.ts.map +1 -0
  197. package/dist/registry/load.js +108 -0
  198. package/dist/registry/load.js.map +1 -0
  199. package/dist/registry/validate.d.ts +24 -0
  200. package/dist/registry/validate.d.ts.map +1 -0
  201. package/dist/registry/validate.js +160 -0
  202. package/dist/registry/validate.js.map +1 -0
  203. package/dist/report/index.d.ts +10 -0
  204. package/dist/report/index.d.ts.map +1 -0
  205. package/dist/report/index.js +30 -0
  206. package/dist/report/index.js.map +1 -0
  207. package/dist/report/json.d.ts +20 -0
  208. package/dist/report/json.d.ts.map +1 -0
  209. package/dist/report/json.js +34 -0
  210. package/dist/report/json.js.map +1 -0
  211. package/dist/report/sarif.d.ts +21 -0
  212. package/dist/report/sarif.d.ts.map +1 -0
  213. package/dist/report/sarif.js +138 -0
  214. package/dist/report/sarif.js.map +1 -0
  215. package/dist/report/terminal.d.ts +3 -0
  216. package/dist/report/terminal.d.ts.map +1 -0
  217. package/dist/report/terminal.js +117 -0
  218. package/dist/report/terminal.js.map +1 -0
  219. package/dist/report/types.d.ts +28 -0
  220. package/dist/report/types.d.ts.map +1 -0
  221. package/dist/report/types.js +11 -0
  222. package/dist/report/types.js.map +1 -0
  223. package/dist/report/verdict.d.ts +21 -0
  224. package/dist/report/verdict.d.ts.map +1 -0
  225. package/dist/report/verdict.js +31 -0
  226. package/dist/report/verdict.js.map +1 -0
  227. package/dist/result.d.ts +22 -0
  228. package/dist/result.d.ts.map +1 -0
  229. package/dist/result.js +19 -0
  230. package/dist/result.js.map +1 -0
  231. package/dist/types.d.ts +178 -0
  232. package/dist/types.d.ts.map +1 -0
  233. package/dist/types.js +11 -0
  234. package/dist/types.js.map +1 -0
  235. package/dist/util/compilerApi.d.ts +2 -0
  236. package/dist/util/compilerApi.d.ts.map +1 -0
  237. package/dist/util/compilerApi.js +28 -0
  238. package/dist/util/compilerApi.js.map +1 -0
  239. package/dist/util/glob.d.ts +43 -0
  240. package/dist/util/glob.d.ts.map +1 -0
  241. package/dist/util/glob.js +195 -0
  242. package/dist/util/glob.js.map +1 -0
  243. package/dist/util/paths.d.ts +6 -0
  244. package/dist/util/paths.d.ts.map +1 -0
  245. package/dist/util/paths.js +22 -0
  246. package/dist/util/paths.js.map +1 -0
  247. package/dist/version.d.ts +13 -0
  248. package/dist/version.d.ts.map +1 -0
  249. package/dist/version.js +12 -0
  250. package/dist/version.js.map +1 -0
  251. package/package.json +72 -0
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * archcheck's public surface.
3
+ *
4
+ * What a caller must know: this is what `import ... from 'archcheck'` gets. Explicit named exports
5
+ * only — never `export *`, so a dead-code pass can find a symbol nobody consumes. Nothing here has
6
+ * behaviour of its own; it re-exports the facade and the types an agent needs to read a RunResult.
7
+ */
8
+ export { buildInitManifest, CONFIG_FILENAME, describeViolations, EXIT, exitCodeFor, inferLayers, initSummary, load, renderManifest, } from './archcheck.js';
9
+ export { BASELINE_PATH, identitiesOf, readBaseline, } from './baseline/index.js';
10
+ export { implementedKinds, } from './contracts/index.js';
11
+ export { automatablePercent, FENCE_BEGIN, FENCE_END, MARK, RULES_JSON, renderRulesTable, replaceFencedRegion, rulesDocument, } from './coverage/index.js';
12
+ export { applyExceptions, todayIso, validateExceptions, } from './exceptions/index.js';
13
+ export { createMemoryFileSystem, createNodeFileSystem, } from './fs/index.js';
14
+ export { createModelProbe, DEFAULT_BUDGET, DEFAULT_MODEL, } from './probes/model/index.js';
15
+ export { defineArchitecture } from './registry/define.js';
16
+ export { reporterFor, reporterNames, verdictFor, } from './report/index.js';
17
+ export { allOk, err, ok } from './result.js';
18
+ export { CONTRACT_KINDS, } from './types.js';
19
+ export { SCHEMA_VERSION } from './version.js';
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAGL,iBAAiB,EAEjB,eAAe,EACf,kBAAkB,EAElB,IAAI,EAEJ,WAAW,EACX,WAAW,EACX,WAAW,EAEX,IAAI,EACJ,cAAc,GACf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,aAAa,EAEb,YAAY,EACZ,YAAY,GACb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAIL,gBAAgB,GAGjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,SAAS,EACT,IAAI,EACJ,UAAU,EAGV,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,GACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEL,eAAe,EACf,QAAQ,EACR,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,sBAAsB,EACtB,oBAAoB,GAErB,MAAM,eAAe,CAAC;AACvB,OAAO,EAEL,gBAAgB,EAChB,cAAc,EACd,aAAa,GAKd,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAA6B,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAErF,OAAO,EAKL,WAAW,EACX,aAAa,EAEb,UAAU,GACX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAe,MAAM,aAAa,CAAC;AAC1D,OAAO,EAEL,cAAc,GAcf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAsB,MAAM,cAAc,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { type Result } from '../result.js';
2
+ type CandidateLayer = {
3
+ name: string;
4
+ match: string;
5
+ };
6
+ export declare function inferLayers(root: string): CandidateLayer[];
7
+ export declare function renderManifest(layers: readonly CandidateLayer[], mayImport: ReadonlyMap<string, readonly string[]>): string;
8
+ export type InitResult = {
9
+ manifest: string;
10
+ layers: readonly CandidateLayer[];
11
+ /**
12
+ * Whether the module graph could be read. When it could not, every `mayImport` is empty and the
13
+ * manifest describes nothing — the caller MUST say so. An init that quietly produces an empty
14
+ * allow-list looks like a working manifest and is not one.
15
+ */
16
+ observed: boolean;
17
+ /** Why the graph could not be read. Present only when `observed` is false. */
18
+ notObservedBecause?: string[];
19
+ };
20
+ export declare function buildInitManifest(root: string, tsconfigPath: string): Result<InitResult>;
21
+ export declare function initSummary(root: string, layers: readonly CandidateLayer[]): string[];
22
+ export {};
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/init/index.ts"],"names":[],"mappings":"AAyBA,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAkCpD,KAAK,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,EAAE,CAgB1D;AAcD,wBAAgB,cAAc,CAC5B,MAAM,EAAE,SAAS,cAAc,EAAE,EACjC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,GAChD,MAAM,CA6FR;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAClC;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB,8EAA8E;IAC9E,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAkCxF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,EAAE,CAKrF"}
@@ -0,0 +1,218 @@
1
+ /**
2
+ * `archcheck init` — write a starting manifest from the folder structure.
3
+ *
4
+ * This lives in its own layer rather than in `src/cli/`, because the CLI owns no behaviour: every
5
+ * capability has to be reachable programmatically, since the agent hook does not shell out.
6
+ * archcheck's own self-host run caught it sitting in the wrong place.
7
+ *
8
+ * Why this arrives last and not first: on a greenfield repository inference has nothing to read.
9
+ * By now archcheck has a graph reader, so it can infer the layers a codebase ALREADY has and the
10
+ * imports it ALREADY makes — which is a starting point somebody can tighten, rather than a blank
11
+ * file (plan 23).
12
+ *
13
+ * Two deliberate properties of the output:
14
+ *
15
+ * EVERY RULE IS `warn`. Day one is green. Nothing about this file is a claim that the code is
16
+ * correct — it is a description of what the code currently does, and the work is promoting rules
17
+ * to `error` one at a time, each with a fixture.
18
+ *
19
+ * `mayImport` DESCRIBES, it does not prescribe. It lists the layers each layer imports today. A
20
+ * generated manifest that forbade everything would produce hundreds of findings on the first run
21
+ * and be deleted the same afternoon.
22
+ */
23
+ import { readdirSync, statSync } from 'node:fs';
24
+ import { join } from 'node:path';
25
+ import { readGraph } from '../graph/index.js';
26
+ import { err, ok } from '../result.js';
27
+ import { repoRelative, toPosix } from '../util/paths.js';
28
+ const IGNORED = new Set(['node_modules', '.git', 'dist', 'generated', '.archcheck', 'fixtures']);
29
+ /** Directories that look like source roots, in the order a reader would expect. */
30
+ function sourceRoots(root) {
31
+ const candidates = ['src', 'packages', 'modules', 'apps', 'lib'];
32
+ return candidates.filter((name) => {
33
+ try {
34
+ return statSync(join(root, name)).isDirectory();
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ });
40
+ }
41
+ function subdirectories(root, relative) {
42
+ try {
43
+ return readdirSync(join(root, relative))
44
+ .filter((name) => !IGNORED.has(name))
45
+ .filter((name) => {
46
+ try {
47
+ return statSync(join(root, relative, name)).isDirectory();
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ })
53
+ .sort();
54
+ }
55
+ catch {
56
+ return [];
57
+ }
58
+ }
59
+ export function inferLayers(root) {
60
+ const roots = sourceRoots(root);
61
+ if (roots.length === 0)
62
+ return [];
63
+ const layers = [];
64
+ for (const sourceRoot of roots) {
65
+ const children = subdirectories(root, sourceRoot);
66
+ if (children.length === 0) {
67
+ layers.push({ name: sourceRoot, match: `${sourceRoot}/**` });
68
+ continue;
69
+ }
70
+ for (const child of children) {
71
+ layers.push({ name: child, match: `${sourceRoot}/${child}/**` });
72
+ }
73
+ }
74
+ return layers;
75
+ }
76
+ function layerOfPath(layers, relative) {
77
+ // Longest prefix wins, so a nested folder is attributed to the most specific layer.
78
+ let best;
79
+ for (const layer of layers) {
80
+ const prefix = layer.match.replace(/\*\*$/, '');
81
+ if (relative.startsWith(prefix) && (best === undefined || prefix.length > best.length)) {
82
+ best = { name: layer.name, length: prefix.length };
83
+ }
84
+ }
85
+ return best?.name;
86
+ }
87
+ export function renderManifest(layers, mayImport) {
88
+ const entries = layers.map((layer, index) => {
89
+ const allowed = [...(mayImport.get(layer.name) ?? [])].sort();
90
+ const allowedText = allowed.length === 0 ? '[]' : `[${allowed.map((n) => `'${n}'`).join(', ')}]`;
91
+ return [
92
+ ' {',
93
+ ` id: '${layer.name}-imports-only-what-it-declares',`,
94
+ " kind: 'layers',",
95
+ ` statement: '${layer.name} imports only what it declares',`,
96
+ " // Generated as 'warn'. Promote to 'error' one rule at a time, each with a fixture.",
97
+ " severity: 'warn',",
98
+ " confidence: 'deterministic',",
99
+ ' params: {',
100
+ ` name: '${layer.name}',`,
101
+ ` match: '${layer.match}',`,
102
+ ` // Observed, not prescribed: this is what ${layer.name} imports today. Remove an`,
103
+ ' // entry to tighten the rule, then fix what it finds.',
104
+ ` mayImport: ${allowedText},`,
105
+ ...(index === 0
106
+ ? [
107
+ ' // Exactly one layers rule owns the unlayered-file report.',
108
+ ' catchesUnlayered: true,',
109
+ ]
110
+ : []),
111
+ ' },',
112
+ ` remedy: { summary: 'declare the dependency, or invert the call' },`,
113
+ ' },',
114
+ ].join('\n');
115
+ });
116
+ return [
117
+ '/**',
118
+ ' * GENERATED BY `archcheck init` — then edited by you.',
119
+ ' *',
120
+ ' * This file describes the architecture your code has TODAY. Every rule is `warn`, so the',
121
+ ' * first run is green. The work is to tighten it:',
122
+ ' *',
123
+ ' * 1. Remove a `mayImport` entry that should not be there.',
124
+ ' * 2. Run `npx archcheck` and read what it finds.',
125
+ ' * 3. Fix the code, or record the debt with `npx archcheck baseline --write`.',
126
+ ' * 4. Promote that one rule to `severity: error`, and add its fixture.',
127
+ ' *',
128
+ ' * Repeat. One rule at a time is the point — a manifest switched on all at once gets reverted.',
129
+ ' */',
130
+ "import { defineArchitecture } from 'archcheck';",
131
+ '',
132
+ 'export default defineArchitecture({',
133
+ ' rules: [',
134
+ ...entries,
135
+ '',
136
+ ' // Four bans that are worth having in every repository. Tighten `appliesTo` to match',
137
+ ' // your entry points, then promote them to `error`.',
138
+ ' {',
139
+ " id: 'no-computed-dynamic-import',",
140
+ " kind: 'banned-syntax',",
141
+ " statement: 'a dynamic import names its module literally, so the graph stays complete',",
142
+ " severity: 'warn',",
143
+ " confidence: 'deterministic',",
144
+ " params: { appliesTo: '**/*.ts', selector: 'computed-dynamic-import' },",
145
+ " remedy: { summary: 'import the candidates statically and pick between them' },",
146
+ ' },',
147
+ ' {',
148
+ " id: 'no-empty-catch',",
149
+ " kind: 'banned-syntax',",
150
+ " statement: 'a caught error is handled, rethrown, or its silence is explained',",
151
+ " severity: 'warn',",
152
+ " confidence: 'deterministic',",
153
+ " params: { appliesTo: '**/*.ts', selector: 'empty-catch' },",
154
+ " remedy: { summary: 'handle it, rethrow it, or say in a comment why ignoring is safe' },",
155
+ ' },',
156
+ ' {',
157
+ " id: 'no-inline-suppression',",
158
+ " kind: 'banned-syntax',",
159
+ " statement: 'an exception lives in the manifest with an owner and an expiry, never inline',",
160
+ " severity: 'warn',",
161
+ " confidence: 'proxy',",
162
+ " params: { appliesTo: '**/*.ts', selector: 'suppression-comment' },",
163
+ " remedy: { summary: 'add an exception to this file, with an owner and an expiry' },",
164
+ ' },',
165
+ ' {',
166
+ " id: 'no-console-outside-an-entry-point',",
167
+ " kind: 'banned-syntax',",
168
+ " statement: 'logging goes through one seam, never straight to the console',",
169
+ " severity: 'warn',",
170
+ " confidence: 'deterministic',",
171
+ " params: { appliesTo: '**/*.ts', selector: 'member-access', target: 'console' },",
172
+ " remedy: { summary: 'inject a logger, or list this path as an entry point in appliesTo' },",
173
+ ' },',
174
+ ' ],',
175
+ '});',
176
+ '',
177
+ ].join('\n');
178
+ }
179
+ export function buildInitManifest(root, tsconfigPath) {
180
+ const layers = inferLayers(root);
181
+ if (layers.length === 0) {
182
+ return err([
183
+ 'found no source directory to infer layers from',
184
+ ' → archcheck init looks for src/, packages/, modules/, apps/ or lib/',
185
+ ]);
186
+ }
187
+ const mayImport = new Map();
188
+ for (const layer of layers)
189
+ mayImport.set(layer.name, new Set());
190
+ // The graph is optional here on purpose: without a tsconfig we can still name the layers. But
191
+ // "optional" must never mean "silent" — the caller is told, and says so.
192
+ const graph = readGraph({ root, tsconfigPath, cache: false });
193
+ if (graph.ok) {
194
+ for (const edge of graph.value.edges) {
195
+ if (edge.external)
196
+ continue;
197
+ const from = layerOfPath(layers, repoRelative(root, edge.from));
198
+ const to = layerOfPath(layers, repoRelative(root, edge.to));
199
+ if (from === undefined || to === undefined || from === to)
200
+ continue;
201
+ mayImport.get(from)?.add(to);
202
+ }
203
+ }
204
+ const frozen = new Map([...mayImport.entries()].map(([name, set]) => [name, [...set]]));
205
+ return ok({
206
+ manifest: renderManifest(layers, frozen),
207
+ layers,
208
+ observed: graph.ok,
209
+ ...(graph.ok ? {} : { notObservedBecause: graph.error }),
210
+ });
211
+ }
212
+ export function initSummary(root, layers) {
213
+ return [
214
+ `inferred ${layers.length} layer(s) from ${toPosix(root)}:`,
215
+ ...layers.map((layer) => ` ${layer.name.padEnd(20)} ${layer.match}`),
216
+ ];
217
+ }
218
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/init/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function canonicalJson(value: unknown): string;
2
+ export declare const IDENTITY_LENGTH = 16;
3
+ export declare function computeIdentity(ruleId: string, evidence: Record<string, unknown>): string;
4
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/normalize/identity.ts"],"names":[],"mappings":"AAgBA,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAOpD;AAED,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAKzF"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The identity hash — archcheck's contract with the future.
3
+ *
4
+ * What rule this enforces: baselining, deduplication and "did my fix work" all key off this one
5
+ * string. It is computed from the ruleId plus the SEMANTIC evidence, and it contains
6
+ * **no line, no column, and no absolute path**. Moving a function must not produce a new identity,
7
+ * or the baseline ratchet becomes noise and people stop trusting it — at which point the ratchet
8
+ * gets deleted and no rule can ever be adopted on existing code again (LLD §1.4, plan 07).
9
+ *
10
+ * What a caller must know: `canonicalJson` sorts keys at every depth, so two probes that build the
11
+ * same evidence object in a different order agree. Any path put into evidence must already be
12
+ * repo-relative POSIX — `repoRelative()` is not applied here, because silently rewriting evidence
13
+ * would hide the mistake rather than surface it.
14
+ */
15
+ import { createHash } from 'node:crypto';
16
+ export function canonicalJson(value) {
17
+ if (value === null || typeof value !== 'object')
18
+ return JSON.stringify(value) ?? 'null';
19
+ if (Array.isArray(value))
20
+ return `[${value.map(canonicalJson).join(',')}]`;
21
+ const entries = Object.entries(value)
22
+ .filter(([, v]) => v !== undefined)
23
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
24
+ return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(',')}}`;
25
+ }
26
+ export const IDENTITY_LENGTH = 16;
27
+ export function computeIdentity(ruleId, evidence) {
28
+ return createHash('sha256')
29
+ .update([ruleId, canonicalJson(evidence)].join('\u0000'))
30
+ .digest('hex')
31
+ .slice(0, IDENTITY_LENGTH);
32
+ }
33
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/normalize/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC;IACxF,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC3E,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC;SAC7D,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC;SAClC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;AAC9F,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAC;AAElC,MAAM,UAAU,eAAe,CAAC,MAAc,EAAE,QAAiC;IAC/E,OAAO,UAAU,CAAC,QAAQ,CAAC;SACxB,MAAM,CAAC,CAAC,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;SACxD,MAAM,CAAC,KAAK,CAAC;SACb,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { canonicalJson, computeIdentity, IDENTITY_LENGTH } from './identity.js';
2
+ export { type NormalizeInput, type NormalizeOptions, normalize } from './normalize.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/normalize/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { canonicalJson, computeIdentity, IDENTITY_LENGTH } from './identity.js';
2
+ export { normalize } from './normalize.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/normalize/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAA8C,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * RawFinding + Rule → Diagnostic. The single place a finding becomes something an agent can act on.
3
+ *
4
+ * What rule this enforces: the normaliser NEVER drops a finding. A finding whose ruleId is not in
5
+ * the registry is an error, not a silent skip — it means an emitted config has drifted from the
6
+ * manifest, which is precisely the failure the whole emit pipeline exists to prevent (plan 07).
7
+ *
8
+ * What a caller must know: it only ever ADDS. `Diagnostic extends RawFinding`, and nothing here
9
+ * rewrites what a probe observed, so probe output stays auditable against the diagnostic a person
10
+ * finally reads.
11
+ */
12
+ import { type Result } from '../result.js';
13
+ import type { Diagnostic, RawFinding, Remedy, Rule } from '../types.js';
14
+ export type NormalizeInput = {
15
+ findings: readonly RawFinding[];
16
+ /** The probe that produced them. Becomes `detectedBy`. */
17
+ detectedBy: string;
18
+ };
19
+ export type NormalizeOptions = {
20
+ byId(id: string): Rule | undefined;
21
+ /** Identities already recorded in the baseline. Set here, consumed by the reporters. */
22
+ baselined?: ReadonlySet<string>;
23
+ /**
24
+ * Injected per-finding sharpening, wired by the facade to the contract kinds. The normaliser
25
+ * does not know what a `layers` rule is, and must not learn — this is the seam that lets one rule
26
+ * say different things about findings that need different fixes.
27
+ */
28
+ refine?(finding: RawFinding, rule: Rule): {
29
+ statement?: string;
30
+ remedy?: Remedy;
31
+ } | undefined;
32
+ };
33
+ export declare function normalize(inputs: readonly NormalizeInput[], options: NormalizeOptions): Result<Diagnostic[]>;
34
+ //# sourceMappingURL=normalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/normalize/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAIxE,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,SAAS,UAAU,EAAE,CAAC;IAChC,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACnC,wFAAwF;IACxF,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAChC;;;;OAIG;IACH,MAAM,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,GAAG;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;CAC/F,CAAC;AA2BF,wBAAgB,SAAS,CACvB,MAAM,EAAE,SAAS,cAAc,EAAE,EACjC,OAAO,EAAE,gBAAgB,GACxB,MAAM,CAAC,UAAU,EAAE,CAAC,CAmEtB"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * RawFinding + Rule → Diagnostic. The single place a finding becomes something an agent can act on.
3
+ *
4
+ * What rule this enforces: the normaliser NEVER drops a finding. A finding whose ruleId is not in
5
+ * the registry is an error, not a silent skip — it means an emitted config has drifted from the
6
+ * manifest, which is precisely the failure the whole emit pipeline exists to prevent (plan 07).
7
+ *
8
+ * What a caller must know: it only ever ADDS. `Diagnostic extends RawFinding`, and nothing here
9
+ * rewrites what a probe observed, so probe output stays auditable against the diagnostic a person
10
+ * finally reads.
11
+ */
12
+ import { err, ok } from '../result.js';
13
+ import { SCHEMA_VERSION } from '../version.js';
14
+ import { computeIdentity } from './identity.js';
15
+ /**
16
+ * Two probes may legitimately observe the same violation — the graph sees the edge, lint sees the
17
+ * statement. Same identity, one diagnostic, `detectedBy` becomes the union.
18
+ */
19
+ function merge(existing, incoming) {
20
+ const detectedBy = [...new Set([...existing.detectedBy, ...incoming.detectedBy])].sort();
21
+ // Keep the more precise position if one probe has it and the other does not.
22
+ return {
23
+ ...existing,
24
+ detectedBy,
25
+ line: existing.line ?? incoming.line,
26
+ col: existing.col ?? incoming.col,
27
+ endLine: existing.endLine ?? incoming.endLine,
28
+ // The count is the HIGHEST any single probe saw, never the sum: two probes each seeing this
29
+ // violation once is one violation, not two ('two probes observing ONE violation is one
30
+ // occurrence, not two' in normalize.test.ts), while three empty catches reported by one probe
31
+ // really are three ('several occurrences in one file collapse to ONE diagnostic that counts
32
+ // them'). Neither number reaches `evidence`, so the identity hash is untouched.
33
+ occurrences: Math.max(existing.occurrences ?? 1, incoming.occurrences ?? 1),
34
+ occurrenceLines: [
35
+ ...new Set([...(existing.occurrenceLines ?? []), ...(incoming.occurrenceLines ?? [])]),
36
+ ].sort((a, b) => a - b),
37
+ };
38
+ }
39
+ export function normalize(inputs, options) {
40
+ const baselined = options.baselined ?? new Set();
41
+ const byIdentity = new Map();
42
+ const unknown = new Map();
43
+ /** `identity\u0000probe` → how many findings THAT probe reported for it. See `merge`. */
44
+ const seenByProbe = new Map();
45
+ for (const input of inputs) {
46
+ for (const finding of input.findings) {
47
+ const rule = options.byId(finding.ruleId);
48
+ if (!rule) {
49
+ const probes = unknown.get(finding.ruleId) ?? new Set();
50
+ probes.add(input.detectedBy);
51
+ unknown.set(finding.ruleId, probes);
52
+ continue;
53
+ }
54
+ const identity = finding.identity.length > 0
55
+ ? finding.identity
56
+ : computeIdentity(finding.ruleId, finding.evidence);
57
+ const probeKey = `${identity}\u0000${input.detectedBy}`;
58
+ const seen = (seenByProbe.get(probeKey) ?? 0) + 1;
59
+ seenByProbe.set(probeKey, seen);
60
+ const refinement = options.refine?.(finding, rule);
61
+ const diagnostic = {
62
+ ...finding,
63
+ identity,
64
+ schemaVersion: SCHEMA_VERSION,
65
+ statement: refinement?.statement ?? rule.statement,
66
+ severity: rule.severity,
67
+ confidence: rule.confidence,
68
+ remedy: refinement?.remedy ?? rule.remedy,
69
+ detectedBy: [input.detectedBy],
70
+ occurrences: seen,
71
+ occurrenceLines: finding.line === undefined ? [] : [finding.line],
72
+ baselined: baselined.has(identity),
73
+ // Set by applyExceptions() once today's date is known, which the normaliser has no
74
+ // business knowing. Debt and policy are decided in different places on purpose.
75
+ exempted: false,
76
+ };
77
+ const existing = byIdentity.get(identity);
78
+ byIdentity.set(identity, existing ? merge(existing, diagnostic) : diagnostic);
79
+ }
80
+ }
81
+ if (unknown.size > 0) {
82
+ return err([...unknown.entries()].map(([ruleId, probes]) => `${[...probes].sort().join(', ')} reported rule '${ruleId}', which the manifest does not define\n` +
83
+ ' → an emitted config has drifted from the manifest: run `archcheck emit`'));
84
+ }
85
+ // Deterministic order: by file, then by line, then by identity. A reporter must not have to sort.
86
+ const diagnostics = [...byIdentity.values()].sort((a, b) => a.file.localeCompare(b.file) ||
87
+ (a.line ?? 0) - (b.line ?? 0) ||
88
+ a.identity.localeCompare(b.identity));
89
+ return ok(diagnostics);
90
+ }
91
+ //# sourceMappingURL=normalize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/normalize/normalize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { Edge, ModuleGraph } from '../graph/types.js';
2
+ import type { Probe } from './types.js';
3
+ export declare const GRAPH_REPORT_PATH = ".archcheck/graph.json";
4
+ /** What CI writes once and every other gate reads. Edges without their AST nodes. */
5
+ export type GraphReport = {
6
+ schemaVersion: 1;
7
+ root: string;
8
+ files: string[];
9
+ edges: Omit<Edge, 'node'>[];
10
+ facts: Record<string, {
11
+ implementsPorts: string[];
12
+ exports: string[];
13
+ }>;
14
+ };
15
+ export declare function toGraphReport(graph: ModuleGraph, root: string): GraphReport;
16
+ /** A ModuleGraph restored from a report. Absolute paths again, so evaluation is identical. */
17
+ export declare function fromGraphReport(report: GraphReport, root: string): ModuleGraph;
18
+ export declare function createGraphProbe(): Probe;
19
+ //# sourceMappingURL=graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/probes/graph.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,IAAI,EAAa,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGtE,OAAO,KAAK,EAAE,KAAK,EAAgB,MAAM,YAAY,CAAC;AAEtD,eAAO,MAAM,iBAAiB,0BAA0B,CAAC;AAEzD,qFAAqF;AACrF,MAAM,MAAM,WAAW,GAAG;IACxB,aAAa,EAAE,CAAC,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,eAAe,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CACzE,CAAC;AAEF,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAwB3E;AAED,8FAA8F;AAC9F,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CA4B9E;AAmGD,wBAAgB,gBAAgB,IAAI,KAAK,CAuBxC"}