@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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/core/annotation-store.ts"],
4
- "sourcesContent": ["/**\n * AnnotationStore - filesystem-backed storage for element annotations.\n *\n * Persists annotations under documentation-robotics/annotations/{id}.yaml, one\n * file per annotation with its replies embedded. This is what gives the MCP\n * annotation_* tools (a separate stdio process per session, with no shared\n * memory with a running `dr server`) the same annotation capabilities as the\n * REST server's in-memory /api/annotations endpoints, backed by durable\n * storage that survives process restarts.\n */\n\nimport { readFile, readdir, rm } from \"fs/promises\";\nimport { randomBytes } from \"crypto\";\nimport path from \"path\";\nimport yaml from \"yaml\";\nimport { z } from \"zod\";\nimport { atomicWrite, ensureDir, fileExists } from \"../utils/file-io.js\";\nimport { FileLock } from \"../utils/file-lock.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\n\nconst AnnotationReplySchema = z.object({\n id: z.string(),\n author: z.string(),\n content: z.string(),\n createdAt: z.string(),\n});\n\n/**\n * Runtime schema for an annotation file on disk, used to validate parsed YAML\n * before it is trusted as an `Annotation` \u2014 hand-edited files can otherwise\n * omit required fields (e.g. `tags`) and crash downstream consumers.\n */\nconst AnnotationSchema = z.object({\n id: z.string(),\n elementId: z.string(),\n author: z.string(),\n content: z.string(),\n createdAt: z.string(),\n updatedAt: z.string().optional(),\n tags: z.array(z.string()),\n resolved: z.boolean(),\n replies: z.array(AnnotationReplySchema),\n});\n\nexport type AnnotationReply = z.infer<typeof AnnotationReplySchema>;\n\nexport type Annotation = z.infer<typeof AnnotationSchema>;\n\nexport interface AnnotationCreateInput {\n elementId: string;\n author?: string;\n content: string;\n tags?: string[];\n}\n\nexport interface AnnotationUpdateInput {\n content?: string;\n tags?: string[];\n resolved?: boolean;\n}\n\nexport interface AnnotationReplyCreateInput {\n author: string;\n content: string;\n}\n\nexport class AnnotationStore {\n private annotationsDir: string;\n\n constructor(rootPath: string) {\n this.annotationsDir = path.join(rootPath, \"documentation-robotics\", \"annotations\");\n }\n\n /** Lists all annotations, optionally filtered to a single element, oldest first. Skips (and warns on) individual files that fail to load rather than failing the whole list. */\n async list(elementId?: string): Promise<Annotation[]> {\n if (!(await fileExists(this.annotationsDir))) {\n return [];\n }\n\n const entries = await readdir(this.annotationsDir, { withFileTypes: true });\n const annotations: Annotation[] = [];\n\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(\".yaml\")) continue;\n const id = entry.name.slice(0, -\".yaml\".length);\n\n let annotation: Annotation | null;\n try {\n annotation = await this.get(id);\n } catch (error) {\n process.stderr.write(`Warning: Skipping annotation '${id}': ${getErrorMessage(error)}\\n`);\n continue;\n }\n\n if (annotation && (!elementId || annotation.elementId === elementId)) {\n annotations.push(annotation);\n }\n }\n\n return annotations.sort((a, b) => a.createdAt.localeCompare(b.createdAt));\n }\n\n async get(id: string): Promise<Annotation | null> {\n const filePath = this.getPath(id);\n if (!(await fileExists(filePath))) {\n return null;\n }\n\n let parsed: unknown;\n try {\n const content = await readFile(filePath, \"utf-8\");\n parsed = yaml.parse(content);\n } catch (error) {\n throw new Error(`Failed to load annotation '${id}': ${getErrorMessage(error)}`);\n }\n\n const result = AnnotationSchema.safeParse(parsed);\n if (!result.success) {\n throw new Error(\n `Failed to load annotation '${id}': invalid annotation data - ${result.error.message}`\n );\n }\n return result.data;\n }\n\n async create(input: AnnotationCreateInput): Promise<Annotation> {\n const annotation: Annotation = {\n id: this.generateAnnotationId(),\n elementId: input.elementId,\n author: input.author?.trim() || \"Anonymous\",\n content: input.content,\n createdAt: new Date().toISOString(),\n tags: input.tags ?? [],\n resolved: false,\n replies: [],\n };\n\n await ensureDir(this.annotationsDir);\n await atomicWrite(this.getPath(annotation.id), yaml.stringify(annotation));\n return annotation;\n }\n\n /** Partially updates an annotation's content, tags, and/or resolved state. Returns null if not found. */\n async update(id: string, updates: AnnotationUpdateInput): Promise<Annotation | null> {\n const filePath = this.getPath(id);\n await ensureDir(this.annotationsDir);\n const lock = new FileLock(filePath);\n\n return lock.withLock(async () => {\n const annotation = await this.get(id);\n if (!annotation) return null;\n\n const updated: Annotation = {\n ...annotation,\n content: updates.content ?? annotation.content,\n tags: updates.tags ?? annotation.tags,\n resolved: updates.resolved ?? annotation.resolved,\n updatedAt: new Date().toISOString(),\n };\n\n await atomicWrite(filePath, yaml.stringify(updated));\n return updated;\n });\n }\n\n /** Deletes an annotation and its replies. Returns false if it didn't exist. */\n async delete(id: string): Promise<boolean> {\n const filePath = this.getPath(id);\n if (!(await fileExists(filePath))) {\n return false;\n }\n await rm(filePath, { force: true });\n return true;\n }\n\n /** Appends a reply to an annotation. Returns null if the annotation doesn't exist. */\n async addReply(id: string, input: AnnotationReplyCreateInput): Promise<AnnotationReply | null> {\n const filePath = this.getPath(id);\n await ensureDir(this.annotationsDir);\n const lock = new FileLock(filePath);\n\n return lock.withLock(async () => {\n const annotation = await this.get(id);\n if (!annotation) return null;\n\n const reply: AnnotationReply = {\n id: this.generateReplyId(),\n author: input.author,\n content: input.content,\n createdAt: new Date().toISOString(),\n };\n\n annotation.replies.push(reply);\n await atomicWrite(filePath, yaml.stringify(annotation));\n return reply;\n });\n }\n\n private getPath(id: string): string {\n return path.join(this.annotationsDir, `${this.sanitizeId(id)}.yaml`);\n }\n\n /** Whitelists safe characters so an annotation ID can't escape annotationsDir via path traversal. */\n private sanitizeId(id: string): string {\n const sanitized = id.replace(/[^a-zA-Z0-9_-]/g, \"\");\n if (!sanitized) {\n throw new Error(`Invalid annotation ID '${id}'`);\n }\n return sanitized;\n }\n\n private generateAnnotationId(): string {\n return `ann-${Date.now()}-${randomBytes(4).toString(\"hex\")}`;\n }\n\n private generateReplyId(): string {\n return `reply-${Date.now()}-${randomBytes(4).toString(\"hex\")}`;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * AnnotationStore - filesystem-backed storage for element annotations.\n *\n * Persists annotations under documentation-robotics/annotations/{id}.yaml, one\n * file per annotation with its replies embedded. It is the one annotation store:\n * the viewer server's /api/annotations routes and the MCP annotation_* tools (a\n * separate process per session) both read and write it, so each sees the other's\n * annotations and replies, and they survive restarts. The viewer server watches\n * the directory and broadcasts changes other processes make.\n */\n\nimport { readFile, readdir, rm } from \"fs/promises\";\nimport { randomBytes } from \"crypto\";\nimport path from \"path\";\nimport yaml from \"yaml\";\nimport { z } from \"zod\";\nimport { atomicWrite, ensureDir, fileExists } from \"../utils/file-io.js\";\nimport { FileLock } from \"../utils/file-lock.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\n\nconst AnnotationReplySchema = z.object({\n id: z.string(),\n author: z.string(),\n content: z.string(),\n createdAt: z.string(),\n});\n\n/**\n * Runtime schema for an annotation file on disk, used to validate parsed YAML\n * before it is trusted as an `Annotation` \u2014 hand-edited files can otherwise\n * omit required fields (e.g. `tags`) and crash downstream consumers.\n */\nconst AnnotationSchema = z.object({\n id: z.string(),\n elementId: z.string(),\n author: z.string(),\n content: z.string(),\n createdAt: z.string(),\n updatedAt: z.string().optional(),\n tags: z.array(z.string()),\n resolved: z.boolean(),\n replies: z.array(AnnotationReplySchema),\n});\n\nexport type AnnotationReply = z.infer<typeof AnnotationReplySchema>;\n\nexport type Annotation = z.infer<typeof AnnotationSchema>;\n\nexport interface AnnotationCreateInput {\n elementId: string;\n author?: string;\n content: string;\n tags?: string[];\n}\n\nexport interface AnnotationUpdateInput {\n content?: string;\n tags?: string[];\n resolved?: boolean;\n}\n\nexport interface AnnotationReplyCreateInput {\n author: string;\n content: string;\n}\n\nlet lastCreatedAt = 0;\n\n/**\n * A creation time that never repeats within this process. Annotations list oldest first by\n * createdAt, so two made in the same millisecond (a viewer comment and its follow-up, say) would\n * otherwise come back in directory order.\n */\nfunction nextCreatedAt(): string {\n lastCreatedAt = Math.max(Date.now(), lastCreatedAt + 1);\n return new Date(lastCreatedAt).toISOString();\n}\n\n/** Now, or the given time if the clock is behind it (nextCreatedAt can run ahead in a burst). */\nfunction notBefore(iso: string): string {\n return new Date(Math.max(Date.now(), Date.parse(iso) || 0)).toISOString();\n}\n\nexport class AnnotationStore {\n private annotationsDir: string;\n\n constructor(rootPath: string) {\n this.annotationsDir = path.join(rootPath, \"documentation-robotics\", \"annotations\");\n }\n\n /** Lists all annotations, optionally filtered to a single element, oldest first. Skips (and warns on) individual files that fail to load rather than failing the whole list. */\n async list(elementId?: string): Promise<Annotation[]> {\n if (!(await fileExists(this.annotationsDir))) {\n return [];\n }\n\n const entries = await readdir(this.annotationsDir, { withFileTypes: true });\n const annotations: Annotation[] = [];\n\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(\".yaml\")) continue;\n const id = entry.name.slice(0, -\".yaml\".length);\n\n let annotation: Annotation | null;\n try {\n annotation = await this.get(id);\n } catch (error) {\n process.stderr.write(`Warning: Skipping annotation '${id}': ${getErrorMessage(error)}\\n`);\n continue;\n }\n\n if (annotation && (!elementId || annotation.elementId === elementId)) {\n annotations.push(annotation);\n }\n }\n\n return annotations.sort((a, b) => a.createdAt.localeCompare(b.createdAt));\n }\n\n async get(id: string): Promise<Annotation | null> {\n const filePath = this.getPath(id);\n if (!(await fileExists(filePath))) {\n return null;\n }\n\n let parsed: unknown;\n try {\n const content = await readFile(filePath, \"utf-8\");\n parsed = yaml.parse(content);\n } catch (error) {\n throw new Error(`Failed to load annotation '${id}': ${getErrorMessage(error)}`);\n }\n\n const result = AnnotationSchema.safeParse(parsed);\n if (!result.success) {\n throw new Error(\n `Failed to load annotation '${id}': invalid annotation data - ${result.error.message}`\n );\n }\n return result.data;\n }\n\n async create(input: AnnotationCreateInput): Promise<Annotation> {\n const annotation: Annotation = {\n id: this.generateAnnotationId(),\n elementId: input.elementId,\n author: input.author?.trim() || \"Anonymous\",\n content: input.content,\n createdAt: nextCreatedAt(),\n tags: input.tags ?? [],\n resolved: false,\n replies: [],\n };\n\n await ensureDir(this.annotationsDir);\n await atomicWrite(this.getPath(annotation.id), yaml.stringify(annotation));\n return annotation;\n }\n\n /** Partially updates an annotation's content, tags, and/or resolved state. Returns null if not found. */\n async update(id: string, updates: AnnotationUpdateInput): Promise<Annotation | null> {\n const filePath = this.getPath(id);\n await ensureDir(this.annotationsDir);\n const lock = new FileLock(filePath);\n\n return lock.withLock(async () => {\n const annotation = await this.get(id);\n if (!annotation) return null;\n\n const updated: Annotation = {\n ...annotation,\n content: updates.content ?? annotation.content,\n tags: updates.tags ?? annotation.tags,\n resolved: updates.resolved ?? annotation.resolved,\n updatedAt: notBefore(annotation.createdAt),\n };\n\n await atomicWrite(filePath, yaml.stringify(updated));\n return updated;\n });\n }\n\n /**\n * Deletes an annotation and its replies. Returns false if it didn't exist. Holds the same lock\n * as update and addReply, so a write in flight can't bring the annotation back.\n */\n async delete(id: string): Promise<boolean> {\n const filePath = this.getPath(id);\n if (!(await fileExists(filePath))) {\n return false;\n }\n const lock = new FileLock(filePath);\n return lock.withLock(async () => {\n if (!(await fileExists(filePath))) return false;\n await rm(filePath, { force: true });\n return true;\n });\n }\n\n /** Appends a reply to an annotation. Returns null if the annotation doesn't exist. */\n async addReply(id: string, input: AnnotationReplyCreateInput): Promise<AnnotationReply | null> {\n const filePath = this.getPath(id);\n await ensureDir(this.annotationsDir);\n const lock = new FileLock(filePath);\n\n return lock.withLock(async () => {\n const annotation = await this.get(id);\n if (!annotation) return null;\n\n const reply: AnnotationReply = {\n id: this.generateReplyId(),\n author: input.author,\n content: input.content,\n createdAt: notBefore(annotation.createdAt),\n };\n\n annotation.replies.push(reply);\n await atomicWrite(filePath, yaml.stringify(annotation));\n return reply;\n });\n }\n\n private getPath(id: string): string {\n return path.join(this.annotationsDir, `${this.sanitizeId(id)}.yaml`);\n }\n\n /** Whitelists safe characters so an annotation ID can't escape annotationsDir via path traversal. */\n private sanitizeId(id: string): string {\n const sanitized = id.replace(/[^a-zA-Z0-9_-]/g, \"\");\n if (!sanitized) {\n throw new Error(`Invalid annotation ID '${id}'`);\n }\n return sanitized;\n }\n\n private generateAnnotationId(): string {\n return `ann-${Date.now()}-${randomBytes(4).toString(\"hex\")}`;\n }\n\n private generateReplyId(): string {\n return `reply-${Date.now()}-${randomBytes(4).toString(\"hex\")}`;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,34 @@
1
+ import { migrateElementTypes, summarizeElementTypeChanges } from "./element-type-migration.js";
2
+ import { migrateRelationshipsFile, migrateStagedChangesets, summarizeRelationshipChanges } from "./relationship-migration.js";
3
+ async function applyCatalogMigrations(model, options = {}) {
4
+ const dryRun = options.dryRun ?? !1, types = await migrateElementTypes(model, void 0, { dryRun }), relationships = types.errors.length ? { filesModified: 0, changes: [], committed: { before: [], after: [] }, error: void 0 } : await migrateRelationshipsFile(model, void 0, { dryRun }), staged = relationships.error ? { changesetsModified: 0, changes: [], errors: [] } : await migrateStagedChangesets(model, relationships.committed, void 0, { dryRun }), summaries = [
5
+ ...types.changes.length > 0 ? [`element types renamed: ${summarizeElementTypeChanges(types.changes)}`] : [],
6
+ relationships.changes.length > 0 ? summarizeRelationshipChanges(relationships.changes) : "no relationships to rewrite",
7
+ ...staged.changes.length > 0 ? [summarizeRelationshipChanges(staged.changes, "staged relationship changes")] : []
8
+ ];
9
+ return {
10
+ filesModified: types.filesModified + types.changesetsModified + relationships.filesModified + staged.changesetsModified,
11
+ summaries,
12
+ errors: [...types.errors, ...relationships.error ? [relationships.error] : [], ...staged.errors],
13
+ details: {
14
+ elementTypeChanges: types.changes,
15
+ relationshipChanges: relationships.changes,
16
+ stagedRelationshipChanges: staged.changes,
17
+ ...!types.errors.length && !relationships.error ? { committedRelationshipsAfter: relationships.committed.after } : {}
18
+ }
19
+ };
20
+ }
21
+ async function pendingCatalogMigrations(model) {
22
+ const r = await applyCatalogMigrations(model, { dryRun: !0 });
23
+ return {
24
+ elementTypes: r.details.elementTypeChanges.length,
25
+ relationships: r.details.relationshipChanges.length,
26
+ stagedRelationships: r.details.stagedRelationshipChanges.length,
27
+ errors: r.errors
28
+ };
29
+ }
30
+ export {
31
+ applyCatalogMigrations,
32
+ pendingCatalogMigrations
33
+ };
34
+ //# sourceMappingURL=catalog-migration.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/core/catalog-migration.ts"],
4
+ "sourcesContent": ["/**\n * Catalog migration: brings a model's elements and relationships in line with the current spec\n * catalog, using the generated rewrite table (relationship-migration-table.ts).\n *\n * 1. element type renames (element-type-migration.ts), so the rules below see the new types\n * 2. relationship rewrites in relationships.yaml (relationship-migration.ts)\n * 3. the same rewrites in staged changesets\n *\n * It runs inside the 0.9.0 -> 0.10.0 and 0.10.0 -> 0.11.0 migrations, and on its own when `dr upgrade` finds a model\n * already at the current spec version that still names retired types or relationships: the\n * catalog can change between spec version bumps, and a model stamped with the version before\n * a change would otherwise never receive it. Every step only rewrites what the table matches,\n * so running it again on a migrated model changes nothing.\n */\nimport type { Model } from \"./model.js\";\nimport type { Relationship } from \"./relationships.js\";\nimport { migrateElementTypes, summarizeElementTypeChanges, type ElementTypeRenameChange } from \"./element-type-migration.js\";\nimport {\n migrateRelationshipsFile,\n migrateStagedChangesets,\n summarizeRelationshipChanges,\n type RelationshipRewriteChange,\n type StagedRelationshipRewriteChange,\n} from \"./relationship-migration.js\";\n\nexport interface CatalogMigrationResult {\n filesModified: number;\n /** One phrase per kind of change, for the migration description */\n summaries: string[];\n errors: string[];\n details: {\n elementTypeChanges: ElementTypeRenameChange[];\n relationshipChanges: RelationshipRewriteChange[];\n stagedRelationshipChanges: StagedRelationshipRewriteChange[];\n /**\n * relationships.yaml as the migration leaves it (as it would, in a dry run). Absent when the\n * relationships were not migrated (an element-type error, an unreadable file).\n */\n committedRelationshipsAfter?: Relationship[];\n };\n}\n\nexport async function applyCatalogMigrations(\n model: Model,\n options: { dryRun?: boolean } = {}\n): Promise<CatalogMigrationResult> {\n const dryRun = options.dryRun ?? false;\n // Renamed element types first (product.capability -> product.softwarecapability), so the\n // relationship rules, which name the new types, resolve the renamed elements.\n const types = await migrateElementTypes(model, undefined, { dryRun });\n\n const relationships = types.errors.length\n ? { filesModified: 0, changes: [], committed: { before: [], after: [] }, error: undefined }\n : await migrateRelationshipsFile(model, undefined, { dryRun });\n const staged = relationships.error\n ? { changesetsModified: 0, changes: [], errors: [] }\n : await migrateStagedChangesets(model, relationships.committed, undefined, { dryRun });\n\n const summaries = [\n ...(types.changes.length > 0 ? [`element types renamed: ${summarizeElementTypeChanges(types.changes)}`] : []),\n relationships.changes.length > 0 ? summarizeRelationshipChanges(relationships.changes) : \"no relationships to rewrite\",\n ...(staged.changes.length > 0 ? [summarizeRelationshipChanges(staged.changes, \"staged relationship changes\")] : []),\n ];\n\n return {\n filesModified: types.filesModified + types.changesetsModified + relationships.filesModified + staged.changesetsModified,\n summaries,\n errors: [...types.errors, ...(relationships.error ? [relationships.error] : []), ...staged.errors],\n details: {\n elementTypeChanges: types.changes,\n relationshipChanges: relationships.changes,\n stagedRelationshipChanges: staged.changes,\n ...(!types.errors.length && !relationships.error ? { committedRelationshipsAfter: relationships.committed.after } : {}),\n },\n };\n}\n\n/** How many catalog changes the model still needs; all zero when it is in line with the catalog */\nexport async function pendingCatalogMigrations(\n model: Model\n): Promise<{ elementTypes: number; relationships: number; stagedRelationships: number; errors: string[] }> {\n const r = await applyCatalogMigrations(model, { dryRun: true });\n return {\n elementTypes: r.details.elementTypeChanges.length,\n relationships: r.details.relationshipChanges.length,\n stagedRelationships: r.details.stagedRelationshipChanges.length,\n errors: r.errors,\n };\n}\n"],
5
+ "mappings": "AAgBA,SAAS,qBAAqB,mCAAiE;AAC/F,SACE,0BACA,yBACA,oCAGK;AAmBP,eAAsB,uBACpB,OACA,UAAgC,CAAA,GAAE;AAElC,QAAM,SAAS,QAAQ,UAAU,IAG3B,QAAQ,MAAM,oBAAoB,OAAO,QAAW,EAAE,OAAM,CAAE,GAE9D,gBAAgB,MAAM,OAAO,SAC/B,EAAE,eAAe,GAAG,SAAS,CAAA,GAAI,WAAW,EAAE,QAAQ,CAAA,GAAI,OAAO,CAAA,EAAE,GAAI,OAAO,OAAS,IACvF,MAAM,yBAAyB,OAAO,QAAW,EAAE,OAAM,CAAE,GACzD,SAAS,cAAc,QACzB,EAAE,oBAAoB,GAAG,SAAS,CAAA,GAAI,QAAQ,CAAA,EAAE,IAChD,MAAM,wBAAwB,OAAO,cAAc,WAAW,QAAW,EAAE,OAAM,CAAE,GAEjF,YAAY;IAChB,GAAI,MAAM,QAAQ,SAAS,IAAI,CAAC,0BAA0B,4BAA4B,MAAM,OAAO,CAAC,EAAE,IAAI,CAAA;IAC1G,cAAc,QAAQ,SAAS,IAAI,6BAA6B,cAAc,OAAO,IAAI;IACzF,GAAI,OAAO,QAAQ,SAAS,IAAI,CAAC,6BAA6B,OAAO,SAAS,6BAA6B,CAAC,IAAI,CAAA;;AAGlH,SAAO;IACL,eAAe,MAAM,gBAAgB,MAAM,qBAAqB,cAAc,gBAAgB,OAAO;IACrG;IACA,QAAQ,CAAC,GAAG,MAAM,QAAQ,GAAI,cAAc,QAAQ,CAAC,cAAc,KAAK,IAAI,CAAA,GAAK,GAAG,OAAO,MAAM;IACjG,SAAS;MACP,oBAAoB,MAAM;MAC1B,qBAAqB,cAAc;MACnC,2BAA2B,OAAO;MAClC,GAAI,CAAC,MAAM,OAAO,UAAU,CAAC,cAAc,QAAQ,EAAE,6BAA6B,cAAc,UAAU,MAAK,IAAK,CAAA;;;AAG1H;AAGA,eAAsB,yBACpB,OAAY;AAEZ,QAAM,IAAI,MAAM,uBAAuB,OAAO,EAAE,QAAQ,GAAI,CAAE;AAC9D,SAAO;IACL,cAAc,EAAE,QAAQ,mBAAmB;IAC3C,eAAe,EAAE,QAAQ,oBAAoB;IAC7C,qBAAqB,EAAE,QAAQ,0BAA0B;IACzD,QAAQ,EAAE;;AAEd;",
6
+ "names": []
7
+ }
@@ -0,0 +1,421 @@
1
+ import { getAllLayerIds } from "../generated/layer-registry.js";
2
+ import { normalizeNodeType } from "../generated/node-types.js";
3
+ import { getValidRelationships, RELATIONSHIPS_BY_DESTINATION, RELATIONSHIPS_BY_SOURCE } from "../generated/relationship-index.js";
4
+ import { changesetRevisionToken } from "./changeset-graph.js";
5
+ const INTENT_LAYERS = /* @__PURE__ */ new Set(["motivation", "business", "product"]), INTENT_TRACE_DEPTH = 4, DEFAULT_HUB_THRESHOLD = 15;
6
+ function computeImpact(graph, options = {}) {
7
+ const depth = options.depth ?? 2, maxElements = options.maxElements ?? 200, hubThreshold = options.hubThreshold ?? DEFAULT_HUB_THRESHOLD, expandHubs = options.expandHubs ?? !1, changed = new Set(graph.changedElements().map((e) => e.id));
8
+ let start, unknownFrom;
9
+ if (options.from) {
10
+ start = /* @__PURE__ */ new Set(), unknownFrom = [];
11
+ for (const id of options.from) {
12
+ const element = graph.getElement(id);
13
+ element ? start.add(element.id) : unknownFrom.push(id);
14
+ }
15
+ } else
16
+ start = changed;
17
+ const reached = /* @__PURE__ */ new Map(), visited = new Set(start), hubs = [], perHop = /* @__PURE__ */ new Map();
18
+ let truncated = !1;
19
+ const reach = (from, neighbor, distance) => {
20
+ if (visited.has(neighbor.other))
21
+ return !1;
22
+ const element = graph.getElement(neighbor.other);
23
+ if (!element || element.status === "deleted")
24
+ return !1;
25
+ if (visited.add(neighbor.other), reached.size >= maxElements)
26
+ return truncated = !0, !1;
27
+ reached.set(element.id, {
28
+ id: element.id,
29
+ layer: element.layer,
30
+ name: element.name,
31
+ type: element.type,
32
+ distance,
33
+ via: describeHop(from, neighbor),
34
+ files: sourceFiles(element),
35
+ ...element.status === "added" || element.status === "updated" ? { status: element.status } : {}
36
+ }), perHop.set(distance, (perHop.get(distance) ?? 0) + 1);
37
+ const degree = graph.neighbors(element.id).length;
38
+ return !(degree > hubThreshold && (hubs.push({ id: element.id, degree, depth: distance }), !expandHubs));
39
+ };
40
+ let frontier = [];
41
+ if (!options.from)
42
+ for (const rel of graph.changedRelationships()) {
43
+ if (changed.has(rel.source) || changed.has(rel.target))
44
+ continue;
45
+ const asNeighbor = (other, direction) => ({ relationship: rel, direction, other });
46
+ reach(rel.source, asNeighbor(rel.target, "out"), 1) && frontier.push(rel.target), reach(rel.target, asNeighbor(rel.source, "in"), 1) && frontier.push(rel.source);
47
+ }
48
+ let current = [...start];
49
+ for (let distance = 1; distance <= depth && current.length + frontier.length > 0 && !truncated; distance++) {
50
+ const next = [];
51
+ for (const id of current) {
52
+ for (const neighbor of graph.neighbors(id, { includeDeleted: changed.has(id) }))
53
+ if (reach(id, neighbor, distance) && next.push(neighbor.other), truncated)
54
+ break;
55
+ if (truncated)
56
+ break;
57
+ }
58
+ current = [...frontier, ...next], frontier = [];
59
+ }
60
+ const byLayer = {};
61
+ for (const layer of orderedLayers([...reached.values()].map((e) => e.layer)))
62
+ byLayer[layer] = [...reached.values()].filter((e) => e.layer === layer).sort((a, b) => a.distance - b.distance || a.id.localeCompare(b.id)).map(({ layer: _layer, ...rest }) => rest);
63
+ return {
64
+ depth,
65
+ changed: changed.size,
66
+ reached: reached.size,
67
+ truncated,
68
+ byLayer,
69
+ perHop: [...perHop.entries()].sort(([a], [b]) => a - b).map(([d, n]) => ({ depth: d, reached: n })),
70
+ hubs: hubs.sort((a, b) => a.depth - b.depth || b.degree - a.degree || a.id.localeCompare(b.id)),
71
+ hubThreshold,
72
+ ...options.from ? { from: [...start], unknownFrom: unknownFrom ?? [] } : {},
73
+ ...options.from && start.size === 0 ? {
74
+ warnings: [
75
+ options.from.length === 0 ? "from is empty, so the walk had no start element and reached nothing; omit from to walk from every changed element." : "None of the from ids is in the projected model, so the walk had no start element and reached nothing; see unknownFrom."
76
+ ]
77
+ } : {}
78
+ };
79
+ }
80
+ function buildBrief({ graph, annotations = [], drift, unwired: unwiredMode = "summary", unwiredLayers, hubThreshold = DEFAULT_HUB_THRESHOLD }) {
81
+ const changeset = graph.changeset, changed = graph.changedElements(), changedIds = new Set(changed.map((e) => e.id)), changedRels = graph.changedRelationships(), inBase = graph.alreadyInBase(), layers = [];
82
+ for (const layer of orderedLayers(changed.map((e) => e.layer)))
83
+ layers.push({
84
+ layer,
85
+ changes: changed.filter((e) => e.layer === layer).map((e) => describeChange(graph, e))
86
+ });
87
+ const relationshipChanges = changedRels.filter((rel) => !changedIds.has(rel.source) && !changedIds.has(rel.target)).map((rel) => ({ status: rel.status, source: rel.source, predicate: rel.predicate, target: rel.target })), touchedLayers = new Set(changed.map((e) => e.layer));
88
+ for (const rel of changedRels)
89
+ for (const end of [rel.source, rel.target]) {
90
+ const element = graph.getElement(end);
91
+ element && touchedLayers.add(element.layer);
92
+ }
93
+ const allLayers = getAllLayerIds(), reach = {
94
+ touched: allLayers.filter((l) => touchedLayers.has(l)),
95
+ untouched: allLayers.filter((l) => !touchedLayers.has(l))
96
+ }, gaps = [];
97
+ for (const element of changed.filter((e) => e.status === "added")) {
98
+ const live = graph.neighbors(element.id);
99
+ live.length === 0 && gaps.push({
100
+ kind: "unconnected",
101
+ element: element.id,
102
+ detail: "Added with no relationships: nothing in the model says what it serves, realizes or uses."
103
+ });
104
+ for (const required of requiredOutgoing(element))
105
+ live.some((n) => {
106
+ if (n.direction !== "out" || n.relationship.predicate !== required.predicate)
107
+ return !1;
108
+ const other = graph.getElement(n.other);
109
+ return !!other && specNodeId(other) === required.destinationSpecNodeId;
110
+ }) || gaps.push({
111
+ kind: "missing-required",
112
+ element: element.id,
113
+ detail: `The catalog requires ${required.sourceSpecNodeId} --${required.predicate}--> ${required.destinationSpecNodeId}.`
114
+ });
115
+ gaps.push(...peerPatternGaps(graph, element));
116
+ }
117
+ const unwired = unwiredMode === "none" ? void 0 : presentUnwired(computeUnwired(graph), unwiredMode, new Set(reach.touched), unwiredLayers), traced = [], untraced = [], unanchored = [], intentWalk = new IntentWalk(graph, hubThreshold);
118
+ for (const element of changed) {
119
+ if (element.status === "deleted" || INTENT_LAYERS.has(element.layer))
120
+ continue;
121
+ const result = intentWalk.trace(element.id);
122
+ if (result.traced)
123
+ traced.push({ element: element.id, ...result.traced });
124
+ else if (untraced.push(element.id), result.unanchored) {
125
+ const { id, path } = result.unanchored;
126
+ unanchored.push({
127
+ element: element.id,
128
+ tracesTo: id,
129
+ path,
130
+ detail: `traces to added intent element ${id}, which traces to nothing`
131
+ });
132
+ }
133
+ }
134
+ const files = /* @__PURE__ */ new Map(), addFiles = (element, role) => {
135
+ for (const file of sourceFiles(element)) {
136
+ const entry = files.get(file) ?? /* @__PURE__ */ new Map();
137
+ entry.get(element.id) !== "changed" && entry.set(element.id, role), files.set(file, entry);
138
+ }
139
+ }, neighborIds = /* @__PURE__ */ new Set();
140
+ for (const element of changed) {
141
+ addFiles(element, "changed");
142
+ for (const n of graph.neighbors(element.id, { includeDeleted: !0 }))
143
+ changedIds.has(n.other) || neighborIds.add(n.other);
144
+ }
145
+ for (const rel of changedRels)
146
+ for (const end of [rel.source, rel.target])
147
+ changedIds.has(end) || neighborIds.add(end);
148
+ for (const id of neighborIds) {
149
+ const element = graph.getElement(id);
150
+ element && element.status !== "deleted" && addFiles(element, "neighbor");
151
+ }
152
+ const codeTouched = [...files.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([file, elements]) => ({
153
+ file,
154
+ elements: [...elements.entries()].map(([id, role]) => ({ id, role }))
155
+ })), watched = /* @__PURE__ */ new Set([...changedIds, ...neighborIds]), feedback = annotations.filter((a) => !a.resolved && watched.has(graph.resolve(a.elementId))).map((a) => ({
156
+ annotationId: a.id,
157
+ elementId: a.elementId,
158
+ author: a.author,
159
+ content: a.content,
160
+ tags: a.tags,
161
+ replies: a.replies.length
162
+ }));
163
+ return {
164
+ revision: graph.revision,
165
+ revisionToken: changesetRevisionToken(graph.changeset, graph.revision),
166
+ changeset: {
167
+ id: changeset.id,
168
+ name: changeset.name,
169
+ ...changeset.description ? { description: changeset.description } : {},
170
+ status: changeset.status,
171
+ created: changeset.created,
172
+ modified: changeset.modified,
173
+ ...drift ? { drift } : {}
174
+ },
175
+ summary: {
176
+ elementsAdded: changed.filter((e) => e.status === "added").length,
177
+ elementsUpdated: changed.filter((e) => e.status === "updated").length,
178
+ elementsDeleted: changed.filter((e) => e.status === "deleted").length,
179
+ relationshipsAdded: changedRels.filter((r) => r.status === "added").length,
180
+ relationshipsDeleted: changedRels.filter((r) => r.status === "deleted").length,
181
+ elementsAlreadyInBase: inBase.elements.length,
182
+ relationshipsAlreadyInBase: inBase.relationships.length
183
+ },
184
+ alreadyInBase: {
185
+ elements: inBase.elements.map((e) => e.id),
186
+ relationships: inBase.relationships.map((r) => ({ source: r.source, predicate: r.predicate, target: r.target }))
187
+ },
188
+ layers,
189
+ relationshipChanges,
190
+ reach,
191
+ gaps,
192
+ ...unwired ? { unwired } : {},
193
+ intent: { traced, untraced, ...unanchored.length > 0 ? { unanchored } : {} },
194
+ codeTouched,
195
+ feedback
196
+ };
197
+ }
198
+ function describeChange(graph, element) {
199
+ const op = element.status, connections = graph.neighbors(element.id, { includeDeleted: !0 }).map((n) => {
200
+ const other = graph.getElement(n.other);
201
+ return {
202
+ direction: n.direction,
203
+ predicate: n.relationship.predicate,
204
+ other: n.other,
205
+ ...other ? { otherName: other.name, otherLayer: other.layer } : {},
206
+ status: n.relationship.status
207
+ };
208
+ }), change = {
209
+ op,
210
+ id: element.id,
211
+ type: element.type,
212
+ name: element.name,
213
+ ...element.description ? { description: element.description } : {},
214
+ connections
215
+ };
216
+ return op !== "deleted" && Object.keys(element.attributes).length > 0 && (change.attributes = element.attributes), op === "updated" && element.before && (change.changedFields = diffFields(element.before, element)), change;
217
+ }
218
+ function diffFields(before, after) {
219
+ const fields = [], compare = (field, a, b) => {
220
+ JSON.stringify(a) !== JSON.stringify(b) && fields.push({ field, before: a, after: b });
221
+ };
222
+ compare("name", before.name, after.name), compare("description", before.description, after.description);
223
+ const beforeAttrs = before.attributes ?? {};
224
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(beforeAttrs), ...Object.keys(after.attributes)]))
225
+ compare(`attributes.${key}`, beforeAttrs[key], after.attributes[key]);
226
+ return fields;
227
+ }
228
+ class IntentWalk {
229
+ graph;
230
+ hubThreshold;
231
+ anchors = /* @__PURE__ */ new Map();
232
+ constructor(graph, hubThreshold) {
233
+ this.graph = graph, this.hubThreshold = hubThreshold;
234
+ }
235
+ trace(start) {
236
+ let unanchored;
237
+ const hit = this.walk(start, (element, path) => element.status !== "added" || this.anchorOf(element.id) ? !0 : (unanchored ??= { id: element.id, path }, !1));
238
+ if (!hit)
239
+ return unanchored ? { unanchored } : {};
240
+ const anchor = this.graph.getElement(hit.id)?.status === "added" ? this.anchorOf(hit.id) : null;
241
+ return {
242
+ traced: {
243
+ tracesTo: hit.id,
244
+ distance: hit.path.length - 1,
245
+ path: hit.path,
246
+ ...anchor ? { anchoredBy: anchor } : {}
247
+ }
248
+ };
249
+ }
250
+ /** The existing intent element an added intent element traces to, or null. */
251
+ anchorOf(id) {
252
+ if (this.anchors.has(id))
253
+ return this.anchors.get(id);
254
+ this.anchors.set(id, null);
255
+ const hit = this.walk(id, (element) => element.status !== "added");
256
+ return this.anchors.set(id, hit), hit;
257
+ }
258
+ /**
259
+ * Walks from `start` and returns the first intent element `accept` takes, with the path to it.
260
+ * Intent elements it refuses are walked through like any other.
261
+ */
262
+ walk(start, accept) {
263
+ const parent = /* @__PURE__ */ new Map(), visited = /* @__PURE__ */ new Set([start]), pathTo = (id) => {
264
+ const path = [id];
265
+ for (let at = parent.get(id); at !== void 0; at = parent.get(at))
266
+ path.unshift(at);
267
+ return path;
268
+ };
269
+ let frontier = [start];
270
+ for (let distance = 1; distance <= INTENT_TRACE_DEPTH && frontier.length > 0; distance++) {
271
+ const next = [];
272
+ for (const id of frontier)
273
+ if (!(id !== start && this.graph.neighbors(id).length > this.hubThreshold))
274
+ for (const n of this.graph.neighbors(id)) {
275
+ if (visited.has(n.other))
276
+ continue;
277
+ visited.add(n.other);
278
+ const element = this.graph.getElement(n.other);
279
+ if (!(!element || element.status === "deleted")) {
280
+ if (parent.set(element.id, id), INTENT_LAYERS.has(element.layer) && accept(element, pathTo(element.id)))
281
+ return { id: element.id, path: pathTo(element.id) };
282
+ next.push(element.id);
283
+ }
284
+ }
285
+ frontier = next;
286
+ }
287
+ return null;
288
+ }
289
+ }
290
+ const PEER_MIN = 2, PEER_SHARE = 0.5;
291
+ function peerPatternGaps(graph, element) {
292
+ const own = graph.neighbors(element.id);
293
+ if (own.length === 0)
294
+ return [];
295
+ const type = specNodeId(element), peers = /* @__PURE__ */ new Set();
296
+ for (const mine of own)
297
+ for (const theirs of graph.neighbors(mine.other)) {
298
+ if (theirs.relationship.predicate !== mine.relationship.predicate || theirs.direction === mine.direction)
299
+ continue;
300
+ const peer = graph.getElement(theirs.other);
301
+ !peer || peer.id === element.id || peer.status === "added" || peer.status === "deleted" || specNodeId(peer) === type && peers.add(peer.id);
302
+ }
303
+ if (peers.size < PEER_MIN)
304
+ return [];
305
+ const shapeOf = (n) => {
306
+ const other = graph.getElement(n.other);
307
+ return other ? `${n.direction}|${n.relationship.predicate}|${specNodeId(other)}` : null;
308
+ }, ownShapes = new Set(own.map(shapeOf)), shapes = /* @__PURE__ */ new Map();
309
+ for (const peerId of peers)
310
+ for (const n of graph.neighbors(peerId)) {
311
+ if (n.other === element.id)
312
+ continue;
313
+ const shape = shapeOf(n);
314
+ if (!shape || ownShapes.has(shape))
315
+ continue;
316
+ const entry = shapes.get(shape) ?? { peers: /* @__PURE__ */ new Set(), examples: [] };
317
+ !entry.peers.has(peerId) && entry.examples.length < 3 && entry.examples.push(describeHop(peerId, n)), entry.peers.add(peerId), shapes.set(shape, entry);
318
+ }
319
+ const threshold = Math.max(PEER_MIN, Math.ceil(peers.size * PEER_SHARE));
320
+ return [...shapes.entries()].filter(([, entry]) => entry.peers.size >= threshold).filter(([shape]) => {
321
+ const [direction, predicate, otherType] = shape.split("|");
322
+ return direction === "out" ? getValidRelationships(type, predicate, otherType).length > 0 : getValidRelationships(otherType, predicate, type).length > 0;
323
+ }).sort(([, a], [, b]) => b.peers.size - a.peers.size).map(([shape, entry]) => {
324
+ const [direction, predicate, otherType] = shape.split("|"), connection = direction === "out" ? `--${predicate}--> ${otherType}` : `${otherType} --${predicate}--> them`;
325
+ return {
326
+ kind: "peer-pattern",
327
+ element: element.id,
328
+ detail: `${entry.peers.size} of ${peers.size} existing ${type} elements wired like this one have ${connection}; this one has none.`,
329
+ examples: entry.examples
330
+ };
331
+ });
332
+ }
333
+ function computeUnwired(graph) {
334
+ const existing = /* @__PURE__ */ new Map(), added = /* @__PURE__ */ new Map();
335
+ for (const element of graph.elements.values()) {
336
+ if (element.status === "deleted")
337
+ continue;
338
+ const counts = element.status === "added" ? added : existing, type = specNodeId(element);
339
+ counts.set(type, (counts.get(type) ?? 0) + 1);
340
+ }
341
+ const result = [];
342
+ for (const element of graph.changedElements()) {
343
+ if (element.status !== "added" && element.status !== "updated")
344
+ continue;
345
+ const type = specNodeId(element), have = /* @__PURE__ */ new Set();
346
+ for (const n of graph.neighbors(element.id)) {
347
+ const other = graph.getElement(n.other);
348
+ other && other.status !== "deleted" && have.add(`${n.direction}|${n.relationship.predicate}|${specNodeId(other)}`);
349
+ }
350
+ const entries = /* @__PURE__ */ new Map(), consider = (direction, predicate, otherType) => {
351
+ const key = `${direction}|${predicate}|${otherType}`;
352
+ if (have.has(key) || entries.has(key))
353
+ return;
354
+ const self = otherType === type ? 1 : 0, existingCount = (existing.get(otherType) ?? 0) - (element.status === "added" ? 0 : self), addedCount = (added.get(otherType) ?? 0) - (element.status === "added" ? self : 0);
355
+ entries.set(key, { direction, predicate, otherType, existingCount, addedCount });
356
+ };
357
+ for (const spec of RELATIONSHIPS_BY_SOURCE.get(type) ?? [])
358
+ consider("out", spec.predicate, spec.destinationSpecNodeId);
359
+ for (const spec of RELATIONSHIPS_BY_DESTINATION.get(type) ?? [])
360
+ consider("in", spec.predicate, spec.sourceSpecNodeId);
361
+ const layerOf = (specId) => specId.split(".")[0], all = [...entries.values()], byLayer = orderedLayers(all.map((e) => layerOf(e.otherType))).map((layer) => ({
362
+ layer,
363
+ entries: all.filter((e) => layerOf(e.otherType) === layer).sort((a, b) => (a.direction === b.direction ? 0 : a.direction === "out" ? -1 : 1) || a.predicate.localeCompare(b.predicate) || a.otherType.localeCompare(b.otherType))
364
+ }));
365
+ result.push({ element: element.id, type, byLayer });
366
+ }
367
+ return result;
368
+ }
369
+ function presentUnwired(computed, mode, reachedLayers, layerFilter) {
370
+ const known = new Set(getAllLayerIds()), filter = layerFilter ? new Set(layerFilter) : void 0, unknownLayers = layerFilter?.filter((l) => !known.has(l)) ?? [], elements = computed.map(({ element, type, byLayer }) => {
371
+ const shown = byLayer.filter((l) => !filter || filter.has(l.layer)).map((l) => {
372
+ const count = l.entries.length;
373
+ if (mode === "full")
374
+ return { layer: l.layer, count, entries: l.entries };
375
+ const inModel = l.entries.filter((e) => e.existingCount + e.addedCount > 0), typesNotInModel = count - inModel.length, withEntries = reachedLayers.has(l.layer) || !!filter?.has(l.layer);
376
+ return {
377
+ layer: l.layer,
378
+ count,
379
+ ...withEntries ? { entries: inModel } : {},
380
+ ...typesNotInModel > 0 ? { typesNotInModel } : {}
381
+ };
382
+ });
383
+ return { element, type, total: shown.reduce((sum, l) => sum + l.count, 0), byLayer: shown };
384
+ });
385
+ return {
386
+ mode,
387
+ ...layerFilter ? { layers: layerFilter } : {},
388
+ ...unknownLayers.length > 0 ? { unknownLayers } : {},
389
+ elements
390
+ };
391
+ }
392
+ function unwiredKey(element, entry) {
393
+ return `${element}|${entry.direction}|${entry.predicate}|${entry.otherType}`;
394
+ }
395
+ function requiredOutgoing(element) {
396
+ return getValidRelationships(specNodeId(element)).filter((r) => r.required);
397
+ }
398
+ function specNodeId(element) {
399
+ return `${element.layer}.${normalizeNodeType(element.layer, element.type)}`;
400
+ }
401
+ function describeHop(from, neighbor) {
402
+ const { predicate, status } = neighbor.relationship, suffix = status === "unchanged" ? "" : ` (${status} by this changeset)`;
403
+ return neighbor.direction === "out" ? `${from} --${predicate}--> ${neighbor.other}${suffix}` : `${neighbor.other} --${predicate}--> ${from}${suffix}`;
404
+ }
405
+ function sourceFiles(element) {
406
+ const locations = element.sourceReference?.locations ?? [];
407
+ return [...new Set(locations.map((l) => l.file).filter((f) => !!f))];
408
+ }
409
+ function orderedLayers(layers) {
410
+ const present = new Set(layers), known = getAllLayerIds().filter((l) => present.has(l)), unknown = [...present].filter((l) => !known.includes(l)).sort();
411
+ return [...known, ...unknown];
412
+ }
413
+ export {
414
+ DEFAULT_HUB_THRESHOLD,
415
+ INTENT_LAYERS,
416
+ buildBrief,
417
+ computeImpact,
418
+ computeUnwired,
419
+ unwiredKey
420
+ };
421
+ //# sourceMappingURL=changeset-analysis.js.map