@documentation-robotics/cli 0.1.0 → 0.1.5

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 (510) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +466 -693
  3. package/dist/analysis/data-model-analyzer.js +189 -0
  4. package/dist/analysis/data-model-analyzer.js.map +7 -0
  5. package/dist/analysis/index.js +3 -0
  6. package/dist/analysis/index.js.map +7 -0
  7. package/dist/analysis/relationship-classifier.js +181 -0
  8. package/dist/analysis/relationship-classifier.js.map +7 -0
  9. package/dist/analyzers/base-analyzer.js +1 -0
  10. package/dist/analyzers/base-analyzer.js.map +7 -0
  11. package/dist/analyzers/call-graph-utils.js +39 -0
  12. package/dist/analyzers/call-graph-utils.js.map +7 -0
  13. package/dist/analyzers/cbm-analyzer.js +1205 -0
  14. package/dist/analyzers/cbm-analyzer.js.map +7 -0
  15. package/dist/analyzers/heuristic-utils.js +55 -0
  16. package/dist/analyzers/heuristic-utils.js.map +7 -0
  17. package/dist/analyzers/mapping-loader.js +146 -0
  18. package/dist/analyzers/mapping-loader.js.map +7 -0
  19. package/dist/analyzers/registry.js +124 -0
  20. package/dist/analyzers/registry.js.map +7 -0
  21. package/dist/analyzers/session-state.js +176 -0
  22. package/dist/analyzers/session-state.js.map +7 -0
  23. package/dist/analyzers/stdio-client.js +206 -0
  24. package/dist/analyzers/stdio-client.js.map +7 -0
  25. package/dist/analyzers/types.js +15 -0
  26. package/dist/analyzers/types.js.map +7 -0
  27. package/dist/analyzers/verify-engine.js +209 -0
  28. package/dist/analyzers/verify-engine.js.map +7 -0
  29. package/dist/analyzers/verify-ignore.js +130 -0
  30. package/dist/analyzers/verify-ignore.js.map +7 -0
  31. package/dist/analyzers/version.js +8 -0
  32. package/dist/analyzers/version.js.map +7 -0
  33. package/dist/audit/ai/runner.js +41 -0
  34. package/dist/audit/ai/runner.js.map +7 -0
  35. package/dist/audit/differential-analyzer.js +183 -0
  36. package/dist/audit/differential-analyzer.js.map +7 -0
  37. package/dist/audit/index.js +32 -0
  38. package/dist/audit/index.js.map +7 -0
  39. package/dist/audit/nodes/ai/evaluator.js +49 -0
  40. package/dist/audit/nodes/ai/evaluator.js.map +7 -0
  41. package/dist/audit/nodes/ai/parser.js +55 -0
  42. package/dist/audit/nodes/ai/parser.js.map +7 -0
  43. package/dist/audit/nodes/ai/prompts.js +103 -0
  44. package/dist/audit/nodes/ai/prompts.js.map +7 -0
  45. package/dist/audit/nodes/analysis/node-completeness-checker.js +32 -0
  46. package/dist/audit/nodes/analysis/node-completeness-checker.js.map +7 -0
  47. package/dist/audit/nodes/analysis/node-definition-analyzer.js +47 -0
  48. package/dist/audit/nodes/analysis/node-definition-analyzer.js.map +7 -0
  49. package/dist/audit/nodes/analysis/node-overlap-detector.js +79 -0
  50. package/dist/audit/nodes/analysis/node-overlap-detector.js.map +7 -0
  51. package/dist/audit/nodes/model/orchestrator.js +82 -0
  52. package/dist/audit/nodes/model/orchestrator.js.map +7 -0
  53. package/dist/audit/nodes/spec/loader.js +116 -0
  54. package/dist/audit/nodes/spec/loader.js.map +7 -0
  55. package/dist/audit/nodes/spec/orchestrator.js +68 -0
  56. package/dist/audit/nodes/spec/orchestrator.js.map +7 -0
  57. package/dist/audit/nodes/types.js +1 -0
  58. package/dist/audit/nodes/types.js.map +7 -0
  59. package/dist/audit/pipeline/index.js +5 -0
  60. package/dist/audit/pipeline/index.js.map +7 -0
  61. package/dist/audit/pipeline/pipeline-orchestrator.js +218 -0
  62. package/dist/audit/pipeline/pipeline-orchestrator.js.map +7 -0
  63. package/dist/audit/relationships/ai/evaluator.js +235 -0
  64. package/dist/audit/relationships/ai/evaluator.js.map +7 -0
  65. package/dist/audit/relationships/ai/parser.js +197 -0
  66. package/dist/audit/relationships/ai/parser.js.map +7 -0
  67. package/dist/audit/relationships/ai/prompts.js +274 -0
  68. package/dist/audit/relationships/ai/prompts.js.map +7 -0
  69. package/dist/audit/relationships/analysis/balance-assessor.js +115 -0
  70. package/dist/audit/relationships/analysis/balance-assessor.js.map +7 -0
  71. package/dist/audit/relationships/analysis/coverage-analyzer.js +99 -0
  72. package/dist/audit/relationships/analysis/coverage-analyzer.js.map +7 -0
  73. package/dist/audit/relationships/analysis/duplicate-detector.js +95 -0
  74. package/dist/audit/relationships/analysis/duplicate-detector.js.map +7 -0
  75. package/dist/audit/relationships/analysis/gap-analyzer.js +99 -0
  76. package/dist/audit/relationships/analysis/gap-analyzer.js.map +7 -0
  77. package/dist/audit/relationships/analysis/layer-templates.js +294 -0
  78. package/dist/audit/relationships/analysis/layer-templates.js.map +7 -0
  79. package/dist/audit/relationships/graph/connectivity.js +129 -0
  80. package/dist/audit/relationships/graph/connectivity.js.map +7 -0
  81. package/dist/audit/relationships/graph/relationship-graph.js +179 -0
  82. package/dist/audit/relationships/graph/relationship-graph.js.map +7 -0
  83. package/dist/audit/relationships/model/balance-assessor.js +49 -0
  84. package/dist/audit/relationships/model/balance-assessor.js.map +7 -0
  85. package/dist/audit/relationships/model/coverage-analyzer.js +34 -0
  86. package/dist/audit/relationships/model/coverage-analyzer.js.map +7 -0
  87. package/dist/audit/relationships/model/duplicate-detector.js +68 -0
  88. package/dist/audit/relationships/model/duplicate-detector.js.map +7 -0
  89. package/dist/audit/relationships/model/gap-analyzer.js +41 -0
  90. package/dist/audit/relationships/model/gap-analyzer.js.map +7 -0
  91. package/dist/audit/relationships/model/orchestrator.js +126 -0
  92. package/dist/audit/relationships/model/orchestrator.js.map +7 -0
  93. package/dist/audit/relationships/spec/orchestrator.js +157 -0
  94. package/dist/audit/relationships/spec/orchestrator.js.map +7 -0
  95. package/dist/audit/reports/index.js +5 -0
  96. package/dist/audit/reports/index.js.map +7 -0
  97. package/dist/audit/reports/report-generator.js +118 -0
  98. package/dist/audit/reports/report-generator.js.map +7 -0
  99. package/dist/audit/snapshot-storage.js +163 -0
  100. package/dist/audit/snapshot-storage.js.map +7 -0
  101. package/dist/audit/types.js +21 -0
  102. package/dist/audit/types.js.map +7 -0
  103. package/dist/cli.js +223 -92
  104. package/dist/cli.js.map +3 -3
  105. package/dist/coding-agents/agents/claude-code.js +147 -0
  106. package/dist/coding-agents/agents/claude-code.js.map +7 -0
  107. package/dist/coding-agents/agents/index.js +5 -0
  108. package/dist/coding-agents/agents/index.js.map +7 -0
  109. package/dist/coding-agents/agents/types.js +1 -0
  110. package/dist/coding-agents/agents/types.js.map +7 -0
  111. package/dist/coding-agents/base-chat-client.js +45 -0
  112. package/dist/coding-agents/base-chat-client.js.map +7 -0
  113. package/dist/coding-agents/chat-utils.js +23 -0
  114. package/dist/coding-agents/chat-utils.js.map +7 -0
  115. package/dist/coding-agents/claude-code-client.js +246 -0
  116. package/dist/coding-agents/claude-code-client.js.map +7 -0
  117. package/dist/coding-agents/copilot-client.js +209 -0
  118. package/dist/coding-agents/copilot-client.js.map +7 -0
  119. package/dist/commands/add.js +95 -33
  120. package/dist/commands/add.js.map +2 -2
  121. package/dist/commands/analyzer.js +511 -0
  122. package/dist/commands/analyzer.js.map +7 -0
  123. package/dist/commands/audit-diff.js +49 -0
  124. package/dist/commands/audit-diff.js.map +7 -0
  125. package/dist/commands/audit-snapshots.js +62 -0
  126. package/dist/commands/audit-snapshots.js.map +7 -0
  127. package/dist/commands/audit.js +81 -0
  128. package/dist/commands/audit.js.map +7 -0
  129. package/dist/commands/catalog.js +248 -0
  130. package/dist/commands/catalog.js.map +7 -0
  131. package/dist/commands/changeset.js +577 -66
  132. package/dist/commands/changeset.js.map +2 -2
  133. package/dist/commands/chat.js +106 -83
  134. package/dist/commands/chat.js.map +2 -2
  135. package/dist/commands/claude.js +74 -0
  136. package/dist/commands/claude.js.map +7 -0
  137. package/dist/commands/conformance.js +71 -68
  138. package/dist/commands/conformance.js.map +2 -2
  139. package/dist/commands/copilot.js +75 -0
  140. package/dist/commands/copilot.js.map +7 -0
  141. package/dist/commands/delete.js +125 -11
  142. package/dist/commands/delete.js.map +3 -3
  143. package/dist/commands/discover-logic.js +44 -0
  144. package/dist/commands/discover-logic.js.map +7 -0
  145. package/dist/commands/docs.js +87 -0
  146. package/dist/commands/docs.js.map +7 -0
  147. package/dist/commands/element.js +14 -20
  148. package/dist/commands/element.js.map +2 -2
  149. package/dist/commands/export.js +13 -3
  150. package/dist/commands/export.js.map +2 -2
  151. package/dist/commands/import.js +51 -0
  152. package/dist/commands/import.js.map +7 -0
  153. package/dist/commands/info.js +30 -24
  154. package/dist/commands/info.js.map +2 -2
  155. package/dist/commands/init.js +27 -11
  156. package/dist/commands/init.js.map +2 -2
  157. package/dist/commands/list.js +63 -13
  158. package/dist/commands/list.js.map +3 -3
  159. package/dist/commands/relationship.js +225 -37
  160. package/dist/commands/relationship.js.map +2 -2
  161. package/dist/commands/report.js +54 -0
  162. package/dist/commands/report.js.map +7 -0
  163. package/dist/commands/reports.js +45 -0
  164. package/dist/commands/reports.js.map +7 -0
  165. package/dist/commands/schema.js +135 -0
  166. package/dist/commands/schema.js.map +7 -0
  167. package/dist/commands/search.js +40 -12
  168. package/dist/commands/search.js.map +2 -2
  169. package/dist/commands/show.js +58 -27
  170. package/dist/commands/show.js.map +2 -2
  171. package/dist/commands/stats.js +35 -0
  172. package/dist/commands/stats.js.map +7 -0
  173. package/dist/commands/trace.js +39 -16
  174. package/dist/commands/trace.js.map +2 -2
  175. package/dist/commands/update.js +141 -14
  176. package/dist/commands/update.js.map +2 -2
  177. package/dist/commands/upgrade.js +210 -46
  178. package/dist/commands/upgrade.js.map +3 -3
  179. package/dist/commands/validate.js +142 -23
  180. package/dist/commands/validate.js.map +3 -3
  181. package/dist/commands/version.js +8 -12
  182. package/dist/commands/version.js.map +2 -2
  183. package/dist/commands/visualize.js +155 -25
  184. package/dist/commands/visualize.js.map +2 -2
  185. package/dist/core/base-snapshot-manager.js +84 -0
  186. package/dist/core/base-snapshot-manager.js.map +7 -0
  187. package/dist/core/changeset-exporter.js +305 -0
  188. package/dist/core/changeset-exporter.js.map +7 -0
  189. package/dist/core/changeset-validator.js +41 -0
  190. package/dist/core/changeset-validator.js.map +7 -0
  191. package/dist/core/changeset.js +88 -173
  192. package/dist/core/changeset.js.map +2 -2
  193. package/dist/core/dependency-tracker.js +4 -2
  194. package/dist/core/dependency-tracker.js.map +2 -2
  195. package/dist/core/element.js +98 -27
  196. package/dist/core/element.js.map +2 -2
  197. package/dist/core/golden-copy-cache.js +290 -0
  198. package/dist/core/golden-copy-cache.js.map +7 -0
  199. package/dist/core/graph-model.js +266 -0
  200. package/dist/core/graph-model.js.map +7 -0
  201. package/dist/core/layer.js +159 -16
  202. package/dist/core/layer.js.map +2 -2
  203. package/dist/core/layers.js +32 -0
  204. package/dist/core/layers.js.map +7 -0
  205. package/dist/core/manifest.js +32 -11
  206. package/dist/core/manifest.js.map +2 -2
  207. package/dist/core/migration-registry.js +56 -1
  208. package/dist/core/migration-registry.js.map +2 -2
  209. package/dist/core/model.js +424 -217
  210. package/dist/core/model.js.map +3 -3
  211. package/dist/core/mutation-handler.js +179 -0
  212. package/dist/core/mutation-handler.js.map +7 -0
  213. package/dist/core/reference-registry.js +29 -21
  214. package/dist/core/reference-registry.js.map +2 -2
  215. package/dist/core/relationship-catalog.js +189 -0
  216. package/dist/core/relationship-catalog.js.map +7 -0
  217. package/dist/core/relationship-registry.js.map +2 -2
  218. package/dist/core/relationships.js.map +1 -1
  219. package/dist/core/report-data-model.js +285 -0
  220. package/dist/core/report-data-model.js.map +7 -0
  221. package/dist/core/spec-data-service.js +185 -0
  222. package/dist/core/spec-data-service.js.map +7 -0
  223. package/dist/core/spec-loader-types.js +1 -0
  224. package/dist/core/spec-loader-types.js.map +7 -0
  225. package/dist/core/spec-loader.js +234 -0
  226. package/dist/core/spec-loader.js.map +7 -0
  227. package/dist/core/staged-changeset-storage.js +282 -0
  228. package/dist/core/staged-changeset-storage.js.map +7 -0
  229. package/dist/core/staging-area.js +1002 -0
  230. package/dist/core/staging-area.js.map +7 -0
  231. package/dist/core/stats-collector.js +169 -0
  232. package/dist/core/stats-collector.js.map +7 -0
  233. package/dist/core/virtual-projection.js +443 -0
  234. package/dist/core/virtual-projection.js.map +7 -0
  235. package/dist/export/archimate-exporter.js +99 -60
  236. package/dist/export/archimate-exporter.js.map +2 -2
  237. package/dist/export/audit-formatters.js +479 -0
  238. package/dist/export/audit-formatters.js.map +7 -0
  239. package/dist/export/diff-formatters.js +99 -0
  240. package/dist/export/diff-formatters.js.map +7 -0
  241. package/dist/export/export-manager.js.map +2 -2
  242. package/dist/export/graphml-exporter.js +44 -27
  243. package/dist/export/graphml-exporter.js.map +2 -2
  244. package/dist/export/index.js +4 -0
  245. package/dist/export/index.js.map +2 -2
  246. package/dist/export/json-schema-exporter.js +55 -42
  247. package/dist/export/json-schema-exporter.js.map +2 -2
  248. package/dist/export/markdown-exporter.js +92 -70
  249. package/dist/export/markdown-exporter.js.map +2 -2
  250. package/dist/export/markdown-generator.js +248 -0
  251. package/dist/export/markdown-generator.js.map +7 -0
  252. package/dist/export/markdown-utils.js +30 -0
  253. package/dist/export/markdown-utils.js.map +7 -0
  254. package/dist/export/mermaid-markdown-exporter.js +37 -0
  255. package/dist/export/mermaid-markdown-exporter.js.map +7 -0
  256. package/dist/export/openapi-exporter.js +119 -71
  257. package/dist/export/openapi-exporter.js.map +2 -2
  258. package/dist/export/plantuml-exporter.js +104 -39
  259. package/dist/export/plantuml-exporter.js.map +2 -2
  260. package/dist/export/report-formatters.js +111 -0
  261. package/dist/export/report-formatters.js.map +7 -0
  262. package/dist/export/stats-formatters.js +98 -0
  263. package/dist/export/stats-formatters.js.map +7 -0
  264. package/dist/export/types.js +2 -14
  265. package/dist/export/types.js.map +2 -2
  266. package/dist/export/verify-formatters.js +153 -0
  267. package/dist/export/verify-formatters.js.map +7 -0
  268. package/dist/generated/compiled-validators.js +17 -0
  269. package/dist/generated/compiled-validators.js.map +7 -0
  270. package/dist/generated/index.js +31 -0
  271. package/dist/generated/index.js.map +7 -0
  272. package/dist/generated/layer-registry.js +190 -0
  273. package/dist/generated/layer-registry.js.map +7 -0
  274. package/dist/generated/layer-types.js +7 -0
  275. package/dist/generated/layer-types.js.map +7 -0
  276. package/dist/generated/node-types.js +1719 -0
  277. package/dist/generated/node-types.js.map +7 -0
  278. package/dist/generated/relationship-index.js +14552 -0
  279. package/dist/generated/relationship-index.js.map +7 -0
  280. package/dist/import/archimate-importer.js +166 -0
  281. package/dist/import/archimate-importer.js.map +7 -0
  282. package/dist/import/index.js +47 -0
  283. package/dist/import/index.js.map +7 -0
  284. package/dist/import/openapi-importer.js +143 -0
  285. package/dist/import/openapi-importer.js.map +7 -0
  286. package/dist/import/types.js +1 -0
  287. package/dist/import/types.js.map +7 -0
  288. package/dist/integrations/base-manager.js +311 -0
  289. package/dist/integrations/base-manager.js.map +7 -0
  290. package/dist/integrations/claude-manager.js +360 -0
  291. package/dist/integrations/claude-manager.js.map +7 -0
  292. package/dist/integrations/claude_code/agents/dr-advisor.md +253 -0
  293. package/dist/integrations/claude_code/agents/dr-architect.md +278 -0
  294. package/dist/integrations/claude_code/agents/dr-extractor.md +286 -0
  295. package/dist/integrations/claude_code/commands/dr-changeset.md +657 -0
  296. package/dist/integrations/claude_code/commands/dr-design.md +373 -0
  297. package/dist/integrations/claude_code/commands/dr-info.md +132 -0
  298. package/dist/integrations/claude_code/commands/dr-init.md +251 -0
  299. package/dist/integrations/claude_code/commands/dr-map.md +936 -0
  300. package/dist/integrations/claude_code/commands/dr-model.md +493 -0
  301. package/dist/integrations/claude_code/commands/dr-relate.md +814 -0
  302. package/dist/integrations/claude_code/commands/dr-sync.md +279 -0
  303. package/dist/integrations/claude_code/commands/dr-validate.md +654 -0
  304. package/dist/integrations/claude_code/commands/dr-verify.md +559 -0
  305. package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +903 -0
  306. package/dist/integrations/claude_code/docs/QUICK_START.md +271 -0
  307. package/dist/integrations/claude_code/docs/README.md +347 -0
  308. package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +550 -0
  309. package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +620 -0
  310. package/dist/integrations/claude_code/skills/dr_03_security_layer/SKILL.md +642 -0
  311. package/dist/integrations/claude_code/skills/dr_04_application_layer/SKILL.md +911 -0
  312. package/dist/integrations/claude_code/skills/dr_05_technology_layer/SKILL.md +836 -0
  313. package/dist/integrations/claude_code/skills/dr_06_api_layer/SKILL.md +933 -0
  314. package/dist/integrations/claude_code/skills/dr_07_data_model_layer/SKILL.md +210 -0
  315. package/dist/integrations/claude_code/skills/dr_08_dataset_layer/SKILL.md +319 -0
  316. package/dist/integrations/claude_code/skills/dr_09_ux_layer/SKILL.md +319 -0
  317. package/dist/integrations/claude_code/skills/dr_10_navigation_layer/SKILL.md +318 -0
  318. package/dist/integrations/claude_code/skills/dr_11_apm_layer/SKILL.md +273 -0
  319. package/dist/integrations/claude_code/skills/dr_12_testing_layer/SKILL.md +273 -0
  320. package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +187 -0
  321. package/dist/integrations/claude_code/skills/dr_codebase_memory/SKILL.md +590 -0
  322. package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +295 -0
  323. package/dist/integrations/copilot-manager.js +328 -0
  324. package/dist/integrations/copilot-manager.js.map +7 -0
  325. package/dist/integrations/github_copilot/agents/dr-advisor.md +243 -0
  326. package/dist/integrations/github_copilot/agents/dr-architect.md +1567 -0
  327. package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +491 -0
  328. package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +539 -0
  329. package/dist/integrations/github_copilot/skills/dr_03_security_layer/SKILL.md +667 -0
  330. package/dist/integrations/github_copilot/skills/dr_04_application_layer/SKILL.md +670 -0
  331. package/dist/integrations/github_copilot/skills/dr_05_technology_layer/SKILL.md +660 -0
  332. package/dist/integrations/github_copilot/skills/dr_06_api_layer/SKILL.md +855 -0
  333. package/dist/integrations/github_copilot/skills/dr_07_data_model_layer/SKILL.md +171 -0
  334. package/dist/integrations/github_copilot/skills/dr_08_dataset_layer/SKILL.md +216 -0
  335. package/dist/integrations/github_copilot/skills/dr_09_ux_layer/SKILL.md +230 -0
  336. package/dist/integrations/github_copilot/skills/dr_10_navigation_layer/SKILL.md +228 -0
  337. package/dist/integrations/github_copilot/skills/dr_11_apm_layer/SKILL.md +234 -0
  338. package/dist/integrations/github_copilot/skills/dr_12_testing_layer/SKILL.md +230 -0
  339. package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +187 -0
  340. package/dist/integrations/hash-utils.js +51 -0
  341. package/dist/integrations/hash-utils.js.map +7 -0
  342. package/dist/integrations/types.js +16 -0
  343. package/dist/integrations/types.js.map +7 -0
  344. package/dist/reports/model-layer-report-generator.js +278 -0
  345. package/dist/reports/model-layer-report-generator.js.map +7 -0
  346. package/dist/reports/model-report-data.js +57 -0
  347. package/dist/reports/model-report-data.js.map +7 -0
  348. package/dist/reports/model-report-orchestrator.js +103 -0
  349. package/dist/reports/model-report-orchestrator.js.map +7 -0
  350. package/dist/schemas/bundled/analyzers/cbm.json +595 -0
  351. package/dist/schemas/bundled/analyzers/manifest.json +9 -0
  352. package/dist/schemas/bundled/api.json +3411 -0
  353. package/dist/schemas/bundled/apm.json +2371 -0
  354. package/dist/schemas/bundled/application.json +1253 -0
  355. package/dist/schemas/bundled/base.json +1622 -0
  356. package/dist/schemas/bundled/business.json +1279 -0
  357. package/dist/schemas/bundled/data-model.json +1298 -0
  358. package/dist/schemas/bundled/data-store.json +2025 -0
  359. package/dist/schemas/bundled/manifest.json +93 -0
  360. package/dist/schemas/bundled/motivation.json +1360 -0
  361. package/dist/schemas/bundled/navigation.json +1874 -0
  362. package/dist/schemas/bundled/security.json +3488 -0
  363. package/dist/schemas/bundled/technology.json +2067 -0
  364. package/dist/schemas/bundled/testing.json +3120 -0
  365. package/dist/schemas/bundled/ux.json +3592 -0
  366. package/dist/server/schemas.js +202 -0
  367. package/dist/server/schemas.js.map +7 -0
  368. package/dist/server/server.js +1336 -252
  369. package/dist/server/server.js.map +3 -3
  370. package/dist/server/telemetry-middleware.js +22 -15
  371. package/dist/server/telemetry-middleware.js.map +2 -2
  372. package/dist/server-entry.js +65 -0
  373. package/dist/server-entry.js.map +7 -0
  374. package/dist/telemetry/config.js +89 -0
  375. package/dist/telemetry/config.js.map +7 -0
  376. package/dist/telemetry/console-interceptor.js.map +1 -1
  377. package/dist/telemetry/detect.js +24 -0
  378. package/dist/telemetry/detect.js.map +7 -0
  379. package/dist/telemetry/fetch-otlp-exporter.js +112 -0
  380. package/dist/telemetry/fetch-otlp-exporter.js.map +7 -0
  381. package/dist/telemetry/index.js +153 -3
  382. package/dist/telemetry/index.js.map +2 -2
  383. package/dist/telemetry/resilient-exporter.js +22 -8
  384. package/dist/telemetry/resilient-exporter.js.map +2 -2
  385. package/dist/telemetry/resilient-log-exporter.js +17 -8
  386. package/dist/telemetry/resilient-log-exporter.js.map +2 -2
  387. package/dist/telemetry/test-instrumentation.js.map +2 -2
  388. package/dist/types/index.js +84 -0
  389. package/dist/types/index.js.map +3 -3
  390. package/dist/types/source-reference.js +1 -0
  391. package/dist/types/source-reference.js.map +7 -0
  392. package/dist/utils/chat-logger.js +292 -0
  393. package/dist/utils/chat-logger.js.map +7 -0
  394. package/dist/utils/claude-stream.js +8 -0
  395. package/dist/utils/claude-stream.js.map +7 -0
  396. package/dist/utils/date-utils.js +7 -0
  397. package/dist/utils/date-utils.js.map +7 -0
  398. package/dist/utils/element-utils.js.map +1 -1
  399. package/dist/utils/errors.js +88 -15
  400. package/dist/utils/errors.js.map +3 -3
  401. package/dist/utils/export-python-annotations.js +82 -0
  402. package/dist/utils/export-python-annotations.js.map +7 -0
  403. package/dist/utils/file-io.js +7 -2
  404. package/dist/utils/file-io.js.map +2 -2
  405. package/dist/utils/file-lock.js +156 -0
  406. package/dist/utils/file-lock.js.map +7 -0
  407. package/dist/utils/id-generator.js +18 -0
  408. package/dist/utils/id-generator.js.map +7 -0
  409. package/dist/utils/layer-name-formatter.js +16 -0
  410. package/dist/utils/layer-name-formatter.js.map +7 -0
  411. package/dist/utils/markdown-anchor.js +7 -0
  412. package/dist/utils/markdown-anchor.js.map +7 -0
  413. package/dist/utils/markdown-link.js +12 -0
  414. package/dist/utils/markdown-link.js.map +7 -0
  415. package/dist/utils/markdown-table.js +17 -0
  416. package/dist/utils/markdown-table.js.map +7 -0
  417. package/dist/utils/mermaid-utils.js +11 -0
  418. package/dist/utils/mermaid-utils.js.map +7 -0
  419. package/dist/utils/project-paths.js.map +2 -2
  420. package/dist/utils/source-reference.js +52 -0
  421. package/dist/utils/source-reference.js.map +7 -0
  422. package/dist/utils/spec-installer.js +40 -53
  423. package/dist/utils/spec-installer.js.map +2 -2
  424. package/dist/utils/spec-version.js +5 -1
  425. package/dist/utils/spec-version.js.map +2 -2
  426. package/dist/utils/string-utils.js +12 -0
  427. package/dist/utils/string-utils.js.map +7 -0
  428. package/dist/utils/table-formatting.js +19 -0
  429. package/dist/utils/table-formatting.js.map +7 -0
  430. package/dist/validators/index.js +4 -0
  431. package/dist/validators/index.js.map +2 -2
  432. package/dist/validators/naming-validator.js +20 -23
  433. package/dist/validators/naming-validator.js.map +2 -2
  434. package/dist/validators/reference-validator.js +23 -38
  435. package/dist/validators/reference-validator.js.map +2 -2
  436. package/dist/validators/relationship-schema-validator.js +243 -0
  437. package/dist/validators/relationship-schema-validator.js.map +7 -0
  438. package/dist/validators/schema-validator.js +168 -68
  439. package/dist/validators/schema-validator.js.map +2 -2
  440. package/dist/validators/semantic-validator.js +36 -60
  441. package/dist/validators/semantic-validator.js.map +2 -2
  442. package/dist/validators/types.js.map +2 -2
  443. package/dist/validators/validation-formatter.js +159 -0
  444. package/dist/validators/validation-formatter.js.map +7 -0
  445. package/dist/validators/validator.js +24 -38
  446. package/dist/validators/validator.js.map +2 -2
  447. package/dist/viewer/README.md +139 -0
  448. package/dist/viewer/assets/main-CY0Xufuj.css +1 -0
  449. package/dist/viewer/assets/main-DtgErnwF.js +81 -0
  450. package/dist/viewer/assets/vendor-CxKvDp2x.js +17 -0
  451. package/dist/viewer/favicon.svg +11 -0
  452. package/dist/viewer/fonts/inter/Inter-Black.woff2 +0 -0
  453. package/dist/viewer/fonts/inter/Inter-Bold.woff2 +0 -0
  454. package/dist/viewer/fonts/inter/Inter-ExtraBold.woff2 +0 -0
  455. package/dist/viewer/fonts/inter/Inter-Light.woff2 +0 -0
  456. package/dist/viewer/fonts/inter/Inter-Medium.woff2 +0 -0
  457. package/dist/viewer/fonts/inter/Inter-Regular.woff2 +0 -0
  458. package/dist/viewer/fonts/inter/Inter-SemiBold.woff2 +0 -0
  459. package/dist/viewer/fonts/jetbrains-mono/JetBrainsMono-Medium.woff2 +0 -0
  460. package/dist/viewer/fonts/jetbrains-mono/JetBrainsMono-Regular.woff2 +0 -0
  461. package/dist/viewer/fonts/jetbrains-mono/JetBrainsMono-SemiBold.woff2 +0 -0
  462. package/dist/viewer/index.html +15 -0
  463. package/dist/viewer/manifest.json +99 -0
  464. package/dist/viewer/version.json +1 -0
  465. package/package.json +37 -13
  466. package/PYTHON_CLI_PARITY.md +0 -265
  467. package/PYTHON_CLI_STRUCTURE_UPDATE.md +0 -246
  468. package/biome.json +0 -42
  469. package/bunfig.toml +0 -3
  470. package/dist/ai/claude-client.js +0 -71
  471. package/dist/ai/claude-client.js.map +0 -7
  472. package/dist/ai/context-provider.js +0 -129
  473. package/dist/ai/context-provider.js.map +0 -7
  474. package/dist/ai/tools.js +0 -231
  475. package/dist/ai/tools.js.map +0 -7
  476. package/dist/commands/migrate.js +0 -36
  477. package/dist/commands/migrate.js.map +0 -7
  478. package/dist/commands/project.js +0 -74
  479. package/dist/commands/project.js.map +0 -7
  480. package/dist/core/active-changeset.js +0 -52
  481. package/dist/core/active-changeset.js.map +0 -7
  482. package/dist/core/projection-engine.js +0 -288
  483. package/dist/core/projection-engine.js.map +0 -7
  484. package/dist/schemas/bundled/01-motivation-layer.schema.json +0 -942
  485. package/dist/schemas/bundled/02-business-layer.schema.json +0 -1293
  486. package/dist/schemas/bundled/03-security-layer.schema.json +0 -2177
  487. package/dist/schemas/bundled/04-application-layer.schema.json +0 -1328
  488. package/dist/schemas/bundled/05-technology-layer.schema.json +0 -1465
  489. package/dist/schemas/bundled/06-api-layer.schema.json +0 -1743
  490. package/dist/schemas/bundled/07-data-model-layer.schema.json +0 -1253
  491. package/dist/schemas/bundled/08-datastore-layer.schema.json +0 -892
  492. package/dist/schemas/bundled/09-ux-layer.schema.json +0 -1506
  493. package/dist/schemas/bundled/10-navigation-layer.schema.json +0 -843
  494. package/dist/schemas/bundled/11-apm-observability-layer.schema.json +0 -1430
  495. package/dist/schemas/bundled/12-testing-layer.schema.json +0 -1091
  496. package/dist/schemas/bundled/common/layer-extensions.schema.json +0 -266
  497. package/dist/schemas/bundled/common/predicates.schema.json +0 -493
  498. package/dist/schemas/bundled/common/relationships.schema.json +0 -791
  499. package/dist/schemas/bundled/common/source-references.schema.json +0 -71
  500. package/dist/schemas/bundled/link-registry.json +0 -1839
  501. package/dist/schemas/bundled/link-registry.schema.json +0 -339
  502. package/dist/schemas/bundled/relationship-catalog.json +0 -1302
  503. package/dist/schemas/bundled/relationship-type.schema.json +0 -221
  504. package/dist/utils/model-path.js +0 -22
  505. package/dist/utils/model-path.js.map +0 -7
  506. package/docs/TEST-INSTRUMENTATION.md +0 -405
  507. package/docs/telemetry.md +0 -427
  508. package/test-debug.js +0 -33
  509. package/test-special.mjs +0 -46
  510. package/test_runner.ts +0 -7
