@documentation-robotics/cli 0.1.13 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (351) hide show
  1. package/README.md +74 -2
  2. package/dist/analysis/relationship-classifier.js +2 -9
  3. package/dist/analysis/relationship-classifier.js.map +2 -2
  4. package/dist/analyzers/call-graph-utils.js +2 -2
  5. package/dist/analyzers/call-graph-utils.js.map +2 -2
  6. package/dist/analyzers/cards/build.js +171 -0
  7. package/dist/analyzers/cards/build.js.map +7 -0
  8. package/dist/analyzers/cards/classify.js +42 -0
  9. package/dist/analyzers/cards/classify.js.map +7 -0
  10. package/dist/analyzers/cards/edge-index.js +44 -0
  11. package/dist/analyzers/cards/edge-index.js.map +7 -0
  12. package/dist/analyzers/cards/facts.js +46 -0
  13. package/dist/analyzers/cards/facts.js.map +7 -0
  14. package/dist/analyzers/cards/graph.js +42 -0
  15. package/dist/analyzers/cards/graph.js.map +7 -0
  16. package/dist/analyzers/cards/index.js +8 -0
  17. package/dist/analyzers/cards/index.js.map +7 -0
  18. package/dist/analyzers/cards/tier0.js +18 -0
  19. package/dist/analyzers/cards/tier0.js.map +7 -0
  20. package/dist/analyzers/cards/types.js +1 -0
  21. package/dist/analyzers/cards/types.js.map +7 -0
  22. package/dist/analyzers/cbm-analyzer.js +195 -171
  23. package/dist/analyzers/cbm-analyzer.js.map +2 -2
  24. package/dist/analyzers/cbm-responses.js +336 -0
  25. package/dist/analyzers/cbm-responses.js.map +7 -0
  26. package/dist/analyzers/edge-relationships.js +45 -0
  27. package/dist/analyzers/edge-relationships.js.map +7 -0
  28. package/dist/analyzers/mapping-loader.js +9 -0
  29. package/dist/analyzers/mapping-loader.js.map +2 -2
  30. package/dist/analyzers/tier0-rules.js +153 -0
  31. package/dist/analyzers/tier0-rules.js.map +7 -0
  32. package/dist/analyzers/types.js.map +1 -1
  33. package/dist/analyzers/verify-engine.js +22 -19
  34. package/dist/analyzers/verify-engine.js.map +2 -2
  35. package/dist/audit/relationships/ai/prompts.js +10 -10
  36. package/dist/audit/relationships/ai/prompts.js.map +2 -2
  37. package/dist/audit/relationships/analysis/layer-templates.js +52 -52
  38. package/dist/audit/relationships/analysis/layer-templates.js.map +1 -1
  39. package/dist/cli.js +112 -28
  40. package/dist/cli.js.map +3 -3
  41. package/dist/coding-agents/agents/claude-code.js +3 -1
  42. package/dist/coding-agents/agents/claude-code.js.map +2 -2
  43. package/dist/coding-agents/claude-code-client.js +2 -1
  44. package/dist/coding-agents/claude-code-client.js.map +2 -2
  45. package/dist/coding-agents/copilot-client.js +17 -12
  46. package/dist/coding-agents/copilot-client.js.map +2 -2
  47. package/dist/coding-agents/default-permissions.js +152 -0
  48. package/dist/coding-agents/default-permissions.js.map +7 -0
  49. package/dist/coding-agents/drbot-mcp.js +167 -0
  50. package/dist/coding-agents/drbot-mcp.js.map +7 -0
  51. package/dist/commands/add.js +12 -7
  52. package/dist/commands/add.js.map +2 -2
  53. package/dist/commands/analyzer.js +134 -23
  54. package/dist/commands/analyzer.js.map +2 -2
  55. package/dist/commands/audit.js +6 -6
  56. package/dist/commands/audit.js.map +2 -2
  57. package/dist/commands/changeset-verify.js +55 -0
  58. package/dist/commands/changeset-verify.js.map +7 -0
  59. package/dist/commands/changeset.js +261 -147
  60. package/dist/commands/changeset.js.map +2 -2
  61. package/dist/commands/delete.js +52 -19
  62. package/dist/commands/delete.js.map +3 -3
  63. package/dist/commands/element.js +3 -2
  64. package/dist/commands/element.js.map +2 -2
  65. package/dist/commands/farm.js +773 -0
  66. package/dist/commands/farm.js.map +7 -0
  67. package/dist/commands/init.js +11 -1
  68. package/dist/commands/init.js.map +2 -2
  69. package/dist/commands/mcp.js +122 -0
  70. package/dist/commands/mcp.js.map +7 -0
  71. package/dist/commands/relationship.js +251 -47
  72. package/dist/commands/relationship.js.map +2 -2
  73. package/dist/commands/reports.js +1 -1
  74. package/dist/commands/reports.js.map +2 -2
  75. package/dist/commands/update.js +56 -24
  76. package/dist/commands/update.js.map +2 -2
  77. package/dist/commands/upgrade.js +99 -11
  78. package/dist/commands/upgrade.js.map +3 -3
  79. package/dist/commands/validate.js +41 -3
  80. package/dist/commands/validate.js.map +2 -2
  81. package/dist/commands/version.js +2 -2
  82. package/dist/commands/version.js.map +2 -2
  83. package/dist/commands/visualize.js +4 -3
  84. package/dist/commands/visualize.js.map +2 -2
  85. package/dist/config.js +83 -0
  86. package/dist/config.js.map +7 -0
  87. package/dist/core/annotation-store.js +138 -0
  88. package/dist/core/annotation-store.js.map +7 -0
  89. package/dist/core/catalog-migration.js +33 -0
  90. package/dist/core/catalog-migration.js.map +7 -0
  91. package/dist/core/changeset-analysis.js +421 -0
  92. package/dist/core/changeset-analysis.js.map +7 -0
  93. package/dist/core/changeset-base-index.js +153 -0
  94. package/dist/core/changeset-base-index.js.map +7 -0
  95. package/dist/core/changeset-brief-delta.js +102 -0
  96. package/dist/core/changeset-brief-delta.js.map +7 -0
  97. package/dist/core/changeset-graph.js +281 -0
  98. package/dist/core/changeset-graph.js.map +7 -0
  99. package/dist/core/changeset-only-guard.js +32 -0
  100. package/dist/core/changeset-only-guard.js.map +7 -0
  101. package/dist/core/changeset-summary.js +68 -0
  102. package/dist/core/changeset-summary.js.map +7 -0
  103. package/dist/core/changeset-validator.js +66 -20
  104. package/dist/core/changeset-validator.js.map +2 -2
  105. package/dist/core/changeset-verify.js +518 -0
  106. package/dist/core/changeset-verify.js.map +7 -0
  107. package/dist/core/changeset.js +56 -9
  108. package/dist/core/changeset.js.map +2 -2
  109. package/dist/core/element-type-migration.js +145 -0
  110. package/dist/core/element-type-migration.js.map +7 -0
  111. package/dist/core/farm-manifest.js +140 -0
  112. package/dist/core/farm-manifest.js.map +7 -0
  113. package/dist/core/farm-sync-engine.js +346 -0
  114. package/dist/core/farm-sync-engine.js.map +7 -0
  115. package/dist/core/farm-sync-state.js +167 -0
  116. package/dist/core/farm-sync-state.js.map +7 -0
  117. package/dist/core/manifest.js +6 -3
  118. package/dist/core/manifest.js.map +2 -2
  119. package/dist/core/migration-registry.js +84 -1
  120. package/dist/core/migration-registry.js.map +2 -2
  121. package/dist/core/model-upgrade.js +102 -0
  122. package/dist/core/model-upgrade.js.map +7 -0
  123. package/dist/core/model.js +257 -44
  124. package/dist/core/model.js.map +3 -3
  125. package/dist/core/mutation-handler.js +71 -6
  126. package/dist/core/mutation-handler.js.map +2 -2
  127. package/dist/core/relationship-migration-table.js +6639 -0
  128. package/dist/core/relationship-migration-table.js.map +7 -0
  129. package/dist/core/relationship-migration.js +272 -0
  130. package/dist/core/relationship-migration.js.map +7 -0
  131. package/dist/core/relationship-prune.js +49 -0
  132. package/dist/core/relationship-prune.js.map +7 -0
  133. package/dist/core/relationship-staging.js +68 -0
  134. package/dist/core/relationship-staging.js.map +7 -0
  135. package/dist/core/relationships.js +26 -4
  136. package/dist/core/relationships.js.map +2 -2
  137. package/dist/core/report-data-model.js +7 -13
  138. package/dist/core/report-data-model.js.map +2 -2
  139. package/dist/core/staged-changeset-storage.js +64 -17
  140. package/dist/core/staged-changeset-storage.js.map +2 -2
  141. package/dist/core/staged-source-reference.js +28 -0
  142. package/dist/core/staged-source-reference.js.map +7 -0
  143. package/dist/core/staging-area.js +83 -18
  144. package/dist/core/staging-area.js.map +2 -2
  145. package/dist/core/virtual-projection.js +14 -5
  146. package/dist/core/virtual-projection.js.map +2 -2
  147. package/dist/export/archimate-exporter.js +1 -0
  148. package/dist/export/archimate-exporter.js.map +2 -2
  149. package/dist/generated/compiled-validators.js +2 -2
  150. package/dist/generated/compiled-validators.js.map +2 -2
  151. package/dist/generated/layer-registry.js +16 -16
  152. package/dist/generated/layer-registry.js.map +2 -2
  153. package/dist/generated/node-types.js +186 -168
  154. package/dist/generated/node-types.js.map +2 -2
  155. package/dist/generated/relationship-index.js +2209 -7727
  156. package/dist/generated/relationship-index.js.map +2 -2
  157. package/dist/integrations/claude_code/agents/dr-architect.md +16 -3
  158. package/dist/integrations/claude_code/agents/dr-extractor.md +1 -1
  159. package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
  160. package/dist/integrations/claude_code/commands/dr-map.md +13 -0
  161. package/dist/integrations/claude_code/commands/dr-plan.md +40 -0
  162. package/dist/integrations/claude_code/commands/dr-relate.md +106 -96
  163. package/dist/integrations/claude_code/commands/dr-verify.md +45 -2
  164. package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +14 -0
  165. package/dist/integrations/claude_code/docs/MCP_SERVER.md +79 -0
  166. package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
  167. package/dist/integrations/claude_code/docs/README.md +52 -1
  168. package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +93 -72
  169. package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +163 -126
  170. package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +161 -126
  171. package/dist/integrations/claude_code/skills/dr_04_security_layer/SKILL.md +153 -133
  172. package/dist/integrations/claude_code/skills/dr_05_application_layer/SKILL.md +129 -100
  173. package/dist/integrations/claude_code/skills/dr_06_technology_layer/SKILL.md +111 -94
  174. package/dist/integrations/claude_code/skills/dr_07_api_layer/SKILL.md +143 -157
  175. package/dist/integrations/claude_code/skills/dr_08_data_model_layer/SKILL.md +117 -33
  176. package/dist/integrations/claude_code/skills/dr_09_dataset_layer/SKILL.md +74 -25
  177. package/dist/integrations/claude_code/skills/dr_10_ux_layer/SKILL.md +88 -12
  178. package/dist/integrations/claude_code/skills/dr_11_navigation_layer/SKILL.md +83 -24
  179. package/dist/integrations/claude_code/skills/dr_12_apm_layer/SKILL.md +57 -12
  180. package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +92 -15
  181. package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +119 -0
  182. package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +3 -0
  183. package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +1 -1
  184. package/dist/integrations/github_copilot/agents/dr-architect.md +1 -1
  185. package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +182 -102
  186. package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +266 -148
  187. package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +547 -86
  188. package/dist/integrations/github_copilot/skills/dr_04_security_layer/SKILL.md +164 -169
  189. package/dist/integrations/github_copilot/skills/dr_05_application_layer/SKILL.md +420 -150
  190. package/dist/integrations/github_copilot/skills/dr_06_technology_layer/SKILL.md +331 -138
  191. package/dist/integrations/github_copilot/skills/dr_07_api_layer/SKILL.md +265 -201
  192. package/dist/integrations/github_copilot/skills/dr_08_data_model_layer/SKILL.md +181 -58
  193. package/dist/integrations/github_copilot/skills/dr_09_dataset_layer/SKILL.md +209 -57
  194. package/dist/integrations/github_copilot/skills/dr_10_ux_layer/SKILL.md +277 -112
  195. package/dist/integrations/github_copilot/skills/dr_11_navigation_layer/SKILL.md +219 -70
  196. package/dist/integrations/github_copilot/skills/dr_12_apm_layer/SKILL.md +189 -105
  197. package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +222 -102
  198. package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +119 -0
  199. package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +3 -0
  200. package/dist/integrations/hash-utils.js.map +2 -2
  201. package/dist/mcp/api-key-manager.js +111 -0
  202. package/dist/mcp/api-key-manager.js.map +7 -0
  203. package/dist/mcp/changeset-only.js +34 -0
  204. package/dist/mcp/changeset-only.js.map +7 -0
  205. package/dist/mcp/http-transport.js +176 -0
  206. package/dist/mcp/http-transport.js.map +7 -0
  207. package/dist/mcp/resource-registry.js +97 -0
  208. package/dist/mcp/resource-registry.js.map +7 -0
  209. package/dist/mcp/tool-registry.js +60 -0
  210. package/dist/mcp/tool-registry.js.map +7 -0
  211. package/dist/mcp/tools/annotation-create.js +39 -0
  212. package/dist/mcp/tools/annotation-create.js.map +7 -0
  213. package/dist/mcp/tools/annotation-delete.js +29 -0
  214. package/dist/mcp/tools/annotation-delete.js.map +7 -0
  215. package/dist/mcp/tools/annotation-get.js +29 -0
  216. package/dist/mcp/tools/annotation-get.js.map +7 -0
  217. package/dist/mcp/tools/annotation-list.js +24 -0
  218. package/dist/mcp/tools/annotation-list.js.map +7 -0
  219. package/dist/mcp/tools/annotation-reply.js +34 -0
  220. package/dist/mcp/tools/annotation-reply.js.map +7 -0
  221. package/dist/mcp/tools/annotation-shared.js +10 -0
  222. package/dist/mcp/tools/annotation-shared.js.map +7 -0
  223. package/dist/mcp/tools/annotation-update.js +39 -0
  224. package/dist/mcp/tools/annotation-update.js.map +7 -0
  225. package/dist/mcp/tools/changeset-analysis-tools.js +78 -0
  226. package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
  227. package/dist/mcp/tools/changeset-editing.js +184 -0
  228. package/dist/mcp/tools/changeset-editing.js.map +7 -0
  229. package/dist/mcp/tools/changeset-lifecycle.js +83 -0
  230. package/dist/mcp/tools/changeset-lifecycle.js.map +7 -0
  231. package/dist/mcp/tools/changeset-list.js +33 -0
  232. package/dist/mcp/tools/changeset-list.js.map +7 -0
  233. package/dist/mcp/tools/changeset-shared.js +31 -0
  234. package/dist/mcp/tools/changeset-shared.js.map +7 -0
  235. package/dist/mcp/tools/changeset-show.js +55 -0
  236. package/dist/mcp/tools/changeset-show.js.map +7 -0
  237. package/dist/mcp/tools/changeset-verify.js +43 -0
  238. package/dist/mcp/tools/changeset-verify.js.map +7 -0
  239. package/dist/mcp/tools/chat-launch.js +50 -0
  240. package/dist/mcp/tools/chat-launch.js.map +7 -0
  241. package/dist/mcp/tools/chat-status.js +26 -0
  242. package/dist/mcp/tools/chat-status.js.map +7 -0
  243. package/dist/mcp/tools/index.js +65 -0
  244. package/dist/mcp/tools/index.js.map +7 -0
  245. package/dist/mcp/tools/model-add.js +107 -0
  246. package/dist/mcp/tools/model-add.js.map +7 -0
  247. package/dist/mcp/tools/model-delete.js +99 -0
  248. package/dist/mcp/tools/model-delete.js.map +7 -0
  249. package/dist/mcp/tools/model-export.js +72 -0
  250. package/dist/mcp/tools/model-export.js.map +7 -0
  251. package/dist/mcp/tools/model-info.js +51 -0
  252. package/dist/mcp/tools/model-info.js.map +7 -0
  253. package/dist/mcp/tools/model-list.js +53 -0
  254. package/dist/mcp/tools/model-list.js.map +7 -0
  255. package/dist/mcp/tools/model-reload.js +34 -0
  256. package/dist/mcp/tools/model-reload.js.map +7 -0
  257. package/dist/mcp/tools/model-search.js +102 -0
  258. package/dist/mcp/tools/model-search.js.map +7 -0
  259. package/dist/mcp/tools/model-show.js +68 -0
  260. package/dist/mcp/tools/model-show.js.map +7 -0
  261. package/dist/mcp/tools/model-stats.js +22 -0
  262. package/dist/mcp/tools/model-stats.js.map +7 -0
  263. package/dist/mcp/tools/model-trace.js +118 -0
  264. package/dist/mcp/tools/model-trace.js.map +7 -0
  265. package/dist/mcp/tools/model-update.js +121 -0
  266. package/dist/mcp/tools/model-update.js.map +7 -0
  267. package/dist/mcp/tools/model-validate.js +78 -0
  268. package/dist/mcp/tools/model-validate.js.map +7 -0
  269. package/dist/mcp/tools/relationship-tools.js +98 -0
  270. package/dist/mcp/tools/relationship-tools.js.map +7 -0
  271. package/dist/mcp/tools/shared.js +126 -0
  272. package/dist/mcp/tools/shared.js.map +7 -0
  273. package/dist/mcp/tools/spec-type.js +90 -0
  274. package/dist/mcp/tools/spec-type.js.map +7 -0
  275. package/dist/reports/model-layer-report-generator.js +5 -3
  276. package/dist/reports/model-layer-report-generator.js.map +2 -2
  277. package/dist/reports/model-readme-generator.js +116 -0
  278. package/dist/reports/model-readme-generator.js.map +7 -0
  279. package/dist/reports/model-report-data.js +37 -1
  280. package/dist/reports/model-report-data.js.map +2 -2
  281. package/dist/reports/model-report-orchestrator.js +29 -6
  282. package/dist/reports/model-report-orchestrator.js.map +2 -2
  283. package/dist/reports/model-report-utils.js +9 -0
  284. package/dist/reports/model-report-utils.js.map +7 -0
  285. package/dist/schemas/bundled/analyzers/cbm.json +1186 -68
  286. package/dist/schemas/bundled/analyzers/manifest.json +2 -2
  287. package/dist/schemas/bundled/api.json +1029 -1658
  288. package/dist/schemas/bundled/apm.json +499 -734
  289. package/dist/schemas/bundled/application.json +745 -370
  290. package/dist/schemas/bundled/base.json +3 -220
  291. package/dist/schemas/bundled/business.json +452 -366
  292. package/dist/schemas/bundled/data-model.json +403 -383
  293. package/dist/schemas/bundled/data-store.json +334 -558
  294. package/dist/schemas/bundled/manifest.json +18 -18
  295. package/dist/schemas/bundled/motivation.json +349 -368
  296. package/dist/schemas/bundled/navigation.json +415 -731
  297. package/dist/schemas/bundled/product.json +325 -159
  298. package/dist/schemas/bundled/security.json +864 -1232
  299. package/dist/schemas/bundled/technology.json +574 -703
  300. package/dist/schemas/bundled/testing.json +718 -1643
  301. package/dist/schemas/bundled/ux.json +739 -1352
  302. package/dist/server/schemas.js +8 -0
  303. package/dist/server/schemas.js.map +2 -2
  304. package/dist/server/server.js +234 -31
  305. package/dist/server/server.js.map +2 -2
  306. package/dist/server-entry.js +2 -1
  307. package/dist/server-entry.js.map +2 -2
  308. package/dist/telemetry/config.js +2 -40
  309. package/dist/telemetry/config.js.map +2 -2
  310. package/dist/telemetry/index.js +1 -1
  311. package/dist/types/index.js.map +2 -2
  312. package/dist/utils/ansi-suppressor.js +23 -0
  313. package/dist/utils/ansi-suppressor.js.map +7 -0
  314. package/dist/utils/attribute-patch.js +41 -0
  315. package/dist/utils/attribute-patch.js.map +7 -0
  316. package/dist/utils/color-utils.js +34 -0
  317. package/dist/utils/color-utils.js.map +7 -0
  318. package/dist/utils/element-reference-shape.js +15 -1
  319. package/dist/utils/element-reference-shape.js.map +3 -3
  320. package/dist/utils/error-hints.js +29 -0
  321. package/dist/utils/error-hints.js.map +7 -0
  322. package/dist/utils/errors.js +75 -10
  323. package/dist/utils/errors.js.map +2 -2
  324. package/dist/utils/globals.js +17 -1
  325. package/dist/utils/globals.js.map +2 -2
  326. package/dist/utils/project-paths.js +50 -3
  327. package/dist/utils/project-paths.js.map +2 -2
  328. package/dist/utils/reference-path-parser.js +49 -0
  329. package/dist/utils/reference-path-parser.js.map +7 -0
  330. package/dist/utils/source-reference.js +17 -10
  331. package/dist/utils/source-reference.js.map +2 -2
  332. package/dist/utils/spec-version.js +10 -2
  333. package/dist/utils/spec-version.js.map +2 -2
  334. package/dist/validators/composed-reference-validator.js +240 -0
  335. package/dist/validators/composed-reference-validator.js.map +7 -0
  336. package/dist/validators/reference-validator.js +57 -13
  337. package/dist/validators/reference-validator.js.map +3 -3
  338. package/dist/validators/relationship-schema-validator.js +33 -5
  339. package/dist/validators/relationship-schema-validator.js.map +3 -3
  340. package/dist/validators/schema-validator.js +27 -9
  341. package/dist/validators/schema-validator.js.map +2 -2
  342. package/dist/validators/types.js.map +1 -1
  343. package/dist/viewer/README.md +6 -6
  344. package/dist/viewer/assets/main-CxzcuU_o.js +84 -0
  345. package/dist/viewer/assets/main-j-7ULYpm.css +1 -0
  346. package/dist/viewer/index.html +2 -2
  347. package/dist/viewer/manifest.json +10 -10
  348. package/dist/viewer/version.json +1 -1
  349. package/package.json +7 -3
  350. package/dist/viewer/assets/main-B7AOQXXX.js +0 -81
  351. package/dist/viewer/assets/main-vdrFrG7q.css +0 -1
