@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,78 @@
1
+ import { z } from "zod";
2
+ import { getAllLayerIds, isValidLayer } from "../../generated/layer-registry.js";
3
+ import { CLIError, ErrorCategory, findSimilar, formatValidOptions } from "../../utils/errors.js";
4
+ import { ValidationFormatter } from "../../validators/validation-formatter.js";
5
+ import { Validator } from "../../validators/validator.js";
6
+ import { jsonResult, loadModel, rootPathSchema, runTool } from "./shared.js";
7
+ const inputSchema = {
8
+ layers: z.array(z.string()).optional().describe("Restrict validation to these layers. Omit to validate the whole model."),
9
+ strict: z.boolean().optional().describe("Also flag elements with no description as warnings."),
10
+ orphans: z.boolean().optional().describe("Return only the orphaned-elements report instead of running full validation."),
11
+ rootPath: rootPathSchema
12
+ };
13
+ async function modelValidateHandler(args) {
14
+ return runTool(async () => {
15
+ const model = await loadModel(args.rootPath), activeChangesetId = model.getActiveChangesetId();
16
+ let modelToValidate = activeChangesetId ? { ...await model.getVirtualProjectionEngine().projectModel(model, activeChangesetId), rootPath: model.rootPath } : model;
17
+ if (args.layers && args.layers.length > 0) {
18
+ const unknownLayers = args.layers.filter((name) => !isValidLayer(name));
19
+ if (unknownLayers.length > 0) {
20
+ const validLayers = getAllLayerIds(), suggestions = [`Use a valid layer name: ${formatValidOptions(validLayers)}`];
21
+ for (const unknown of unknownLayers) {
22
+ const similar = findSimilar(unknown, validLayers, 3);
23
+ similar.length > 0 && suggestions.unshift(`Did you mean "${similar.join(" or ")}" instead of "${unknown}"?`);
24
+ }
25
+ throw new CLIError(`Unknown layer(s): ${unknownLayers.join(", ")}`, ErrorCategory.USER, suggestions);
26
+ }
27
+ const requestedLayers = new Set(args.layers);
28
+ modelToValidate = {
29
+ ...modelToValidate,
30
+ layers: new Map(Array.from(modelToValidate.layers.entries()).filter(([name]) => requestedLayers.has(name)))
31
+ };
32
+ }
33
+ if (args.orphans) {
34
+ const stats2 = ValidationFormatter.calculateStats(modelToValidate);
35
+ return jsonResult({
36
+ orphanCount: stats2.orphanedElements.length,
37
+ orphanedElements: stats2.orphanedElements
38
+ });
39
+ }
40
+ const result = await new Validator().validateModel(modelToValidate), stats = ValidationFormatter.calculateStats(modelToValidate);
41
+ for (const orphanId of stats.orphanedElements)
42
+ result.addWarning({
43
+ message: `Element '${orphanId}' is orphaned (no cross-layer references or intra-layer relationships)`,
44
+ layer: orphanId.split(".")[0] ?? "",
45
+ elementId: orphanId,
46
+ category: "orphan",
47
+ fixSuggestion: "Add cross-layer references or relationships to connect this element to the rest of the model"
48
+ });
49
+ for (const [layerName, layer] of modelToValidate.layers)
50
+ for (const element of layer.listElements()) {
51
+ const elementId = element.path || element.id;
52
+ element.source_reference || result.addWarning({
53
+ message: `Element '${elementId}' has no source reference`,
54
+ layer: layerName,
55
+ elementId,
56
+ fixSuggestion: "Add a source_reference with provenance to link this element to its implementation"
57
+ }), args.strict && (!element.description || element.description.trim() === "") && result.addWarning({
58
+ message: `Element '${elementId}' has no description`,
59
+ layer: layerName,
60
+ elementId,
61
+ fixSuggestion: "Add a description to document what this element represents"
62
+ });
63
+ }
64
+ const formatted = ValidationFormatter.toJSON(result, modelToValidate), valid = result.isValid() && !(args.strict && result.warnings.length > 0);
65
+ return jsonResult({ ...formatted, valid });
66
+ });
67
+ }
68
+ const modelValidateTool = {
69
+ name: "model_validate",
70
+ description: "Run the full schema/naming/reference/semantic/relationship validation pipeline against the model.",
71
+ inputSchema,
72
+ handler: modelValidateHandler
73
+ };
74
+ export {
75
+ modelValidateHandler,
76
+ modelValidateTool
77
+ };
78
+ //# sourceMappingURL=model-validate.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/mcp/tools/model-validate.ts"],
4
+ "sourcesContent": ["/**\n * model_validate \u2014 run the 5-stage validation pipeline against the model.\n * Mirrors `dr validate`, reusing the shared Validator and ValidationFormatter.\n */\n\nimport { z } from \"zod\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Model } from \"../../core/model.js\";\nimport { getAllLayerIds, isValidLayer } from \"../../generated/layer-registry.js\";\nimport { CLIError, ErrorCategory, findSimilar, formatValidOptions } from \"../../utils/errors.js\";\nimport { ValidationFormatter } from \"../../validators/validation-formatter.js\";\nimport { Validator } from \"../../validators/validator.js\";\nimport { jsonResult, loadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\n\nconst inputSchema = {\n layers: z.array(z.string()).optional().describe(\"Restrict validation to these layers. Omit to validate the whole model.\"),\n strict: z.boolean().optional().describe(\"Also flag elements with no description as warnings.\"),\n orphans: z\n .boolean()\n .optional()\n .describe(\"Return only the orphaned-elements report instead of running full validation.\"),\n rootPath: rootPathSchema,\n};\n\nexport type ModelValidateArgs = z.infer<z.ZodObject<typeof inputSchema>>;\n\nexport async function modelValidateHandler(args: ModelValidateArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n\n // `Validator` is typed to accept `Model`, but a projected changeset is a\n // `ProjectedModel` (manifest/layers/relationships only, no `rootPath` or the\n // mutation methods `Model` exposes). The validation pipeline only reads\n // `rootPath` for telemetry (validator.ts), so it's carried over explicitly\n // rather than left undefined. This mirrors the identical cast in\n // `cli/src/commands/validate.ts`; a proper fix would give `Validator` a\n // narrower \"validatable model\" interface shared by both types.\n const activeChangesetId = model.getActiveChangesetId();\n let modelToValidate = (\n activeChangesetId\n ? { ...(await model.getVirtualProjectionEngine().projectModel(model, activeChangesetId)), rootPath: model.rootPath }\n : model\n ) as unknown as Model;\n\n // `loadModel()` returns the full cached model (all layers), so a `layers` scope\n // is applied here rather than at load time. Copies the layers Map onto a new\n // object instead of mutating `model.layers` directly, since that Map is the\n // shared cached instance other tool calls read from.\n if (args.layers && args.layers.length > 0) {\n const unknownLayers = args.layers.filter((name) => !isValidLayer(name));\n if (unknownLayers.length > 0) {\n const validLayers = getAllLayerIds();\n const suggestions: string[] = [`Use a valid layer name: ${formatValidOptions(validLayers)}`];\n for (const unknown of unknownLayers) {\n const similar = findSimilar(unknown, validLayers, 3);\n if (similar.length > 0) suggestions.unshift(`Did you mean \"${similar.join(\" or \")}\" instead of \"${unknown}\"?`);\n }\n throw new CLIError(`Unknown layer(s): ${unknownLayers.join(\", \")}`, ErrorCategory.USER, suggestions);\n }\n\n const requestedLayers = new Set(args.layers);\n modelToValidate = {\n ...modelToValidate,\n layers: new Map(\n Array.from(modelToValidate.layers.entries()).filter(([name]) => requestedLayers.has(name))\n ),\n } as unknown as Model;\n }\n\n if (args.orphans) {\n const stats = ValidationFormatter.calculateStats(modelToValidate);\n return jsonResult({\n orphanCount: stats.orphanedElements.length,\n orphanedElements: stats.orphanedElements,\n });\n }\n\n const validator = new Validator();\n const result = await validator.validateModel(modelToValidate);\n\n const stats = ValidationFormatter.calculateStats(modelToValidate);\n for (const orphanId of stats.orphanedElements) {\n result.addWarning({\n message: `Element '${orphanId}' is orphaned (no cross-layer references or intra-layer relationships)`,\n layer: orphanId.split(\".\")[0] ?? \"\",\n elementId: orphanId,\n category: \"orphan\",\n fixSuggestion: \"Add cross-layer references or relationships to connect this element to the rest of the model\",\n });\n }\n\n for (const [layerName, layer] of modelToValidate.layers) {\n for (const element of layer.listElements()) {\n const elementId = element.path || element.id;\n if (!element.source_reference) {\n result.addWarning({\n message: `Element '${elementId}' has no source reference`,\n layer: layerName,\n elementId,\n fixSuggestion: \"Add a source_reference with provenance to link this element to its implementation\",\n });\n }\n if (args.strict && (!element.description || element.description.trim() === \"\")) {\n result.addWarning({\n message: `Element '${elementId}' has no description`,\n layer: layerName,\n elementId,\n fixSuggestion: \"Add a description to document what this element represents\",\n });\n }\n }\n }\n\n const formatted = ValidationFormatter.toJSON(result, modelToValidate);\n const valid = result.isValid() && !(args.strict && result.warnings.length > 0);\n\n return jsonResult({ ...formatted, valid });\n });\n}\n\nexport const modelValidateTool: McpToolDefinition<typeof inputSchema> = {\n name: \"model_validate\",\n description: \"Run the full schema/naming/reference/semantic/relationship validation pipeline against the model.\",\n inputSchema,\n handler: modelValidateHandler,\n};\n"],
5
+ "mappings": "AAKA,SAAS,SAAS;AAGlB,SAAS,gBAAgB,oBAAoB;AAC7C,SAAS,UAAU,eAAe,aAAa,0BAA0B;AACzE,SAAS,2BAA2B;AACpC,SAAS,iBAAiB;AAC1B,SAAS,YAAY,WAAW,gBAAgB,eAAuC;AAEvF,MAAM,cAAc;EAClB,QAAQ,EAAE,MAAM,EAAE,OAAM,CAAE,EAAE,SAAQ,EAAG,SAAS,wEAAwE;EACxH,QAAQ,EAAE,QAAO,EAAG,SAAQ,EAAG,SAAS,qDAAqD;EAC7F,SAAS,EACN,QAAO,EACP,SAAQ,EACR,SAAS,8EAA8E;EAC1F,UAAU;;AAKZ,eAAsB,qBAAqB,MAAuB;AAChE,SAAO,QAAQ,YAAW;AACxB,UAAM,QAAQ,MAAM,UAAU,KAAK,QAAQ,GASrC,oBAAoB,MAAM,qBAAoB;AACpD,QAAI,kBACF,oBACI,EAAE,GAAI,MAAM,MAAM,2BAA0B,EAAG,aAAa,OAAO,iBAAiB,GAAI,UAAU,MAAM,SAAQ,IAChH;AAON,QAAI,KAAK,UAAU,KAAK,OAAO,SAAS,GAAG;AACzC,YAAM,gBAAgB,KAAK,OAAO,OAAO,CAAC,SAAS,CAAC,aAAa,IAAI,CAAC;AACtE,UAAI,cAAc,SAAS,GAAG;AAC5B,cAAM,cAAc,eAAc,GAC5B,cAAwB,CAAC,2BAA2B,mBAAmB,WAAW,CAAC,EAAE;AAC3F,mBAAW,WAAW,eAAe;AACnC,gBAAM,UAAU,YAAY,SAAS,aAAa,CAAC;AACnD,UAAI,QAAQ,SAAS,KAAG,YAAY,QAAQ,iBAAiB,QAAQ,KAAK,MAAM,CAAC,iBAAiB,OAAO,IAAI;QAC/G;AACA,cAAM,IAAI,SAAS,qBAAqB,cAAc,KAAK,IAAI,CAAC,IAAI,cAAc,MAAM,WAAW;MACrG;AAEA,YAAM,kBAAkB,IAAI,IAAI,KAAK,MAAM;AAC3C,wBAAkB;QAChB,GAAG;QACH,QAAQ,IAAI,IACV,MAAM,KAAK,gBAAgB,OAAO,QAAO,CAAE,EAAE,OAAO,CAAC,CAAC,IAAI,MAAM,gBAAgB,IAAI,IAAI,CAAC,CAAC;;IAGhG;AAEA,QAAI,KAAK,SAAS;AAChB,YAAMA,SAAQ,oBAAoB,eAAe,eAAe;AAChE,aAAO,WAAW;QAChB,aAAaA,OAAM,iBAAiB;QACpC,kBAAkBA,OAAM;OACzB;IACH;AAGA,UAAM,SAAS,MADG,IAAI,UAAS,EACA,cAAc,eAAe,GAEtD,QAAQ,oBAAoB,eAAe,eAAe;AAChE,eAAW,YAAY,MAAM;AAC3B,aAAO,WAAW;QAChB,SAAS,YAAY,QAAQ;QAC7B,OAAO,SAAS,MAAM,GAAG,EAAE,CAAC,KAAK;QACjC,WAAW;QACX,UAAU;QACV,eAAe;OAChB;AAGH,eAAW,CAAC,WAAW,KAAK,KAAK,gBAAgB;AAC/C,iBAAW,WAAW,MAAM,aAAY,GAAI;AAC1C,cAAM,YAAY,QAAQ,QAAQ,QAAQ;AAC1C,QAAK,QAAQ,oBACX,OAAO,WAAW;UAChB,SAAS,YAAY,SAAS;UAC9B,OAAO;UACP;UACA,eAAe;SAChB,GAEC,KAAK,WAAW,CAAC,QAAQ,eAAe,QAAQ,YAAY,KAAI,MAAO,OACzE,OAAO,WAAW;UAChB,SAAS,YAAY,SAAS;UAC9B,OAAO;UACP;UACA,eAAe;SAChB;MAEL;AAGF,UAAM,YAAY,oBAAoB,OAAO,QAAQ,eAAe,GAC9D,QAAQ,OAAO,QAAO,KAAM,EAAE,KAAK,UAAU,OAAO,SAAS,SAAS;AAE5E,WAAO,WAAW,EAAE,GAAG,WAAW,MAAK,CAAE;EAC3C,CAAC;AACH;AAEO,MAAM,oBAA2D;EACtE,MAAM;EACN,aAAa;EACb;EACA,SAAS;;",
6
+ "names": ["stats"]
7
+ }
@@ -0,0 +1,98 @@
1
+ import { z } from "zod";
2
+ import { ChangesetGraph } from "../../core/changeset-graph.js";
3
+ import { Element } from "../../core/element.js";
4
+ import { StagingAreaManager } from "../../core/staging-area.js";
5
+ import { liveRelationshipsBetween, stageRelationshipDeletes } from "../../core/relationship-staging.js";
6
+ import { addRelationship, deleteRelationshipHandler } from "../../commands/relationship.js";
7
+ import { CLIError, ErrorCategory } from "../../utils/errors.js";
8
+ import { jsonResult, loadModel, reloadModel, rootPathSchema, runTool } from "./shared.js";
9
+ import { activeStagedChangeset } from "./changeset-shared.js";
10
+ const addInputSchema = {
11
+ source: z.string().describe("Source element ID (layer.type.name)."),
12
+ predicate: z.string().describe("Relationship predicate, e.g. 'realizes', 'serves', 'accesses'. Use spec_type to see what the catalog allows."),
13
+ target: z.string().describe("Target element ID (layer.type.name)."),
14
+ properties: z.record(z.string(), z.unknown()).optional().describe("Optional relationship properties."),
15
+ rootPath: rootPathSchema
16
+ };
17
+ async function relationshipAddHandler(args) {
18
+ return runTool(async () => {
19
+ const model = await loadModel(args.rootPath), manager = new StagingAreaManager(model.rootPath, model), active = await activeStagedChangeset(manager);
20
+ if (!active) {
21
+ const outcome2 = await addRelationship(model, args.source, args.target, args.predicate, args.properties);
22
+ return jsonResult({
23
+ status: outcome2,
24
+ source: args.source,
25
+ predicate: args.predicate,
26
+ target: args.target
27
+ });
28
+ }
29
+ const graph = await ChangesetGraph.build(model, active), sourceElement = liveElement(graph, args.source, "Source"), targetElement = liveElement(graph, args.target, "Target"), source = sourceElement.id, target = targetElement.id, triple = { source, predicate: args.predicate, target }, outcome = await addRelationship(model, source, target, args.predicate, args.properties, {
30
+ source: preResolved(sourceElement),
31
+ target: preResolved(targetElement)
32
+ });
33
+ return outcome !== "duplicate" && await reloadModel(args.rootPath), jsonResult({ status: outcome, changeset: active.name, ...triple });
34
+ });
35
+ }
36
+ const relationshipAddTool = {
37
+ name: "relationship_add",
38
+ description: "Add a relationship between two elements, validated against the relationship catalog. Stages it in the active changeset when there is one (including between elements the changeset itself adds), else writes the model. Status: 'staged' or 'added'; 'duplicate' when the model or the changeset already has it (nothing staged); 'restored' when the changeset had staged its deletion (that deletion is unstaged).",
39
+ inputSchema: addInputSchema,
40
+ handler: relationshipAddHandler
41
+ }, deleteInputSchema = {
42
+ source: z.string().describe("Source element ID."),
43
+ target: z.string().describe("Target element ID."),
44
+ predicate: z.string().optional().describe("Only this predicate. Omit to remove every relationship from source to target."),
45
+ rootPath: rootPathSchema
46
+ };
47
+ async function relationshipDeleteHandler(args) {
48
+ return runTool(async () => {
49
+ const model = await loadModel(args.rootPath), manager = new StagingAreaManager(model.rootPath, model), active = await activeStagedChangeset(manager);
50
+ if (!active) {
51
+ const { deletedCount } = await deleteRelationshipHandler(model, args.source, args.target, args.predicate, void 0, "mcp");
52
+ return jsonResult({ status: "deleted", deletedCount, source: args.source, target: args.target });
53
+ }
54
+ const graph = await ChangesetGraph.build(model, active), source = graph.resolve(args.source), target = graph.resolve(args.target), matches = liveRelationshipsBetween(graph, source, target, args.predicate);
55
+ if (matches.length === 0)
56
+ throw new CLIError("No matching relationships found", ErrorCategory.USER, [
57
+ `Use "model_show" on ${args.source} to see its relationships`,
58
+ 'Use "changeset_brief" to see what this changeset already stages'
59
+ ]);
60
+ const results = await stageRelationshipDeletes(manager, graph, active, matches);
61
+ return await reloadModel(args.rootPath), jsonResult({ changeset: active.name, source, target, relationships: results });
62
+ });
63
+ }
64
+ const relationshipDeleteTool = {
65
+ name: "relationship_delete",
66
+ description: "Remove relationships from source to target. In an active changeset, stages the removal, or unstages the relationship if this changeset added it; else writes the model.",
67
+ inputSchema: deleteInputSchema,
68
+ handler: relationshipDeleteHandler
69
+ };
70
+ function liveElement(graph, id, role) {
71
+ const element = graph.getElement(id);
72
+ if (!element || element.status === "deleted")
73
+ throw new CLIError(`${role} element ${id} not found${element ? " (this changeset deletes it)" : ""}`, ErrorCategory.USER, ['Use "model_search" to find elements, or "changeset_brief" to see what this changeset adds']);
74
+ return element;
75
+ }
76
+ function preResolved(element) {
77
+ return {
78
+ layerName: element.layer,
79
+ element: new Element({
80
+ id: element.uuid ?? element.id,
81
+ path: element.id,
82
+ spec_node_id: `${element.layer}.${element.type}`,
83
+ layer_id: element.layer,
84
+ type: element.type,
85
+ name: element.name,
86
+ description: element.description,
87
+ attributes: element.attributes,
88
+ layer: element.layer
89
+ })
90
+ };
91
+ }
92
+ export {
93
+ relationshipAddHandler,
94
+ relationshipAddTool,
95
+ relationshipDeleteHandler,
96
+ relationshipDeleteTool
97
+ };
98
+ //# sourceMappingURL=relationship-tools.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/mcp/tools/relationship-tools.ts"],
4
+ "sourcesContent": ["/**\n * relationship_add, relationship_delete \u2014 stage (or write) relationships. Mirror\n * `dr relationship add|delete`, validated against the relationship catalog.\n *\n * Inside an active changeset both tools read the changeset's projected graph, so they work between\n * elements that exist only in the changeset, report a repeated add as a duplicate rather than\n * staging it twice, and undo instead of stacking: deleting a relationship this changeset added\n * unstages the add, and re-adding one it deleted unstages the delete. The projected-graph logic is\n * shared with the CLI (`cli/src/core/relationship-staging.ts`).\n */\n\nimport { z } from \"zod\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { ChangesetGraph, type GraphElement } from \"../../core/changeset-graph.js\";\nimport { Element } from \"../../core/element.js\";\nimport { StagingAreaManager } from \"../../core/staging-area.js\";\nimport { liveRelationshipsBetween, stageRelationshipDeletes } from \"../../core/relationship-staging.js\";\nimport { addRelationship, deleteRelationshipHandler } from \"../../commands/relationship.js\";\nimport { CLIError, ErrorCategory } from \"../../utils/errors.js\";\nimport { jsonResult, loadModel, reloadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\nimport { activeStagedChangeset } from \"./changeset-shared.js\";\n\n// ---------------------------------------------------------------------------\n// relationship_add\n// ---------------------------------------------------------------------------\n\nconst addInputSchema = {\n source: z.string().describe(\"Source element ID (layer.type.name).\"),\n predicate: z\n .string()\n .describe(\"Relationship predicate, e.g. 'realizes', 'serves', 'accesses'. Use spec_type to see what the catalog allows.\"),\n target: z.string().describe(\"Target element ID (layer.type.name).\"),\n properties: z.record(z.string(), z.unknown()).optional().describe(\"Optional relationship properties.\"),\n rootPath: rootPathSchema,\n};\n\nexport type RelationshipAddArgs = z.infer<z.ZodObject<typeof addInputSchema>>;\n\nexport async function relationshipAddHandler(args: RelationshipAddArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const active = await activeStagedChangeset(manager);\n\n if (!active) {\n const outcome = await addRelationship(model, args.source, args.target, args.predicate, args.properties);\n return jsonResult({\n status: outcome,\n source: args.source,\n predicate: args.predicate,\n target: args.target,\n });\n }\n\n // Resolve the ends in the projection first, for a clear error when the changeset deletes one.\n const graph = await ChangesetGraph.build(model, active);\n const sourceElement = liveElement(graph, args.source, \"Source\");\n const targetElement = liveElement(graph, args.target, \"Target\");\n const source = sourceElement.id;\n const target = targetElement.id;\n const triple = { source, predicate: args.predicate, target };\n\n // Hand addRelationship the projection's resolution: its own lookup reads the cached model's\n // layers, which need not know a layer only this changeset adds elements to.\n const outcome = await addRelationship(model, source, target, args.predicate, args.properties, {\n source: preResolved(sourceElement),\n target: preResolved(targetElement),\n });\n // Model.load merges the active changeset's relationships into model.relationships, and other tools\n // (model_validate, model_export, model_delete's cascade) read that merged view: reload after any staging.\n if (outcome !== \"duplicate\") {\n await reloadModel(args.rootPath);\n }\n return jsonResult({ status: outcome, changeset: active.name, ...triple });\n });\n}\n\nexport const relationshipAddTool: McpToolDefinition<typeof addInputSchema> = {\n name: \"relationship_add\",\n description:\n \"Add a relationship between two elements, validated against the relationship catalog. Stages it in the \" +\n \"active changeset when there is one (including between elements the changeset itself adds), else writes the model. \" +\n \"Status: 'staged' or 'added'; 'duplicate' when the model or the changeset already has it (nothing staged); \" +\n \"'restored' when the changeset had staged its deletion (that deletion is unstaged).\",\n inputSchema: addInputSchema,\n handler: relationshipAddHandler,\n};\n\n// ---------------------------------------------------------------------------\n// relationship_delete\n// ---------------------------------------------------------------------------\n\nconst deleteInputSchema = {\n source: z.string().describe(\"Source element ID.\"),\n target: z.string().describe(\"Target element ID.\"),\n predicate: z.string().optional().describe(\"Only this predicate. Omit to remove every relationship from source to target.\"),\n rootPath: rootPathSchema,\n};\n\nexport type RelationshipDeleteArgs = z.infer<z.ZodObject<typeof deleteInputSchema>>;\n\nexport async function relationshipDeleteHandler(args: RelationshipDeleteArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const active = await activeStagedChangeset(manager);\n\n if (!active) {\n const { deletedCount } = await deleteRelationshipHandler(\n model,\n args.source,\n args.target,\n args.predicate,\n undefined,\n \"mcp\"\n );\n return jsonResult({ status: \"deleted\", deletedCount, source: args.source, target: args.target });\n }\n\n const graph = await ChangesetGraph.build(model, active);\n const source = graph.resolve(args.source);\n const target = graph.resolve(args.target);\n const matches = liveRelationshipsBetween(graph, source, target, args.predicate);\n\n if (matches.length === 0) {\n throw new CLIError(\"No matching relationships found\", ErrorCategory.USER, [\n `Use \"model_show\" on ${args.source} to see its relationships`,\n 'Use \"changeset_brief\" to see what this changeset already stages',\n ]);\n }\n\n const results = await stageRelationshipDeletes(manager, graph, active, matches);\n // Staged or unstaged, the cached model's merged relationships are now stale.\n await reloadModel(args.rootPath);\n return jsonResult({ changeset: active.name, source, target, relationships: results });\n });\n}\n\nexport const relationshipDeleteTool: McpToolDefinition<typeof deleteInputSchema> = {\n name: \"relationship_delete\",\n description:\n \"Remove relationships from source to target. In an active changeset, stages the removal, or unstages the \" +\n \"relationship if this changeset added it; else writes the model.\",\n inputSchema: deleteInputSchema,\n handler: relationshipDeleteHandler,\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction liveElement(graph: ChangesetGraph, id: string, role: \"Source\" | \"Target\"): GraphElement {\n const element = graph.getElement(id);\n if (!element || element.status === \"deleted\") {\n throw new CLIError(\n `${role} element ${id} not found${element ? \" (this changeset deletes it)\" : \"\"}`,\n ErrorCategory.USER,\n ['Use \"model_search\" to find elements, or \"changeset_brief\" to see what this changeset adds']\n );\n }\n return element;\n}\n\n/** A projected element in the shape addRelationship's catalog check reads. */\nfunction preResolved(element: GraphElement): { element: Element; layerName: string } {\n return {\n layerName: element.layer,\n element: new Element({\n id: element.uuid ?? element.id,\n path: element.id,\n spec_node_id: `${element.layer}.${element.type}`,\n layer_id: element.layer,\n type: element.type,\n name: element.name,\n description: element.description,\n attributes: element.attributes,\n layer: element.layer,\n }),\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["outcome"]
7
+ }
@@ -0,0 +1,126 @@
1
+ import { z } from "zod";
2
+ import { Model } from "../../core/model.js";
3
+ import { CLIError, ModelNotFoundError, getErrorMessage } from "../../utils/errors.js";
4
+ import { ChangesetOnlyError } from "../../core/changeset-only-guard.js";
5
+ import { changesetOnlyError } from "../changeset-only.js";
6
+ const rootPathSchema = z.string().optional().describe("Absolute path to the project root containing documentation-robotics/. Defaults to the MCP server's working directory."), modelCache = /* @__PURE__ */ new Map();
7
+ function modelCacheKey(rootPath) {
8
+ return rootPath ?? "";
9
+ }
10
+ function clearModelCache() {
11
+ modelCache.clear();
12
+ }
13
+ async function readModelFromDisk(rootPath) {
14
+ try {
15
+ return await Model.load(rootPath, { lazyLoad: !1 });
16
+ } catch (error) {
17
+ if (error instanceof CLIError)
18
+ throw error;
19
+ const message = getErrorMessage(error).toLowerCase();
20
+ throw message.includes("no dr project") || message.includes("model not found") || message.includes("no model found") ? new ModelNotFoundError(rootPath) : error;
21
+ }
22
+ }
23
+ async function loadModel(rootPath) {
24
+ const key = modelCacheKey(rootPath), cached = modelCache.get(key);
25
+ if (cached)
26
+ return cached;
27
+ const promise = readModelFromDisk(rootPath);
28
+ return modelCache.set(key, promise), evictOnFailure(key, promise), promise;
29
+ }
30
+ async function reloadModel(rootPath) {
31
+ const key = modelCacheKey(rootPath), promise = readModelFromDisk(rootPath);
32
+ return modelCache.set(key, promise), evictOnFailure(key, promise), promise;
33
+ }
34
+ function evictOnFailure(key, promise) {
35
+ promise.catch(() => {
36
+ modelCache.get(key) === promise && modelCache.delete(key);
37
+ });
38
+ }
39
+ function jsonResult(data, options) {
40
+ return {
41
+ content: [{ type: "text", text: options?.compact ? compactJson(data) : JSON.stringify(data, null, 2) }],
42
+ ...options?.isError ? { isError: !0 } : {}
43
+ };
44
+ }
45
+ const COMPACT_LINE = 140;
46
+ function compactJson(data) {
47
+ const write = (raw, indent) => {
48
+ const value = raw !== null && typeof raw == "object" && typeof raw.toJSON == "function" ? raw.toJSON() : raw, inline = JSON.stringify(value);
49
+ if (inline === void 0 || value === null || typeof value != "object" || inline.length + indent.length <= COMPACT_LINE)
50
+ return inline;
51
+ const inner = indent + " ";
52
+ return Array.isArray(value) ? `[
53
+ ${value.map((item) => inner + (write(item, inner) ?? "null")).join(`,
54
+ `)}
55
+ ${indent}]` : `{
56
+ ${Object.entries(value).map(([key, item]) => {
57
+ const text = write(item, inner);
58
+ return text === void 0 ? void 0 : `${inner}${JSON.stringify(key)}: ${text}`;
59
+ }).filter((line) => line !== void 0).join(`,
60
+ `)}
61
+ ${indent}}`;
62
+ };
63
+ return write(data, "") ?? "null";
64
+ }
65
+ function errorResult(error) {
66
+ if (error instanceof CLIError) {
67
+ const context = error.context;
68
+ return {
69
+ content: [
70
+ {
71
+ type: "text",
72
+ text: JSON.stringify({
73
+ error: error.message,
74
+ category: errorCategoryName(error.exitCode),
75
+ suggestions: error.suggestions ?? [],
76
+ ...context?.operation ? { operation: context.operation } : {},
77
+ ...context?.relatedElements ? { relatedElements: context.relatedElements } : {},
78
+ ...context?.partialProgress ? { partialProgress: context.partialProgress } : {}
79
+ }, null, 2)
80
+ }
81
+ ],
82
+ isError: !0
83
+ };
84
+ }
85
+ return {
86
+ content: [{ type: "text", text: JSON.stringify({ error: getErrorMessage(error) }, null, 2) }],
87
+ isError: !0
88
+ };
89
+ }
90
+ function errorCategoryName(exitCode) {
91
+ switch (exitCode) {
92
+ case 1:
93
+ return "user";
94
+ case 2:
95
+ return "not_found";
96
+ case 3:
97
+ return "system";
98
+ case 4:
99
+ return "validation";
100
+ case 5:
101
+ return "breaking_change";
102
+ default:
103
+ return "unknown";
104
+ }
105
+ }
106
+ async function runTool(fn) {
107
+ try {
108
+ return await fn();
109
+ } catch (error) {
110
+ return logToolError(error), error instanceof ChangesetOnlyError ? (clearModelCache(), errorResult(changesetOnlyError())) : errorResult(error);
111
+ }
112
+ }
113
+ function logToolError(error) {
114
+ error instanceof CLIError ? console.error(`[mcp] tool error: ${error.message}`) : error instanceof Error ? console.error(`[mcp] tool error: ${error.stack ?? error.message}`) : console.error(`[mcp] tool error: ${getErrorMessage(error)}`);
115
+ }
116
+ export {
117
+ clearModelCache,
118
+ compactJson,
119
+ errorResult,
120
+ jsonResult,
121
+ loadModel,
122
+ reloadModel,
123
+ rootPathSchema,
124
+ runTool
125
+ };
126
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/mcp/tools/shared.ts"],
4
+ "sourcesContent": ["/**\n * Shared helpers for MCP model tool handlers.\n *\n * Centralizes model loading and the CLIError -> structured error content\n * translation so individual tool handlers stay focused on their own domain\n * logic.\n */\n\nimport { z, type ZodRawShape } from \"zod\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { Model } from \"../../core/model.js\";\nimport {\n CLIError,\n ModelNotFoundError,\n getErrorMessage,\n type ErrorContext,\n} from \"../../utils/errors.js\";\nimport { ChangesetOnlyError } from \"../../core/changeset-only-guard.js\";\nimport { changesetOnlyError } from \"../changeset-only.js\";\n\n/** Shared input field: lets a client point at a model outside the server's cwd. */\nexport const rootPathSchema = z\n .string()\n .optional()\n .describe(\n \"Absolute path to the project root containing documentation-robotics/. Defaults to the MCP server's working directory.\"\n );\n\nexport interface McpToolDefinition<Shape extends ZodRawShape = ZodRawShape> {\n name: string;\n description: string;\n inputSchema: Shape;\n // Method-shorthand (not an arrow-typed property) so tool definitions with\n // differently-shaped Args can share the heterogeneous MODEL_TOOLS array in\n // tool-registry.ts without a manual cast back to `any` at the storage site.\n handler(args: z.infer<z.ZodObject<Shape>>): Promise<CallToolResult>;\n}\n\n/**\n * Per-rootPath cache of the loaded architecture model, held for the lifetime of the\n * MCP server process: the Model is loaded once and reused across tool calls rather\n * than reread from disk on every invocation. Keyed by the raw `rootPath` argument\n * (`\"\"` for the server's default cwd), matching how MCP clients address a model\n * across calls.\n */\nconst modelCache = new Map<string, Promise<Model>>();\n\nfunction modelCacheKey(rootPath?: string): string {\n return rootPath ?? \"\";\n}\n\n/** Test-only escape hatch: clears the module-private model cache between test cases. */\nexport function clearModelCache(): void {\n modelCache.clear();\n}\n\n/** Reads the model fresh from disk, translating \"no model found\" into a CLIError consistently. */\nasync function readModelFromDisk(rootPath?: string): Promise<Model> {\n try {\n // Always a full, non-lazy load: the cached instance must be able to answer any\n // tool call regardless of which layers/options the first caller happened to need.\n return await Model.load(rootPath, { lazyLoad: false });\n } catch (error) {\n if (error instanceof CLIError) {\n throw error;\n }\n const message = getErrorMessage(error).toLowerCase();\n if (\n message.includes(\"no dr project\") ||\n message.includes(\"model not found\") ||\n message.includes(\"no model found\")\n ) {\n throw new ModelNotFoundError(rootPath);\n }\n throw error;\n }\n}\n\n/** Returns the cached model for this rootPath, loading it from disk on first use. */\nexport async function loadModel(rootPath?: string): Promise<Model> {\n const key = modelCacheKey(rootPath);\n const cached = modelCache.get(key);\n if (cached) {\n return cached;\n }\n\n const promise = readModelFromDisk(rootPath);\n modelCache.set(key, promise);\n evictOnFailure(key, promise);\n return promise;\n}\n\n/** Forces a fresh read from disk, replacing any cached instance for this rootPath. */\nexport async function reloadModel(rootPath?: string): Promise<Model> {\n const key = modelCacheKey(rootPath);\n const promise = readModelFromDisk(rootPath);\n modelCache.set(key, promise);\n evictOnFailure(key, promise);\n return promise;\n}\n\n/**\n * Don't let a failed load poison the cache for subsequent (possibly-fixed) calls.\n * Guarded by identity: if a newer promise has since replaced this one for `key`\n * (e.g. a concurrent reloadModel()), a stale rejection must not evict it.\n */\nfunction evictOnFailure(key: string, promise: Promise<Model>): void {\n promise.catch(() => {\n if (modelCache.get(key) === promise) {\n modelCache.delete(key);\n }\n });\n}\n\n/**\n * Wraps structured data as the tool's JSON text content. `compact` keeps the indentation of the\n * outer structure but writes any object or array that fits on a line on one line (the same JSON,\n * a fraction of the lines), for large results such as the brief.\n */\nexport function jsonResult(data: unknown, options?: { isError?: boolean; compact?: boolean }): CallToolResult {\n return {\n content: [{ type: \"text\", text: options?.compact ? compactJson(data) : JSON.stringify(data, null, 2) }],\n ...(options?.isError ? { isError: true } : {}),\n };\n}\n\nconst COMPACT_LINE = 140;\n\n/** JSON indented by two spaces, with every object or array whose one-line form is short kept on one line. */\nexport function compactJson(data: unknown): string {\n const write = (raw: unknown, indent: string): string | undefined => {\n const value =\n raw !== null && typeof raw === \"object\" && typeof (raw as { toJSON?: unknown }).toJSON === \"function\"\n ? (raw as { toJSON: () => unknown }).toJSON()\n : raw;\n const inline = JSON.stringify(value);\n if (inline === undefined || value === null || typeof value !== \"object\") return inline;\n if (inline.length + indent.length <= COMPACT_LINE) return inline;\n const inner = indent + \" \";\n if (Array.isArray(value)) {\n const items = value.map((item) => inner + (write(item, inner) ?? \"null\"));\n return `[\\n${items.join(\",\\n\")}\\n${indent}]`;\n }\n const fields = Object.entries(value as Record<string, unknown>)\n .map(([key, item]) => {\n const text = write(item, inner);\n return text === undefined ? undefined : `${inner}${JSON.stringify(key)}: ${text}`;\n })\n .filter((line): line is string => line !== undefined);\n return `{\\n${fields.join(\",\\n\")}\\n${indent}}`;\n };\n return write(data, \"\") ?? \"null\";\n}\n\n/** Converts a thrown error into a structured, non-throwing tool error result. */\nexport function errorResult(error: unknown): CallToolResult {\n if (error instanceof CLIError) {\n const context: ErrorContext | undefined = error.context;\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify(\n {\n error: error.message,\n category: errorCategoryName(error.exitCode),\n suggestions: error.suggestions ?? [],\n ...(context?.operation ? { operation: context.operation } : {}),\n ...(context?.relatedElements ? { relatedElements: context.relatedElements } : {}),\n ...(context?.partialProgress ? { partialProgress: context.partialProgress } : {}),\n },\n null,\n 2\n ),\n },\n ],\n isError: true,\n };\n }\n\n return {\n content: [{ type: \"text\", text: JSON.stringify({ error: getErrorMessage(error) }, null, 2) }],\n isError: true,\n };\n}\n\nfunction errorCategoryName(exitCode: number): string {\n switch (exitCode) {\n case 1:\n return \"user\";\n case 2:\n return \"not_found\";\n case 3:\n return \"system\";\n case 4:\n return \"validation\";\n case 5:\n return \"breaking_change\";\n default:\n return \"unknown\";\n }\n}\n\n/** Runs a tool handler body, catching any error into a structured tool result instead of throwing. */\nexport async function runTool(fn: () => Promise<CallToolResult>): Promise<CallToolResult> {\n try {\n return await fn();\n } catch (error) {\n logToolError(error);\n if (error instanceof ChangesetOnlyError) {\n // A base-model save was refused (DR_CHANGESET_ONLY) after the handler may already have\n // edited the cached model in memory: drop the cache so the next call rereads disk, and\n // answer in MCP terms (changeset_create) rather than CLI ones.\n clearModelCache();\n return errorResult(changesetOnlyError());\n }\n return errorResult(error);\n }\n}\n\n/**\n * Logs a tool failure to stderr unconditionally (independent of telemetry\n * settings, which are off by default) so server operators have visibility\n * into failures. Non-CLIError exceptions include their stack trace, since\n * that's otherwise discarded once errorResult() reduces them to a message.\n */\nfunction logToolError(error: unknown): void {\n if (error instanceof CLIError) {\n console.error(`[mcp] tool error: ${error.message}`);\n } else if (error instanceof Error) {\n console.error(`[mcp] tool error: ${error.stack ?? error.message}`);\n } else {\n console.error(`[mcp] tool error: ${getErrorMessage(error)}`);\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,90 @@
1
+ import { z } from "zod";
2
+ import { SpecDataLoader } from "../../core/spec-loader.js";
3
+ import { getAllLayerIds, isValidLayer } from "../../generated/layer-registry.js";
4
+ import { getNodeType, getNodeTypesForLayer, isValidNodeType, normalizeNodeType } from "../../generated/node-types.js";
5
+ import { RELATIONSHIPS_BY_DESTINATION, RELATIONSHIPS_BY_SOURCE } from "../../generated/relationship-index.js";
6
+ import { CLIError, ErrorCategory, findSimilar, formatValidOptions } from "../../utils/errors.js";
7
+ import { jsonResult, runTool } from "./shared.js";
8
+ const inputSchema = {
9
+ layer: z.string().describe("Canonical layer name, e.g. 'api', 'data-model'."),
10
+ type: z.string().optional().describe("Node type within the layer, e.g. 'operation'. Omit to list the layer's types."),
11
+ predicate: z.string().optional().describe("Only list relationships with this predicate."),
12
+ otherLayer: z.string().optional().describe("Only list relationships whose other end is in this layer.")
13
+ };
14
+ let loaderPromise = null;
15
+ function specLoader() {
16
+ if (!loaderPromise) {
17
+ const loader = new SpecDataLoader();
18
+ loaderPromise = loader.load().then(() => loader), loaderPromise.catch(() => {
19
+ loaderPromise = null;
20
+ });
21
+ }
22
+ return loaderPromise;
23
+ }
24
+ async function specTypeHandler(args) {
25
+ return runTool(async () => {
26
+ if (!isValidLayer(args.layer)) {
27
+ const layers = getAllLayerIds(), similar = findSimilar(args.layer, layers, 3);
28
+ throw new CLIError(`Unknown layer "${args.layer}"`, ErrorCategory.USER, [
29
+ ...similar.length > 0 ? [`Did you mean: ${similar.join(" or ")}?`] : [],
30
+ `Valid layers: ${formatValidOptions(layers)}`
31
+ ]);
32
+ }
33
+ const loader = await specLoader();
34
+ if (!args.type) {
35
+ const layer = loader.getLayer(args.layer);
36
+ return jsonResult({
37
+ layer: args.layer,
38
+ description: layer?.description,
39
+ types: getNodeTypesForLayer(args.layer).map((t) => ({
40
+ type: t.type,
41
+ title: t.title,
42
+ definition: firstSentence(loader.getNodeType(t.specNodeId)?.description),
43
+ requiredAttributes: t.requiredAttributes
44
+ }))
45
+ });
46
+ }
47
+ if (!isValidNodeType(args.layer, args.type)) {
48
+ const names = getNodeTypesForLayer(args.layer).map((t) => t.type), similar = findSimilar(args.type, names, 3);
49
+ throw new CLIError(`Invalid type "${args.type}" for layer "${args.layer}"`, ErrorCategory.USER, [
50
+ ...similar.length > 0 ? [`Did you mean: ${similar.join(" or ")}?`] : [],
51
+ `Valid types for ${args.layer}: ${formatValidOptions(names)}`
52
+ ]);
53
+ }
54
+ const specNodeId = `${args.layer}.${normalizeNodeType(args.layer, args.type)}`, info = getNodeType(specNodeId), spec = loader.getNodeType(specNodeId), keep = (rel, otherEnd) => (!args.predicate || rel.predicate === args.predicate) && (!args.otherLayer || otherEnd.startsWith(`${args.otherLayer}.`)), outgoing = (RELATIONSHIPS_BY_SOURCE.get(specNodeId) ?? []).filter((r) => keep(r, r.destinationSpecNodeId)).map((r) => ({ predicate: r.predicate, target: r.destinationSpecNodeId, ...r.required ? { required: !0 } : {} })), incoming = (RELATIONSHIPS_BY_DESTINATION.get(specNodeId) ?? []).filter((r) => keep(r, r.sourceSpecNodeId)).map((r) => ({ source: r.sourceSpecNodeId, predicate: r.predicate, ...r.required ? { required: !0 } : {} })), attributes = Object.entries(info.attributeConstraints).map(([name, constraint]) => {
55
+ const c = constraint;
56
+ return {
57
+ name,
58
+ required: info.requiredAttributes.includes(name),
59
+ ...c.type ? { type: c.type } : {},
60
+ ...c.enum ? { enum: c.enum } : {},
61
+ ...c.description ? { description: c.description } : {}
62
+ };
63
+ });
64
+ return jsonResult({
65
+ specNodeId,
66
+ title: info.title,
67
+ definition: spec?.description,
68
+ attributes,
69
+ outgoing,
70
+ incoming
71
+ });
72
+ });
73
+ }
74
+ const specTypeTool = {
75
+ name: "spec_type",
76
+ description: "Look up a node type in the spec: its definition, attributes (required ones, enum values) and the relationships the catalog allows from and to it. Omit 'type' to list a layer's types. Use before model_add and relationship_add.",
77
+ inputSchema,
78
+ handler: specTypeHandler
79
+ };
80
+ function firstSentence(text) {
81
+ if (!text)
82
+ return;
83
+ const match = text.match(/^.*?[.!?](\s|$)/);
84
+ return (match ? match[0] : text).trim();
85
+ }
86
+ export {
87
+ specTypeHandler,
88
+ specTypeTool
89
+ };
90
+ //# sourceMappingURL=spec-type.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/mcp/tools/spec-type.ts"],
4
+ "sourcesContent": ["/**\n * spec_type \u2014 what the spec says about one node type: its definition, its attributes (required,\n * enums), and every relationship the catalog allows from it and to it.\n *\n * This is the lookup a planner needs before staging an element: model_add fails on a missing\n * required attribute, and relationship_add fails on a triple the catalog does not list.\n */\n\nimport { z } from \"zod\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { SpecDataLoader } from \"../../core/spec-loader.js\";\nimport { getAllLayerIds, isValidLayer } from \"../../generated/layer-registry.js\";\nimport {\n getNodeType,\n getNodeTypesForLayer,\n isValidNodeType,\n normalizeNodeType,\n type SpecNodeId,\n} from \"../../generated/node-types.js\";\nimport { RELATIONSHIPS_BY_DESTINATION, RELATIONSHIPS_BY_SOURCE, type RelationshipSpec } from \"../../generated/relationship-index.js\";\nimport { CLIError, ErrorCategory, findSimilar, formatValidOptions } from \"../../utils/errors.js\";\nimport { jsonResult, runTool, type McpToolDefinition } from \"./shared.js\";\n\nconst inputSchema = {\n layer: z.string().describe(\"Canonical layer name, e.g. 'api', 'data-model'.\"),\n type: z\n .string()\n .optional()\n .describe(\"Node type within the layer, e.g. 'operation'. Omit to list the layer's types.\"),\n predicate: z.string().optional().describe(\"Only list relationships with this predicate.\"),\n otherLayer: z.string().optional().describe(\"Only list relationships whose other end is in this layer.\"),\n};\n\nexport type SpecTypeArgs = z.infer<z.ZodObject<typeof inputSchema>>;\n\nlet loaderPromise: Promise<SpecDataLoader> | null = null;\nfunction specLoader(): Promise<SpecDataLoader> {\n if (!loaderPromise) {\n const loader = new SpecDataLoader();\n loaderPromise = loader.load().then(() => loader);\n loaderPromise.catch(() => {\n loaderPromise = null;\n });\n }\n return loaderPromise;\n}\n\nexport async function specTypeHandler(args: SpecTypeArgs): Promise<CallToolResult> {\n return runTool(async () => {\n if (!isValidLayer(args.layer)) {\n const layers = getAllLayerIds();\n const similar = findSimilar(args.layer, layers, 3);\n throw new CLIError(`Unknown layer \"${args.layer}\"`, ErrorCategory.USER, [\n ...(similar.length > 0 ? [`Did you mean: ${similar.join(\" or \")}?`] : []),\n `Valid layers: ${formatValidOptions(layers)}`,\n ]);\n }\n\n const loader = await specLoader();\n\n if (!args.type) {\n const layer = loader.getLayer(args.layer);\n return jsonResult({\n layer: args.layer,\n description: layer?.description,\n types: getNodeTypesForLayer(args.layer).map((t) => ({\n type: t.type,\n title: t.title,\n definition: firstSentence(loader.getNodeType(t.specNodeId)?.description),\n requiredAttributes: t.requiredAttributes,\n })),\n });\n }\n\n if (!isValidNodeType(args.layer, args.type)) {\n const names = getNodeTypesForLayer(args.layer).map((t) => t.type);\n const similar = findSimilar(args.type, names, 3);\n throw new CLIError(`Invalid type \"${args.type}\" for layer \"${args.layer}\"`, ErrorCategory.USER, [\n ...(similar.length > 0 ? [`Did you mean: ${similar.join(\" or \")}?`] : []),\n `Valid types for ${args.layer}: ${formatValidOptions(names)}`,\n ]);\n }\n\n const specNodeId = `${args.layer}.${normalizeNodeType(args.layer, args.type)}` as SpecNodeId;\n const info = getNodeType(specNodeId)!;\n const spec = loader.getNodeType(specNodeId);\n\n const keep = (rel: RelationshipSpec, otherEnd: string) =>\n (!args.predicate || rel.predicate === args.predicate) &&\n (!args.otherLayer || otherEnd.startsWith(`${args.otherLayer}.`));\n\n const outgoing = (RELATIONSHIPS_BY_SOURCE.get(specNodeId) ?? [])\n .filter((r) => keep(r, r.destinationSpecNodeId))\n .map((r) => ({ predicate: r.predicate, target: r.destinationSpecNodeId, ...(r.required ? { required: true } : {}) }));\n const incoming = (RELATIONSHIPS_BY_DESTINATION.get(specNodeId) ?? [])\n .filter((r) => keep(r, r.sourceSpecNodeId))\n .map((r) => ({ source: r.sourceSpecNodeId, predicate: r.predicate, ...(r.required ? { required: true } : {}) }));\n\n const attributes = Object.entries(info.attributeConstraints).map(([name, constraint]) => {\n const c = constraint as Record<string, unknown>;\n return {\n name,\n required: info.requiredAttributes.includes(name),\n ...(c.type ? { type: c.type } : {}),\n ...(c.enum ? { enum: c.enum } : {}),\n ...(c.description ? { description: c.description } : {}),\n };\n });\n\n return jsonResult({\n specNodeId,\n title: info.title,\n definition: spec?.description,\n attributes,\n outgoing,\n incoming,\n });\n });\n}\n\nexport const specTypeTool: McpToolDefinition<typeof inputSchema> = {\n name: \"spec_type\",\n description:\n \"Look up a node type in the spec: its definition, attributes (required ones, enum values) and the relationships \" +\n \"the catalog allows from and to it. Omit 'type' to list a layer's types. Use before model_add and relationship_add.\",\n inputSchema,\n handler: specTypeHandler,\n};\n\nfunction firstSentence(text: string | undefined): string | undefined {\n if (!text) return undefined;\n const match = text.match(/^.*?[.!?](\\s|$)/);\n return (match ? match[0] : text).trim();\n}\n"],
5
+ "mappings": "AAQA,SAAS,SAAS;AAElB,SAAS,sBAAsB;AAC/B,SAAS,gBAAgB,oBAAoB;AAC7C,SACE,aACA,sBACA,iBACA,yBAEK;AACP,SAAS,8BAA8B,+BAAsD;AAC7F,SAAS,UAAU,eAAe,aAAa,0BAA0B;AACzE,SAAS,YAAY,eAAuC;AAE5D,MAAM,cAAc;EAClB,OAAO,EAAE,OAAM,EAAG,SAAS,iDAAiD;EAC5E,MAAM,EACH,OAAM,EACN,SAAQ,EACR,SAAS,+EAA+E;EAC3F,WAAW,EAAE,OAAM,EAAG,SAAQ,EAAG,SAAS,8CAA8C;EACxF,YAAY,EAAE,OAAM,EAAG,SAAQ,EAAG,SAAS,2DAA2D;;AAKxG,IAAI,gBAAgD;AACpD,SAAS,aAAU;AACjB,MAAI,CAAC,eAAe;AAClB,UAAM,SAAS,IAAI,eAAc;AACjC,oBAAgB,OAAO,KAAI,EAAG,KAAK,MAAM,MAAM,GAC/C,cAAc,MAAM,MAAK;AACvB,sBAAgB;IAClB,CAAC;EACH;AACA,SAAO;AACT;AAEA,eAAsB,gBAAgB,MAAkB;AACtD,SAAO,QAAQ,YAAW;AACxB,QAAI,CAAC,aAAa,KAAK,KAAK,GAAG;AAC7B,YAAM,SAAS,eAAc,GACvB,UAAU,YAAY,KAAK,OAAO,QAAQ,CAAC;AACjD,YAAM,IAAI,SAAS,kBAAkB,KAAK,KAAK,KAAK,cAAc,MAAM;QACtE,GAAI,QAAQ,SAAS,IAAI,CAAC,iBAAiB,QAAQ,KAAK,MAAM,CAAC,GAAG,IAAI,CAAA;QACtE,iBAAiB,mBAAmB,MAAM,CAAC;OAC5C;IACH;AAEA,UAAM,SAAS,MAAM,WAAU;AAE/B,QAAI,CAAC,KAAK,MAAM;AACd,YAAM,QAAQ,OAAO,SAAS,KAAK,KAAK;AACxC,aAAO,WAAW;QAChB,OAAO,KAAK;QACZ,aAAa,OAAO;QACpB,OAAO,qBAAqB,KAAK,KAAK,EAAE,IAAI,CAAC,OAAO;UAClD,MAAM,EAAE;UACR,OAAO,EAAE;UACT,YAAY,cAAc,OAAO,YAAY,EAAE,UAAU,GAAG,WAAW;UACvE,oBAAoB,EAAE;UACtB;OACH;IACH;AAEA,QAAI,CAAC,gBAAgB,KAAK,OAAO,KAAK,IAAI,GAAG;AAC3C,YAAM,QAAQ,qBAAqB,KAAK,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,GAC1D,UAAU,YAAY,KAAK,MAAM,OAAO,CAAC;AAC/C,YAAM,IAAI,SAAS,iBAAiB,KAAK,IAAI,gBAAgB,KAAK,KAAK,KAAK,cAAc,MAAM;QAC9F,GAAI,QAAQ,SAAS,IAAI,CAAC,iBAAiB,QAAQ,KAAK,MAAM,CAAC,GAAG,IAAI,CAAA;QACtE,mBAAmB,KAAK,KAAK,KAAK,mBAAmB,KAAK,CAAC;OAC5D;IACH;AAEA,UAAM,aAAa,GAAG,KAAK,KAAK,IAAI,kBAAkB,KAAK,OAAO,KAAK,IAAI,CAAC,IACtE,OAAO,YAAY,UAAU,GAC7B,OAAO,OAAO,YAAY,UAAU,GAEpC,OAAO,CAAC,KAAuB,cAClC,CAAC,KAAK,aAAa,IAAI,cAAc,KAAK,eAC1C,CAAC,KAAK,cAAc,SAAS,WAAW,GAAG,KAAK,UAAU,GAAG,IAE1D,YAAY,wBAAwB,IAAI,UAAU,KAAK,CAAA,GAC1D,OAAO,CAAC,MAAM,KAAK,GAAG,EAAE,qBAAqB,CAAC,EAC9C,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,QAAQ,EAAE,uBAAuB,GAAI,EAAE,WAAW,EAAE,UAAU,GAAI,IAAK,CAAA,EAAG,EAAG,GAChH,YAAY,6BAA6B,IAAI,UAAU,KAAK,CAAA,GAC/D,OAAO,CAAC,MAAM,KAAK,GAAG,EAAE,gBAAgB,CAAC,EACzC,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,kBAAkB,WAAW,EAAE,WAAW,GAAI,EAAE,WAAW,EAAE,UAAU,GAAI,IAAK,CAAA,EAAG,EAAG,GAE3G,aAAa,OAAO,QAAQ,KAAK,oBAAoB,EAAE,IAAI,CAAC,CAAC,MAAM,UAAU,MAAK;AACtF,YAAM,IAAI;AACV,aAAO;QACL;QACA,UAAU,KAAK,mBAAmB,SAAS,IAAI;QAC/C,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAI,IAAK,CAAA;QAChC,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAI,IAAK,CAAA;QAChC,GAAI,EAAE,cAAc,EAAE,aAAa,EAAE,YAAW,IAAK,CAAA;;IAEzD,CAAC;AAED,WAAO,WAAW;MAChB;MACA,OAAO,KAAK;MACZ,YAAY,MAAM;MAClB;MACA;MACA;KACD;EACH,CAAC;AACH;AAEO,MAAM,eAAsD;EACjE,MAAM;EACN,aACE;EAEF;EACA,SAAS;;AAGX,SAAS,cAAc,MAAwB;AAC7C,MAAI,CAAC;AAAM;AACX,QAAM,QAAQ,KAAK,MAAM,iBAAiB;AAC1C,UAAQ,QAAQ,MAAM,CAAC,IAAI,MAAM,KAAI;AACvC;",
6
+ "names": []
7
+ }
@@ -5,6 +5,7 @@ import { createAnchor } from "../utils/markdown-anchor.js";
5
5
  import { escapeMarkdown, getLayerDescription, valueToMarkdown } from "../export/markdown-utils.js";
6
6
  import { getLayerOrder, CANONICAL_LAYER_NAMES } from "../core/layers.js";
7
7
  import { getValidRelationships } from "../generated/relationship-index.js";
8
+ import { getLayerReportFileName } from "./model-report-utils.js";
8
9
  class ModelLayerReportGenerator {
9
10
  modelVersion;
10
11
  generatedAt;
@@ -22,6 +23,8 @@ class ModelLayerReportGenerator {
22
23
  const lines = [];
23
24
  return lines.push(`# ${formatLayerName(data.layerName)}
24
25
  `), lines.push(`
26
+ `), lines.push(`[\u2190 Back to README](../README.md)
27
+ `), lines.push(`
25
28
  `), lines.push(`${getLayerDescription(data.layerName)}
26
29
  `), lines.push(`
27
30
  `), lines.join("");
@@ -245,10 +248,9 @@ class ModelLayerReportGenerator {
245
248
  * Create a markdown link to a layer report
246
249
  */
247
250
  createLayerLink(layerName) {
248
- const layerNumber = getLayerOrder(layerName);
249
- if (layerNumber === -1)
251
+ if (getLayerOrder(layerName) === -1)
250
252
  return layerName;
251
- const filename = `./${String(layerNumber).padStart(2, "0")}-${layerName}-layer-report.md`;
253
+ const filename = `./${getLayerReportFileName(layerName)}`;
252
254
  return `[${formatLayerName(layerName)}](${filename})`;
253
255
  }
254
256
  /**