@@ -0,0 +1,243 @@
1
+ import { ValidationResult } from "./types.js";
2
+ import { fileURLToPath } from "url";
3
+ import path from "path";
4
+ import { readFile } from "../utils/file-io.js";
5
+ import { existsSync } from "fs";
6
+ import { startActiveSpan } from "../telemetry/index.js";
7
+ const isTelemetryEnabled = !1;
8
+ class RelationshipValidator {
9
+ schemasDir;
10
+ relationshipSchemas = /* @__PURE__ */ new Map();
11
+ constructor() {
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+ this.schemasDir = path.join(__dirname, "..", "schemas", "bundled");
14
+ }
15
+ /**
16
+ * Initialize and load relationship schemas
17
+ */
18
+ async initialize() {
19
+ await this.loadRelationshipSchemas();
20
+ }
21
+ /**
22
+ * Load relationship schemas from compiled bundled spec (manifest.json + per-layer JSON files)
23
+ */
24
+ async loadRelationshipSchemas() {
25
+ const manifestPath = path.join(this.schemasDir, "manifest.json");
26
+ if (!existsSync(manifestPath))
27
+ throw new Error(`Bundled manifest not found at ${manifestPath}`);
28
+ const manifestContent = await readFile(manifestPath), manifest = JSON.parse(manifestContent);
29
+ for (const { id: layerId } of manifest.layers) {
30
+ const layerPath = path.join(this.schemasDir, `${layerId}.json`);
31
+ if (existsSync(layerPath))
32
+ try {
33
+ const layerContent = await readFile(layerPath), layerData = JSON.parse(layerContent);
34
+ for (const [relId, relSchema] of Object.entries(layerData.relationshipSchemas || {}))
35
+ this.relationshipSchemas.set(relId, {
36
+ id: relSchema.id || relId,
37
+ source_spec_node_id: relSchema.source_spec_node_id || "",
38
+ source_layer: relSchema.source_layer || "",
39
+ destination_spec_node_id: relSchema.destination_spec_node_id || "",
40
+ destination_layer: relSchema.destination_layer || "",
41
+ predicate: relSchema.predicate || "",
42
+ cardinality: relSchema.cardinality || "many-to-many",
43
+ strength: relSchema.strength || "medium",
44
+ required: relSchema.required || !1,
45
+ attributes: relSchema.attributes
46
+ });
47
+ } catch (error) {
48
+ throw new Error(`Failed to load relationship schemas for layer '${layerId}': ${error.message}`);
49
+ }
50
+ }
51
+ }
52
+ /**
53
+ * Validate relationships in a model with cardinality enforcement
54
+ */
55
+ async validateModel(model) {
56
+ return startActiveSpan("relationship-schema.validate-model", async (span) => {
57
+ if (this.relationshipSchemas.size === 0)
58
+ try {
59
+ await this.initialize();
60
+ } catch (error) {
61
+ const result2 = new ValidationResult();
62
+ return result2.addError({
63
+ layer: "system",
64
+ message: `${error.message} Relationship validation is not available.`
65
+ }), result2;
66
+ }
67
+ if (this.relationshipSchemas.size === 0) {
68
+ const result2 = new ValidationResult();
69
+ return result2.addError({
70
+ layer: "system",
71
+ message: `No relationship schemas loaded from ${path.join(this.schemasDir, "manifest.json")}. Relationship validation is not available.`
72
+ }), result2;
73
+ }
74
+ const result = new ValidationResult(), allRelationships = model.relationships.getAll();
75
+ if (allRelationships.length === 0)
76
+ return result;
77
+ const relationshipsBySource = this.groupRelationshipsBySourceTarget(allRelationships, "source"), relationshipsByTarget = this.groupRelationshipsBySourceTarget(allRelationships, "target");
78
+ for (const relationship of allRelationships) {
79
+ const relErrors = await this.validateRelationship(relationship, model, relationshipsBySource, relationshipsByTarget);
80
+ if (relErrors.length > 0)
81
+ for (const error of relErrors)
82
+ result.addError(error);
83
+ }
84
+ return isTelemetryEnabled && (span.setAttribute("relationship.error_count", result.errors.length), span.setAttribute("relationship.warning_count", result.warnings.length), span.setAttribute("relationship.validated_count", allRelationships.length)), result;
85
+ }, { "relationship.layer_count": model.layers.size });
86
+ }
87
+ /**
88
+ * Validate a single relationship
89
+ */
90
+ async validateRelationship(relationship, model, relationshipsBySource, relationshipsByTarget) {
91
+ const errors = [], sourceElement = this.findElementInModel(model, relationship.source), targetElement = this.findElementInModel(model, relationship.target);
92
+ if (!sourceElement)
93
+ return errors.push({
94
+ layer: relationship.layer,
95
+ elementId: relationship.source,
96
+ message: `Relationship source element '${relationship.source}' not found`
97
+ }), errors;
98
+ if (!targetElement)
99
+ return errors.push({
100
+ layer: relationship.layer,
101
+ elementId: relationship.target,
102
+ message: `Relationship target element '${relationship.target}' not found`
103
+ }), errors;
104
+ const sourceLayer = sourceElement.layer_id || sourceElement.layer || relationship.layer, targetLayer = targetElement.layer_id || targetElement.layer || relationship.layer, sourceSpecId = sourceElement.spec_node_id ? sourceElement.spec_node_id : `${sourceLayer}.${sourceElement.type}`, targetSpecId = targetElement.spec_node_id ? targetElement.spec_node_id : `${targetLayer}.${targetElement.type}`, schemaKey = this.findRelationshipSchemaKeyBySpecId(sourceSpecId, targetSpecId, relationship.predicate);
105
+ if (!schemaKey) {
106
+ const sourceType = sourceSpecId.includes(".") ? sourceSpecId.split(".")[1] : sourceElement.type, targetType = targetSpecId.includes(".") ? targetSpecId.split(".")[1] : targetElement.type;
107
+ return errors.push({
108
+ layer: relationship.layer,
109
+ elementId: relationship.source,
110
+ message: `No schema found for relationship: ${sourceLayer}.${sourceType} --[${relationship.predicate}]--> ${targetLayer}.${targetType}`
111
+ }), errors;
112
+ }
113
+ const schema = this.relationshipSchemas.get(schemaKey);
114
+ if (!schema)
115
+ return errors.push({
116
+ layer: relationship.layer,
117
+ elementId: relationship.source,
118
+ message: `Failed to load schema for relationship: ${schemaKey}`
119
+ }), errors;
120
+ const cardinalityErrors = this.validateCardinality(relationship, schema, relationshipsBySource, relationshipsByTarget);
121
+ if (errors.push(...cardinalityErrors), relationship.properties && schema.attributes) {
122
+ const attrErrors = this.validateRelationshipAttributes(relationship, schema);
123
+ errors.push(...attrErrors);
124
+ }
125
+ return errors;
126
+ }
127
+ /**
128
+ * Validate cardinality constraints for a relationship
129
+ *
130
+ * Cardinality constraint semantics:
131
+ * - "one-to-one": Each source can have at most 1 such relationship, and each target can be involved in at most 1
132
+ * - "one-to-many": Each target can receive at most 1 such relationship (target is the "one"); source is unconstrained
133
+ * - "many-to-one": Each source can be involved in at most 1 such relationship (maps to one target); multiple sources may share the same target
134
+ * - "many-to-many": No cardinality constraints
135
+ */
136
+ validateCardinality(relationship, schema, relationshipsBySource, relationshipsByTarget) {
137
+ const errors = [], cardinality = schema.cardinality, sourceRelCount = relationshipsBySource.get(relationship.source)?.filter(
138
+ (r) => r.predicate === relationship.predicate && r.target !== relationship.target
139
+ // Exclude current relationship
140
+ ).length || 0, targetRelCount = relationshipsByTarget.get(relationship.target)?.filter(
141
+ (r) => r.predicate === relationship.predicate && r.source !== relationship.source
142
+ // Exclude current relationship
143
+ ).length || 0;
144
+ switch (cardinality) {
145
+ case "one-to-one":
146
+ sourceRelCount > 0 && errors.push({
147
+ layer: relationship.layer,
148
+ elementId: relationship.source,
149
+ message: `Cardinality violation: '${relationship.predicate}' has cardinality 'one-to-one', but source '${relationship.source}' already has this relationship`
150
+ }), targetRelCount > 0 && errors.push({
151
+ layer: relationship.layer,
152
+ elementId: relationship.target,
153
+ message: `Cardinality violation: '${relationship.predicate}' has cardinality 'one-to-one', but target '${relationship.target}' is already involved in this relationship`
154
+ });
155
+ break;
156
+ case "one-to-many":
157
+ targetRelCount > 0 && errors.push({
158
+ layer: relationship.layer,
159
+ elementId: relationship.target,
160
+ message: `Cardinality violation: '${relationship.predicate}' has cardinality 'one-to-many', but target '${relationship.target}' already has this relationship`
161
+ });
162
+ break;
163
+ case "many-to-one":
164
+ sourceRelCount > 0 && errors.push({
165
+ layer: relationship.layer,
166
+ elementId: relationship.source,
167
+ message: `Cardinality violation: '${relationship.predicate}' has cardinality 'many-to-one', but source '${relationship.source}' already has this relationship`
168
+ });
169
+ break;
170
+ case "many-to-many":
171
+ break;
172
+ }
173
+ return errors;
174
+ }
175
+ /**
176
+ * Validate relationship attributes against schema
177
+ */
178
+ validateRelationshipAttributes(relationship, schema) {
179
+ const errors = [];
180
+ if (!schema.attributes || !relationship.properties)
181
+ return errors;
182
+ for (const key of Object.keys(relationship.properties))
183
+ key in schema.attributes || errors.push({
184
+ layer: relationship.layer,
185
+ elementId: relationship.source,
186
+ message: `Relationship property '${key}' not defined in schema for predicate '${relationship.predicate}'`
187
+ });
188
+ return errors;
189
+ }
190
+ /**
191
+ * Find relationship schema ID by source/dest spec node IDs and predicate
192
+ *
193
+ * Matches relationship schemas by:
194
+ * 1. Predicate (must match exactly)
195
+ * 2. Spec node ID pair (source and destination must match exactly)
196
+ *
197
+ * Spec node IDs are in the format "layer.type" (e.g., "application.applicationservice")
198
+ * and uniquely identify an element type across the model.
199
+ *
200
+ * Supports both intra-layer (motivation -> motivation) and cross-layer
201
+ * (motivation -> business) relationships.
202
+ */
203
+ findRelationshipSchemaKeyBySpecId(sourceSpecId, destSpecId, predicate) {
204
+ for (const [schemaId, schema] of this.relationshipSchemas.entries())
205
+ if (schema.predicate === predicate && schema.source_spec_node_id === sourceSpecId && schema.destination_spec_node_id === destSpecId)
206
+ return schemaId;
207
+ return null;
208
+ }
209
+ /**
210
+ * Group relationships by source or target element
211
+ */
212
+ groupRelationshipsBySourceTarget(relationships, by) {
213
+ const grouped = /* @__PURE__ */ new Map();
214
+ for (const rel of relationships) {
215
+ const key = by === "source" ? rel.source : rel.target;
216
+ grouped.has(key) || grouped.set(key, []), grouped.get(key).push(rel);
217
+ }
218
+ return grouped;
219
+ }
220
+ /**
221
+ * Find an element in the model by ID
222
+ *
223
+ * Performs element lookup using Layer.getElement(), which supports multiple lookup strategies:
224
+ * - Direct lookup by graph key (path)
225
+ * - UUID lookup
226
+ * - Semantic ID lookup (for legacy format elements converted to UUID)
227
+ * - Element ID lookup
228
+ *
229
+ * This comprehensive lookup ensures elements are found regardless of how they were stored.
230
+ */
231
+ findElementInModel(model, elementId) {
232
+ for (const layer of model.layers.values()) {
233
+ const element = layer.getElement(elementId);
234
+ if (element)
235
+ return element;
236
+ }
237
+ return null;
238
+ }
239
+ }
240
+ export {
241
+ RelationshipValidator
242
+ };
243
+ //# sourceMappingURL=relationship-schema-validator.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/validators/relationship-schema-validator.ts"],
4
+ "sourcesContent": ["/**\n * Relationship Schema Validation with Cardinality Enforcement\n *\n * Validates relationships against their spec relationship schemas, including:\n * - Schema compliance (source/destination node types)\n * - Cardinality constraints (one-to-one, one-to-many, many-to-one, many-to-many)\n * - Predicate validity\n * - Relationship attributes against schema\n */\n\nimport { ValidationResult } from \"./types.js\";\nimport type { Model } from \"../core/model.js\";\nimport type { Relationship } from \"../core/relationships.js\";\nimport { fileURLToPath } from \"url\";\nimport path from \"path\";\nimport { readFile } from \"../utils/file-io.js\";\nimport { existsSync } from \"fs\";\nimport { startActiveSpan } from \"../telemetry/index.js\";\n\ninterface ManifestDistFile {\n layers: Array<{ id: string }>;\n}\n\ninterface LayerDistFile {\n relationshipSchemas: Record<string, {\n id: string;\n source_spec_node_id: string;\n source_layer: string;\n destination_spec_node_id: string;\n destination_layer: string;\n predicate: string;\n cardinality: \"one-to-one\" | \"one-to-many\" | \"many-to-one\" | \"many-to-many\";\n strength: string;\n required?: boolean;\n attributes?: Record<string, unknown>;\n }>;\n}\n\ndeclare const TELEMETRY_ENABLED: boolean | undefined;\nconst isTelemetryEnabled = typeof TELEMETRY_ENABLED !== \"undefined\" ? TELEMETRY_ENABLED : false;\n\nexport interface RelationshipSchema {\n id: string;\n source_spec_node_id: string;\n source_layer: string;\n destination_spec_node_id: string;\n destination_layer: string;\n predicate: string;\n cardinality: \"one-to-one\" | \"one-to-many\" | \"many-to-one\" | \"many-to-many\";\n strength: string;\n required?: boolean;\n attributes?: Record<string, unknown>;\n}\n\n/**\n * Validator for relationship schema compliance and cardinality constraints\n *\n * Validates that relationships:\n * 1. Conform to their spec relationship schemas\n * 2. Respect cardinality constraints (e.g., one-to-one means max 1 relationship of that type)\n * 3. Reference valid predicates\n * 4. Have valid source/destination element types\n */\nexport class RelationshipValidator {\n private schemasDir: string;\n private relationshipSchemas: Map<string, RelationshipSchema> = new Map();\n\n constructor() {\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n this.schemasDir = path.join(__dirname, \"..\", \"schemas\", \"bundled\");\n }\n\n /**\n * Initialize and load relationship schemas\n */\n async initialize(): Promise<void> {\n await this.loadRelationshipSchemas();\n }\n\n /**\n * Load relationship schemas from compiled bundled spec (manifest.json + per-layer JSON files)\n */\n private async loadRelationshipSchemas(): Promise<void> {\n const manifestPath = path.join(this.schemasDir, \"manifest.json\");\n\n if (!existsSync(manifestPath)) {\n throw new Error(`Bundled manifest not found at ${manifestPath}`);\n }\n\n const manifestContent = await readFile(manifestPath);\n const manifest = JSON.parse(manifestContent) as ManifestDistFile;\n\n for (const { id: layerId } of manifest.layers) {\n const layerPath = path.join(this.schemasDir, `${layerId}.json`);\n if (!existsSync(layerPath)) {\n continue;\n }\n\n try {\n const layerContent = await readFile(layerPath);\n const layerData = JSON.parse(layerContent) as LayerDistFile;\n\n for (const [relId, relSchema] of Object.entries(layerData.relationshipSchemas || {})) {\n this.relationshipSchemas.set(relId, {\n id: relSchema.id || relId,\n source_spec_node_id: relSchema.source_spec_node_id || \"\",\n source_layer: relSchema.source_layer || \"\",\n destination_spec_node_id: relSchema.destination_spec_node_id || \"\",\n destination_layer: relSchema.destination_layer || \"\",\n predicate: relSchema.predicate || \"\",\n cardinality: relSchema.cardinality || \"many-to-many\",\n strength: relSchema.strength || \"medium\",\n required: relSchema.required || false,\n attributes: relSchema.attributes,\n });\n }\n } catch (error: any) {\n throw new Error(`Failed to load relationship schemas for layer '${layerId}': ${error.message}`);\n }\n }\n }\n\n /**\n * Validate relationships in a model with cardinality enforcement\n */\n async validateModel(model: Model): Promise<ValidationResult> {\n return startActiveSpan(\n \"relationship-schema.validate-model\",\n async (span) => {\n // Ensure schemas are loaded\n if (this.relationshipSchemas.size === 0) {\n try {\n await this.initialize();\n } catch (error: any) {\n const result = new ValidationResult();\n result.addError({\n layer: \"system\",\n message: `${error.message} Relationship validation is not available.`,\n });\n return result;\n }\n }\n\n // Verify schemas were loaded. This covers two cases:\n // 1. initialize() succeeded but the manifest has no layers with relationship schemas\n // 2. Edge case where manifest exists but is empty\n // In both cases, relationship validation cannot proceed.\n if (this.relationshipSchemas.size === 0) {\n const result = new ValidationResult();\n result.addError({\n layer: \"system\",\n message: `No relationship schemas loaded from ${path.join(this.schemasDir, \"manifest.json\")}. Relationship validation is not available.`,\n });\n return result;\n }\n\n const result = new ValidationResult();\n const allRelationships = model.relationships.getAll();\n\n if (allRelationships.length === 0) {\n return result;\n }\n\n // Group relationships for cardinality validation\n const relationshipsBySource = this.groupRelationshipsBySourceTarget(\n allRelationships,\n \"source\"\n );\n const relationshipsByTarget = this.groupRelationshipsBySourceTarget(\n allRelationships,\n \"target\"\n );\n\n // Validate each relationship\n for (const relationship of allRelationships) {\n const relErrors = await this.validateRelationship(\n relationship,\n model,\n relationshipsBySource,\n relationshipsByTarget\n );\n\n if (relErrors.length > 0) {\n for (const error of relErrors) {\n result.addError(error);\n }\n }\n }\n\n if (isTelemetryEnabled) {\n span.setAttribute(\"relationship.error_count\", result.errors.length);\n span.setAttribute(\"relationship.warning_count\", result.warnings.length);\n span.setAttribute(\"relationship.validated_count\", allRelationships.length);\n }\n\n return result;\n },\n { \"relationship.layer_count\": model.layers.size }\n );\n }\n\n /**\n * Validate a single relationship\n */\n private async validateRelationship(\n relationship: Relationship,\n model: Model,\n relationshipsBySource: Map<string, Relationship[]>,\n relationshipsByTarget: Map<string, Relationship[]>\n ): Promise<Array<{ layer: string; message: string; elementId?: string }>> {\n const errors: Array<{ layer: string; message: string; elementId?: string }> = [];\n\n // Find source and destination elements\n const sourceElement = this.findElementInModel(model, relationship.source);\n const targetElement = this.findElementInModel(model, relationship.target);\n\n if (!sourceElement) {\n errors.push({\n layer: relationship.layer,\n elementId: relationship.source,\n message: `Relationship source element '${relationship.source}' not found`,\n });\n return errors;\n }\n\n if (!targetElement) {\n errors.push({\n layer: relationship.layer,\n elementId: relationship.target,\n message: `Relationship target element '${relationship.target}' not found`,\n });\n return errors;\n }\n\n // Extract element types AND layers (CRITICAL FIX for cross-layer relationships)\n // The source and target elements may be in different layers\n const sourceLayer = sourceElement.layer_id || sourceElement.layer || relationship.layer;\n const targetLayer = targetElement.layer_id || targetElement.layer || relationship.layer;\n\n // Use spec_node_id if present, otherwise construct from layer and type\n // spec_node_id contains the canonical type (e.g., \"application.applicationservice\")\n // Guard against empty spec_node_id from older CLI versions or manually authored elements\n const sourceSpecId = sourceElement.spec_node_id\n ? sourceElement.spec_node_id\n : `${sourceLayer}.${sourceElement.type}`;\n const targetSpecId = targetElement.spec_node_id\n ? targetElement.spec_node_id\n : `${targetLayer}.${targetElement.type}`;\n\n // Find applicable relationship schema using actual source/target layers and spec node IDs\n // (not relationship.layer for both, which assumes intra-layer relationships)\n const schemaKey = this.findRelationshipSchemaKeyBySpecId(\n sourceSpecId,\n targetSpecId,\n relationship.predicate\n );\n\n if (!schemaKey) {\n // Extract type names from spec_node_id for error message (e.g., \"applicationservice\" from \"application.applicationservice\")\n const sourceType = sourceSpecId.includes(\".\") ? sourceSpecId.split(\".\")[1] : sourceElement.type;\n const targetType = targetSpecId.includes(\".\") ? targetSpecId.split(\".\")[1] : targetElement.type;\n errors.push({\n layer: relationship.layer,\n elementId: relationship.source,\n message: `No schema found for relationship: ${sourceLayer}.${sourceType} --[${relationship.predicate}]--> ${targetLayer}.${targetType}`,\n });\n return errors;\n }\n\n const schema = this.relationshipSchemas.get(schemaKey);\n if (!schema) {\n errors.push({\n layer: relationship.layer,\n elementId: relationship.source,\n message: `Failed to load schema for relationship: ${schemaKey}`,\n });\n return errors;\n }\n\n // Validate cardinality constraints\n const cardinalityErrors = this.validateCardinality(\n relationship,\n schema,\n relationshipsBySource,\n relationshipsByTarget\n );\n\n errors.push(...cardinalityErrors);\n\n // Validate relationship attributes if schema specifies them\n if (relationship.properties && schema.attributes) {\n const attrErrors = this.validateRelationshipAttributes(\n relationship,\n schema\n );\n errors.push(...attrErrors);\n }\n\n return errors;\n }\n\n /**\n * Validate cardinality constraints for a relationship\n *\n * Cardinality constraint semantics:\n * - \"one-to-one\": Each source can have at most 1 such relationship, and each target can be involved in at most 1\n * - \"one-to-many\": Each target can receive at most 1 such relationship (target is the \"one\"); source is unconstrained\n * - \"many-to-one\": Each source can be involved in at most 1 such relationship (maps to one target); multiple sources may share the same target\n * - \"many-to-many\": No cardinality constraints\n */\n private validateCardinality(\n relationship: Relationship,\n schema: RelationshipSchema,\n relationshipsBySource: Map<string, Relationship[]>,\n relationshipsByTarget: Map<string, Relationship[]>\n ): Array<{ layer: string; message: string; elementId?: string }> {\n const errors: Array<{ layer: string; message: string; elementId?: string }> = [];\n const cardinality = schema.cardinality;\n\n // Count OTHER relationships for this predicate from source and to target\n // (excluding the current relationship to get accurate violation detection)\n const sourceRelCount =\n relationshipsBySource\n .get(relationship.source)\n ?.filter(\n (r) =>\n r.predicate === relationship.predicate &&\n r.target !== relationship.target // Exclude current relationship\n ).length || 0;\n\n const targetRelCount =\n relationshipsByTarget\n .get(relationship.target)\n ?.filter(\n (r) =>\n r.predicate === relationship.predicate &&\n r.source !== relationship.source // Exclude current relationship\n ).length || 0;\n\n // Validate based on cardinality type\n switch (cardinality) {\n case \"one-to-one\":\n // Source can have at most 1 such relationship\n if (sourceRelCount > 0) {\n errors.push({\n layer: relationship.layer,\n elementId: relationship.source,\n message: `Cardinality violation: '${relationship.predicate}' has cardinality 'one-to-one', but source '${relationship.source}' already has this relationship`,\n });\n }\n // Target can be involved in at most 1 such relationship\n if (targetRelCount > 0) {\n errors.push({\n layer: relationship.layer,\n elementId: relationship.target,\n message: `Cardinality violation: '${relationship.predicate}' has cardinality 'one-to-one', but target '${relationship.target}' is already involved in this relationship`,\n });\n }\n break;\n\n case \"one-to-many\":\n // Standard ER one-to-many: the \"one\" is at the target end.\n // Each target can receive at most 1 incoming relationship of this type.\n // The source is unconstrained \u2014 it may have many outgoing of this type\n // to different targets.\n if (targetRelCount > 0) {\n errors.push({\n layer: relationship.layer,\n elementId: relationship.target,\n message: `Cardinality violation: '${relationship.predicate}' has cardinality 'one-to-many', but target '${relationship.target}' already has this relationship`,\n });\n }\n break;\n\n case \"many-to-one\":\n // Standard ER many-to-one: the \"one\" is at the source end.\n // Each source can be involved in at most 1 relationship of this type\n // (it maps to exactly one target). Multiple sources CAN share the same\n // target \u2014 fan-in is allowed and expected for shared services.\n if (sourceRelCount > 0) {\n errors.push({\n layer: relationship.layer,\n elementId: relationship.source,\n message: `Cardinality violation: '${relationship.predicate}' has cardinality 'many-to-one', but source '${relationship.source}' already has this relationship`,\n });\n }\n break;\n\n case \"many-to-many\":\n // No cardinality constraints\n break;\n }\n\n return errors;\n }\n\n /**\n * Validate relationship attributes against schema\n */\n private validateRelationshipAttributes(\n relationship: Relationship,\n schema: RelationshipSchema\n ): Array<{ layer: string; message: string; elementId?: string }> {\n const errors: Array<{ layer: string; message: string; elementId?: string }> = [];\n\n if (!schema.attributes || !relationship.properties) {\n return errors;\n }\n\n // Simple validation: check that properties match schema keys\n for (const key of Object.keys(relationship.properties)) {\n if (!(key in schema.attributes)) {\n errors.push({\n layer: relationship.layer,\n elementId: relationship.source,\n message: `Relationship property '${key}' not defined in schema for predicate '${relationship.predicate}'`,\n });\n }\n }\n\n return errors;\n }\n\n /**\n * Find relationship schema ID by source/dest spec node IDs and predicate\n *\n * Matches relationship schemas by:\n * 1. Predicate (must match exactly)\n * 2. Spec node ID pair (source and destination must match exactly)\n *\n * Spec node IDs are in the format \"layer.type\" (e.g., \"application.applicationservice\")\n * and uniquely identify an element type across the model.\n *\n * Supports both intra-layer (motivation -> motivation) and cross-layer\n * (motivation -> business) relationships.\n */\n private findRelationshipSchemaKeyBySpecId(\n sourceSpecId: string,\n destSpecId: string,\n predicate: string\n ): string | null {\n // Search for matching schema\n for (const [schemaId, schema] of this.relationshipSchemas.entries()) {\n // First filter: predicate must match exactly\n if (schema.predicate !== predicate) {\n continue;\n }\n\n // Second filter: spec node ID pair must match exactly\n // This directly compares canonical forms like \"application.applicationservice\"\n if (\n schema.source_spec_node_id === sourceSpecId &&\n schema.destination_spec_node_id === destSpecId\n ) {\n return schemaId;\n }\n }\n\n return null;\n }\n\n /**\n * Group relationships by source or target element\n */\n private groupRelationshipsBySourceTarget(\n relationships: Relationship[],\n by: \"source\" | \"target\"\n ): Map<string, Relationship[]> {\n const grouped = new Map<string, Relationship[]>();\n\n for (const rel of relationships) {\n const key = by === \"source\" ? rel.source : rel.target;\n if (!grouped.has(key)) {\n grouped.set(key, []);\n }\n grouped.get(key)!.push(rel);\n }\n\n return grouped;\n }\n\n /**\n * Find an element in the model by ID\n *\n * Performs element lookup using Layer.getElement(), which supports multiple lookup strategies:\n * - Direct lookup by graph key (path)\n * - UUID lookup\n * - Semantic ID lookup (for legacy format elements converted to UUID)\n * - Element ID lookup\n *\n * This comprehensive lookup ensures elements are found regardless of how they were stored.\n */\n private findElementInModel(model: Model, elementId: string) {\n for (const layer of model.layers.values()) {\n const element = layer.getElement(elementId);\n if (element) {\n return element;\n }\n }\n return null;\n }\n\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["result"]
7
+ }
@@ -4,95 +4,177 @@ import { ValidationResult } from "./types.js";
4
4
  import { fileURLToPath } from "url";