@@ -0,0 +1,240 @@
1
+ import { ValidationResult } from "./types.js";
2
+ import { Validator } from "./validator.js";
3
+ import { parseReferencePath, ReferencePathParseError } from "../utils/reference-path-parser.js";
4
+ import { getErrorMessage } from "../utils/errors.js";
5
+ import { promises as fs } from "fs";
6
+ import path from "path";
7
+ import { parse as parseYAML } from "yaml";
8
+ import { FarmManifest } from "../core/farm-manifest.js";
9
+ import { resolveModelDirectory } from "../utils/project-paths.js";
10
+ class ComposedReferenceValidator {
11
+ modelPathOverrides;
12
+ resolvedModels = /* @__PURE__ */ new Map();
13
+ referencedModels = /* @__PURE__ */ new Set();
14
+ resolutionDiagnostics = new ValidationResult();
15
+ constructor(modelPathOverrides = {}) {
16
+ this.modelPathOverrides = modelPathOverrides;
17
+ }
18
+ /**
19
+ * Create a ComposedReferenceValidator from a farm root
20
+ * Automatically builds model-path overrides from the farm's project entries
21
+ * @param farmRoot - Path to the farm root directory
22
+ * @returns ComposedReferenceValidator instance configured for the farm
23
+ */
24
+ static async fromFarm(farmRoot) {
25
+ const farmYamlPath = path.join(farmRoot, "farm.yaml"), manifest = await FarmManifest.load(farmYamlPath), modelPathOverrides = {};
26
+ for (const project of manifest.getAllProjects()) {
27
+ const modelFullPath = path.join(farmRoot, project.model);
28
+ modelPathOverrides[project.name] = modelFullPath;
29
+ }
30
+ return new ComposedReferenceValidator(modelPathOverrides);
31
+ }
32
+ async validateModel(model) {
33
+ const result = await new Validator().validateModel(model), declaredModels = /* @__PURE__ */ new Map();
34
+ for (const modelName of Object.keys(model.manifest.models || {}))
35
+ declaredModels.set(modelName.toLowerCase(), modelName);
36
+ this.referencedModels.clear(), this.resolvedModels.clear(), this.resolutionDiagnostics = new ValidationResult(), this.collectReferencedModels(model);
37
+ for (const declaredName of declaredModels.values())
38
+ await this.resolveExternalModel(declaredName);
39
+ result.merge(this.resolutionDiagnostics);
40
+ for (const [lowerModelName, declaredName] of declaredModels) {
41
+ const isReferenced = this.referencedModels.has(lowerModelName);
42
+ if (!this.resolvedModels.has(declaredName))
43
+ isReferenced ? result.addError({
44
+ layer: "manifest",
45
+ message: `External model '${declaredName}' is referenced in qualified paths but could not be resolved from the configured paths`,
46
+ category: "reference",
47
+ fixSuggestion: `Ensure the model exists on disk, or use '--model-path ${declaredName}=/path/to/project-root' to specify its location`
48
+ }) : result.addWarning({
49
+ layer: "manifest",
50
+ message: `External model '${declaredName}' is declared but not referenced in any qualified paths and could not be found on disk`,
51
+ category: "reference",
52
+ fixSuggestion: `Remove the declaration if not needed, or use '--model-path ${declaredName}=/path/to/project-root' to specify its location`
53
+ });
54
+ else if (isReferenced) {
55
+ const validationResult = this.validateQualifiedReferencesAgainstModel(model, declaredName);
56
+ result.merge(validationResult, `[Composed/${declaredName}]`);
57
+ }
58
+ }
59
+ return result;
60
+ }
61
+ collectReferencedModels(model) {
62
+ for (const [, layer] of model.layers)
63
+ for (const element of layer.listElements())
64
+ for (const ref of element.references || [])
65
+ if (ref.target.startsWith("@"))
66
+ try {
67
+ const parsed = parseReferencePath(ref.target);
68
+ parsed.modelName && this.referencedModels.add(parsed.modelName.toLowerCase());
69
+ } catch (error) {
70
+ if (error instanceof ReferencePathParseError)
71
+ continue;
72
+ throw error;
73
+ }
74
+ }
75
+ async resolveExternalModel(modelName) {
76
+ let modelPath = this.modelPathOverrides[modelName];
77
+ if (!modelPath) {
78
+ const lowerModelName = modelName.toLowerCase();
79
+ for (const [key, overridePath] of Object.entries(this.modelPathOverrides))
80
+ if (key.toLowerCase() === lowerModelName) {
81
+ modelPath = overridePath;
82
+ break;
83
+ }
84
+ }
85
+ if (modelPath)
86
+ try {
87
+ let rootStats;
88
+ try {
89
+ rootStats = await fs.stat(modelPath);
90
+ } catch (err) {
91
+ if (err?.code !== "ENOENT")
92
+ throw err;
93
+ }
94
+ if (rootStats && !rootStats.isDirectory()) {
95
+ this.resolutionDiagnostics.addWarning({
96
+ layer: "manifest",
97
+ message: `External model '${modelName}' path is not a directory: ${modelPath}`,
98
+ category: "reference",
99
+ fixSuggestion: `Ensure '--model-path ${modelName}=${modelPath}' points to a project root containing documentation-robotics/model/`
100
+ });
101
+ return;
102
+ }
103
+ const resolved = await resolveModelDirectory(modelPath);
104
+ if (!resolved) {
105
+ this.addResolutionDiagnostic(modelName, new Error("Model directory not found"), modelPath, !0);
106
+ return;
107
+ }
108
+ const modelDir = resolved.modelDir;
109
+ if (!resolved.isDirectory) {
110
+ this.resolutionDiagnostics.addWarning({
111
+ layer: "manifest",
112
+ message: `External model '${modelName}' path is not a directory: ${modelDir}`,
113
+ category: "reference",
114
+ fixSuggestion: `Ensure '--model-path ${modelName}=${modelPath}' points to a project root or model directory`
115
+ });
116
+ return;
117
+ }
118
+ const elementIds = /* @__PURE__ */ new Set();
119
+ let entries = [];
120
+ try {
121
+ entries = await fs.readdir(modelDir, { withFileTypes: !0 });
122
+ } catch (error) {
123
+ this.addResolutionDiagnostic(modelName, error, modelPath);
124
+ return;
125
+ }
126
+ for (const entry of entries)
127
+ if (entry.isDirectory() && /^\d+_/.test(entry.name)) {
128
+ const layerPath = path.join(modelDir, entry.name);
129
+ await this.collectElementsFromLayer(layerPath, elementIds);
130
+ }
131
+ this.resolvedModels.set(modelName, {
132
+ modelName,
133
+ elementIds
134
+ });
135
+ } catch (error) {
136
+ this.addResolutionDiagnostic(modelName, error, modelPath);
137
+ }
138
+ }
139
+ async collectElementsFromLayer(layerPath, elementIds) {
140
+ try {
141
+ const entries = await fs.readdir(layerPath, { withFileTypes: !0 });
142
+ for (const entry of entries)
143
+ if (entry.isFile() && (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml")))
144
+ try {
145
+ const content = await fs.readFile(path.join(layerPath, entry.name), "utf-8");
146
+ this.extractElementPathsFromYAML(content, elementIds, layerPath, entry.name);
147
+ } catch (error) {
148
+ const filePath = path.join(layerPath, entry.name);
149
+ this.resolutionDiagnostics.addWarning({
150
+ layer: "manifest",
151
+ message: `Failed to read YAML file at ${filePath}: ${getErrorMessage(error)}`,
152
+ category: "reference"
153
+ });
154
+ continue;
155
+ }
156
+ } catch (error) {
157
+ this.resolutionDiagnostics.addWarning({
158
+ layer: "manifest",
159
+ message: `Failed to read layer directory at ${layerPath}: ${getErrorMessage(error)}`,
160
+ category: "reference"
161
+ });
162
+ }
163
+ }
164
+ extractElementPathsFromYAML(content, elementIds, layerPath, fileName) {
165
+ try {
166
+ const parsed = parseYAML(content);
167
+ if (!parsed) {
168
+ this.resolutionDiagnostics.addWarning({
169
+ layer: "manifest",
170
+ message: `YAML file at ${path.join(layerPath, fileName)} parsed to empty content`,
171
+ category: "reference"
172
+ });
173
+ return;
174
+ }
175
+ const items = [];
176
+ if (parsed && typeof parsed == "object" && "elements" in parsed) {
177
+ const elements = parsed.elements;
178
+ Array.isArray(elements) && items.push(...elements);
179
+ } else if (Array.isArray(parsed))
180
+ items.push(...parsed);
181
+ else if (parsed && typeof parsed == "object" && !Array.isArray(parsed)) {
182
+ const values = Object.values(parsed);
183
+ values.length > 0 && values[0] && typeof values[0] == "object" && ("path" in values[0] || "id" in values[0]) ? items.push(...values) : items.push(parsed);
184
+ }
185
+ for (const item of items)
186
+ if (item && typeof item == "object") {
187
+ const elementPath = item.path || item.id;
188
+ elementPath && typeof elementPath == "string" && elementIds.add(elementPath.trim());
189
+ }
190
+ } catch (error) {
191
+ this.resolutionDiagnostics.addWarning({
192
+ layer: "manifest",
193
+ message: `Failed to parse YAML file at ${path.join(layerPath, fileName)}: ${getErrorMessage(error)}`,
194
+ category: "reference"
195
+ });
196
+ }
197
+ }
198
+ validateQualifiedReferencesAgainstModel(model, modelName) {
199
+ const result = new ValidationResult(), resolvedModel = this.resolvedModels.get(modelName);
200
+ if (!resolvedModel)
201
+ return result;
202
+ for (const [sourceLayerName, layer] of model.layers)
203
+ for (const element of layer.listElements()) {
204
+ const elementId = element.path || element.id;
205
+ for (const ref of element.references || [])
206
+ if (ref.target.startsWith("@"))
207
+ try {
208
+ const parsed = parseReferencePath(ref.target);
209
+ if (parsed.modelName?.toLowerCase() !== modelName.toLowerCase())
210
+ continue;
211
+ resolvedModel.elementIds.has(parsed.segment) || result.addError({
212
+ layer: sourceLayerName,
213
+ elementId,
214
+ message: `Broken qualified reference: target '${parsed.segment}' does not exist in external model '${modelName}'`,
215
+ category: "reference",
216
+ fixSuggestion: `Create element '${parsed.segment}' in model '${modelName}' or remove this reference`
217
+ });
218
+ } catch (error) {
219
+ if (error instanceof ReferencePathParseError)
220
+ continue;
221
+ throw error;
222
+ }
223
+ }
224
+ return result;
225
+ }
226
+ addResolutionDiagnostic(modelName, error, modelPath, isPathError = !1) {
227
+ const errorMsg = getErrorMessage(error), code = error?.code;
228
+ let message = "", fixSuggestion = "";
229
+ code === "EACCES" ? (message = `Permission denied accessing external model '${modelName}' at ${modelPath}. Check file permissions.`, fixSuggestion = `Ensure the model path is readable, or use '--model-path ${modelName}=/path/to/alternative-location' to specify another location`) : code === "EIO" ? (message = `I/O error reading external model '${modelName}' at ${modelPath}. There may be a filesystem or device issue.`, fixSuggestion = `Check the filesystem for errors, or provide an alternative model path via '--model-path ${modelName}=/path/to/alternative-location'`) : code === "ENOENT" || isPathError ? (message = `Could not find external model '${modelName}' \u2014 expected at ${modelPath}/documentation-robotics/model or ${modelPath}/model`, fixSuggestion = `Ensure the model exists at the specified path, or use '--model-path ${modelName}=/path/to/project-root' to specify its location`) : (message = `Failed to resolve external model '${modelName}' at ${modelPath}: ${errorMsg}`, fixSuggestion = `Check the path and filesystem permissions, or use '--model-path ${modelName}=/path/to/project-root' to specify a different location`), this.resolutionDiagnostics.addWarning({
230
+ layer: "manifest",
231
+ message,
232
+ category: "reference",
233
+ fixSuggestion
234
+ });
235
+ }
236
+ }
237
+ export {
238
+ ComposedReferenceValidator
239
+ };
240
+ //# sourceMappingURL=composed-reference-validator.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/validators/composed-reference-validator.ts"],
4
+ "sourcesContent": ["import { ValidationResult } from \"./types.js\";\nimport type { Model } from \"../core/model.js\";\nimport { Validator } from \"./validator.js\";\nimport { parseReferencePath, ReferencePathParseError } from \"../utils/reference-path-parser.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\nimport { promises as fs, Dirent, Stats } from \"fs\";\nimport path from \"path\";\nimport { parse as parseYAML } from \"yaml\";\nimport { FarmManifest } from \"../core/farm-manifest.js\";\nimport { resolveModelDirectory } from \"../utils/project-paths.js\";\n\ninterface ResolvedExternalModel {\n modelName: string;\n elementIds: Set<string>;\n}\n\nexport class ComposedReferenceValidator {\n private modelPathOverrides: Record<string, string>;\n private resolvedModels: Map<string, ResolvedExternalModel> = new Map();\n private referencedModels: Set<string> = new Set();\n private resolutionDiagnostics: ValidationResult = new ValidationResult();\n\n constructor(modelPathOverrides: Record<string, string> = {}) {\n this.modelPathOverrides = modelPathOverrides;\n }\n\n /**\n * Create a ComposedReferenceValidator from a farm root\n * Automatically builds model-path overrides from the farm's project entries\n * @param farmRoot - Path to the farm root directory\n * @returns ComposedReferenceValidator instance configured for the farm\n */\n static async fromFarm(farmRoot: string): Promise<ComposedReferenceValidator> {\n const farmYamlPath = path.join(farmRoot, \"farm.yaml\");\n const manifest = await FarmManifest.load(farmYamlPath);\n\n const modelPathOverrides: Record<string, string> = {};\n\n // Build model-path overrides for each project in the farm\n for (const project of manifest.getAllProjects()) {\n // For detached model layouts, point directly to the model folder.\n // The validator will look for manifest.yaml within this folder.\n // If the model is co-located (inside codebase), it will also find it\n // via the documentation-robotics/model/ path search.\n const modelFullPath = path.join(farmRoot, project.model);\n modelPathOverrides[project.name] = modelFullPath;\n }\n\n return new ComposedReferenceValidator(modelPathOverrides);\n }\n\n async validateModel(model: Model): Promise<ValidationResult> {\n const fullValidator = new Validator();\n const result = await fullValidator.validateModel(model);\n\n const declaredModels = new Map<string, string>();\n // Map from lowercase model name to declared model name (preserving original casing)\n for (const modelName of Object.keys(model.manifest.models || {})) {\n declaredModels.set(modelName.toLowerCase(), modelName);\n }\n\n // Clear all state to ensure fresh validation on reuse\n this.referencedModels.clear();\n this.resolvedModels.clear();\n this.resolutionDiagnostics = new ValidationResult();\n this.collectReferencedModels(model);\n\n for (const declaredName of declaredModels.values()) {\n await this.resolveExternalModel(declaredName);\n }\n\n // Merge any diagnostics from resolution phase\n result.merge(this.resolutionDiagnostics);\n\n for (const [lowerModelName, declaredName] of declaredModels) {\n // Check if model is referenced using case-insensitive comparison\n const isReferenced = this.referencedModels.has(lowerModelName);\n // Check if model is resolved using the declared name\n const isResolved = this.resolvedModels.has(declaredName);\n\n if (!isResolved) {\n if (isReferenced) {\n // Declared, referenced, but unresolvable \u2192 error with guidance\n result.addError({\n layer: \"manifest\",\n message: `External model '${declaredName}' is referenced in qualified paths but could not be resolved from the configured paths`,\n category: \"reference\",\n fixSuggestion: `Ensure the model exists on disk, or use '--model-path ${declaredName}=/path/to/project-root' to specify its location`,\n });\n } else {\n // Declared but unreferenced and unresolvable \u2192 warning only\n result.addWarning({\n layer: \"manifest\",\n message: `External model '${declaredName}' is declared but not referenced in any qualified paths and could not be found on disk`,\n category: \"reference\",\n fixSuggestion: `Remove the declaration if not needed, or use '--model-path ${declaredName}=/path/to/project-root' to specify its location`,\n });\n }\n } else if (isReferenced) {\n // Declared, referenced, and resolvable \u2192 validate qualified references\n const validationResult = this.validateQualifiedReferencesAgainstModel(model, declaredName);\n result.merge(validationResult, `[Composed/${declaredName}]`);\n }\n }\n\n return result;\n }\n\n private collectReferencedModels(model: Model): void {\n for (const [, layer] of model.layers) {\n for (const element of layer.listElements()) {\n for (const ref of element.references || []) {\n if (ref.target.startsWith(\"@\")) {\n try {\n const parsed = parseReferencePath(ref.target);\n if (parsed.modelName) {\n // Store lowercase for case-insensitive comparison with declared models\n this.referencedModels.add(parsed.modelName.toLowerCase());\n }\n } catch (error) {\n if (error instanceof ReferencePathParseError) {\n // Parse errors are already handled by base validator\n continue;\n } else {\n throw error;\n }\n }\n }\n }\n }\n }\n }\n\n private async resolveExternalModel(modelName: string): Promise<void> {\n // Try exact match first, then case-insensitive match\n let modelPath = this.modelPathOverrides[modelName];\n if (!modelPath) {\n // Case-insensitive lookup in modelPathOverrides\n const lowerModelName = modelName.toLowerCase();\n for (const [key, overridePath] of Object.entries(this.modelPathOverrides)) {\n if (key.toLowerCase() === lowerModelName) {\n modelPath = overridePath;\n break;\n }\n }\n }\n\n if (!modelPath) {\n return;\n }\n\n try {\n // Verify modelPath itself is a directory\n let rootStats: Stats | undefined;\n try {\n rootStats = await fs.stat(modelPath);\n } catch (err) {\n const errorCode = (err as NodeJS.ErrnoException)?.code;\n if (errorCode !== \"ENOENT\") {\n throw err;\n }\n }\n\n if (rootStats && !rootStats.isDirectory()) {\n this.resolutionDiagnostics.addWarning({\n layer: \"manifest\",\n message: `External model '${modelName}' path is not a directory: ${modelPath}`,\n category: \"reference\",\n fixSuggestion: `Ensure '--model-path ${modelName}=${modelPath}' points to a project root containing documentation-robotics/model/`,\n });\n return;\n }\n\n // Standard ({modelPath}/documentation-robotics/model/), legacy ({modelPath}/model/)\n // and detached (modelPath itself holds the layer directories) layouts, in that order\n const resolved = await resolveModelDirectory(modelPath);\n\n if (!resolved) {\n this.addResolutionDiagnostic(\n modelName,\n new Error(\"Model directory not found\"),\n modelPath,\n true\n );\n return;\n }\n\n const modelDir = resolved.modelDir;\n\n if (!resolved.isDirectory) {\n this.resolutionDiagnostics.addWarning({\n layer: \"manifest\",\n message: `External model '${modelName}' path is not a directory: ${modelDir}`,\n category: \"reference\",\n fixSuggestion: `Ensure '--model-path ${modelName}=${modelPath}' points to a project root or model directory`,\n });\n return;\n }\n\n const elementIds = new Set<string>();\n\n // Try to load all layer directories\n let entries: Dirent[] = [];\n try {\n entries = await fs.readdir(modelDir, { withFileTypes: true });\n } catch (error) {\n this.addResolutionDiagnostic(modelName, error, modelPath);\n return;\n }\n\n // Load elements from each layer directory\n for (const entry of entries) {\n if (entry.isDirectory() && /^\\d+_/.test(entry.name)) {\n // This looks like a layer directory (e.g., \"01_motivation\", \"08_data-model\")\n const layerPath = path.join(modelDir, entry.name);\n await this.collectElementsFromLayer(layerPath, elementIds);\n }\n }\n\n // Successfully resolved the model (track it even if empty)\n this.resolvedModels.set(modelName, {\n modelName,\n elementIds,\n });\n } catch (error) {\n // Failed to resolve model\n this.addResolutionDiagnostic(modelName, error, modelPath);\n }\n }\n\n private async collectElementsFromLayer(layerPath: string, elementIds: Set<string>): Promise<void> {\n try {\n const entries = await fs.readdir(layerPath, { withFileTypes: true });\n\n for (const entry of entries) {\n if (entry.isFile() && (entry.name.endsWith(\".yaml\") || entry.name.endsWith(\".yml\"))) {\n // Read the YAML file and extract element paths/IDs\n try {\n const content = await fs.readFile(path.join(layerPath, entry.name), \"utf-8\");\n this.extractElementPathsFromYAML(content, elementIds, layerPath, entry.name);\n } catch (error) {\n const filePath = path.join(layerPath, entry.name);\n this.resolutionDiagnostics.addWarning({\n layer: \"manifest\",\n message: `Failed to read YAML file at ${filePath}: ${getErrorMessage(error)}`,\n category: \"reference\",\n });\n continue;\n }\n }\n }\n } catch (error) {\n this.resolutionDiagnostics.addWarning({\n layer: \"manifest\",\n message: `Failed to read layer directory at ${layerPath}: ${getErrorMessage(error)}`,\n category: \"reference\",\n });\n }\n }\n\n private extractElementPathsFromYAML(\n content: string,\n elementIds: Set<string>,\n layerPath: string,\n fileName: string\n ): void {\n try {\n const parsed = parseYAML(content);\n\n if (!parsed) {\n this.resolutionDiagnostics.addWarning({\n layer: \"manifest\",\n message: `YAML file at ${path.join(layerPath, fileName)} parsed to empty content`,\n category: \"reference\",\n });\n return;\n }\n\n // Collect items to process from various YAML formats\n const items: unknown[] = [];\n\n // Format 1: elements: [{ path: \"...\" }, ...]\n if (parsed && typeof parsed === \"object\" && \"elements\" in parsed) {\n const elements = parsed.elements;\n if (Array.isArray(elements)) {\n items.push(...elements);\n }\n }\n // Format 2: Direct array [{ path: \"...\" }, ...]\n else if (Array.isArray(parsed)) {\n items.push(...parsed);\n }\n // Format 3: Object-of-objects { \"key\": { path: \"...\" }, ... }\n else if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n // Check if this looks like an object-of-objects (values have path/id fields)\n const values = Object.values(parsed);\n if (values.length > 0 && values[0] && typeof values[0] === \"object\" && (\"path\" in values[0] || \"id\" in values[0])) {\n items.push(...values);\n } else {\n // Object without path/id fields, push as-is for type safety\n items.push(parsed);\n }\n }\n\n // Extract paths from collected items\n for (const item of items) {\n if (item && typeof item === \"object\") {\n const elementPath = (item as Record<string, unknown>).path || (item as Record<string, unknown>).id;\n if (elementPath && typeof elementPath === \"string\") {\n elementIds.add(elementPath.trim());\n }\n }\n }\n } catch (error) {\n this.resolutionDiagnostics.addWarning({\n layer: \"manifest\",\n message: `Failed to parse YAML file at ${path.join(layerPath, fileName)}: ${getErrorMessage(error)}`,\n category: \"reference\",\n });\n }\n }\n\n private validateQualifiedReferencesAgainstModel(\n model: Model,\n modelName: string\n ): ValidationResult {\n const result = new ValidationResult();\n const resolvedModel = this.resolvedModels.get(modelName);\n\n if (!resolvedModel) {\n return result;\n }\n\n for (const [sourceLayerName, layer] of model.layers) {\n for (const element of layer.listElements()) {\n const elementId = element.path || element.id;\n\n for (const ref of element.references || []) {\n if (!ref.target.startsWith(\"@\")) {\n continue;\n }\n\n try {\n const parsed = parseReferencePath(ref.target);\n // Compare model names case-insensitively\n if (parsed.modelName?.toLowerCase() !== modelName.toLowerCase()) {\n continue;\n }\n\n // Check if target element exists in the resolved external model\n if (!resolvedModel.elementIds.has(parsed.segment)) {\n result.addError({\n layer: sourceLayerName,\n elementId,\n message: `Broken qualified reference: target '${parsed.segment}' does not exist in external model '${modelName}'`,\n category: \"reference\",\n fixSuggestion: `Create element '${parsed.segment}' in model '${modelName}' or remove this reference`,\n });\n }\n } catch (error) {\n if (error instanceof ReferencePathParseError) {\n // Parse errors are already handled by base validator\n continue;\n } else {\n throw error;\n }\n }\n }\n }\n }\n\n return result;\n }\n\n private addResolutionDiagnostic(\n modelName: string,\n error: unknown,\n modelPath: string,\n isPathError: boolean = false\n ): void {\n const errorMsg = getErrorMessage(error);\n const code = (error as NodeJS.ErrnoException)?.code;\n\n let message = \"\";\n let fixSuggestion = \"\";\n\n if (code === \"EACCES\") {\n message =\n `Permission denied accessing external model '${modelName}' at ${modelPath}. ` +\n `Check file permissions.`;\n fixSuggestion = `Ensure the model path is readable, or use '--model-path ${modelName}=/path/to/alternative-location' to specify another location`;\n } else if (code === \"EIO\") {\n message =\n `I/O error reading external model '${modelName}' at ${modelPath}. ` +\n `There may be a filesystem or device issue.`;\n fixSuggestion = `Check the filesystem for errors, or provide an alternative model path via '--model-path ${modelName}=/path/to/alternative-location'`;\n } else if (code === \"ENOENT\" || isPathError) {\n message =\n `Could not find external model '${modelName}' \u2014 expected at ${modelPath}/documentation-robotics/model or ${modelPath}/model`;\n fixSuggestion = `Ensure the model exists at the specified path, or use '--model-path ${modelName}=/path/to/project-root' to specify its location`;\n } else {\n message =\n `Failed to resolve external model '${modelName}' at ${modelPath}: ${errorMsg}`;\n fixSuggestion = `Check the path and filesystem permissions, or use '--model-path ${modelName}=/path/to/project-root' to specify a different location`;\n }\n\n this.resolutionDiagnostics.addWarning({\n layer: \"manifest\",\n message,\n category: \"reference\",\n fixSuggestion,\n });\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,5 +1,6 @@
1
1
  import { ValidationResult } from "./types.js";
2
2
  import { getLayerByNumber, LAYER_HIERARCHY, getAllLayerIds } from "../generated/layer-registry.js";
3
+ import { parseReferencePath, ReferencePathParseError } from "../utils/reference-path-parser.js";
3
4
  class ReferenceValidator {
4
5
  // Known layer names from generated registry (including hyphenated ones)
5
6
  KNOWN_LAYERS = getAllLayerIds();
@@ -20,7 +21,7 @@ class ReferenceValidator {
20
21
  const result = new ValidationResult(), allElementIds = this.collectAllElementIds(model);
21
22
  for (const [layerName, layer] of model.layers)
22
23
  for (const element of layer.listElements())
23
- this.validateReferences(element.path || element.id, element.references || [], layerName, allElementIds, result);
24
+ this.validateReferences(element.path || element.id, element.references || [], layerName, allElementIds, model, result);
24
25
  return result;
25
26
  }
26
27
  /**
@@ -36,24 +37,60 @@ class ReferenceValidator {
36
37
  /**
37
38
  * Validate references for an element
38
39
  */
39
- validateReferences(elementId, references, sourceLayerName, validIds, result) {
40
+ validateReferences(elementId, references, sourceLayerName, validIds, model, result) {
40
41
  for (const ref of references) {
41
- if (!validIds.has(ref.target)) {
42
+ let parsedModel, targetSegment = ref.target;
43
+ if (ref.target.startsWith("@")) {
44
+ try {
45
+ const parsed = parseReferencePath(ref.target);
46
+ parsedModel = parsed.modelName, targetSegment = parsed.segment;
47
+ } catch (error) {
48
+ if (error instanceof ReferencePathParseError)
49
+ result.addError({
50
+ layer: sourceLayerName,
51
+ elementId,
52
+ message: `Malformed qualified reference path: ${error.message}`,
53
+ fixSuggestion: "Use format '@{model-name}/{layer}.{type}.{name}' for qualified references or '{layer}.{type}.{name}' for unqualified references",
54
+ category: "reference"
55
+ });
56
+ else
57
+ throw error;
58
+ continue;
59
+ }
60
+ const modelFound = parsedModel && model.manifest.models && this.findManifestModel(model.manifest.models, parsedModel);
61
+ if (parsedModel && !modelFound) {
62
+ result.addError({
63
+ layer: sourceLayerName,
64
+ elementId,
65
+ message: `Unknown external model reference: model '${parsedModel}' is not declared in manifest`,
66
+ fixSuggestion: `Add model '${parsedModel}' to the 'models' section of manifest.yaml`,
67
+ category: "reference"
68
+ });
69
+ continue;
70
+ }
71
+ } else if (!validIds.has(ref.target)) {
72
+ if (model.loadedLayerFilter && model.loadedLayerFilter.length > 0) {
73
+ const targetLayerName2 = this.extractLayerFromId(ref.target);
74
+ if (!model.loadedLayerFilter.includes(targetLayerName2))
75
+ continue;
76
+ }
42
77
  result.addError({
43
78
  layer: sourceLayerName,
44
79
  elementId,
45
80
  message: `Broken reference: target '${ref.target}' does not exist`,
46
- fixSuggestion: `Remove reference or create element '${ref.target}'`
81
+ fixSuggestion: `Remove reference or create element '${ref.target}'`,
82
+ category: "reference"
47
83
  });
48
84
  continue;
49
85
  }
50
- const targetLayerName = this.extractLayerFromId(ref.target), sourceLevel = this.LAYER_HIERARCHY[sourceLayerName], targetLevel = this.LAYER_HIERARCHY[targetLayerName];
86
+ const targetLayerName = this.extractLayerFromId(targetSegment), sourceLevel = this.LAYER_HIERARCHY[sourceLayerName], targetLevel = this.LAYER_HIERARCHY[targetLayerName];
51
87
  if (sourceLevel === void 0) {
52
88
  result.addError({
53
89
  layer: sourceLayerName,
54
90
  elementId,
55
91
  message: `Unknown source layer: ${sourceLayerName}`,
56
- fixSuggestion: `Use one of the valid layers: ${this.KNOWN_LAYERS.join(", ")}`
92
+ fixSuggestion: `Use one of the valid layers: ${this.KNOWN_LAYERS.join(", ")}`,
93
+ category: "reference"
57
94
  });
58
95
  continue;
59
96
  }
@@ -62,16 +99,11 @@ class ReferenceValidator {
62
99
  layer: sourceLayerName,
63
100
  elementId,
64
101
  message: `Target element '${ref.target}' has unknown layer: ${targetLayerName}`,
65
- fixSuggestion: `Check that element ID '${ref.target}' uses a valid layer prefix (e.g., motivation-, business-, api-)`
102
+ fixSuggestion: `Check that element ID '${ref.target}' uses a valid layer prefix (e.g., motivation-, business-, api-)`,
103
+ category: "reference"
66
104
  });
67
105
  continue;
68
106
  }
69
- sourceLevel > targetLevel && result.addError({
70
- layer: sourceLayerName,
71
- elementId,
72
- message: `Invalid reference direction: ${sourceLayerName} (level ${sourceLevel}) cannot reference ${targetLayerName} (level ${targetLevel})`,
73
- fixSuggestion: "References must go from higher layers to lower layers (motivation \u2192 testing)"
74
- });
75
107
  }
76
108
  }
77
109
  /**
@@ -84,6 +116,18 @@ class ReferenceValidator {
84
116
  return layer;
85
117
  return elementId.split(separator)[0] || "";
86
118
  }
119
+ /**
120
+ * Find a model in the manifest using case-insensitive lookup
121
+ * Handles the case where parseReferencePath normalizes model names to lowercase,
122
+ * but the manifest may have different casing for the declared model keys.
123
+ */
124
+ findManifestModel(models, modelName) {
125
+ const lowerModelName = modelName.toLowerCase();
126
+ for (const key of Object.keys(models))
127
+ if (key.toLowerCase() === lowerModelName)
128
+ return !0;
129
+ return !1;
130
+ }
87
131
  }
88
132
  export {
89
133
  ReferenceValidator
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/validators/reference-validator.ts"],
4
- "sourcesContent": ["/**\n * Reference validation for cross-layer integrity\n */\n\nimport { ValidationResult } from \"./types.js\";\nimport type { Model } from \"../core/model.js\";\nimport { getLayerByNumber, LAYER_HIERARCHY, getAllLayerIds } from \"../generated/layer-registry.js\";\n\n/**\n * Validator for cross-layer references\n */\nexport class ReferenceValidator {\n // Known layer names from generated registry (including hyphenated ones)\n private readonly KNOWN_LAYERS = getAllLayerIds();\n\n // Build layer hierarchy map at instantiation from generated registry\n private readonly LAYER_HIERARCHY: Record<string, number> = this.buildLayerHierarchy();\n\n private buildLayerHierarchy(): Record<string, number> {\n const hierarchy: Record<string, number> = {};\n for (const layerNumber of LAYER_HIERARCHY) {\n const layer = getLayerByNumber(layerNumber);\n if (layer) {\n hierarchy[layer.id] = layer.number;\n }\n }\n return hierarchy;\n }\n\n /**\n * Validate all references in a model\n */\n validateModel(model: Model): ValidationResult {\n const result = new ValidationResult();\n const allElementIds = this.collectAllElementIds(model);\n\n for (const [layerName, layer] of model.layers) {\n for (const element of layer.listElements()) {\n this.validateReferences(\n element.path || element.id,\n element.references || [],\n layerName,\n allElementIds,\n result\n );\n }\n }\n\n return result;\n }\n\n /**\n * Collect all element IDs across the model\n */\n private collectAllElementIds(model: Model): Set<string> {\n const ids = new Set<string>();\n\n for (const layer of model.layers.values()) {\n for (const element of layer.listElements()) {\n ids.add(element.path || element.id);\n // Also add raw UUID so references stored by UUID still resolve\n ids.add(element.id);\n }\n }\n\n return ids;\n }\n\n /**\n * Validate references for an element\n */\n private validateReferences(\n elementId: string,\n references: Array<{ target: string; type?: string }>,\n sourceLayerName: string,\n validIds: Set<string>,\n result: ValidationResult\n ): void {\n for (const ref of references) {\n // Check target exists\n if (!validIds.has(ref.target)) {\n result.addError({\n layer: sourceLayerName,\n elementId,\n message: `Broken reference: target '${ref.target}' does not exist`,\n fixSuggestion: `Remove reference or create element '${ref.target}'`,\n });\n continue;\n }\n\n // Check directional constraint (higher \u2192 lower only)\n const targetLayerName = this.extractLayerFromId(ref.target);\n const sourceLevel = this.LAYER_HIERARCHY[sourceLayerName];\n const targetLevel = this.LAYER_HIERARCHY[targetLayerName];\n\n if (sourceLevel === undefined) {\n result.addError({\n layer: sourceLayerName,\n elementId,\n message: `Unknown source layer: ${sourceLayerName}`,\n fixSuggestion:\n `Use one of the valid layers: ${this.KNOWN_LAYERS.join(\", \")}`,\n });\n continue;\n }\n\n if (targetLevel === undefined) {\n result.addError({\n layer: sourceLayerName,\n elementId,\n message: `Target element '${ref.target}' has unknown layer: ${targetLayerName}`,\n fixSuggestion: `Check that element ID '${ref.target}' uses a valid layer prefix (e.g., motivation-, business-, api-)`,\n });\n continue;\n }\n\n // References must go from higher layers (lower numbers) to lower layers (higher numbers)\n // or within the same layer\n if (sourceLevel > targetLevel) {\n result.addError({\n layer: sourceLayerName,\n elementId,\n message: `Invalid reference direction: ${sourceLayerName} (level ${sourceLevel}) cannot reference ${targetLayerName} (level ${targetLevel})`,\n fixSuggestion:\n \"References must go from higher layers to lower layers (motivation \u2192 testing)\",\n });\n }\n }\n }\n\n /**\n * Extract layer name from element ID, handling both dot-separated and hyphenated layer names\n */\n private extractLayerFromId(elementId: string): string {\n // Determine if using dot-separated format (e.g., motivation.goal.name) or hyphenated (e.g., motivation-goal-name)\n const isDotSeparated = elementId.includes(\".\");\n const separator = isDotSeparated ? \".\" : \"-\";\n\n // Try to match known layers in order of specificity (longest first)\n const sortedLayers = [...this.KNOWN_LAYERS].sort((a, b) => b.length - a.length);\n\n for (const layer of sortedLayers) {\n if (elementId.startsWith(layer + separator)) {\n return layer;\n }\n }\n\n // Fallback: return first segment (shouldn't reach here with valid element IDs)\n return elementId.split(separator)[0] || \"\";\n }\n}\n"],
5
- "mappings": "AAIA,SAAS,wBAAwB;AAEjC,SAAS,kBAAkB,iBAAiB,sBAAsB;AAK5D,MAAO,mBAAkB;;EAEZ,eAAe,eAAc;;EAG7B,kBAA0C,KAAK,oBAAmB;EAE3E,sBAAmB;AACzB,UAAM,YAAoC,CAAA;AAC1C,eAAW,eAAe,iBAAiB;AACzC,YAAM,QAAQ,iBAAiB,WAAW;AAC1C,MAAI,UACF,UAAU,MAAM,EAAE,IAAI,MAAM;IAEhC;AACA,WAAO;EACT;;;;EAKA,cAAc,OAAY;AACxB,UAAM,SAAS,IAAI,iBAAgB,GAC7B,gBAAgB,KAAK,qBAAqB,KAAK;AAErD,eAAW,CAAC,WAAW,KAAK,KAAK,MAAM;AACrC,iBAAW,WAAW,MAAM,aAAY;AACtC,aAAK,mBACH,QAAQ,QAAQ,QAAQ,IACxB,QAAQ,cAAc,CAAA,GACtB,WACA,eACA,MAAM;AAKZ,WAAO;EACT;;;;EAKQ,qBAAqB,OAAY;AACvC,UAAM,MAAM,oBAAI,IAAG;AAEnB,eAAW,SAAS,MAAM,OAAO,OAAM;AACrC,iBAAW,WAAW,MAAM,aAAY;AACtC,YAAI,IAAI,QAAQ,QAAQ,QAAQ,EAAE,GAElC,IAAI,IAAI,QAAQ,EAAE;AAItB,WAAO;EACT;;;;EAKQ,mBACN,WACA,YACA,iBACA,UACA,QAAwB;AAExB,eAAW,OAAO,YAAY;AAE5B,UAAI,CAAC,SAAS,IAAI,IAAI,MAAM,GAAG;AAC7B,eAAO,SAAS;UACd,OAAO;UACP;UACA,SAAS,6BAA6B,IAAI,MAAM;UAChD,eAAe,uCAAuC,IAAI,MAAM;SACjE;AACD;MACF;AAGA,YAAM,kBAAkB,KAAK,mBAAmB,IAAI,MAAM,GACpD,cAAc,KAAK,gBAAgB,eAAe,GAClD,cAAc,KAAK,gBAAgB,eAAe;AAExD,UAAI,gBAAgB,QAAW;AAC7B,eAAO,SAAS;UACd,OAAO;UACP;UACA,SAAS,yBAAyB,eAAe;UACjD,eACE,gCAAgC,KAAK,aAAa,KAAK,IAAI,CAAC;SAC/D;AACD;MACF;AAEA,UAAI,gBAAgB,QAAW;AAC7B,eAAO,SAAS;UACd,OAAO;UACP;UACA,SAAS,mBAAmB,IAAI,MAAM,wBAAwB,eAAe;UAC7E,eAAe,0BAA0B,IAAI,MAAM;SACpD;AACD;MACF;AAIA,MAAI,cAAc,eAChB,OAAO,SAAS;QACd,OAAO;QACP;QACA,SAAS,gCAAgC,eAAe,WAAW,WAAW,sBAAsB,eAAe,WAAW,WAAW;QACzI,eACE;OACH;IAEL;EACF;;;;EAKQ,mBAAmB,WAAiB;AAG1C,UAAM,YADiB,UAAU,SAAS,GAAG,IACV,MAAM,KAGnC,eAAe,CAAC,GAAG,KAAK,YAAY,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AAE9E,eAAW,SAAS;AAClB,UAAI,UAAU,WAAW,QAAQ,SAAS;AACxC,eAAO;AAKX,WAAO,UAAU,MAAM,SAAS,EAAE,CAAC,KAAK;EAC1C;;",
6
- "names": []
4
+ "sourcesContent": ["/**\n * Reference validation for cross-layer integrity\n */\n\nimport { ValidationResult } from \"./types.js\";\nimport type { Model } from \"../core/model.js\";\nimport { getLayerByNumber, LAYER_HIERARCHY, getAllLayerIds } from \"../generated/layer-registry.js\";\nimport { parseReferencePath, ReferencePathParseError } from \"../utils/reference-path-parser.js\";\n\n/**\n * Validator for cross-layer references\n */\nexport class ReferenceValidator {\n // Known layer names from generated registry (including hyphenated ones)\n private readonly KNOWN_LAYERS = getAllLayerIds();\n\n // Build layer hierarchy map at instantiation from generated registry\n private readonly LAYER_HIERARCHY: Record<string, number> = this.buildLayerHierarchy();\n\n private buildLayerHierarchy(): Record<string, number> {\n const hierarchy: Record<string, number> = {};\n for (const layerNumber of LAYER_HIERARCHY) {\n const layer = getLayerByNumber(layerNumber);\n if (layer) {\n hierarchy[layer.id] = layer.number;\n }\n }\n return hierarchy;\n }\n\n /**\n * Validate all references in a model\n */\n validateModel(model: Model): ValidationResult {\n const result = new ValidationResult();\n const allElementIds = this.collectAllElementIds(model);\n\n for (const [layerName, layer] of model.layers) {\n for (const element of layer.listElements()) {\n this.validateReferences(\n element.path || element.id,\n element.references || [],\n layerName,\n allElementIds,\n model,\n result\n );\n }\n }\n\n return result;\n }\n\n /**\n * Collect all element IDs across the model\n */\n private collectAllElementIds(model: Model): Set<string> {\n const ids = new Set<string>();\n\n for (const layer of model.layers.values()) {\n for (const element of layer.listElements()) {\n ids.add(element.path || element.id);\n // Also add raw UUID so references stored by UUID still resolve\n ids.add(element.id);\n }\n }\n\n return ids;\n }\n\n /**\n * Validate references for an element\n */\n private validateReferences(\n elementId: string,\n references: Array<{ target: string; type?: string }>,\n sourceLayerName: string,\n validIds: Set<string>,\n model: Model,\n result: ValidationResult\n ): void {\n for (const ref of references) {\n let parsedModel: string | undefined;\n let targetSegment = ref.target;\n\n // Check if this is a qualified reference (starts with @)\n if (ref.target.startsWith(\"@\")) {\n // Try to parse the qualified reference\n try {\n const parsed = parseReferencePath(ref.target);\n parsedModel = parsed.modelName;\n targetSegment = parsed.segment;\n } catch (error) {\n if (error instanceof ReferencePathParseError) {\n result.addError({\n layer: sourceLayerName,\n elementId,\n message: `Malformed qualified reference path: ${error.message}`,\n fixSuggestion: `Use format '@{model-name}/{layer}.{type}.{name}' for qualified references or '{layer}.{type}.{name}' for unqualified references`,\n category: \"reference\",\n });\n } else {\n throw error;\n }\n continue;\n }\n\n // For qualified references, check if the model is declared\n // Normalize lookup to handle case-insensitive manifest keys\n const modelFound = parsedModel && model.manifest.models && this.findManifestModel(model.manifest.models, parsedModel);\n if (parsedModel && !modelFound) {\n result.addError({\n layer: sourceLayerName,\n elementId,\n message: `Unknown external model reference: model '${parsedModel}' is not declared in manifest`,\n fixSuggestion: `Add model '${parsedModel}' to the 'models' section of manifest.yaml`,\n category: \"reference\",\n });\n continue;\n }\n } else {\n // For unqualified references, check if target exists locally\n if (!validIds.has(ref.target)) {\n // Skip validation for references to unloaded layers when filter is active\n if (model.loadedLayerFilter && model.loadedLayerFilter.length > 0) {\n const targetLayerName = this.extractLayerFromId(ref.target);\n if (!model.loadedLayerFilter.includes(targetLayerName)) {\n // Target is in an unloaded layer; skip this reference\n continue;\n }\n }\n\n result.addError({\n layer: sourceLayerName,\n elementId,\n message: `Broken reference: target '${ref.target}' does not exist`,\n fixSuggestion: `Remove reference or create element '${ref.target}'`,\n category: \"reference\",\n });\n continue;\n }\n }\n\n // Check that both layers exist\n // For qualified references, extract layer from the parsed segment\n // For unqualified references, extract layer from the target directly\n const targetLayerName = this.extractLayerFromId(targetSegment);\n const sourceLevel = this.LAYER_HIERARCHY[sourceLayerName];\n const targetLevel = this.LAYER_HIERARCHY[targetLayerName];\n\n if (sourceLevel === undefined) {\n result.addError({\n layer: sourceLayerName,\n elementId,\n message: `Unknown source layer: ${sourceLayerName}`,\n fixSuggestion:\n `Use one of the valid layers: ${this.KNOWN_LAYERS.join(\", \")}`,\n category: \"reference\",\n });\n continue;\n }\n\n if (targetLevel === undefined) {\n result.addError({\n layer: sourceLayerName,\n elementId,\n message: `Target element '${ref.target}' has unknown layer: ${targetLayerName}`,\n fixSuggestion: `Check that element ID '${ref.target}' uses a valid layer prefix (e.g., motivation-, business-, api-)`,\n category: \"reference\",\n });\n continue;\n }\n\n // No directional constraint: direction is a property of the predicate, not of the\n // layers (spec/DESIGN_CHARTER.md). Layer levels are resolved above only to reject\n // references to unknown layers.\n }\n }\n\n /**\n * Extract layer name from element ID, handling both dot-separated and hyphenated layer names\n */\n private extractLayerFromId(elementId: string): string {\n // Determine if using dot-separated format (e.g., motivation.goal.name) or hyphenated (e.g., motivation-goal-name)\n const isDotSeparated = elementId.includes(\".\");\n const separator = isDotSeparated ? \".\" : \"-\";\n\n // Try to match known layers in order of specificity (longest first)\n const sortedLayers = [...this.KNOWN_LAYERS].sort((a, b) => b.length - a.length);\n\n for (const layer of sortedLayers) {\n if (elementId.startsWith(layer + separator)) {\n return layer;\n }\n }\n\n // Fallback: return first segment (shouldn't reach here with valid element IDs)\n return elementId.split(separator)[0] || \"\";\n }\n\n /**\n * Find a model in the manifest using case-insensitive lookup\n * Handles the case where parseReferencePath normalizes model names to lowercase,\n * but the manifest may have different casing for the declared model keys.\n */\n private findManifestModel(\n models: Record<string, unknown>,\n modelName: string\n ): boolean {\n const lowerModelName = modelName.toLowerCase();\n for (const key of Object.keys(models)) {\n if (key.toLowerCase() === lowerModelName) {\n return true;\n }\n }\n return false;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["targetLayerName"]
7
7
  }
@@ -88,8 +88,32 @@ class RelationshipValidator {
88
88
  * Validate a single relationship
89
89
  */
90
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)
91
+ const errors = [];
92
+ let sourceElement = null, targetElement = null, bothInFilterAndFound = !1;
93
+ if (model.loadedLayerFilter && model.loadedLayerFilter.length > 0) {
94
+ const sourceLayer2 = this.extractLayerFromElementId(relationship.source), targetLayer2 = this.extractLayerFromElementId(relationship.target);
95
+ if (sourceLayer2 && targetLayer2) {
96
+ const sourceInFilter = model.loadedLayerFilter.includes(sourceLayer2), targetInFilter = model.loadedLayerFilter.includes(targetLayer2);
97
+ if (!sourceInFilter && !targetInFilter)
98
+ return errors;
99
+ if (sourceInFilter && (sourceElement = this.findElementInModel(model, relationship.source), !sourceElement))
100
+ return errors.push({
101
+ layer: relationship.layer,
102
+ elementId: relationship.source,
103
+ message: `Relationship source element '${relationship.source}' not found`
104
+ }), errors;
105
+ if (targetInFilter && (targetElement = this.findElementInModel(model, relationship.target), !targetElement))
106
+ return errors.push({
107
+ layer: relationship.layer,
108
+ elementId: relationship.target,
109
+ message: `Relationship target element '${relationship.target}' not found`
110
+ }), errors;
111
+ if (!sourceInFilter || !targetInFilter)
112
+ return errors;
113
+ bothInFilterAndFound = !0;
114
+ }
115
+ }
116
+ if (bothInFilterAndFound || (sourceElement = this.findElementInModel(model, relationship.source), targetElement = this.findElementInModel(model, relationship.target)), !sourceElement)
93
117
  return errors.push({
94
118
  layer: relationship.layer,
95
119
  elementId: relationship.source,
@@ -107,7 +131,7 @@ class RelationshipValidator {
107
131
  return errors.push({
108
132
  layer: relationship.layer,
109
133
  elementId: relationship.source,
110
- message: `No schema found for relationship: ${sourceLayer}.${sourceType} --[${relationship.predicate}]--> ${targetLayer}.${targetType}`
134
+ message: `No schema found for relationship: ${relationship.source} --[${relationship.predicate}]--> ${relationship.target} (${sourceLayer}.${sourceType} --[${relationship.predicate}]--> ${targetLayer}.${targetType})`
111
135
  }), errors;
112
136
  }
113
137
  const schema = this.relationshipSchemas.get(schemaKey);
@@ -115,7 +139,7 @@ class RelationshipValidator {
115
139
  return errors.push({
116
140
  layer: relationship.layer,
117
141
  elementId: relationship.source,
118
- message: `Failed to load schema for relationship: ${schemaKey}`
142
+ message: `Failed to load schema for relationship: ${relationship.source} --[${relationship.predicate}]--> ${relationship.target} (${schemaKey})`
119
143
  }), errors;
120
144
  const cardinalityErrors = this.validateCardinality(relationship, schema, relationshipsBySource, relationshipsByTarget);
121
145
  if (errors.push(...cardinalityErrors), relationship.properties && schema.attributes) {
@@ -183,7 +207,7 @@ class RelationshipValidator {
183
207
  key in schema.attributes || errors.push({
184
208
  layer: relationship.layer,
185
209
  elementId: relationship.source,
186
- message: `Relationship property '${key}' not defined in schema for predicate '${relationship.predicate}'`
210
+ message: `Relationship property '${key}' on ${relationship.source} --[${relationship.predicate}]--> ${relationship.target} not defined in schema for predicate '${relationship.predicate}'`
187
211
  });
188
212
  return errors;
189
213
  }
@@ -236,6 +260,10 @@ class RelationshipValidator {
236
260
  }
237
261
  return null;
238
262
  }
263
+ extractLayerFromElementId(elementId) {
264
+ const parts = elementId.split(".");
265
+ return parts.length < 2 ? null : parts[0];
266
+ }
239
267
  }
240
268
  export {
241
269
  RelationshipValidator