@documentation-robotics/cli 0.2.0 → 0.4.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 (335) hide show
  1. package/README.md +46 -16
  2. package/dist/analysis/relationship-classifier.js +2 -9
  3. package/dist/analysis/relationship-classifier.js.map +2 -2
  4. package/dist/analyzers/active-analyzer.js +62 -0
  5. package/dist/analyzers/active-analyzer.js.map +7 -0
  6. package/dist/analyzers/call-graph-utils.js +2 -2
  7. package/dist/analyzers/call-graph-utils.js.map +2 -2
  8. package/dist/analyzers/cards/build.js +191 -0
  9. package/dist/analyzers/cards/build.js.map +7 -0
  10. package/dist/analyzers/cards/classify.js +42 -0
  11. package/dist/analyzers/cards/classify.js.map +7 -0
  12. package/dist/analyzers/cards/edge-index.js +44 -0
  13. package/dist/analyzers/cards/edge-index.js.map +7 -0
  14. package/dist/analyzers/cards/extract.js +138 -0
  15. package/dist/analyzers/cards/extract.js.map +7 -0
  16. package/dist/analyzers/cards/facts.js +46 -0
  17. package/dist/analyzers/cards/facts.js.map +7 -0
  18. package/dist/analyzers/cards/graph.js +42 -0
  19. package/dist/analyzers/cards/graph.js.map +7 -0
  20. package/dist/analyzers/cards/index.js +8 -0
  21. package/dist/analyzers/cards/index.js.map +7 -0
  22. package/dist/analyzers/cards/routes.js +96 -0
  23. package/dist/analyzers/cards/routes.js.map +7 -0
  24. package/dist/analyzers/cards/tier0.js +18 -0
  25. package/dist/analyzers/cards/tier0.js.map +7 -0
  26. package/dist/analyzers/cards/types.js +1 -0
  27. package/dist/analyzers/cards/types.js.map +7 -0
  28. package/dist/analyzers/cbm-analyzer.js +270 -174
  29. package/dist/analyzers/cbm-analyzer.js.map +2 -2
  30. package/dist/analyzers/cbm-responses.js +342 -0
  31. package/dist/analyzers/cbm-responses.js.map +7 -0
  32. package/dist/analyzers/code-changes.js +159 -0
  33. package/dist/analyzers/code-changes.js.map +7 -0
  34. package/dist/analyzers/edge-relationships.js +49 -0
  35. package/dist/analyzers/edge-relationships.js.map +7 -0
  36. package/dist/analyzers/extraction-plan.js +541 -0
  37. package/dist/analyzers/extraction-plan.js.map +7 -0
  38. package/dist/analyzers/index-job.js +229 -0
  39. package/dist/analyzers/index-job.js.map +7 -0
  40. package/dist/analyzers/mapping-loader.js +9 -0
  41. package/dist/analyzers/mapping-loader.js.map +2 -2
  42. package/dist/analyzers/tier0-rules.js +153 -0
  43. package/dist/analyzers/tier0-rules.js.map +7 -0
  44. package/dist/analyzers/types.js.map +1 -1
  45. package/dist/audit/relationships/ai/prompts.js +10 -10
  46. package/dist/audit/relationships/ai/prompts.js.map +2 -2
  47. package/dist/audit/relationships/analysis/layer-templates.js +52 -52
  48. package/dist/audit/relationships/analysis/layer-templates.js.map +1 -1
  49. package/dist/cli.js +23 -11
  50. package/dist/cli.js.map +2 -2
  51. package/dist/coding-agents/default-permissions.js +31 -1
  52. package/dist/coding-agents/default-permissions.js.map +2 -2
  53. package/dist/coding-agents/drbot-mcp.js +269 -0
  54. package/dist/coding-agents/drbot-mcp.js.map +7 -0
  55. package/dist/commands/add.js +7 -4
  56. package/dist/commands/add.js.map +2 -2
  57. package/dist/commands/analyzer.js +143 -40
  58. package/dist/commands/analyzer.js.map +2 -2
  59. package/dist/commands/changeset-verify.js +63 -0
  60. package/dist/commands/changeset-verify.js.map +7 -0
  61. package/dist/commands/changeset.js +80 -29
  62. package/dist/commands/changeset.js.map +2 -2
  63. package/dist/commands/delete.js +10 -4
  64. package/dist/commands/delete.js.map +2 -2
  65. package/dist/commands/farm.js +176 -60
  66. package/dist/commands/farm.js.map +2 -2
  67. package/dist/commands/init.js +9 -26
  68. package/dist/commands/init.js.map +2 -2
  69. package/dist/commands/mcp.js +40 -21
  70. package/dist/commands/mcp.js.map +3 -3
  71. package/dist/commands/relationship.js +208 -37
  72. package/dist/commands/relationship.js.map +2 -2
  73. package/dist/commands/reports.js +3 -3
  74. package/dist/commands/reports.js.map +2 -2
  75. package/dist/commands/update.js +52 -32
  76. package/dist/commands/update.js.map +2 -2
  77. package/dist/commands/upgrade.js +99 -103
  78. package/dist/commands/upgrade.js.map +3 -3
  79. package/dist/commands/validate.js +19 -3
  80. package/dist/commands/validate.js.map +2 -2
  81. package/dist/commands/version.js +1 -1
  82. package/dist/commands/visualize.js +34 -15
  83. package/dist/commands/visualize.js.map +2 -2
  84. package/dist/core/annotation-store.js +15 -5
  85. package/dist/core/annotation-store.js.map +2 -2
  86. package/dist/core/catalog-migration.js +34 -0
  87. package/dist/core/catalog-migration.js.map +7 -0
  88. package/dist/core/changeset-analysis.js +421 -0
  89. package/dist/core/changeset-analysis.js.map +7 -0
  90. package/dist/core/changeset-base-index.js +160 -0
  91. package/dist/core/changeset-base-index.js.map +7 -0
  92. package/dist/core/changeset-brief-compact.js +256 -0
  93. package/dist/core/changeset-brief-compact.js.map +7 -0
  94. package/dist/core/changeset-brief-delta.js +102 -0
  95. package/dist/core/changeset-brief-delta.js.map +7 -0
  96. package/dist/core/changeset-graph.js +281 -0
  97. package/dist/core/changeset-graph.js.map +7 -0
  98. package/dist/core/changeset-only-guard.js +32 -0
  99. package/dist/core/changeset-only-guard.js.map +7 -0
  100. package/dist/core/changeset-summary.js +68 -0
  101. package/dist/core/changeset-summary.js.map +7 -0
  102. package/dist/core/changeset-validator.js +66 -20
  103. package/dist/core/changeset-validator.js.map +2 -2
  104. package/dist/core/changeset-verify.js +848 -0
  105. package/dist/core/changeset-verify.js.map +7 -0
  106. package/dist/core/changeset.js +56 -9
  107. package/dist/core/changeset.js.map +2 -2
  108. package/dist/core/element-type-migration.js +145 -0
  109. package/dist/core/element-type-migration.js.map +7 -0
  110. package/dist/core/extraction-observability.js +161 -0
  111. package/dist/core/extraction-observability.js.map +7 -0
  112. package/dist/core/farm-manifest.js +12 -6
  113. package/dist/core/farm-manifest.js.map +2 -2
  114. package/dist/core/farm-sync-engine.js +1 -1
  115. package/dist/core/farm-sync-engine.js.map +2 -2
  116. package/dist/core/farm-sync-state.js +10 -2
  117. package/dist/core/farm-sync-state.js.map +2 -2
  118. package/dist/core/migration-registry.js +88 -47
  119. package/dist/core/migration-registry.js.map +2 -2
  120. package/dist/core/model-upgrade.js +102 -0
  121. package/dist/core/model-upgrade.js.map +7 -0
  122. package/dist/core/model.js +82 -30
  123. package/dist/core/model.js.map +2 -2
  124. package/dist/core/mutation-handler.js +71 -6
  125. package/dist/core/mutation-handler.js.map +2 -2
  126. package/dist/core/project-init.js +53 -0
  127. package/dist/core/project-init.js.map +7 -0
  128. package/dist/core/relationship-migration-table.js +6639 -0
  129. package/dist/core/relationship-migration-table.js.map +7 -0
  130. package/dist/core/relationship-migration.js +272 -0
  131. package/dist/core/relationship-migration.js.map +7 -0
  132. package/dist/core/relationship-prune.js +49 -0
  133. package/dist/core/relationship-prune.js.map +7 -0
  134. package/dist/core/relationship-staging.js +161 -0
  135. package/dist/core/relationship-staging.js.map +7 -0
  136. package/dist/core/relationships.js +26 -4
  137. package/dist/core/relationships.js.map +2 -2
  138. package/dist/core/report-data-model.js +7 -13
  139. package/dist/core/report-data-model.js.map +2 -2
  140. package/dist/core/staged-changeset-storage.js +145 -18
  141. package/dist/core/staged-changeset-storage.js.map +2 -2
  142. package/dist/core/staged-source-reference.js +28 -0
  143. package/dist/core/staged-source-reference.js.map +7 -0
  144. package/dist/core/staging-area.js +166 -24
  145. package/dist/core/staging-area.js.map +2 -2
  146. package/dist/core/upgrade-plan.js +117 -0
  147. package/dist/core/upgrade-plan.js.map +7 -0
  148. package/dist/core/virtual-projection.js +13 -5
  149. package/dist/core/virtual-projection.js.map +2 -2
  150. package/dist/export/archimate-exporter.js +1 -0
  151. package/dist/export/archimate-exporter.js.map +2 -2
  152. package/dist/generated/compiled-validators.js +2 -2
  153. package/dist/generated/compiled-validators.js.map +2 -2
  154. package/dist/generated/layer-registry.js +16 -16
  155. package/dist/generated/layer-registry.js.map +2 -2
  156. package/dist/generated/node-types.js +186 -168
  157. package/dist/generated/node-types.js.map +2 -2
  158. package/dist/generated/relationship-index.js +2468 -7913
  159. package/dist/generated/relationship-index.js.map +2 -2
  160. package/dist/integrations/claude_code/.claude-plugin/plugin.json +24 -0
  161. package/dist/integrations/claude_code/agents/dr-architect.md +30 -3
  162. package/dist/integrations/claude_code/agents/dr-extractor.md +8 -2
  163. package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
  164. package/dist/integrations/claude_code/commands/dr-changeset.md +2 -1
  165. package/dist/integrations/claude_code/commands/dr-map.md +20 -1
  166. package/dist/integrations/claude_code/commands/dr-plan.md +66 -0
  167. package/dist/integrations/claude_code/commands/dr-relate.md +106 -96
  168. package/dist/integrations/claude_code/commands/dr-verify.md +45 -2
  169. package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +14 -0
  170. package/dist/integrations/claude_code/docs/MCP_SERVER.md +47 -26
  171. package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
  172. package/dist/integrations/claude_code/docs/README.md +68 -2
  173. package/dist/integrations/claude_code/plugin-agents/dr-advisor.md +255 -0
  174. package/dist/integrations/claude_code/plugin-agents/dr-architect.md +306 -0
  175. package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +93 -72
  176. package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +163 -126
  177. package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +161 -126
  178. package/dist/integrations/claude_code/skills/dr_04_security_layer/SKILL.md +153 -133
  179. package/dist/integrations/claude_code/skills/dr_05_application_layer/SKILL.md +129 -100
  180. package/dist/integrations/claude_code/skills/dr_06_technology_layer/SKILL.md +111 -94
  181. package/dist/integrations/claude_code/skills/dr_07_api_layer/SKILL.md +143 -157
  182. package/dist/integrations/claude_code/skills/dr_08_data_model_layer/SKILL.md +117 -33
  183. package/dist/integrations/claude_code/skills/dr_09_dataset_layer/SKILL.md +74 -25
  184. package/dist/integrations/claude_code/skills/dr_10_ux_layer/SKILL.md +88 -12
  185. package/dist/integrations/claude_code/skills/dr_11_navigation_layer/SKILL.md +83 -24
  186. package/dist/integrations/claude_code/skills/dr_12_apm_layer/SKILL.md +57 -12
  187. package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +95 -15
  188. package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +178 -0
  189. package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +8 -0
  190. package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +6 -1
  191. package/dist/integrations/github_copilot/agents/dr-architect.md +3 -1
  192. package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +182 -102
  193. package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +266 -148
  194. package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +547 -86
  195. package/dist/integrations/github_copilot/skills/dr_04_security_layer/SKILL.md +164 -169
  196. package/dist/integrations/github_copilot/skills/dr_05_application_layer/SKILL.md +420 -150
  197. package/dist/integrations/github_copilot/skills/dr_06_technology_layer/SKILL.md +331 -138
  198. package/dist/integrations/github_copilot/skills/dr_07_api_layer/SKILL.md +265 -201
  199. package/dist/integrations/github_copilot/skills/dr_08_data_model_layer/SKILL.md +181 -58
  200. package/dist/integrations/github_copilot/skills/dr_09_dataset_layer/SKILL.md +209 -57
  201. package/dist/integrations/github_copilot/skills/dr_10_ux_layer/SKILL.md +277 -112
  202. package/dist/integrations/github_copilot/skills/dr_11_navigation_layer/SKILL.md +219 -70
  203. package/dist/integrations/github_copilot/skills/dr_12_apm_layer/SKILL.md +189 -105
  204. package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +225 -102
  205. package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +177 -0
  206. package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +3 -0
  207. package/dist/integrations/hash-utils.js.map +2 -2
  208. package/dist/mcp/api-key-manager.js +14 -1
  209. package/dist/mcp/api-key-manager.js.map +2 -2
  210. package/dist/mcp/changeset-only.js +45 -0
  211. package/dist/mcp/changeset-only.js.map +7 -0
  212. package/dist/mcp/project-root.js +85 -0
  213. package/dist/mcp/project-root.js.map +7 -0
  214. package/dist/mcp/server-instructions.js +27 -0
  215. package/dist/mcp/server-instructions.js.map +7 -0
  216. package/dist/mcp/strict-schema.js +52 -0
  217. package/dist/mcp/strict-schema.js.map +7 -0
  218. package/dist/mcp/tool-registry.js +55 -5
  219. package/dist/mcp/tool-registry.js.map +2 -2
  220. package/dist/mcp/tools/changeset-analysis-tools.js +98 -0
  221. package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
  222. package/dist/mcp/tools/changeset-commit.js +224 -0
  223. package/dist/mcp/tools/changeset-commit.js.map +7 -0
  224. package/dist/mcp/tools/changeset-editing.js +260 -0
  225. package/dist/mcp/tools/changeset-editing.js.map +7 -0
  226. package/dist/mcp/tools/changeset-lifecycle.js +83 -0
  227. package/dist/mcp/tools/changeset-lifecycle.js.map +7 -0
  228. package/dist/mcp/tools/changeset-shared.js +31 -0
  229. package/dist/mcp/tools/changeset-shared.js.map +7 -0
  230. package/dist/mcp/tools/changeset-show.js +17 -1
  231. package/dist/mcp/tools/changeset-show.js.map +2 -2
  232. package/dist/mcp/tools/changeset-verify.js +49 -0
  233. package/dist/mcp/tools/changeset-verify.js.map +7 -0
  234. package/dist/mcp/tools/codebase-extract.js +836 -0
  235. package/dist/mcp/tools/codebase-extract.js.map +7 -0
  236. package/dist/mcp/tools/codebase-index.js +125 -0
  237. package/dist/mcp/tools/codebase-index.js.map +7 -0
  238. package/dist/mcp/tools/codebase-shared.js +265 -0
  239. package/dist/mcp/tools/codebase-shared.js.map +7 -0
  240. package/dist/mcp/tools/codebase-verify.js +48 -0
  241. package/dist/mcp/tools/codebase-verify.js.map +7 -0
  242. package/dist/mcp/tools/index.js +34 -1
  243. package/dist/mcp/tools/index.js.map +2 -2
  244. package/dist/mcp/tools/model-add.js +191 -74
  245. package/dist/mcp/tools/model-add.js.map +2 -2
  246. package/dist/mcp/tools/model-delete.js +2 -2
  247. package/dist/mcp/tools/model-delete.js.map +2 -2
  248. package/dist/mcp/tools/model-search.js +14 -0
  249. package/dist/mcp/tools/model-search.js.map +2 -2
  250. package/dist/mcp/tools/model-show.js +29 -2
  251. package/dist/mcp/tools/model-show.js.map +2 -2
  252. package/dist/mcp/tools/model-trace.js +93 -30
  253. package/dist/mcp/tools/model-trace.js.map +2 -2
  254. package/dist/mcp/tools/model-update.js +46 -26
  255. package/dist/mcp/tools/model-update.js.map +2 -2
  256. package/dist/mcp/tools/model-validate.js +46 -4
  257. package/dist/mcp/tools/model-validate.js.map +2 -2
  258. package/dist/mcp/tools/project-status-collectors.js +381 -0
  259. package/dist/mcp/tools/project-status-collectors.js.map +7 -0
  260. package/dist/mcp/tools/project-status.js +237 -0
  261. package/dist/mcp/tools/project-status.js.map +7 -0
  262. package/dist/mcp/tools/project-tools.js +505 -0
  263. package/dist/mcp/tools/project-tools.js.map +7 -0
  264. package/dist/mcp/tools/relationship-tools.js +167 -0
  265. package/dist/mcp/tools/relationship-tools.js.map +7 -0
  266. package/dist/mcp/tools/review-open.js +210 -0
  267. package/dist/mcp/tools/review-open.js.map +7 -0
  268. package/dist/mcp/tools/shared.js +35 -4
  269. package/dist/mcp/tools/shared.js.map +2 -2
  270. package/dist/mcp/tools/spec-type.js +90 -0
  271. package/dist/mcp/tools/spec-type.js.map +7 -0
  272. package/dist/reports/model-report-orchestrator.js +31 -2
  273. package/dist/reports/model-report-orchestrator.js.map +2 -2
  274. package/dist/schemas/bundled/analyzers/cbm.json +1200 -70
  275. package/dist/schemas/bundled/analyzers/manifest.json +2 -2
  276. package/dist/schemas/bundled/api.json +1029 -1658
  277. package/dist/schemas/bundled/apm.json +499 -734
  278. package/dist/schemas/bundled/application.json +735 -371
  279. package/dist/schemas/bundled/base.json +3 -220
  280. package/dist/schemas/bundled/business.json +452 -366
  281. package/dist/schemas/bundled/data-model.json +395 -411
  282. package/dist/schemas/bundled/data-store.json +334 -558
  283. package/dist/schemas/bundled/manifest.json +18 -18
  284. package/dist/schemas/bundled/motivation.json +349 -368
  285. package/dist/schemas/bundled/navigation.json +415 -731
  286. package/dist/schemas/bundled/product.json +325 -159
  287. package/dist/schemas/bundled/security.json +864 -1232
  288. package/dist/schemas/bundled/technology.json +574 -703
  289. package/dist/schemas/bundled/testing.json +865 -1646
  290. package/dist/schemas/bundled/ux.json +739 -1352
  291. package/dist/server/schemas.js +8 -0
  292. package/dist/server/schemas.js.map +2 -2
  293. package/dist/server/server.js +686 -231
  294. package/dist/server/server.js.map +3 -3
  295. package/dist/server/viewer-registry.js +264 -0
  296. package/dist/server/viewer-registry.js.map +7 -0
  297. package/dist/server-entry.js +2 -1
  298. package/dist/server-entry.js.map +2 -2
  299. package/dist/telemetry/index.js +1 -1
  300. package/dist/utils/ansi-suppressor.js +6 -8
  301. package/dist/utils/ansi-suppressor.js.map +2 -2
  302. package/dist/utils/attribute-patch.js +41 -0
  303. package/dist/utils/attribute-patch.js.map +7 -0
  304. package/dist/utils/error-hints.js +29 -0
  305. package/dist/utils/error-hints.js.map +7 -0
  306. package/dist/utils/errors.js +9 -3
  307. package/dist/utils/errors.js.map +2 -2
  308. package/dist/utils/id-generator.js +5 -2
  309. package/dist/utils/id-generator.js.map +2 -2
  310. package/dist/utils/project-paths.js +26 -1
  311. package/dist/utils/project-paths.js.map +2 -2
  312. package/dist/utils/source-reference.js +64 -31
  313. package/dist/utils/source-reference.js.map +2 -2
  314. package/dist/utils/spec-version.js +2 -2
  315. package/dist/utils/spec-version.js.map +1 -1
  316. package/dist/validators/composed-reference-validator.js +5 -27
  317. package/dist/validators/composed-reference-validator.js.map +2 -2
  318. package/dist/validators/model-warnings.js +28 -0
  319. package/dist/validators/model-warnings.js.map +7 -0
  320. package/dist/validators/reference-validator.js +0 -7
  321. package/dist/validators/reference-validator.js.map +2 -2
  322. package/dist/validators/relationship-schema-validator.js +3 -3
  323. package/dist/validators/relationship-schema-validator.js.map +2 -2
  324. package/dist/validators/schema-validator.js +27 -9
  325. package/dist/validators/schema-validator.js.map +2 -2
  326. package/dist/validators/types.js.map +1 -1
  327. package/dist/viewer/README.md +6 -6
  328. package/dist/viewer/assets/main-CUOQGGRA.css +1 -0
  329. package/dist/viewer/assets/main-DvH3gYrI.js +84 -0
  330. package/dist/viewer/index.html +2 -2
  331. package/dist/viewer/manifest.json +10 -10
  332. package/dist/viewer/version.json +1 -1
  333. package/package.json +2 -1
  334. package/dist/viewer/assets/main-B7AOQXXX.js +0 -81
  335. package/dist/viewer/assets/main-vdrFrG7q.css +0 -1
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/core/changeset-only-guard.ts"],
4
+ "sourcesContent": ["/**\n * Changeset-only guard for chat agents.\n *\n * `dr visualize --with-changesets` lets its chat agent run `dr add`, `dr update` and\n * `dr relationship add`. Those commands stage their change when a changeset is active,\n * but write straight to the model when none is, and Claude Code's permission rules can't\n * tell the two apart. So the agent process runs with `CHANGESET_ONLY_ENV` set, and every\n * method that saves the base model calls `assertBaseModelWritable()` first: with the\n * variable set, the write fails and nothing on disk changes. Staging writes go to the\n * changeset store, not through the model, so they are unaffected.\n */\nimport { CLIError, ErrorCategory } from \"../utils/errors.js\";\n\nexport const CHANGESET_ONLY_ENV = \"DR_CHANGESET_ONLY\";\n\nconst ON_VALUES = new Set([\"true\", \"1\", \"yes\", \"on\"]);\nconst OFF_VALUES = new Set([\"false\", \"0\", \"no\", \"off\"]);\nconst warnedValues = new Set<string>();\n\n/**\n * Whether this process may only change the model through a changeset.\n *\n * Unset or empty means off. true/1/yes/on (any case) mean on, false/0/no/off mean off. Any\n * other value fails safe: the guard is on, and a warning naming the value goes to stderr once,\n * so a typo never silently lets an agent write the base model.\n */\nexport function isChangesetOnly(env: NodeJS.ProcessEnv = process.env): boolean {\n const raw = env[CHANGESET_ONLY_ENV];\n if (raw === undefined) return false;\n const value = raw.trim().toLowerCase();\n if (value === \"\" || OFF_VALUES.has(value)) return false;\n if (ON_VALUES.has(value)) return true;\n if (!warnedValues.has(raw)) {\n warnedValues.add(raw);\n console.error(\n `[dr] ${CHANGESET_ONLY_ENV}=${JSON.stringify(raw)} is not a recognised value ` +\n \"(use true/1/yes/on or false/0/no/off); treating it as on, so base-model writes are refused.\"\n );\n }\n return true;\n}\n\n/** Test hook: forget which unrecognised values have already been warned about. */\nexport function resetChangesetOnlyWarnings(): void {\n warnedValues.clear();\n}\n\n/** Thrown by `assertBaseModelWritable()`, so callers (e.g. the MCP server) can recognise the refusal. */\nexport class ChangesetOnlyError extends CLIError {\n constructor(...args: ConstructorParameters<typeof CLIError>) {\n super(...args);\n this.name = \"ChangesetOnlyError\";\n }\n}\n\n/** Throws when this process may only change the model through a changeset. */\nexport function assertBaseModelWritable(env: NodeJS.ProcessEnv = process.env): void {\n if (!isChangesetOnly(env)) return;\n throw new ChangesetOnlyError(\n \"This session can only change the model through a changeset, and no changeset is active.\",\n ErrorCategory.USER,\n [\n 'Run \"dr changeset create <name>\" (it activates the new changeset), or \"dr changeset activate <name>\"',\n \"Then re-run the command: it will be staged in the changeset for the user to review and commit\",\n ],\n { operation: \"save model\", context: `${CHANGESET_ONLY_ENV}=${env[CHANGESET_ONLY_ENV]}` }\n );\n}\n"],
5
+ "mappings": "AAWA,SAAS,UAAU,qBAAqB;AAEjC,MAAM,qBAAqB,qBAE5B,YAAY,oBAAI,IAAI,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,GAC9C,aAAa,oBAAI,IAAI,CAAC,SAAS,KAAK,MAAM,KAAK,CAAC,GAChD,eAAe,oBAAI,IAAG;AAStB,SAAU,gBAAgB,MAAyB,QAAQ,KAAG;AAClE,QAAM,MAAM,IAAI,kBAAkB;AAClC,MAAI,QAAQ;AAAW,WAAO;AAC9B,QAAM,QAAQ,IAAI,KAAI,EAAG,YAAW;AACpC,SAAI,UAAU,MAAM,WAAW,IAAI,KAAK,IAAU,MAC9C,UAAU,IAAI,KAAK,KAClB,aAAa,IAAI,GAAG,MACvB,aAAa,IAAI,GAAG,GACpB,QAAQ,MACN,QAAQ,kBAAkB,IAAI,KAAK,UAAU,GAAG,CAAC,wHAC8C,IAG5F;AACT;AAGM,SAAU,6BAA0B;AACxC,eAAa,MAAK;AACpB;AAGM,MAAO,2BAA2B,SAAQ;EAC9C,eAAe,MAA4C;AACzD,UAAM,GAAG,IAAI,GACb,KAAK,OAAO;EACd;;AAII,SAAU,wBAAwB,MAAyB,QAAQ,KAAG;AAC1E,MAAK,gBAAgB,GAAG;AACxB,UAAM,IAAI,mBACR,2FACA,cAAc,MACd;MACE;MACA;OAEF,EAAE,WAAW,cAAc,SAAS,GAAG,kBAAkB,IAAI,IAAI,kBAAkB,CAAC,GAAE,CAAE;AAE5F;",
6
+ "names": []
7
+ }
@@ -0,0 +1,68 @@
1
+ import { stagedRelationshipTriple } from "./relationship-staging.js";
2
+ const FIELD_ALIASES = { sourceReference: "source_reference" }, NESTED_FIELDS = /* @__PURE__ */ new Set(["attributes", "metadata"]);
3
+ function isRecord(value) {
4
+ return !!value && typeof value == "object" && !Array.isArray(value);
5
+ }
6
+ function canonical(value) {
7
+ return value === void 0 ? "undefined" : Array.isArray(value) ? `[${value.map(canonical).join(",")}]` : isRecord(value) ? `{${Object.keys(value).filter((k) => value[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonical(value[k])}`).join(",")}}` : JSON.stringify(value);
8
+ }
9
+ function isUnset(value) {
10
+ return value == null;
11
+ }
12
+ function sameValue(a, b) {
13
+ return isUnset(a) && isUnset(b) ? !0 : canonical(a) === canonical(b);
14
+ }
15
+ function normalizeSnapshot(snapshot) {
16
+ const out = {};
17
+ if (!snapshot)
18
+ return out;
19
+ for (const [key, value] of Object.entries(snapshot)) {
20
+ const name = FIELD_ALIASES[key] ?? key;
21
+ (name !== key || !(name in out)) && (out[name] = value);
22
+ }
23
+ return out;
24
+ }
25
+ function changedFields(before, after) {
26
+ const a = normalizeSnapshot(before), b = normalizeSnapshot(after), fields = [];
27
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]))
28
+ if (!sameValue(a[key], b[key])) {
29
+ if (NESTED_FIELDS.has(key) && (isRecord(a[key]) || isUnset(a[key])) && (isRecord(b[key]) || isUnset(b[key]))) {
30
+ const inner = { before: a[key] ?? {}, after: b[key] ?? {} };
31
+ for (const sub of /* @__PURE__ */ new Set([...Object.keys(inner.before), ...Object.keys(inner.after)]))
32
+ sameValue(inner.before[sub], inner.after[sub]) || fields.push(`${key}.${sub}`);
33
+ continue;
34
+ }
35
+ fields.push(key);
36
+ }
37
+ return fields.sort();
38
+ }
39
+ function elementType(change) {
40
+ const type = (change.after ?? change.before)?.type;
41
+ return typeof type == "string" ? type : void 0;
42
+ }
43
+ function summarizeChange(change, index) {
44
+ const base = { sequence: change.sequenceNumber ?? index, operation: change.type, layer: change.layerName };
45
+ if (change.type === "relationship-add" || change.type === "relationship-delete") {
46
+ const triple = stagedRelationshipTriple(change);
47
+ return triple ? { ...base, ...triple } : { ...base, elementId: change.elementId };
48
+ }
49
+ const entry = { ...base, elementId: change.elementId }, type = elementType(change);
50
+ return type && (entry.elementType = type), change.type === "update" && (entry.changedFields = changedFields(change.before, change.after)), entry;
51
+ }
52
+ function summarizeChangeset(changes) {
53
+ const totals = {
54
+ add: 0,
55
+ update: 0,
56
+ delete: 0,
57
+ "relationship-add": 0,
58
+ "relationship-delete": 0,
59
+ total: changes.length
60
+ }, entries = changes.map((change, index) => (totals[change.type] = (totals[change.type] ?? 0) + 1, summarizeChange(change, index)));
61
+ return entries.sort((x, y) => x.sequence - y.sequence), { totals, changes: entries };
62
+ }
63
+ export {
64
+ changedFields,
65
+ summarizeChange,
66
+ summarizeChangeset
67
+ };
68
+ //# sourceMappingURL=changeset-summary.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/core/changeset-summary.ts"],
4
+ "sourcesContent": ["/**\n * A compact view of a changeset's staged changes: one entry per change with its operation and what\n * it names, and for updates the fields it changed, without the before/after snapshots.\n *\n * Used by the MCP `changeset_show` tool's `detail: \"summary\"` mode, where an agent iterating on a\n * plan wants to see the plan's shape each pass without paying for every element snapshot.\n * See docs/CHANGE_MODELING.md (\"Iterating on a plan\").\n */\n\nimport type { Change, StagedChange } from \"./changeset.js\";\nimport { stagedRelationshipTriple } from \"./relationship-staging.js\";\n\nexport type ChangeOperation = Change[\"type\"];\n\nexport interface ChangeSummaryEntry {\n sequence: number;\n operation: ChangeOperation;\n layer: string;\n /** Element changes: the element's id. */\n elementId?: string;\n /** Element changes: the element's type (from its snapshot). */\n elementType?: string;\n /** Updates: the fields that differ between before and after (`name`, `attributes.summary`, ...). */\n changedFields?: string[];\n /** Relationship changes: the relationship's ends and predicate. */\n source?: string;\n predicate?: string;\n target?: string;\n}\n\nexport interface ChangesetSummary {\n totals: Record<ChangeOperation, number> & { total: number };\n changes: ChangeSummaryEntry[];\n}\n\n/** Snapshot keys that name the same field. The legacy camelCase key is reported as `source_reference`. */\nconst FIELD_ALIASES: Record<string, string> = { sourceReference: \"source_reference\" };\n\n/** Keys whose nested keys are reported one level down (`attributes.summary`). */\nconst NESTED_FIELDS = new Set([\"attributes\", \"metadata\"]);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === \"object\" && !Array.isArray(value);\n}\n\n/** Stable JSON for comparing snapshot values regardless of key order. */\nfunction canonical(value: unknown): string {\n if (value === undefined) return \"undefined\";\n if (Array.isArray(value)) return `[${value.map(canonical).join(\",\")}]`;\n if (isRecord(value)) {\n const keys = Object.keys(value)\n .filter((k) => value[k] !== undefined)\n .sort();\n return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(value[k])}`).join(\",\")}}`;\n }\n return JSON.stringify(value);\n}\n\n/** `null` and absent both mean \"not set\" in a snapshot (a cleared source reference is written as null). */\nfunction isUnset(value: unknown): boolean {\n return value === undefined || value === null;\n}\n\nfunction sameValue(a: unknown, b: unknown): boolean {\n if (isUnset(a) && isUnset(b)) return true;\n return canonical(a) === canonical(b);\n}\n\nfunction normalizeSnapshot(snapshot: Record<string, unknown> | undefined): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n if (!snapshot) return out;\n for (const [key, value] of Object.entries(snapshot)) {\n const name = FIELD_ALIASES[key] ?? key;\n // The legacy key carries the intended value; it wins over a stale snake_case copy.\n if (name !== key || !(name in out)) out[name] = value;\n }\n return out;\n}\n\n/**\n * The fields an update changes: top-level snapshot keys whose values differ, with `attributes`\n * and `metadata` broken down by key. Sorted for a stable result.\n */\nexport function changedFields(\n before: Record<string, unknown> | undefined,\n after: Record<string, unknown> | undefined\n): string[] {\n const a = normalizeSnapshot(before);\n const b = normalizeSnapshot(after);\n const fields: string[] = [];\n for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {\n if (sameValue(a[key], b[key])) continue;\n if (NESTED_FIELDS.has(key) && (isRecord(a[key]) || isUnset(a[key])) && (isRecord(b[key]) || isUnset(b[key]))) {\n const inner = { before: (a[key] ?? {}) as Record<string, unknown>, after: (b[key] ?? {}) as Record<string, unknown> };\n for (const sub of new Set([...Object.keys(inner.before), ...Object.keys(inner.after)])) {\n if (!sameValue(inner.before[sub], inner.after[sub])) fields.push(`${key}.${sub}`);\n }\n continue;\n }\n fields.push(key);\n }\n return fields.sort();\n}\n\nfunction elementType(change: Change): string | undefined {\n const snapshot = (change.after ?? change.before) as Record<string, unknown> | undefined;\n const type = snapshot?.type;\n return typeof type === \"string\" ? type : undefined;\n}\n\nexport function summarizeChange(change: Change | StagedChange, index: number): ChangeSummaryEntry {\n const sequence = (change as StagedChange).sequenceNumber ?? index;\n const base = { sequence, operation: change.type, layer: change.layerName };\n if (change.type === \"relationship-add\" || change.type === \"relationship-delete\") {\n const triple = stagedRelationshipTriple(change as StagedChange);\n // A malformed row still gets an entry, named by its key, rather than vanishing from the view.\n return triple ? { ...base, ...triple } : { ...base, elementId: change.elementId };\n }\n const entry: ChangeSummaryEntry = { ...base, elementId: change.elementId };\n const type = elementType(change);\n if (type) entry.elementType = type;\n if (change.type === \"update\") {\n entry.changedFields = changedFields(\n change.before as Record<string, unknown> | undefined,\n change.after as Record<string, unknown> | undefined\n );\n }\n return entry;\n}\n\nexport function summarizeChangeset(changes: ReadonlyArray<Change | StagedChange>): ChangesetSummary {\n const totals = {\n add: 0,\n update: 0,\n delete: 0,\n \"relationship-add\": 0,\n \"relationship-delete\": 0,\n total: changes.length,\n } as ChangesetSummary[\"totals\"];\n const entries = changes.map((change, index) => {\n totals[change.type] = (totals[change.type] ?? 0) + 1;\n return summarizeChange(change, index);\n });\n entries.sort((x, y) => x.sequence - y.sequence);\n return { totals, changes: entries };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -2,6 +2,11 @@ import { VirtualProjectionEngine } from "./virtual-projection.js";
2
2
  import { StagedChangesetStorage } from "./staged-changeset-storage.js";