5
5
  import path from "path";
6
6
  import { readFile } from "../utils/file-io.js";
7
- import { startSpan, endSpan } from "../telemetry/index.js";
7
+ import { startActiveSpan } from "../telemetry/index.js";
8
+ import { existsSync } from "fs";
9
+ import { validateSpecNode, validateSpecNodeRelationship, validateSourceReference, validateAttributeSpec } from "../generated/compiled-validators.js";
8
10
  const isTelemetryEnabled = !1;
9
11
  class SchemaValidator {
12
+ // Static shared AJV instance to avoid schema registry conflicts
13
+ // When multiple SchemaValidator instances are created, they all use the same
14
+ // AJV instance which maintains a single schema registry. This prevents
15
+ // "schema with key or id already exists" errors when loading the same schemas.
16
+ static sharedAjv = null;
17
+ static baseSchemaLoadedInSharedInstance = !1;
18
+ // Track schema IDs already registered in the shared AJV instance to avoid re-registering
19
+ static registeredSchemaIds = /* @__PURE__ */ new Set();
20
+ // Layer-level cache: layer ID → nodeSchemas map (avoids re-reading layer dist files)
21
+ static layerCache = /* @__PURE__ */ new Map();
10
22
  ajv;
11
23
  compiledSchemas = /* @__PURE__ */ new Map();
12
- schemasLoaded = !1;
24
+ // Key: layer.type
25
+ bundledDir;
13
26
  constructor() {
14
- this.ajv = new Ajv({
27
+ SchemaValidator.sharedAjv || (SchemaValidator.sharedAjv = new Ajv({
15
28
  allErrors: !0,
16
29
  strict: !1,
17
- validateFormats: !0
18
- }), addFormats(this.ajv);
30
+ validateFormats: !0,
31
+ verbose: !0
32
+ }), addFormats(SchemaValidator.sharedAjv)), this.ajv = SchemaValidator.sharedAjv;
33
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
34
+ this.bundledDir = path.join(__dirname, "..", "schemas", "bundled");
19
35
  }
20
36
  /**
21
- * Ensure schemas are precompiled before validation
37
+ * Extract error message from unknown error type with proper type checking
22
38
  */
23
- async ensureSchemasLoaded() {
24
- this.schemasLoaded || (await this.precompileSchemas(), this.schemasLoaded = !0);
39
+ static getErrorMessage(error) {
40
+ if (error instanceof Error)
41
+ return error.message;
42
+ if (typeof error == "object" && error !== null && "message" in error) {
43
+ const msg = error.message;
44
+ if (typeof msg == "string")
45
+ return msg;
46
+ }
47
+ return "Unknown error";
25
48
  }
26
49
  /**
27
- * Precompile all layer schemas for efficient validation
50
+ * Ensure base schemas are pre-compiled validators are initialized
51
+ * Loads base schemas from base.json and registers them with AJV for reference resolution.
52
+ * This allows per-type schemas to resolve $ref to base schemas during compilation.
28
53
  */
29
- async precompileSchemas() {
30
- const layerMappings = {
31
- motivation: "01-motivation-layer.schema.json",
32
- business: "02-business-layer.schema.json",
33
- security: "03-security-layer.schema.json",
34
- application: "04-application-layer.schema.json",
35
- technology: "05-technology-layer.schema.json",
36
- api: "06-api-layer.schema.json",
37
- "data-model": "07-data-model-layer.schema.json",
38
- "data-store": "08-datastore-layer.schema.json",
39
- ux: "09-ux-layer.schema.json",
40
- navigation: "10-navigation-layer.schema.json",
41
- apm: "11-apm-observability-layer.schema.json",
42
- testing: "12-testing-layer.schema.json"
43
- }, __dirname = path.dirname(fileURLToPath(import.meta.url)), schemasDir = path.join(__dirname, "..", "schemas", "bundled"), commonSchemas = [
44
- "common/source-references.schema.json",
45
- "common/predicates.schema.json",
46
- "common/relationships.schema.json",
47
- "common/layer-extensions.schema.json"
48
- ];
49
- for (const commonSchemaFile of commonSchemas)
50
- try {
51
- const schemaPath = path.join(schemasDir, commonSchemaFile), schemaContent = await readFile(schemaPath), schema = JSON.parse(schemaContent);
52
- this.ajv.addSchema(schema);
53
- } catch (error) {
54
- console.warn(`Warning: Failed to load common schema ${commonSchemaFile}:`, error);
54
+ async ensureBaseSchemaLoaded() {
55
+ if (SchemaValidator.baseSchemaLoadedInSharedInstance)
56
+ return;
57
+ this.compiledSchemas.set("spec-node", validateSpecNode), this.compiledSchemas.set("spec-node-relationship", validateSpecNodeRelationship), this.compiledSchemas.set("source-references", validateSourceReference), this.compiledSchemas.set("attribute-spec", validateAttributeSpec);
58
+ const baseJsonPath = path.join(this.bundledDir, "base.json");
59
+ try {
60
+ const baseContent = await readFile(baseJsonPath), baseData = JSON.parse(baseContent);
61
+ for (const [key, schema] of Object.entries(baseData.schemas)) {
62
+ const s = schema, schemaId = s.$id || `urn:dr:spec:base:${key}`;
63
+ if (!SchemaValidator.registeredSchemaIds.has(schemaId))
64
+ try {
65
+ this.ajv.addSchema(s, schemaId), SchemaValidator.registeredSchemaIds.add(schemaId);
66
+ } catch (error) {
67
+ const errorMsg = SchemaValidator.getErrorMessage(error);
68
+ /schema with key or id [^ ]+ already exists/.test(errorMsg) ? SchemaValidator.registeredSchemaIds.add(schemaId) : console.warn(`[SCHEMA-LOAD-001] Failed to register base schema '${key}': ${errorMsg}`);
69
+ }
55
70
  }
56
- for (const [layerName, schemaFileName] of Object.entries(layerMappings))
71
+ } catch (error) {
72
+ console.warn(`[SCHEMA-LOAD-001] Failed to load base.json from ${baseJsonPath}: ${SchemaValidator.getErrorMessage(error)}`);
73
+ }
74
+ SchemaValidator.baseSchemaLoadedInSharedInstance = !0;
75
+ }
76
+ /**
77
+ * Load the nodeSchemas map for a layer, using a static layer-level cache.
78
+ * Reads bundled/{layer}.json once per layer and caches the nodeSchemas object.
79
+ */
80
+ async loadLayerNodeSchemas(layer) {
81
+ if (SchemaValidator.layerCache.has(layer))
82
+ return SchemaValidator.layerCache.get(layer);
83
+ const layerPath = path.join(this.bundledDir, `${layer}.json`);
84
+ if (!existsSync(layerPath))
85
+ return null;
86
+ try {
87
+ const content = await readFile(layerPath), nodeSchemas = JSON.parse(content).nodeSchemas || {};
88
+ return SchemaValidator.layerCache.set(layer, nodeSchemas), nodeSchemas;
89
+ } catch (error) {
90
+ return console.warn(`[SCHEMA-LOAD-002] Failed to load layer file for '${layer}': ${SchemaValidator.getErrorMessage(error)}`), null;
91
+ }
92
+ }
93
+ /**
94
+ * Load and compile a spec node schema for a specific element type
95
+ */
96
+ async loadSpecNodeSchema(layer, type) {
97
+ const cacheKey = `${layer}.${type}`;
98
+ if (this.compiledSchemas.has(cacheKey))
99
+ return this.compiledSchemas.get(cacheKey);
100
+ await this.ensureBaseSchemaLoaded();
101
+ try {
102
+ const nodeSchemas = await this.loadLayerNodeSchemas(layer);
103
+ if (!nodeSchemas || !nodeSchemas[type])
104
+ return null;
105
+ const schema = nodeSchemas[type], schemaId = schema.$id || `urn:dr:spec:node:${layer}.${type}`;
106
+ if (SchemaValidator.registeredSchemaIds.has(schemaId))
107
+ return this.compiledSchemas.get(cacheKey) || null;
57
108
  try {
58
- let schemaPath = path.join(schemasDir, schemaFileName), schemaContent;
59
- try {
60
- schemaContent = await readFile(schemaPath);
61
- } catch {
62
- schemaPath = path.join(__dirname, "schemas", "bundled", schemaFileName), schemaContent = await readFile(schemaPath);
63
- }
64
- const schema = JSON.parse(schemaContent), validate = this.ajv.compile(schema);
65
- this.compiledSchemas.set(layerName, validate);
66
- } catch (error) {
67
- console.warn(`Failed to load schema for layer ${layerName}:`, error);
109
+ const validate = this.ajv.compile(schema);
110
+ return this.compiledSchemas.set(cacheKey, validate), SchemaValidator.registeredSchemaIds.add(schemaId), validate;
111
+ } catch (registrationError) {
112
+ if (SchemaValidator.getErrorMessage(registrationError).includes("already exists"))
113
+ return SchemaValidator.registeredSchemaIds.add(schemaId), null;
114
+ throw registrationError;
68
115
  }
116
+ } catch (error) {
117
+ const errorMsg = SchemaValidator.getErrorMessage(error);
118
+ return errorMsg.includes("can't resolve reference") || console.warn(`[SCHEMA-LOAD-002] Failed to load spec node schema for ${layer}.${type}: ${errorMsg}`), null;
119
+ }
69
120
  }
70
121
  /**
71
- * Validate a layer against its schema
122
+ * Validate a layer against its spec node schemas
123
+ *
124
+ * This validates each element in the layer against its corresponding spec node schema.
72
125
  */
73
126
  async validateLayer(layer) {
74
- const span = isTelemetryEnabled ? startSpan("schema.validate", {
75
- "schema.layer": layer.name
76
- }) : null;
77
- try {
127
+ return startActiveSpan("schema.validate", async (span) => {
78
128
  const result = new ValidationResult();
79
- await this.ensureSchemasLoaded();
80
- const validate = this.compiledSchemas.get(layer.name);
81
- if (!validate)
82
- return isTelemetryEnabled && span && (span.setAttribute("schema.valid", !0), span.setAttribute("schema.skipped", !0)), result;
83
- const layerData = layer.toJSON(), valid = validate(layerData);
84
- if (!valid && validate.errors)
85
- for (const error of validate.errors)
86
- result.addError({
87
- layer: layer.name,
88
- message: `Schema validation failed: ${this.formatAjvError(error)}`,
89
- location: error.instancePath || "/",
90
- fixSuggestion: this.generateFixSuggestion(error)
91
- });
92
- return isTelemetryEnabled && span && (span.setAttribute("schema.valid", valid), span.setAttribute("schema.error_count", validate.errors?.length || 0)), result;
93
- } finally {
94
- endSpan(span);
129
+ let validatedCount = 0, skippedCount = 0;
130
+ for (const element of layer.elements.values()) {
131
+ const elementResult = await this.validateElement(element, layer.name);
132
+ if (elementResult.hasErrors)
133
+ for (const error of elementResult.errors)
134
+ result.addError({
135
+ layer: layer.name,
136
+ elementId: element.path || element.id,
137
+ message: error.message,
138
+ location: error.location,
139
+ fixSuggestion: error.fixSuggestion
140
+ });
141
+ elementResult.validated ? validatedCount++ : skippedCount++;
142
+ }
143
+ return isTelemetryEnabled && (span.setAttribute("schema.valid", result.isValid()), span.setAttribute("schema.error_count", result.errors.length), span.setAttribute("schema.validated_count", validatedCount), span.setAttribute("schema.skipped_count", skippedCount)), result;
144
+ }, { "schema.layer": layer.name });
145
+ }
146
+ /**
147
+ * Validate a single element against its spec node schema
148
+ */
149
+ async validateElement(element, layerName) {
150
+ const result = {
151
+ validated: !1,
152
+ hasErrors: !1,
153
+ errors: []
154
+ }, specNode = element.toSpecNode();
155
+ if (await this.ensureBaseSchemaLoaded(), !validateSpecNode(specNode) && validateSpecNode.errors) {
156
+ result.hasErrors = !0, result.validated = !0;
157
+ for (const error of validateSpecNode.errors)
158
+ result.errors.push({
159
+ message: `Element '${element.path || element.id}': ${this.formatAjvError(error)}`,
160
+ location: error.instancePath || "/",
161
+ fixSuggestion: this.generateFixSuggestion(error)
162
+ });
163
+ return result;
164
+ }
165
+ const validate = await this.loadSpecNodeSchema(layerName, element.type);
166
+ if (!validate)
167
+ return result.validated = !0, result;
168
+ if (result.validated = !0, !validate(specNode) && validate.errors) {
169
+ result.hasErrors = !0;
170
+ for (const error of validate.errors)
171
+ result.errors.push({
172
+ message: `Element '${element.path || element.id}': ${this.formatAjvError(error)}`,
173
+ location: error.instancePath || "/",
174
+ fixSuggestion: this.generateFixSuggestion(error)
175
+ });
95
176
  }
177
+ return result;
96
178
  }
97
179
  /**
98
180
  * Format AJV error message
@@ -101,7 +183,7 @@ class SchemaValidator {
101
183
  const keyword = error.keyword, dataPath = error.instancePath || "/";
102
184
  switch (keyword) {
103
185
  case "type":
104
- return `At ${dataPath}: expected ${error.params?.type}, got ${typeof error.data}`;
186
+ return `At ${dataPath}: expected ${error.params?.type}, got ${error.data === null ? "null" : typeof error.data}`;
105
187
  case "required":
106
188
  return `At ${dataPath}: missing required property '${error.params?.missingProperty}'`;
107
189
  case "enum":
@@ -118,6 +200,8 @@ class SchemaValidator {
118
200
  return `At ${dataPath}: value must be <= ${error.params?.limit}`;
119
201
  case "additionalProperties":
120
202
  return `At ${dataPath}: unexpected property '${error.params?.additionalProperty}'`;
203
+ case "const":
204
+ return `At ${dataPath}: value must be ${JSON.stringify(error.params?.allowedValue)}`;
121
205
  default:
122
206
  return error.message || "Validation failed";
123
207
  }
@@ -137,10 +221,26 @@ class SchemaValidator {
137
221
  return `Value must match pattern: ${error.params?.pattern}`;
138
222
  case "additionalProperties":
139
223
  return `Remove unexpected property: ${error.params?.additionalProperty}`;
224
+ case "const":
225
+ return `Value must be exactly: ${JSON.stringify(error.params?.allowedValue)}`;
140
226
  default:
141
227
  return;
142
228
  }
143
229
  }
230
+ /**
231
+ * Reset static state for test isolation
232
+ *
233
+ * Clears the shared AJV instance and schema registry, allowing tests
234
+ * to load schemas from a clean state without conflicts.
235
+ *
236
+ * Usage (in test setup):
237
+ * beforeEach(() => {
238
+ * SchemaValidator.reset();
239
+ * });
240
+ */
241
+ static reset() {
242
+ SchemaValidator.sharedAjv = null, SchemaValidator.baseSchemaLoadedInSharedInstance = !1, SchemaValidator.registeredSchemaIds.clear(), SchemaValidator.layerCache.clear();
243
+ }
144
244
  }
145
245
  export {
146
246
  SchemaValidator