@grafema/util 0.3.0-beta

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 (324) hide show
  1. package/LICENSE +190 -0
  2. package/dist/api/GraphAPI.d.ts +87 -0
  3. package/dist/api/GraphAPI.d.ts.map +1 -0
  4. package/dist/api/GraphAPI.js +212 -0
  5. package/dist/api/GraphAPI.js.map +1 -0
  6. package/dist/api/GuaranteeAPI.d.ts +147 -0
  7. package/dist/api/GuaranteeAPI.d.ts.map +1 -0
  8. package/dist/api/GuaranteeAPI.js +290 -0
  9. package/dist/api/GuaranteeAPI.js.map +1 -0
  10. package/dist/config/ConfigLoader.d.ts +214 -0
  11. package/dist/config/ConfigLoader.d.ts.map +1 -0
  12. package/dist/config/ConfigLoader.js +441 -0
  13. package/dist/config/ConfigLoader.js.map +1 -0
  14. package/dist/config/index.d.ts +6 -0
  15. package/dist/config/index.d.ts.map +1 -0
  16. package/dist/config/index.js +5 -0
  17. package/dist/config/index.js.map +1 -0
  18. package/dist/core/CoverageAnalyzer.d.ts +65 -0
  19. package/dist/core/CoverageAnalyzer.d.ts.map +1 -0
  20. package/dist/core/CoverageAnalyzer.js +199 -0
  21. package/dist/core/CoverageAnalyzer.js.map +1 -0
  22. package/dist/core/FileExplainer.d.ts +101 -0
  23. package/dist/core/FileExplainer.d.ts.map +1 -0
  24. package/dist/core/FileExplainer.js +140 -0
  25. package/dist/core/FileExplainer.js.map +1 -0
  26. package/dist/core/FileOverview.d.ts +124 -0
  27. package/dist/core/FileOverview.d.ts.map +1 -0
  28. package/dist/core/FileOverview.js +279 -0
  29. package/dist/core/FileOverview.js.map +1 -0
  30. package/dist/core/GrafemaUri.d.ts +66 -0
  31. package/dist/core/GrafemaUri.d.ts.map +1 -0
  32. package/dist/core/GrafemaUri.js +191 -0
  33. package/dist/core/GrafemaUri.js.map +1 -0
  34. package/dist/core/GraphBackend.d.ts +158 -0
  35. package/dist/core/GraphBackend.d.ts.map +1 -0
  36. package/dist/core/GraphBackend.js +85 -0
  37. package/dist/core/GraphBackend.js.map +1 -0
  38. package/dist/core/GraphFreshnessChecker.d.ts +33 -0
  39. package/dist/core/GraphFreshnessChecker.d.ts.map +1 -0
  40. package/dist/core/GraphFreshnessChecker.js +104 -0
  41. package/dist/core/GraphFreshnessChecker.js.map +1 -0
  42. package/dist/core/GuaranteeManager.d.ts +254 -0
  43. package/dist/core/GuaranteeManager.d.ts.map +1 -0
  44. package/dist/core/GuaranteeManager.js +447 -0
  45. package/dist/core/GuaranteeManager.js.map +1 -0
  46. package/dist/core/HashUtils.d.ts +24 -0
  47. package/dist/core/HashUtils.d.ts.map +1 -0
  48. package/dist/core/HashUtils.js +46 -0
  49. package/dist/core/HashUtils.js.map +1 -0
  50. package/dist/core/IncrementalReanalyzer.d.ts +33 -0
  51. package/dist/core/IncrementalReanalyzer.d.ts.map +1 -0
  52. package/dist/core/IncrementalReanalyzer.js +67 -0
  53. package/dist/core/IncrementalReanalyzer.js.map +1 -0
  54. package/dist/core/ResourceRegistry.d.ts +17 -0
  55. package/dist/core/ResourceRegistry.d.ts.map +1 -0
  56. package/dist/core/ResourceRegistry.js +32 -0
  57. package/dist/core/ResourceRegistry.js.map +1 -0
  58. package/dist/core/SemanticId.d.ts +159 -0
  59. package/dist/core/SemanticId.d.ts.map +1 -0
  60. package/dist/core/SemanticId.js +291 -0
  61. package/dist/core/SemanticId.js.map +1 -0
  62. package/dist/core/VersionManager.d.ts +166 -0
  63. package/dist/core/VersionManager.d.ts.map +1 -0
  64. package/dist/core/VersionManager.js +239 -0
  65. package/dist/core/VersionManager.js.map +1 -0
  66. package/dist/core/brandNodeInternal.d.ts +14 -0
  67. package/dist/core/brandNodeInternal.d.ts.map +1 -0
  68. package/dist/core/brandNodeInternal.js +4 -0
  69. package/dist/core/brandNodeInternal.js.map +1 -0
  70. package/dist/core/nodes/GuaranteeNode.d.ts +76 -0
  71. package/dist/core/nodes/GuaranteeNode.d.ts.map +1 -0
  72. package/dist/core/nodes/GuaranteeNode.js +118 -0
  73. package/dist/core/nodes/GuaranteeNode.js.map +1 -0
  74. package/dist/core/nodes/IssueNode.d.ts +73 -0
  75. package/dist/core/nodes/IssueNode.d.ts.map +1 -0
  76. package/dist/core/nodes/IssueNode.js +130 -0
  77. package/dist/core/nodes/IssueNode.js.map +1 -0
  78. package/dist/core/nodes/NodeKind.d.ts +104 -0
  79. package/dist/core/nodes/NodeKind.d.ts.map +1 -0
  80. package/dist/core/nodes/NodeKind.js +166 -0
  81. package/dist/core/nodes/NodeKind.js.map +1 -0
  82. package/dist/diagnostics/DiagnosticCollector.d.ts +103 -0
  83. package/dist/diagnostics/DiagnosticCollector.d.ts.map +1 -0
  84. package/dist/diagnostics/DiagnosticCollector.js +133 -0
  85. package/dist/diagnostics/DiagnosticCollector.js.map +1 -0
  86. package/dist/diagnostics/DiagnosticReporter.d.ts +122 -0
  87. package/dist/diagnostics/DiagnosticReporter.d.ts.map +1 -0
  88. package/dist/diagnostics/DiagnosticReporter.js +300 -0
  89. package/dist/diagnostics/DiagnosticReporter.js.map +1 -0
  90. package/dist/diagnostics/DiagnosticWriter.d.ts +31 -0
  91. package/dist/diagnostics/DiagnosticWriter.d.ts.map +1 -0
  92. package/dist/diagnostics/DiagnosticWriter.js +44 -0
  93. package/dist/diagnostics/DiagnosticWriter.js.map +1 -0
  94. package/dist/diagnostics/categories.d.ts +57 -0
  95. package/dist/diagnostics/categories.d.ts.map +1 -0
  96. package/dist/diagnostics/categories.js +71 -0
  97. package/dist/diagnostics/categories.js.map +1 -0
  98. package/dist/diagnostics/index.d.ts +17 -0
  99. package/dist/diagnostics/index.d.ts.map +1 -0
  100. package/dist/diagnostics/index.js +15 -0
  101. package/dist/diagnostics/index.js.map +1 -0
  102. package/dist/errors/GrafemaError.d.ts +200 -0
  103. package/dist/errors/GrafemaError.d.ts.map +1 -0
  104. package/dist/errors/GrafemaError.js +209 -0
  105. package/dist/errors/GrafemaError.js.map +1 -0
  106. package/dist/index.d.ts +75 -0
  107. package/dist/index.d.ts.map +1 -0
  108. package/dist/index.js +76 -0
  109. package/dist/index.js.map +1 -0
  110. package/dist/instructions/index.d.ts +8 -0
  111. package/dist/instructions/index.d.ts.map +1 -0
  112. package/dist/instructions/index.js +20 -0
  113. package/dist/instructions/index.js.map +1 -0
  114. package/dist/instructions/onboarding.md +133 -0
  115. package/dist/knowledge/KnowledgeBase.d.ts +113 -0
  116. package/dist/knowledge/KnowledgeBase.d.ts.map +1 -0
  117. package/dist/knowledge/KnowledgeBase.js +420 -0
  118. package/dist/knowledge/KnowledgeBase.js.map +1 -0
  119. package/dist/knowledge/SemanticAddressResolver.d.ts +59 -0
  120. package/dist/knowledge/SemanticAddressResolver.d.ts.map +1 -0
  121. package/dist/knowledge/SemanticAddressResolver.js +160 -0
  122. package/dist/knowledge/SemanticAddressResolver.js.map +1 -0
  123. package/dist/knowledge/git-ingest.d.ts +58 -0
  124. package/dist/knowledge/git-ingest.d.ts.map +1 -0
  125. package/dist/knowledge/git-ingest.js +301 -0
  126. package/dist/knowledge/git-ingest.js.map +1 -0
  127. package/dist/knowledge/git-queries.d.ts +86 -0
  128. package/dist/knowledge/git-queries.d.ts.map +1 -0
  129. package/dist/knowledge/git-queries.js +177 -0
  130. package/dist/knowledge/git-queries.js.map +1 -0
  131. package/dist/knowledge/index.d.ts +14 -0
  132. package/dist/knowledge/index.d.ts.map +1 -0
  133. package/dist/knowledge/index.js +10 -0
  134. package/dist/knowledge/index.js.map +1 -0
  135. package/dist/knowledge/parser.d.ts +39 -0
  136. package/dist/knowledge/parser.d.ts.map +1 -0
  137. package/dist/knowledge/parser.js +292 -0
  138. package/dist/knowledge/parser.js.map +1 -0
  139. package/dist/knowledge/types.d.ts +133 -0
  140. package/dist/knowledge/types.d.ts.map +1 -0
  141. package/dist/knowledge/types.js +8 -0
  142. package/dist/knowledge/types.js.map +1 -0
  143. package/dist/logging/Logger.d.ts +98 -0
  144. package/dist/logging/Logger.d.ts.map +1 -0
  145. package/dist/logging/Logger.js +274 -0
  146. package/dist/logging/Logger.js.map +1 -0
  147. package/dist/notation/archetypes.d.ts +36 -0
  148. package/dist/notation/archetypes.d.ts.map +1 -0
  149. package/dist/notation/archetypes.js +173 -0
  150. package/dist/notation/archetypes.js.map +1 -0
  151. package/dist/notation/fold.d.ts +25 -0
  152. package/dist/notation/fold.d.ts.map +1 -0
  153. package/dist/notation/fold.js +598 -0
  154. package/dist/notation/fold.js.map +1 -0
  155. package/dist/notation/index.d.ts +18 -0
  156. package/dist/notation/index.d.ts.map +1 -0
  157. package/dist/notation/index.js +16 -0
  158. package/dist/notation/index.js.map +1 -0
  159. package/dist/notation/lodExtractor.d.ts +32 -0
  160. package/dist/notation/lodExtractor.d.ts.map +1 -0
  161. package/dist/notation/lodExtractor.js +149 -0
  162. package/dist/notation/lodExtractor.js.map +1 -0
  163. package/dist/notation/nameShortener.d.ts +22 -0
  164. package/dist/notation/nameShortener.d.ts.map +1 -0
  165. package/dist/notation/nameShortener.js +24 -0
  166. package/dist/notation/nameShortener.js.map +1 -0
  167. package/dist/notation/perspectives.d.ts +11 -0
  168. package/dist/notation/perspectives.d.ts.map +1 -0
  169. package/dist/notation/perspectives.js +16 -0
  170. package/dist/notation/perspectives.js.map +1 -0
  171. package/dist/notation/renderer.d.ts +31 -0
  172. package/dist/notation/renderer.d.ts.map +1 -0
  173. package/dist/notation/renderer.js +315 -0
  174. package/dist/notation/renderer.js.map +1 -0
  175. package/dist/notation/traceRenderer.d.ts +39 -0
  176. package/dist/notation/traceRenderer.d.ts.map +1 -0
  177. package/dist/notation/traceRenderer.js +358 -0
  178. package/dist/notation/traceRenderer.js.map +1 -0
  179. package/dist/notation/types.d.ts +66 -0
  180. package/dist/notation/types.d.ts.map +1 -0
  181. package/dist/notation/types.js +10 -0
  182. package/dist/notation/types.js.map +1 -0
  183. package/dist/queries/NodeContext.d.ts +81 -0
  184. package/dist/queries/NodeContext.d.ts.map +1 -0
  185. package/dist/queries/NodeContext.js +196 -0
  186. package/dist/queries/NodeContext.js.map +1 -0
  187. package/dist/queries/findCallsInFunction.d.ts +62 -0
  188. package/dist/queries/findCallsInFunction.d.ts.map +1 -0
  189. package/dist/queries/findCallsInFunction.js +169 -0
  190. package/dist/queries/findCallsInFunction.js.map +1 -0
  191. package/dist/queries/findContainingFunction.d.ts +57 -0
  192. package/dist/queries/findContainingFunction.d.ts.map +1 -0
  193. package/dist/queries/findContainingFunction.js +91 -0
  194. package/dist/queries/findContainingFunction.js.map +1 -0
  195. package/dist/queries/index.d.ts +18 -0
  196. package/dist/queries/index.d.ts.map +1 -0
  197. package/dist/queries/index.js +14 -0
  198. package/dist/queries/index.js.map +1 -0
  199. package/dist/queries/traceDataflow.d.ts +65 -0
  200. package/dist/queries/traceDataflow.d.ts.map +1 -0
  201. package/dist/queries/traceDataflow.js +754 -0
  202. package/dist/queries/traceDataflow.js.map +1 -0
  203. package/dist/queries/traceValues.d.ts +70 -0
  204. package/dist/queries/traceValues.d.ts.map +1 -0
  205. package/dist/queries/traceValues.js +373 -0
  206. package/dist/queries/traceValues.js.map +1 -0
  207. package/dist/queries/types.d.ts +166 -0
  208. package/dist/queries/types.d.ts.map +1 -0
  209. package/dist/queries/types.js +10 -0
  210. package/dist/queries/types.js.map +1 -0
  211. package/dist/schema/GraphSchemaExtractor.d.ts +53 -0
  212. package/dist/schema/GraphSchemaExtractor.d.ts.map +1 -0
  213. package/dist/schema/GraphSchemaExtractor.js +125 -0
  214. package/dist/schema/GraphSchemaExtractor.js.map +1 -0
  215. package/dist/schema/InterfaceSchemaExtractor.d.ts +73 -0
  216. package/dist/schema/InterfaceSchemaExtractor.d.ts.map +1 -0
  217. package/dist/schema/InterfaceSchemaExtractor.js +113 -0
  218. package/dist/schema/InterfaceSchemaExtractor.js.map +1 -0
  219. package/dist/schema/index.d.ts +5 -0
  220. package/dist/schema/index.d.ts.map +1 -0
  221. package/dist/schema/index.js +3 -0
  222. package/dist/schema/index.js.map +1 -0
  223. package/dist/storage/backends/RFDBServerBackend.d.ts +356 -0
  224. package/dist/storage/backends/RFDBServerBackend.d.ts.map +1 -0
  225. package/dist/storage/backends/RFDBServerBackend.js +748 -0
  226. package/dist/storage/backends/RFDBServerBackend.js.map +1 -0
  227. package/dist/storage/backends/typeValidation.d.ts +47 -0
  228. package/dist/storage/backends/typeValidation.d.ts.map +1 -0
  229. package/dist/storage/backends/typeValidation.js +141 -0
  230. package/dist/storage/backends/typeValidation.js.map +1 -0
  231. package/dist/utils/findRfdbBinary.d.ts +67 -0
  232. package/dist/utils/findRfdbBinary.d.ts.map +1 -0
  233. package/dist/utils/findRfdbBinary.js +261 -0
  234. package/dist/utils/findRfdbBinary.js.map +1 -0
  235. package/dist/utils/lazyDownload.d.ts +43 -0
  236. package/dist/utils/lazyDownload.d.ts.map +1 -0
  237. package/dist/utils/lazyDownload.js +175 -0
  238. package/dist/utils/lazyDownload.js.map +1 -0
  239. package/dist/utils/moduleResolution.d.ts +134 -0
  240. package/dist/utils/moduleResolution.d.ts.map +1 -0
  241. package/dist/utils/moduleResolution.js +189 -0
  242. package/dist/utils/moduleResolution.js.map +1 -0
  243. package/dist/utils/resolveNodeFile.d.ts +13 -0
  244. package/dist/utils/resolveNodeFile.d.ts.map +1 -0
  245. package/dist/utils/resolveNodeFile.js +18 -0
  246. package/dist/utils/resolveNodeFile.js.map +1 -0
  247. package/dist/utils/startRfdbServer.d.ts +63 -0
  248. package/dist/utils/startRfdbServer.d.ts.map +1 -0
  249. package/dist/utils/startRfdbServer.js +142 -0
  250. package/dist/utils/startRfdbServer.js.map +1 -0
  251. package/dist/validation/PathValidator.d.ts +80 -0
  252. package/dist/validation/PathValidator.d.ts.map +1 -0
  253. package/dist/validation/PathValidator.js +252 -0
  254. package/dist/validation/PathValidator.js.map +1 -0
  255. package/dist/version.d.ts +11 -0
  256. package/dist/version.d.ts.map +1 -0
  257. package/dist/version.js +26 -0
  258. package/dist/version.js.map +1 -0
  259. package/package.json +50 -0
  260. package/src/api/GraphAPI.ts +307 -0
  261. package/src/api/GuaranteeAPI.ts +402 -0
  262. package/src/config/ConfigLoader.ts +653 -0
  263. package/src/config/index.ts +13 -0
  264. package/src/core/CoverageAnalyzer.ts +243 -0
  265. package/src/core/FileExplainer.ts +179 -0
  266. package/src/core/FileOverview.ts +397 -0
  267. package/src/core/GrafemaUri.ts +216 -0
  268. package/src/core/GraphBackend.ts +266 -0
  269. package/src/core/GraphFreshnessChecker.ts +145 -0
  270. package/src/core/GuaranteeManager.ts +684 -0
  271. package/src/core/HashUtils.ts +48 -0
  272. package/src/core/IncrementalReanalyzer.ts +106 -0
  273. package/src/core/ResourceRegistry.ts +39 -0
  274. package/src/core/SemanticId.ts +423 -0
  275. package/src/core/VersionManager.ts +405 -0
  276. package/src/core/brandNodeInternal.ts +16 -0
  277. package/src/core/nodes/GuaranteeNode.ts +162 -0
  278. package/src/core/nodes/IssueNode.ts +177 -0
  279. package/src/core/nodes/NodeKind.ts +192 -0
  280. package/src/diagnostics/DiagnosticCollector.ts +170 -0
  281. package/src/diagnostics/DiagnosticReporter.ts +395 -0
  282. package/src/diagnostics/DiagnosticWriter.ts +50 -0
  283. package/src/diagnostics/categories.ts +104 -0
  284. package/src/diagnostics/index.ts +30 -0
  285. package/src/errors/GrafemaError.ts +297 -0
  286. package/src/index.ts +261 -0
  287. package/src/instructions/index.ts +21 -0
  288. package/src/instructions/onboarding.md +133 -0
  289. package/src/knowledge/KnowledgeBase.ts +486 -0
  290. package/src/knowledge/SemanticAddressResolver.ts +191 -0
  291. package/src/knowledge/git-ingest.ts +402 -0
  292. package/src/knowledge/git-queries.ts +269 -0
  293. package/src/knowledge/index.ts +29 -0
  294. package/src/knowledge/parser.ts +294 -0
  295. package/src/knowledge/types.ts +146 -0
  296. package/src/logging/Logger.ts +303 -0
  297. package/src/notation/archetypes.ts +189 -0
  298. package/src/notation/fold.ts +696 -0
  299. package/src/notation/index.ts +27 -0
  300. package/src/notation/lodExtractor.ts +177 -0
  301. package/src/notation/nameShortener.ts +24 -0
  302. package/src/notation/perspectives.ts +18 -0
  303. package/src/notation/renderer.ts +394 -0
  304. package/src/notation/traceRenderer.ts +458 -0
  305. package/src/notation/types.ts +79 -0
  306. package/src/queries/NodeContext.ts +280 -0
  307. package/src/queries/findCallsInFunction.ts +249 -0
  308. package/src/queries/findContainingFunction.ts +124 -0
  309. package/src/queries/index.ts +44 -0
  310. package/src/queries/traceDataflow.ts +838 -0
  311. package/src/queries/traceValues.ts +531 -0
  312. package/src/queries/types.ts +191 -0
  313. package/src/schema/GraphSchemaExtractor.ts +177 -0
  314. package/src/schema/InterfaceSchemaExtractor.ts +173 -0
  315. package/src/schema/index.ts +5 -0
  316. package/src/storage/backends/RFDBServerBackend.ts +895 -0
  317. package/src/storage/backends/typeValidation.ts +154 -0
  318. package/src/utils/findRfdbBinary.ts +288 -0
  319. package/src/utils/lazyDownload.ts +206 -0
  320. package/src/utils/moduleResolution.ts +271 -0
  321. package/src/utils/resolveNodeFile.ts +18 -0
  322. package/src/utils/startRfdbServer.ts +197 -0
  323. package/src/validation/PathValidator.ts +334 -0
  324. package/src/version.ts +28 -0