3
3
  import { Validator } from "../validators/validator.js";
4
4
  import { ValidationResult } from "../validators/types.js";
5
+ import { Relationships, elementIdentifiers } from "./relationships.js";
6
+ import { getErrorMessage } from "../utils/errors.js";
7
+ function errorKey(issue) {
8
+ return `${issue.layer}\0${issue.elementId ?? ""}\0${issue.message}`;
9
+ }
5
10
  class ChangesetValidator {
6
11
  projectionEngine;
7
12
  storage;
@@ -10,29 +15,70 @@ class ChangesetValidator {
10
15
  this.projectionEngine = new VirtualProjectionEngine(rootPath), this.storage = new StagedChangesetStorage(rootPath), this.validator = new Validator();
11
16
  }
12
17
  /**
13
- * Validate a changeset by running the full 4-stage pipeline on its projected model.
14
- *
15
- * Only errors affecting elements touched by the changeset (add/update) block the
16
- * commit. Pre-existing errors in unchanged elements are demoted to warnings so that
17
- * incremental fixes can be committed without requiring the entire model to be valid
18
- * first.
18
+ * Validate a changeset by running the full validation pipeline on its projected model.
19
19
  *
20
- * @param baseModel - The base model to project
21
- * @param changesetId - The changeset to validate
22
- * @returns ValidationResult from all 4 stages
20
+ * See {@link validateChangesetReport}: only errors the changeset introduces are
21
+ * returned as errors; errors the base model already had are demoted to warnings.
23
22
  */
24
23
  async validateChangeset(baseModel, changesetId) {
25
- const changeset = await this.storage.load(changesetId), changedElementIds = /* @__PURE__ */ new Set();
26
- if (changeset)
27
- for (const change of changeset.changes)
28
- (change.type === "add" || change.type === "update") && changedElementIds.add(change.elementId);
29
- const projectedModel = await this.projectionEngine.projectModel(baseModel, changesetId), fullResult = await this.validator.validateModel(projectedModel);
30
- if (changedElementIds.size === 0)
31
- return fullResult;
32
- const filtered = new ValidationResult();
33
- for (const error of fullResult.errors)
34
- !error.elementId || changedElementIds.has(error.elementId) ? filtered.errors.push(error) : filtered.warnings.push({ ...error, severity: "warning" });
35
- return filtered.warnings.push(...fullResult.warnings), filtered;
24
+ return (await this.validateChangesetReport(baseModel, changesetId)).result;
25
+ }
26
+ /**
27
+ * Judge the change, not the base: validate the committed base model and the
28
+ * projected model (base + staged changes), and block only on errors the
29
+ * projection has beyond the base's. An error is identified by its layer,
30
+ * element and message, and counted, so a changeset that adds another copy of
31
+ * an existing problem still blocks.
32
+ *
33
+ * A model with pre-existing errors (a relationship the catalog has since
34
+ * retired, say) can therefore take a change to the same element; those errors
35
+ * come back as warnings and in `preExistingErrors`.
36
+ */
37
+ async validateChangesetReport(baseModel, changesetId) {
38
+ const changeset = await this.storage.load(changesetId), projectedModel = await this.projectionEngine.projectModel(baseModel, changesetId), baseRelationships = baseModel.getBaseRelationships(), projectedRelationships = Relationships.fromArray([...baseRelationships]), deletedIdentifiers = /* @__PURE__ */ new Set();
39
+ for (const change of changeset?.changes ?? [])
40
+ if (change.type === "relationship-add" && change.after) {
41
+ const rel = change.after;
42
+ projectedRelationships.find(rel.source, rel.target, rel.predicate).length === 0 && projectedRelationships.add({ ...rel, category: rel.category ?? "structural" });
43
+ } else if (change.type === "relationship-delete" && change.before) {
44
+ const rel = change.before;
45
+ projectedRelationships.delete(rel.source, rel.target, rel.predicate);
46
+ } else if (change.type === "delete") {
47
+ const before = change.before ?? {};
48
+ for (const id of elementIdentifiers(before, change.elementId))
49
+ deletedIdentifiers.add(id);
50
+ }
51
+ projectedModel.relationships = deletedIdentifiers.size > 0 ? Relationships.fromArray(projectedRelationships.getAll().filter((rel) => !deletedIdentifiers.has(rel.source) && !deletedIdentifiers.has(rel.target))) : projectedRelationships;
52
+ const projectedResult = await this.validator.validateModel(projectedModel);
53
+ let baseErrors = [], baseValidationFailure;
54
+ try {
55
+ const baseLayers = /* @__PURE__ */ new Map();
56
+ for (const name of baseModel.getLayerNames()) {
57
+ const layer = await baseModel.getBaseLayer(name);
58
+ layer && baseLayers.set(name, layer);
59
+ }
60
+ const baseView = {
61
+ manifest: baseModel.manifest,
62
+ layers: baseLayers,
63
+ relationships: Relationships.fromArray([...baseRelationships]),
64
+ loadedLayerFilter: baseModel.loadedLayerFilter,
65
+ isProjection: !0
66
+ };
67
+ baseErrors = (await this.validator.validateModel(baseView)).errors;
68
+ } catch (error) {
69
+ baseValidationFailure = getErrorMessage(error);
70
+ }
71
+ const remaining = /* @__PURE__ */ new Map();
72
+ for (const issue of baseErrors) {
73
+ const key = errorKey(issue);
74
+ remaining.set(key, (remaining.get(key) ?? 0) + 1);
75
+ }
76
+ const result = new ValidationResult(), preExistingErrors = [];
77
+ for (const issue of projectedResult.errors) {
78
+ const key = errorKey(issue), count = remaining.get(key) ?? 0;
79
+ count > 0 ? (remaining.set(key, count - 1), preExistingErrors.push(issue), result.warnings.push({ ...issue, severity: "warning", message: `(pre-existing) ${issue.message}` })) : result.errors.push(issue);
80
+ }
81
+ return result.warnings.push(...projectedResult.warnings), { result, preExistingErrors, ...baseValidationFailure ? { baseValidationFailure } : {} };
36
82
  }
37
83
  }