@@ -0,0 +1,653 @@
1
+ import { readFileSync, existsSync, statSync } from 'fs';
2
+ import { join, basename } from 'path';
3
+ import { parse as parseYAML } from 'yaml';
4
+ import type { ServiceDefinition, RoutingRule } from '@grafema/types';
5
+ import { GRAFEMA_VERSION, getSchemaVersion } from '../version.js';
6
+
7
+ /**
8
+ * Grafema configuration schema.
9
+ *
10
+ * YAML Location: .grafema/config.yaml (preferred) or .grafema/config.json (deprecated)
11
+ *
12
+ * Example config.yaml:
13
+ *
14
+ * ```yaml
15
+ * # Plugins for each analysis phase
16
+ * plugins:
17
+ * indexing:
18
+ * - JSModuleIndexer
19
+ * analysis:
20
+ * - CoreV2Analyzer
21
+ * - ExpressRouteAnalyzer
22
+ * enrichment:
23
+ * - ExportEntityLinker
24
+ * validation:
25
+ * - EvalBanValidator
26
+ *
27
+ * # Optional: Explicit service definitions (bypass auto-discovery)
28
+ * services:
29
+ * - name: "backend"
30
+ * path: "apps/backend" # Relative to project root
31
+ * entryPoint: "src/index.ts" # Optional, auto-detected if omitted
32
+ * - name: "frontend"
33
+ * path: "apps/frontend"
34
+ * ```
35
+ *
36
+ * If 'services' is not specified or empty, auto-discovery is used (SimpleProjectDiscovery).
37
+ * If 'services' is specified and non-empty, auto-discovery plugins are skipped entirely.
38
+ */
39
+ export interface GrafemaConfig {
40
+ /**
41
+ * Config schema version (major.minor.patch, no pre-release tag).
42
+ * Must be compatible with the running Grafema version.
43
+ * If omitted, no version check is performed (backward compatibility).
44
+ *
45
+ * @example "0.2.5"
46
+ */
47
+ version?: string;
48
+
49
+ plugins: {
50
+ discovery?: string[];
51
+ indexing: string[];
52
+ analysis: string[];
53
+ enrichment: string[];
54
+ validation: string[];
55
+ };
56
+ /**
57
+ * Optional explicit services for manual configuration.
58
+ * If provided and non-empty, auto-discovery is skipped.
59
+ */
60
+ services: ServiceDefinition[];
61
+
62
+ /**
63
+ * Glob patterns for files to include during indexing (optional).
64
+ * See OrchestratorConfig.include for documentation.
65
+ */
66
+ include?: string[];
67
+
68
+ /**
69
+ * Glob patterns for files to exclude during indexing (optional).
70
+ * See OrchestratorConfig.exclude for documentation.
71
+ */
72
+ exclude?: string[];
73
+
74
+ /**
75
+ * Routing rules for cross-service URL mapping (REG-256).
76
+ * Describes how infrastructure (nginx, gateway) transforms URLs between services.
77
+ *
78
+ * @example
79
+ * ```yaml
80
+ * routing:
81
+ * - from: frontend
82
+ * to: backend
83
+ * stripPrefix: /api
84
+ * - from: frontend
85
+ * to: auth-service
86
+ * stripPrefix: /auth
87
+ * ```
88
+ */
89
+ routing?: RoutingRule[];
90
+
91
+ /**
92
+ * Enable strict mode for fail-fast debugging.
93
+ * When true, analysis fails if enrichers cannot resolve references.
94
+ * When false (default), graceful degradation with warnings.
95
+ *
96
+ * Can be overridden via CLI: --strict
97
+ */
98
+ strict?: boolean;
99
+
100
+ /**
101
+ * Multi-root workspace configuration (REG-76).
102
+ * Allows indexing multiple directories as a single unified graph.
103
+ * Each root is prefixed in semantic IDs to prevent collisions.
104
+ *
105
+ * @example
106
+ * ```yaml
107
+ * workspace:
108
+ * roots:
109
+ * - ./backend
110
+ * - ./frontend
111
+ * - ./shared
112
+ * ```
113
+ */
114
+ workspace?: WorkspaceConfig;
115
+
116
+ /**
117
+ * URI configuration for grafema:// identifiers.
118
+ * Used by MCP and CLI for compact<->URI conversion.
119
+ */
120
+ uri?: {
121
+ /** URI authority (e.g., "github.com/owner/repo"). Auto-detected if omitted. */
122
+ authority?: string;
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Multi-root workspace configuration.
128
+ * Each root directory is indexed separately but produces a unified graph.
129
+ * Root names (basename of path) are prefixed to file paths in semantic IDs.
130
+ */
131
+ export interface WorkspaceConfig {
132
+ /**
133
+ * List of root directories to include in the workspace.
134
+ * Paths are relative to the project root (where .grafema/ is located).
135
+ * Each root's basename is used as prefix in semantic IDs.
136
+ */
137
+ roots: string[];
138
+ }
139
+
140
+ /**
141
+ * Default plugin configuration.
142
+ * Matches current DEFAULT_PLUGINS in analyze.ts and config.ts (MCP).
143
+ */
144
+ export const DEFAULT_CONFIG: GrafemaConfig = {
145
+ version: getSchemaVersion(GRAFEMA_VERSION),
146
+ plugins: {
147
+ discovery: [],
148
+ indexing: ['JSModuleIndexer'],
149
+ analysis: [
150
+ 'CoreV2Analyzer',
151
+ 'ExpressRouteAnalyzer',
152
+ 'ExpressResponseAnalyzer',
153
+ 'NestJSRouteAnalyzer',
154
+ 'SocketIOAnalyzer',
155
+ 'DatabaseAnalyzer',
156
+ 'FetchAnalyzer',
157
+ 'ServiceLayerAnalyzer',
158
+ ],
159
+ enrichment: [
160
+ 'ExportEntityLinker',
161
+ 'CallbackCallResolver',
162
+ 'RejectionPropagationEnricher',
163
+ 'ValueDomainAnalyzer',
164
+ 'MountPointResolver',
165
+ 'ExpressHandlerLinker',
166
+ 'PrefixEvaluator',
167
+ 'ConfigRoutingMapBuilder',
168
+ 'ServiceConnectionEnricher',
169
+ 'RedisEnricher',
170
+ ],
171
+ validation: [
172
+ 'GraphConnectivityValidator',
173
+ 'DataFlowValidator',
174
+ 'EvalBanValidator',
175
+ 'CallResolverValidator',
176
+ 'SQLInjectionValidator',
177
+ 'AwaitInLoopValidator',
178
+ 'ShadowingDetector',
179
+ 'BrokenImportValidator',
180
+ 'UnconnectedRouteValidator',
181
+ 'PackageCoverageValidator',
182
+ ],
183
+ },
184
+ services: [], // Empty by default (uses auto-discovery)
185
+ strict: false, // Graceful degradation by default
186
+ };
187
+
188
+ /**
189
+ * Load Grafema config from project directory.
190
+ *
191
+ * Priority:
192
+ * 1. config.yaml (preferred)
193
+ * 2. config.json (deprecated, fallback)
194
+ * 3. DEFAULT_CONFIG (if neither exists)
195
+ *
196
+ * Warnings:
197
+ * - Logs deprecation warning if config.json is used
198
+ * - Logs parse errors but doesn't throw (returns defaults)
199
+ *
200
+ * @param projectPath - Absolute path to project root
201
+ * @param logger - Optional logger for warnings (defaults to console.warn)
202
+ * @returns Parsed config or defaults
203
+ */
204
+ export function loadConfig(
205
+ projectPath: string,
206
+ logger: { warn: (msg: string) => void } = console
207
+ ): GrafemaConfig {
208
+ const grafemaDir = join(projectPath, '.grafema');
209
+ const yamlPath = join(grafemaDir, 'config.yaml');
210
+ const jsonPath = join(grafemaDir, 'config.json');
211
+
212
+ // 1. Try YAML first (preferred)
213
+ if (existsSync(yamlPath)) {
214
+ let parsed: Partial<GrafemaConfig>;
215
+
216
+ try {
217
+ const content = readFileSync(yamlPath, 'utf-8');
218
+ parsed = parseYAML(content) as Partial<GrafemaConfig>;
219
+
220
+ // Validate structure - ensure plugins sections are arrays if they exist
221
+ if (parsed.plugins) {
222
+ for (const phase of ['discovery', 'indexing', 'analysis', 'enrichment', 'validation'] as const) {
223
+ const value = parsed.plugins[phase];
224
+ if (value !== undefined && value !== null && !Array.isArray(value)) {
225
+ throw new Error(`plugins.${phase} must be an array, got ${typeof value}`);
226
+ }
227
+ }
228
+ }
229
+ } catch (err) {
230
+ const error = err instanceof Error ? err : new Error(String(err));
231
+ logger.warn(`Failed to parse config.yaml: ${error.message}`);
232
+ logger.warn('Using default configuration');
233
+ return DEFAULT_CONFIG;
234
+ }
235
+
236
+ // Validate version compatibility (THROWS on error) - REG-403
237
+ validateVersion(parsed.version);
238
+
239
+ // Validate services array if present (THROWS on error per Linus review)
240
+ // This is OUTSIDE try-catch - config errors MUST throw
241
+ validateServices(parsed.services, projectPath);
242
+
243
+ // Validate include/exclude patterns (THROWS on error)
244
+ validatePatterns(parsed.include, parsed.exclude, logger);
245
+
246
+ // Validate workspace.roots if present (THROWS on error) - REG-76
247
+ validateWorkspace(parsed.workspace, projectPath);
248
+
249
+ // Validate routing rules if present (THROWS on error) - REG-256
250
+ validateRouting(parsed.routing, (parsed.services || []) as ServiceDefinition[]);
251
+
252
+ // Merge with defaults (user config may be partial)
253
+ return mergeConfig(DEFAULT_CONFIG, parsed);
254
+ }
255
+
256
+ // 2. Fallback to JSON (migration path)
257
+ if (existsSync(jsonPath)) {
258
+ logger.warn('⚠ config.json is deprecated. Run "grafema init --force" to migrate to config.yaml');
259
+
260
+ let parsed: Partial<GrafemaConfig>;
261
+
262
+ try {
263
+ const content = readFileSync(jsonPath, 'utf-8');
264
+ parsed = JSON.parse(content) as Partial<GrafemaConfig>;
265
+ } catch (err) {
266
+ const error = err instanceof Error ? err : new Error(String(err));
267
+ logger.warn(`Failed to parse config.json: ${error.message}`);
268
+ logger.warn('Using default configuration');
269
+ return DEFAULT_CONFIG;
270
+ }
271
+
272
+ // Validate version compatibility (THROWS on error) - REG-403
273
+ validateVersion(parsed.version);
274
+
275
+ // Validate services array if present (THROWS on error)
276
+ // This is OUTSIDE try-catch - config errors MUST throw
277
+ validateServices(parsed.services, projectPath);
278
+
279
+ // Validate include/exclude patterns (THROWS on error)
280
+ validatePatterns(parsed.include, parsed.exclude, logger);
281
+
282
+ // Validate workspace.roots if present (THROWS on error) - REG-76
283
+ validateWorkspace(parsed.workspace, projectPath);
284
+
285
+ // Validate routing rules if present (THROWS on error) - REG-256
286
+ validateRouting(parsed.routing, (parsed.services || []) as ServiceDefinition[]);
287
+
288
+ return mergeConfig(DEFAULT_CONFIG, parsed);
289
+ }
290
+
291
+ // 3. No config file - return defaults
292
+ return DEFAULT_CONFIG;
293
+ }
294
+
295
+ /**
296
+ * Validate config version compatibility with running Grafema version.
297
+ * THROWS on error (fail loudly per project convention).
298
+ *
299
+ * Compares major.minor.patch (pre-release tags are stripped).
300
+ * If config has no version field, validation passes silently (backward compat).
301
+ *
302
+ * @param configVersion - Version string from config file (may be undefined)
303
+ * @param currentVersion - Override for testing (defaults to GRAFEMA_VERSION)
304
+ */
305
+ export function validateVersion(
306
+ configVersion: unknown,
307
+ currentVersion?: string
308
+ ): void {
309
+ // No version field = backward compat, accept silently
310
+ if (configVersion === undefined || configVersion === null) {
311
+ return;
312
+ }
313
+
314
+ if (typeof configVersion !== 'string') {
315
+ throw new Error(`Config error: version must be a string, got ${typeof configVersion}`);
316
+ }
317
+
318
+ if (!configVersion.trim()) {
319
+ throw new Error('Config error: version cannot be empty');
320
+ }
321
+
322
+ const current = currentVersion ?? GRAFEMA_VERSION;
323
+ const configSchema = getSchemaVersion(configVersion);
324
+ const currentSchema = getSchemaVersion(current);
325
+
326
+ if (configSchema !== currentSchema) {
327
+ throw new Error(
328
+ `Config error: config version "${configVersion}" is not compatible with ` +
329
+ `Grafema ${current}. Expected "${currentSchema}".\n` +
330
+ ` Run: grafema init --force (to regenerate config for current version)`
331
+ );
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Validate services array structure.
337
+ * THROWS on error (fail loudly per Linus review).
338
+ *
339
+ * @param services - Parsed services array (may be undefined)
340
+ * @param projectPath - Project root for path validation
341
+ */
342
+ export function validateServices(services: unknown, projectPath: string): void {
343
+ // undefined/null is valid (means use defaults)
344
+ if (services === undefined || services === null) {
345
+ return;
346
+ }
347
+
348
+ // Must be an array
349
+ if (!Array.isArray(services)) {
350
+ throw new Error(`Config error: services must be an array, got ${typeof services}`);
351
+ }
352
+
353
+ // Validate each service
354
+ for (let i = 0; i < services.length; i++) {
355
+ const svc = services[i];
356
+
357
+ // Must be an object
358
+ if (typeof svc !== 'object' || svc === null) {
359
+ throw new Error(`Config error: services[${i}] must be an object`);
360
+ }
361
+
362
+ // Name validation - required, non-empty string
363
+ if (typeof svc.name !== 'string') {
364
+ throw new Error(`Config error: services[${i}].name must be a string, got ${typeof svc.name}`);
365
+ }
366
+ if (!svc.name.trim()) {
367
+ throw new Error(`Config error: services[${i}].name cannot be empty or whitespace-only`);
368
+ }
369
+
370
+ // Path validation - required, non-empty string
371
+ if (typeof svc.path !== 'string') {
372
+ throw new Error(`Config error: services[${i}].path must be a string, got ${typeof svc.path}`);
373
+ }
374
+ if (!svc.path.trim()) {
375
+ throw new Error(`Config error: services[${i}].path cannot be empty or whitespace-only`);
376
+ }
377
+
378
+ // Path validation - must be relative (reject absolute paths per Linus review)
379
+ if (svc.path.startsWith('/') || svc.path.startsWith('~')) {
380
+ throw new Error(`Config error: services[${i}].path must be relative to project root, got "${svc.path}"`);
381
+ }
382
+
383
+ // Path validation - must exist
384
+ const absolutePath = join(projectPath, svc.path);
385
+ if (!existsSync(absolutePath)) {
386
+ throw new Error(`Config error: services[${i}].path "${svc.path}" does not exist`);
387
+ }
388
+
389
+ // Path validation - must be directory
390
+ if (!statSync(absolutePath).isDirectory()) {
391
+ throw new Error(`Config error: services[${i}].path "${svc.path}" must be a directory`);
392
+ }
393
+
394
+ // entryPoint validation (optional field) - must be non-empty string if provided
395
+ if (svc.entryPoint !== undefined) {
396
+ if (typeof svc.entryPoint !== 'string') {
397
+ throw new Error(`Config error: services[${i}].entryPoint must be a string, got ${typeof svc.entryPoint}`);
398
+ }
399
+ if (!svc.entryPoint.trim()) {
400
+ throw new Error(`Config error: services[${i}].entryPoint cannot be empty or whitespace-only`);
401
+ }
402
+ }
403
+
404
+ // customerFacing validation (optional field) - must be boolean if provided (REG-256)
405
+ if (svc.customerFacing !== undefined) {
406
+ if (typeof svc.customerFacing !== 'boolean') {
407
+ throw new Error(`Config error: services[${i}].customerFacing must be a boolean, got ${typeof svc.customerFacing}`);
408
+ }
409
+ }
410
+ }
411
+ }
412
+
413
+ /**
414
+ * Validate workspace configuration (REG-76).
415
+ * THROWS on error (fail loudly per project convention).
416
+ *
417
+ * Validation rules:
418
+ * 1. workspace.roots must be an array if provided
419
+ * 2. Each root must be a non-empty string
420
+ * 3. Each root path must exist and be a directory
421
+ * 4. Root basenames must be unique (to prevent semantic ID collisions)
422
+ *
423
+ * @param workspace - Parsed workspace config (may be undefined)
424
+ * @param projectPath - Project root for path validation
425
+ */
426
+ export function validateWorkspace(workspace: unknown, projectPath: string): void {
427
+ // undefined/null is valid (means single-root mode)
428
+ if (workspace === undefined || workspace === null) {
429
+ return;
430
+ }
431
+
432
+ // Must be an object
433
+ if (typeof workspace !== 'object') {
434
+ throw new Error(`Config error: workspace must be an object, got ${typeof workspace}`);
435
+ }
436
+
437
+ const ws = workspace as { roots?: unknown };
438
+
439
+ // roots is optional, but if provided must be valid
440
+ if (ws.roots === undefined || ws.roots === null) {
441
+ return;
442
+ }
443
+
444
+ // roots must be an array
445
+ if (!Array.isArray(ws.roots)) {
446
+ throw new Error(`Config error: workspace.roots must be an array, got ${typeof ws.roots}`);
447
+ }
448
+
449
+ // Track root names for duplicate detection
450
+ const seenNames = new Set<string>();
451
+
452
+ // Validate each root
453
+ for (let i = 0; i < ws.roots.length; i++) {
454
+ const root = ws.roots[i];
455
+
456
+ // Must be a string
457
+ if (typeof root !== 'string') {
458
+ throw new Error(`Config error: workspace.roots[${i}] must be a string, got ${typeof root}`);
459
+ }
460
+
461
+ // Must not be empty
462
+ if (!root.trim()) {
463
+ throw new Error(`Config error: workspace.roots[${i}] cannot be empty or whitespace-only`);
464
+ }
465
+
466
+ // Must be relative path
467
+ if (root.startsWith('/') || root.startsWith('~')) {
468
+ throw new Error(`Config error: workspace.roots[${i}] must be relative to project root, got "${root}"`);
469
+ }
470
+
471
+ // Path must exist
472
+ const absolutePath = join(projectPath, root);
473
+ if (!existsSync(absolutePath)) {
474
+ throw new Error(`Config error: workspace.roots[${i}] "${root}" does not exist`);
475
+ }
476
+
477
+ // Path must be a directory
478
+ if (!statSync(absolutePath).isDirectory()) {
479
+ throw new Error(`Config error: workspace.roots[${i}] "${root}" must be a directory`);
480
+ }
481
+
482
+ // Check for duplicate root names (basename)
483
+ const rootName = basename(root);
484
+ if (seenNames.has(rootName)) {
485
+ throw new Error(`Config error: Duplicate workspace root name "${rootName}" - root names (basenames) must be unique`);
486
+ }
487
+ seenNames.add(rootName);
488
+ }
489
+ }
490
+
491
+ /**
492
+ * Validate routing rules structure (REG-256).
493
+ * THROWS on error (fail loudly per project convention).
494
+ *
495
+ * Validation rules:
496
+ * 1. Must be an array if provided
497
+ * 2. Each rule must have 'from' and 'to' as non-empty strings
498
+ * 3. 'stripPrefix' must start with '/' if provided
499
+ * 4. 'addPrefix' must start with '/' if provided
500
+ * 5. 'from' and 'to' must reference services defined in the services array
501
+ *
502
+ * @param routing - Parsed routing rules (may be undefined)
503
+ * @param services - Parsed services array (for cross-validation)
504
+ */
505
+ export function validateRouting(routing: unknown, services: ServiceDefinition[]): void {
506
+ if (routing === undefined || routing === null) return;
507
+
508
+ if (!Array.isArray(routing)) {
509
+ throw new Error(`Config error: routing must be an array, got ${typeof routing}`);
510
+ }
511
+
512
+ const serviceNames = new Set(services.map(s => s.name));
513
+
514
+ for (let i = 0; i < routing.length; i++) {
515
+ const rule = routing[i];
516
+
517
+ if (typeof rule !== 'object' || rule === null) {
518
+ throw new Error(`Config error: routing[${i}] must be an object`);
519
+ }
520
+
521
+ // from — required
522
+ if (typeof rule.from !== 'string' || !rule.from.trim()) {
523
+ throw new Error(`Config error: routing[${i}].from must be a non-empty string`);
524
+ }
525
+
526
+ // to — required
527
+ if (typeof rule.to !== 'string' || !rule.to.trim()) {
528
+ throw new Error(`Config error: routing[${i}].to must be a non-empty string`);
529
+ }
530
+
531
+ // Cross-validate against services (only if services are defined)
532
+ if (serviceNames.size > 0) {
533
+ if (!serviceNames.has(rule.from)) {
534
+ throw new Error(
535
+ `Config error: routing[${i}].from "${rule.from}" does not match any service name. ` +
536
+ `Available: ${[...serviceNames].join(', ')}`
537
+ );
538
+ }
539
+ if (!serviceNames.has(rule.to)) {
540
+ throw new Error(
541
+ `Config error: routing[${i}].to "${rule.to}" does not match any service name. ` +
542
+ `Available: ${[...serviceNames].join(', ')}`
543
+ );
544
+ }
545
+ }
546
+
547
+ // stripPrefix — optional, must start with /
548
+ if (rule.stripPrefix !== undefined) {
549
+ if (typeof rule.stripPrefix !== 'string') {
550
+ throw new Error(`Config error: routing[${i}].stripPrefix must be a string`);
551
+ }
552
+ if (!rule.stripPrefix.startsWith('/')) {
553
+ throw new Error(`Config error: routing[${i}].stripPrefix must start with '/'`);
554
+ }
555
+ }
556
+
557
+ // addPrefix — optional, must start with /
558
+ if (rule.addPrefix !== undefined) {
559
+ if (typeof rule.addPrefix !== 'string') {
560
+ throw new Error(`Config error: routing[${i}].addPrefix must be a string`);
561
+ }
562
+ if (!rule.addPrefix.startsWith('/')) {
563
+ throw new Error(`Config error: routing[${i}].addPrefix must start with '/'`);
564
+ }
565
+ }
566
+ }
567
+ }
568
+
569
+ /**
570
+ * Validate include/exclude patterns.
571
+ * THROWS on error (fail loudly per project convention).
572
+ *
573
+ * Validation rules:
574
+ * 1. Must be arrays if provided
575
+ * 2. Array elements must be non-empty strings
576
+ * 3. Warn (don't error) if include array is empty (would exclude everything)
577
+ *
578
+ * @param include - Parsed include patterns (may be undefined)
579
+ * @param exclude - Parsed exclude patterns (may be undefined)
580
+ * @param logger - Logger for warnings
581
+ */
582
+ export function validatePatterns(
583
+ include: unknown,
584
+ exclude: unknown,
585
+ logger: { warn: (msg: string) => void }
586
+ ): void {
587
+ // Validate include
588
+ if (include !== undefined && include !== null) {
589
+ if (!Array.isArray(include)) {
590
+ throw new Error(`Config error: include must be an array, got ${typeof include}`);
591
+ }
592
+ for (let i = 0; i < include.length; i++) {
593
+ if (typeof include[i] !== 'string') {
594
+ throw new Error(`Config error: include[${i}] must be a string, got ${typeof include[i]}`);
595
+ }
596
+ if (!include[i].trim()) {
597
+ throw new Error(`Config error: include[${i}] cannot be empty or whitespace-only`);
598
+ }
599
+ }
600
+ // Warn if empty array (would exclude everything)
601
+ if (include.length === 0) {
602
+ logger.warn('Warning: include is an empty array - no files will be processed');
603
+ }
604
+ }
605
+
606
+ // Validate exclude
607
+ if (exclude !== undefined && exclude !== null) {
608
+ if (!Array.isArray(exclude)) {
609
+ throw new Error(`Config error: exclude must be an array, got ${typeof exclude}`);
610
+ }
611
+ for (let i = 0; i < exclude.length; i++) {
612
+ if (typeof exclude[i] !== 'string') {
613
+ throw new Error(`Config error: exclude[${i}] must be a string, got ${typeof exclude[i]}`);
614
+ }
615
+ if (!exclude[i].trim()) {
616
+ throw new Error(`Config error: exclude[${i}] cannot be empty or whitespace-only`);
617
+ }
618
+ }
619
+ }
620
+ }
621
+
622
+ /**
623
+ * Merge user config with defaults.
624
+ * User config takes precedence, but missing sections use defaults.
625
+ */
626
+ function mergeConfig(
627
+ defaults: GrafemaConfig,
628
+ user: Partial<GrafemaConfig>
629
+ ): GrafemaConfig {
630
+ return {
631
+ version: user.version ?? defaults.version,
632
+ plugins: {
633
+ discovery: user.plugins?.discovery ?? defaults.plugins.discovery,
634
+ indexing: user.plugins?.indexing ?? defaults.plugins.indexing,
635
+ analysis: user.plugins?.analysis ?? defaults.plugins.analysis,
636
+ enrichment: user.plugins?.enrichment ?? defaults.plugins.enrichment,
637
+ validation: user.plugins?.validation ?? defaults.plugins.validation,
638
+ },
639
+ services: user.services ?? defaults.services,
640
+ // Include/exclude patterns: pass through if specified, otherwise undefined
641
+ // (don't merge with defaults - undefined means "no filtering")
642
+ // Note: YAML null becomes undefined here (null ?? undefined = undefined)
643
+ include: user.include ?? undefined,
644
+ exclude: user.exclude ?? undefined,
645
+ strict: user.strict ?? defaults.strict,
646
+ // Routing rules: pass through if specified (REG-256)
647
+ routing: user.routing ?? undefined,
648
+ // Workspace config: pass through if specified (REG-76)
649
+ workspace: user.workspace ?? undefined,
650
+ // URI config: pass through if specified (REG-666)
651
+ uri: user.uri ?? undefined,
652
+ };
653
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Configuration loading utilities
3
+ */
4
+ export {
5
+ loadConfig,
6
+ DEFAULT_CONFIG,
7
+ validateVersion,
8
+ validateServices,
9
+ validatePatterns,
10
+ validateWorkspace,
11
+ validateRouting,
12
+ } from './ConfigLoader.js';
13
+ export type { GrafemaConfig } from './ConfigLoader.js';