38
84
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/core/changeset-validator.ts"],
4
- "sourcesContent": ["/**\n * ChangesetValidator - Validates staged changes against projected model\n *\n * Runs the complete 4-stage validation pipeline against a merged view\n * of the base model with staged changes applied.\n */\n\nimport { VirtualProjectionEngine } from \"./virtual-projection.js\";\nimport { StagedChangesetStorage } from \"./staged-changeset-storage.js\";\nimport { Validator } from \"../validators/validator.js\";\nimport { ValidationResult } from \"../validators/types.js\";\nimport type { Model } from \"./model.js\";\n\n/**\n * Validates changesets by running validation pipeline on projected models\n */\nexport class ChangesetValidator {\n private projectionEngine: VirtualProjectionEngine;\n private storage: StagedChangesetStorage;\n private validator: Validator;\n\n constructor(rootPath: string) {\n this.projectionEngine = new VirtualProjectionEngine(rootPath);\n this.storage = new StagedChangesetStorage(rootPath);\n this.validator = new Validator();\n }\n\n /**\n * Validate a changeset by running the full 4-stage pipeline on its projected model.\n *\n * Only errors affecting elements touched by the changeset (add/update) block the\n * commit. Pre-existing errors in unchanged elements are demoted to warnings so that\n * incremental fixes can be committed without requiring the entire model to be valid\n * first.\n *\n * @param baseModel - The base model to project\n * @param changesetId - The changeset to validate\n * @returns ValidationResult from all 4 stages\n */\n async validateChangeset(baseModel: Model, changesetId: string): Promise<ValidationResult> {\n // Collect element IDs touched by this changeset (add/update only)\n const changeset = await this.storage.load(changesetId);\n const changedElementIds = new Set<string>();\n if (changeset) {\n for (const change of changeset.changes) {\n if (change.type === \"add\" || change.type === \"update\") {\n changedElementIds.add(change.elementId);\n }\n }\n }\n\n // Project the model with staged changes applied\n const projectedModel = await this.projectionEngine.projectModel(baseModel, changesetId);\n\n // Run unified validator on projected model\n const fullResult = await this.validator.validateModel(projectedModel as unknown as Model);\n\n if (changedElementIds.size === 0) {\n return fullResult;\n }\n\n // Partition errors: block on errors for changed elements; demote pre-existing\n // errors in unchanged elements to warnings so they don't block this commit.\n const filtered = new ValidationResult();\n for (const error of fullResult.errors) {\n if (!error.elementId || changedElementIds.has(error.elementId)) {\n filtered.errors.push(error);\n } else {\n filtered.warnings.push({ ...error, severity: \"warning\" });\n }\n }\n filtered.warnings.push(...fullResult.warnings);\n\n return filtered;\n }\n}\n"],
5
- "mappings": "AAOA,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,iBAAiB;AAC1B,SAAS,wBAAwB;AAM3B,MAAO,mBAAkB;EACrB;EACA;EACA;EAER,YAAY,UAAgB;AAC1B,SAAK,mBAAmB,IAAI,wBAAwB,QAAQ,GAC5D,KAAK,UAAU,IAAI,uBAAuB,QAAQ,GAClD,KAAK,YAAY,IAAI,UAAS;EAChC;;;;;;;;;;;;;EAcA,MAAM,kBAAkB,WAAkB,aAAmB;AAE3D,UAAM,YAAY,MAAM,KAAK,QAAQ,KAAK,WAAW,GAC/C,oBAAoB,oBAAI,IAAG;AACjC,QAAI;AACF,iBAAW,UAAU,UAAU;AAC7B,SAAI,OAAO,SAAS,SAAS,OAAO,SAAS,aAC3C,kBAAkB,IAAI,OAAO,SAAS;AAM5C,UAAM,iBAAiB,MAAM,KAAK,iBAAiB,aAAa,WAAW,WAAW,GAGhF,aAAa,MAAM,KAAK,UAAU,cAAc,cAAkC;AAExF,QAAI,kBAAkB,SAAS;AAC7B,aAAO;AAKT,UAAM,WAAW,IAAI,iBAAgB;AACrC,eAAW,SAAS,WAAW;AAC7B,MAAI,CAAC,MAAM,aAAa,kBAAkB,IAAI,MAAM,SAAS,IAC3D,SAAS,OAAO,KAAK,KAAK,IAE1B,SAAS,SAAS,KAAK,EAAE,GAAG,OAAO,UAAU,UAAS,CAAE;AAG5D,oBAAS,SAAS,KAAK,GAAG,WAAW,QAAQ,GAEtC;EACT;;",
4
+ "sourcesContent": ["/**\n * ChangesetValidator - Validates staged changes against projected model\n *\n * Runs the validation pipeline against a merged view of the base model with\n * staged changes applied, and against the base model itself, so a commit is\n * judged by the errors the changeset introduces rather than by the base's.\n */\n\nimport { VirtualProjectionEngine, type ProjectedModel } from \"./virtual-projection.js\";\nimport { StagedChangesetStorage } from \"./staged-changeset-storage.js\";\nimport { Validator } from \"../validators/validator.js\";\nimport { ValidationResult, type ValidationIssue } from \"../validators/types.js\";\nimport type { Model } from \"./model.js\";\nimport type { Layer } from \"./layer.js\";\nimport type { StagedChange } from \"./changeset.js\";\nimport { Relationships, elementIdentifiers, type Relationship } from \"./relationships.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\n\n/**\n * The outcome of judging a changeset: what it introduces, and what was already wrong.\n */\nexport interface ChangesetValidationReport {\n /**\n * Errors that block the commit: those the projected model has beyond the base\n * model's. Pre-existing errors are carried as warnings.\n */\n result: ValidationResult;\n /** Errors the base model already had that the projected model still has. */\n preExistingErrors: ValidationIssue[];\n /** Set when the base model could not be validated, so every projected error blocks. */\n baseValidationFailure?: string;\n}\n\n/** Identity of an error, for comparing the base model's errors with the projection's. */\nfunction errorKey(issue: ValidationIssue): string {\n return `${issue.layer}\\u0000${issue.elementId ?? \"\"}\\u0000${issue.message}`;\n}\n\n/**\n * Validates changesets by running validation pipeline on projected models\n */\nexport class ChangesetValidator {\n private projectionEngine: VirtualProjectionEngine;\n private storage: StagedChangesetStorage;\n private validator: Validator;\n\n constructor(rootPath: string) {\n this.projectionEngine = new VirtualProjectionEngine(rootPath);\n this.storage = new StagedChangesetStorage(rootPath);\n this.validator = new Validator();\n }\n\n /**\n * Validate a changeset by running the full validation pipeline on its projected model.\n *\n * See {@link validateChangesetReport}: only errors the changeset introduces are\n * returned as errors; errors the base model already had are demoted to warnings.\n */\n async validateChangeset(baseModel: Model, changesetId: string): Promise<ValidationResult> {\n return (await this.validateChangesetReport(baseModel, changesetId)).result;\n }\n\n /**\n * Judge the change, not the base: validate the committed base model and the\n * projected model (base + staged changes), and block only on errors the\n * projection has beyond the base's. An error is identified by its layer,\n * element and message, and counted, so a changeset that adds another copy of\n * an existing problem still blocks.\n *\n * A model with pre-existing errors (a relationship the catalog has since\n * retired, say) can therefore take a change to the same element; those errors\n * come back as warnings and in `preExistingErrors`.\n */\n async validateChangesetReport(baseModel: Model, changesetId: string): Promise<ChangesetValidationReport> {\n const changeset = await this.storage.load(changesetId);\n\n // Project the model with staged changes applied\n const projectedModel = await this.projectionEngine.projectModel(baseModel, changesetId);\n\n // Relationships as committed, without the active changeset's staged changes that\n // Model.load() merges in, so the result does not depend on which changeset is active.\n const baseRelationships = baseModel.getBaseRelationships();\n\n // Relationships as the changeset leaves them. Those naming an element this changeset\n // deletes are removed at commit (by UUID or by path), so they are left out here too.\n const projectedRelationships = Relationships.fromArray([...baseRelationships]);\n const deletedIdentifiers = new Set<string>();\n for (const change of (changeset?.changes ?? []) as StagedChange[]) {\n if (change.type === \"relationship-add\" && change.after) {\n const rel = change.after as unknown as Relationship;\n if (projectedRelationships.find(rel.source, rel.target, rel.predicate).length === 0) {\n projectedRelationships.add({ ...rel, category: rel.category ?? \"structural\" });\n }\n } else if (change.type === \"relationship-delete\" && change.before) {\n const rel = change.before as { source: string; target: string; predicate?: string };\n projectedRelationships.delete(rel.source, rel.target, rel.predicate);\n } else if (change.type === \"delete\") {\n const before = (change.before ?? {}) as { id?: unknown; path?: unknown };\n for (const id of elementIdentifiers(before, change.elementId)) {\n deletedIdentifiers.add(id);\n }\n }\n }\n projectedModel.relationships =\n deletedIdentifiers.size > 0\n ? Relationships.fromArray(\n projectedRelationships\n .getAll()\n .filter((rel) => !deletedIdentifiers.has(rel.source) && !deletedIdentifiers.has(rel.target))\n )\n : projectedRelationships;\n\n const projectedResult = await this.validator.validateModel(projectedModel as unknown as Model);\n\n // The base model, validated the same way.\n let baseErrors: ValidationIssue[] = [];\n let baseValidationFailure: string | undefined;\n try {\n const baseLayers = new Map<string, Layer>();\n for (const name of baseModel.getLayerNames()) {\n const layer = await baseModel.getBaseLayer(name);\n if (layer) baseLayers.set(name, layer);\n }\n const baseView: ProjectedModel = {\n manifest: baseModel.manifest,\n layers: baseLayers,\n relationships: Relationships.fromArray([...baseRelationships]),\n loadedLayerFilter: baseModel.loadedLayerFilter,\n isProjection: true,\n };\n baseErrors = (await this.validator.validateModel(baseView as unknown as Model)).errors;\n } catch (error) {\n // Without a base to compare against, every projected error blocks (the strict\n // behaviour), and the report says why.\n baseValidationFailure = getErrorMessage(error);\n }\n\n const remaining = new Map<string, number>();\n for (const issue of baseErrors) {\n const key = errorKey(issue);\n remaining.set(key, (remaining.get(key) ?? 0) + 1);\n }\n\n const result = new ValidationResult();\n const preExistingErrors: ValidationIssue[] = [];\n for (const issue of projectedResult.errors) {\n const key = errorKey(issue);\n const count = remaining.get(key) ?? 0;\n if (count > 0) {\n remaining.set(key, count - 1);\n preExistingErrors.push(issue);\n result.warnings.push({ ...issue, severity: \"warning\", message: `(pre-existing) ${issue.message}` });\n } else {\n result.errors.push(issue);\n }\n }\n result.warnings.push(...projectedResult.warnings);\n\n return { result, preExistingErrors, ...(baseValidationFailure ? { baseValidationFailure } : {}) };\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }