@documentation-robotics/cli 0.1.13 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (351) hide show
  1. package/README.md +74 -2
  2. package/dist/analysis/relationship-classifier.js +2 -9
  3. package/dist/analysis/relationship-classifier.js.map +2 -2
  4. package/dist/analyzers/call-graph-utils.js +2 -2
  5. package/dist/analyzers/call-graph-utils.js.map +2 -2
  6. package/dist/analyzers/cards/build.js +171 -0
  7. package/dist/analyzers/cards/build.js.map +7 -0
  8. package/dist/analyzers/cards/classify.js +42 -0
  9. package/dist/analyzers/cards/classify.js.map +7 -0
  10. package/dist/analyzers/cards/edge-index.js +44 -0
  11. package/dist/analyzers/cards/edge-index.js.map +7 -0
  12. package/dist/analyzers/cards/facts.js +46 -0
  13. package/dist/analyzers/cards/facts.js.map +7 -0
  14. package/dist/analyzers/cards/graph.js +42 -0
  15. package/dist/analyzers/cards/graph.js.map +7 -0
  16. package/dist/analyzers/cards/index.js +8 -0
  17. package/dist/analyzers/cards/index.js.map +7 -0
  18. package/dist/analyzers/cards/tier0.js +18 -0
  19. package/dist/analyzers/cards/tier0.js.map +7 -0
  20. package/dist/analyzers/cards/types.js +1 -0
  21. package/dist/analyzers/cards/types.js.map +7 -0
  22. package/dist/analyzers/cbm-analyzer.js +195 -171
  23. package/dist/analyzers/cbm-analyzer.js.map +2 -2
  24. package/dist/analyzers/cbm-responses.js +336 -0
  25. package/dist/analyzers/cbm-responses.js.map +7 -0
  26. package/dist/analyzers/edge-relationships.js +45 -0
  27. package/dist/analyzers/edge-relationships.js.map +7 -0
  28. package/dist/analyzers/mapping-loader.js +9 -0
  29. package/dist/analyzers/mapping-loader.js.map +2 -2
  30. package/dist/analyzers/tier0-rules.js +153 -0
  31. package/dist/analyzers/tier0-rules.js.map +7 -0
  32. package/dist/analyzers/types.js.map +1 -1
  33. package/dist/analyzers/verify-engine.js +22 -19
  34. package/dist/analyzers/verify-engine.js.map +2 -2
  35. package/dist/audit/relationships/ai/prompts.js +10 -10
  36. package/dist/audit/relationships/ai/prompts.js.map +2 -2
  37. package/dist/audit/relationships/analysis/layer-templates.js +52 -52
  38. package/dist/audit/relationships/analysis/layer-templates.js.map +1 -1
  39. package/dist/cli.js +112 -28
  40. package/dist/cli.js.map +3 -3
  41. package/dist/coding-agents/agents/claude-code.js +3 -1
  42. package/dist/coding-agents/agents/claude-code.js.map +2 -2
  43. package/dist/coding-agents/claude-code-client.js +2 -1
  44. package/dist/coding-agents/claude-code-client.js.map +2 -2
  45. package/dist/coding-agents/copilot-client.js +17 -12
  46. package/dist/coding-agents/copilot-client.js.map +2 -2
  47. package/dist/coding-agents/default-permissions.js +152 -0
  48. package/dist/coding-agents/default-permissions.js.map +7 -0
  49. package/dist/coding-agents/drbot-mcp.js +167 -0
  50. package/dist/coding-agents/drbot-mcp.js.map +7 -0
  51. package/dist/commands/add.js +12 -7
  52. package/dist/commands/add.js.map +2 -2
  53. package/dist/commands/analyzer.js +134 -23
  54. package/dist/commands/analyzer.js.map +2 -2
  55. package/dist/commands/audit.js +6 -6
  56. package/dist/commands/audit.js.map +2 -2
  57. package/dist/commands/changeset-verify.js +55 -0
  58. package/dist/commands/changeset-verify.js.map +7 -0
  59. package/dist/commands/changeset.js +261 -147
  60. package/dist/commands/changeset.js.map +2 -2
  61. package/dist/commands/delete.js +52 -19
  62. package/dist/commands/delete.js.map +3 -3
  63. package/dist/commands/element.js +3 -2
  64. package/dist/commands/element.js.map +2 -2
  65. package/dist/commands/farm.js +773 -0
  66. package/dist/commands/farm.js.map +7 -0
  67. package/dist/commands/init.js +11 -1
  68. package/dist/commands/init.js.map +2 -2
  69. package/dist/commands/mcp.js +122 -0
  70. package/dist/commands/mcp.js.map +7 -0
  71. package/dist/commands/relationship.js +251 -47
  72. package/dist/commands/relationship.js.map +2 -2
  73. package/dist/commands/reports.js +1 -1
  74. package/dist/commands/reports.js.map +2 -2
  75. package/dist/commands/update.js +56 -24
  76. package/dist/commands/update.js.map +2 -2
  77. package/dist/commands/upgrade.js +99 -11
  78. package/dist/commands/upgrade.js.map +3 -3
  79. package/dist/commands/validate.js +41 -3
  80. package/dist/commands/validate.js.map +2 -2
  81. package/dist/commands/version.js +2 -2
  82. package/dist/commands/version.js.map +2 -2
  83. package/dist/commands/visualize.js +4 -3
  84. package/dist/commands/visualize.js.map +2 -2
  85. package/dist/config.js +83 -0
  86. package/dist/config.js.map +7 -0
  87. package/dist/core/annotation-store.js +138 -0
  88. package/dist/core/annotation-store.js.map +7 -0
  89. package/dist/core/catalog-migration.js +33 -0
  90. package/dist/core/catalog-migration.js.map +7 -0
  91. package/dist/core/changeset-analysis.js +421 -0
  92. package/dist/core/changeset-analysis.js.map +7 -0
  93. package/dist/core/changeset-base-index.js +153 -0
  94. package/dist/core/changeset-base-index.js.map +7 -0
  95. package/dist/core/changeset-brief-delta.js +102 -0
  96. package/dist/core/changeset-brief-delta.js.map +7 -0
  97. package/dist/core/changeset-graph.js +281 -0
  98. package/dist/core/changeset-graph.js.map +7 -0
  99. package/dist/core/changeset-only-guard.js +32 -0
  100. package/dist/core/changeset-only-guard.js.map +7 -0
  101. package/dist/core/changeset-summary.js +68 -0
  102. package/dist/core/changeset-summary.js.map +7 -0
  103. package/dist/core/changeset-validator.js +66 -20
  104. package/dist/core/changeset-validator.js.map +2 -2
  105. package/dist/core/changeset-verify.js +518 -0
  106. package/dist/core/changeset-verify.js.map +7 -0
  107. package/dist/core/changeset.js +56 -9
  108. package/dist/core/changeset.js.map +2 -2
  109. package/dist/core/element-type-migration.js +145 -0
  110. package/dist/core/element-type-migration.js.map +7 -0
  111. package/dist/core/farm-manifest.js +140 -0
  112. package/dist/core/farm-manifest.js.map +7 -0
  113. package/dist/core/farm-sync-engine.js +346 -0
  114. package/dist/core/farm-sync-engine.js.map +7 -0
  115. package/dist/core/farm-sync-state.js +167 -0
  116. package/dist/core/farm-sync-state.js.map +7 -0
  117. package/dist/core/manifest.js +6 -3
  118. package/dist/core/manifest.js.map +2 -2
  119. package/dist/core/migration-registry.js +84 -1
  120. package/dist/core/migration-registry.js.map +2 -2
  121. package/dist/core/model-upgrade.js +102 -0
  122. package/dist/core/model-upgrade.js.map +7 -0
  123. package/dist/core/model.js +257 -44
  124. package/dist/core/model.js.map +3 -3
  125. package/dist/core/mutation-handler.js +71 -6
  126. package/dist/core/mutation-handler.js.map +2 -2
  127. package/dist/core/relationship-migration-table.js +6639 -0
  128. package/dist/core/relationship-migration-table.js.map +7 -0
  129. package/dist/core/relationship-migration.js +272 -0
  130. package/dist/core/relationship-migration.js.map +7 -0
  131. package/dist/core/relationship-prune.js +49 -0
  132. package/dist/core/relationship-prune.js.map +7 -0
  133. package/dist/core/relationship-staging.js +68 -0
  134. package/dist/core/relationship-staging.js.map +7 -0
  135. package/dist/core/relationships.js +26 -4
  136. package/dist/core/relationships.js.map +2 -2
  137. package/dist/core/report-data-model.js +7 -13
  138. package/dist/core/report-data-model.js.map +2 -2
  139. package/dist/core/staged-changeset-storage.js +64 -17
  140. package/dist/core/staged-changeset-storage.js.map +2 -2
  141. package/dist/core/staged-source-reference.js +28 -0
  142. package/dist/core/staged-source-reference.js.map +7 -0
  143. package/dist/core/staging-area.js +83 -18
  144. package/dist/core/staging-area.js.map +2 -2
  145. package/dist/core/virtual-projection.js +14 -5
  146. package/dist/core/virtual-projection.js.map +2 -2
  147. package/dist/export/archimate-exporter.js +1 -0
  148. package/dist/export/archimate-exporter.js.map +2 -2
  149. package/dist/generated/compiled-validators.js +2 -2
  150. package/dist/generated/compiled-validators.js.map +2 -2
  151. package/dist/generated/layer-registry.js +16 -16
  152. package/dist/generated/layer-registry.js.map +2 -2
  153. package/dist/generated/node-types.js +186 -168
  154. package/dist/generated/node-types.js.map +2 -2
  155. package/dist/generated/relationship-index.js +2209 -7727
  156. package/dist/generated/relationship-index.js.map +2 -2
  157. package/dist/integrations/claude_code/agents/dr-architect.md +16 -3
  158. package/dist/integrations/claude_code/agents/dr-extractor.md +1 -1
  159. package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
  160. package/dist/integrations/claude_code/commands/dr-map.md +13 -0
  161. package/dist/integrations/claude_code/commands/dr-plan.md +40 -0
  162. package/dist/integrations/claude_code/commands/dr-relate.md +106 -96
  163. package/dist/integrations/claude_code/commands/dr-verify.md +45 -2
  164. package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +14 -0
  165. package/dist/integrations/claude_code/docs/MCP_SERVER.md +79 -0
  166. package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
  167. package/dist/integrations/claude_code/docs/README.md +52 -1
  168. package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +93 -72
  169. package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +163 -126
  170. package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +161 -126
  171. package/dist/integrations/claude_code/skills/dr_04_security_layer/SKILL.md +153 -133
  172. package/dist/integrations/claude_code/skills/dr_05_application_layer/SKILL.md +129 -100
  173. package/dist/integrations/claude_code/skills/dr_06_technology_layer/SKILL.md +111 -94
  174. package/dist/integrations/claude_code/skills/dr_07_api_layer/SKILL.md +143 -157
  175. package/dist/integrations/claude_code/skills/dr_08_data_model_layer/SKILL.md +117 -33
  176. package/dist/integrations/claude_code/skills/dr_09_dataset_layer/SKILL.md +74 -25
  177. package/dist/integrations/claude_code/skills/dr_10_ux_layer/SKILL.md +88 -12
  178. package/dist/integrations/claude_code/skills/dr_11_navigation_layer/SKILL.md +83 -24
  179. package/dist/integrations/claude_code/skills/dr_12_apm_layer/SKILL.md +57 -12
  180. package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +92 -15
  181. package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +119 -0
  182. package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +3 -0
  183. package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +1 -1
  184. package/dist/integrations/github_copilot/agents/dr-architect.md +1 -1
  185. package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +182 -102
  186. package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +266 -148
  187. package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +547 -86
  188. package/dist/integrations/github_copilot/skills/dr_04_security_layer/SKILL.md +164 -169
  189. package/dist/integrations/github_copilot/skills/dr_05_application_layer/SKILL.md +420 -150
  190. package/dist/integrations/github_copilot/skills/dr_06_technology_layer/SKILL.md +331 -138
  191. package/dist/integrations/github_copilot/skills/dr_07_api_layer/SKILL.md +265 -201
  192. package/dist/integrations/github_copilot/skills/dr_08_data_model_layer/SKILL.md +181 -58
  193. package/dist/integrations/github_copilot/skills/dr_09_dataset_layer/SKILL.md +209 -57
  194. package/dist/integrations/github_copilot/skills/dr_10_ux_layer/SKILL.md +277 -112
  195. package/dist/integrations/github_copilot/skills/dr_11_navigation_layer/SKILL.md +219 -70
  196. package/dist/integrations/github_copilot/skills/dr_12_apm_layer/SKILL.md +189 -105
  197. package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +222 -102
  198. package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +119 -0
  199. package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +3 -0
  200. package/dist/integrations/hash-utils.js.map +2 -2
  201. package/dist/mcp/api-key-manager.js +111 -0
  202. package/dist/mcp/api-key-manager.js.map +7 -0
  203. package/dist/mcp/changeset-only.js +34 -0
  204. package/dist/mcp/changeset-only.js.map +7 -0
  205. package/dist/mcp/http-transport.js +176 -0
  206. package/dist/mcp/http-transport.js.map +7 -0
  207. package/dist/mcp/resource-registry.js +97 -0
  208. package/dist/mcp/resource-registry.js.map +7 -0
  209. package/dist/mcp/tool-registry.js +60 -0
  210. package/dist/mcp/tool-registry.js.map +7 -0
  211. package/dist/mcp/tools/annotation-create.js +39 -0
  212. package/dist/mcp/tools/annotation-create.js.map +7 -0
  213. package/dist/mcp/tools/annotation-delete.js +29 -0
  214. package/dist/mcp/tools/annotation-delete.js.map +7 -0
  215. package/dist/mcp/tools/annotation-get.js +29 -0
  216. package/dist/mcp/tools/annotation-get.js.map +7 -0
  217. package/dist/mcp/tools/annotation-list.js +24 -0
  218. package/dist/mcp/tools/annotation-list.js.map +7 -0
  219. package/dist/mcp/tools/annotation-reply.js +34 -0
  220. package/dist/mcp/tools/annotation-reply.js.map +7 -0
  221. package/dist/mcp/tools/annotation-shared.js +10 -0
  222. package/dist/mcp/tools/annotation-shared.js.map +7 -0
  223. package/dist/mcp/tools/annotation-update.js +39 -0
  224. package/dist/mcp/tools/annotation-update.js.map +7 -0
  225. package/dist/mcp/tools/changeset-analysis-tools.js +78 -0
  226. package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
  227. package/dist/mcp/tools/changeset-editing.js +184 -0
  228. package/dist/mcp/tools/changeset-editing.js.map +7 -0
  229. package/dist/mcp/tools/changeset-lifecycle.js +83 -0
  230. package/dist/mcp/tools/changeset-lifecycle.js.map +7 -0
  231. package/dist/mcp/tools/changeset-list.js +33 -0
  232. package/dist/mcp/tools/changeset-list.js.map +7 -0
  233. package/dist/mcp/tools/changeset-shared.js +31 -0
  234. package/dist/mcp/tools/changeset-shared.js.map +7 -0
  235. package/dist/mcp/tools/changeset-show.js +55 -0
  236. package/dist/mcp/tools/changeset-show.js.map +7 -0
  237. package/dist/mcp/tools/changeset-verify.js +43 -0
  238. package/dist/mcp/tools/changeset-verify.js.map +7 -0
  239. package/dist/mcp/tools/chat-launch.js +50 -0
  240. package/dist/mcp/tools/chat-launch.js.map +7 -0
  241. package/dist/mcp/tools/chat-status.js +26 -0
  242. package/dist/mcp/tools/chat-status.js.map +7 -0
  243. package/dist/mcp/tools/index.js +65 -0
  244. package/dist/mcp/tools/index.js.map +7 -0
  245. package/dist/mcp/tools/model-add.js +107 -0
  246. package/dist/mcp/tools/model-add.js.map +7 -0
  247. package/dist/mcp/tools/model-delete.js +99 -0
  248. package/dist/mcp/tools/model-delete.js.map +7 -0
  249. package/dist/mcp/tools/model-export.js +72 -0
  250. package/dist/mcp/tools/model-export.js.map +7 -0
  251. package/dist/mcp/tools/model-info.js +51 -0
  252. package/dist/mcp/tools/model-info.js.map +7 -0
  253. package/dist/mcp/tools/model-list.js +53 -0
  254. package/dist/mcp/tools/model-list.js.map +7 -0
  255. package/dist/mcp/tools/model-reload.js +34 -0
  256. package/dist/mcp/tools/model-reload.js.map +7 -0
  257. package/dist/mcp/tools/model-search.js +102 -0
  258. package/dist/mcp/tools/model-search.js.map +7 -0
  259. package/dist/mcp/tools/model-show.js +68 -0
  260. package/dist/mcp/tools/model-show.js.map +7 -0
  261. package/dist/mcp/tools/model-stats.js +22 -0
  262. package/dist/mcp/tools/model-stats.js.map +7 -0
  263. package/dist/mcp/tools/model-trace.js +118 -0
  264. package/dist/mcp/tools/model-trace.js.map +7 -0
  265. package/dist/mcp/tools/model-update.js +121 -0
  266. package/dist/mcp/tools/model-update.js.map +7 -0
  267. package/dist/mcp/tools/model-validate.js +78 -0
  268. package/dist/mcp/tools/model-validate.js.map +7 -0
  269. package/dist/mcp/tools/relationship-tools.js +98 -0
  270. package/dist/mcp/tools/relationship-tools.js.map +7 -0
  271. package/dist/mcp/tools/shared.js +126 -0
  272. package/dist/mcp/tools/shared.js.map +7 -0
  273. package/dist/mcp/tools/spec-type.js +90 -0
  274. package/dist/mcp/tools/spec-type.js.map +7 -0
  275. package/dist/reports/model-layer-report-generator.js +5 -3
  276. package/dist/reports/model-layer-report-generator.js.map +2 -2
  277. package/dist/reports/model-readme-generator.js +116 -0
  278. package/dist/reports/model-readme-generator.js.map +7 -0
  279. package/dist/reports/model-report-data.js +37 -1
  280. package/dist/reports/model-report-data.js.map +2 -2
  281. package/dist/reports/model-report-orchestrator.js +29 -6
  282. package/dist/reports/model-report-orchestrator.js.map +2 -2
  283. package/dist/reports/model-report-utils.js +9 -0
  284. package/dist/reports/model-report-utils.js.map +7 -0
  285. package/dist/schemas/bundled/analyzers/cbm.json +1186 -68
  286. package/dist/schemas/bundled/analyzers/manifest.json +2 -2
  287. package/dist/schemas/bundled/api.json +1029 -1658
  288. package/dist/schemas/bundled/apm.json +499 -734
  289. package/dist/schemas/bundled/application.json +745 -370
  290. package/dist/schemas/bundled/base.json +3 -220
  291. package/dist/schemas/bundled/business.json +452 -366
  292. package/dist/schemas/bundled/data-model.json +403 -383
  293. package/dist/schemas/bundled/data-store.json +334 -558
  294. package/dist/schemas/bundled/manifest.json +18 -18
  295. package/dist/schemas/bundled/motivation.json +349 -368
  296. package/dist/schemas/bundled/navigation.json +415 -731
  297. package/dist/schemas/bundled/product.json +325 -159
  298. package/dist/schemas/bundled/security.json +864 -1232
  299. package/dist/schemas/bundled/technology.json +574 -703
  300. package/dist/schemas/bundled/testing.json +718 -1643
  301. package/dist/schemas/bundled/ux.json +739 -1352
  302. package/dist/server/schemas.js +8 -0
  303. package/dist/server/schemas.js.map +2 -2
  304. package/dist/server/server.js +234 -31
  305. package/dist/server/server.js.map +2 -2
  306. package/dist/server-entry.js +2 -1
  307. package/dist/server-entry.js.map +2 -2
  308. package/dist/telemetry/config.js +2 -40
  309. package/dist/telemetry/config.js.map +2 -2
  310. package/dist/telemetry/index.js +1 -1
  311. package/dist/types/index.js.map +2 -2
  312. package/dist/utils/ansi-suppressor.js +23 -0
  313. package/dist/utils/ansi-suppressor.js.map +7 -0
  314. package/dist/utils/attribute-patch.js +41 -0
  315. package/dist/utils/attribute-patch.js.map +7 -0
  316. package/dist/utils/color-utils.js +34 -0
  317. package/dist/utils/color-utils.js.map +7 -0
  318. package/dist/utils/element-reference-shape.js +15 -1
  319. package/dist/utils/element-reference-shape.js.map +3 -3
  320. package/dist/utils/error-hints.js +29 -0
  321. package/dist/utils/error-hints.js.map +7 -0
  322. package/dist/utils/errors.js +75 -10
  323. package/dist/utils/errors.js.map +2 -2
  324. package/dist/utils/globals.js +17 -1
  325. package/dist/utils/globals.js.map +2 -2
  326. package/dist/utils/project-paths.js +50 -3
  327. package/dist/utils/project-paths.js.map +2 -2
  328. package/dist/utils/reference-path-parser.js +49 -0
  329. package/dist/utils/reference-path-parser.js.map +7 -0
  330. package/dist/utils/source-reference.js +17 -10
  331. package/dist/utils/source-reference.js.map +2 -2
  332. package/dist/utils/spec-version.js +10 -2
  333. package/dist/utils/spec-version.js.map +2 -2
  334. package/dist/validators/composed-reference-validator.js +240 -0
  335. package/dist/validators/composed-reference-validator.js.map +7 -0
  336. package/dist/validators/reference-validator.js +57 -13
  337. package/dist/validators/reference-validator.js.map +3 -3
  338. package/dist/validators/relationship-schema-validator.js +33 -5
  339. package/dist/validators/relationship-schema-validator.js.map +3 -3
  340. package/dist/validators/schema-validator.js +27 -9
  341. package/dist/validators/schema-validator.js.map +2 -2
  342. package/dist/validators/types.js.map +1 -1
  343. package/dist/viewer/README.md +6 -6
  344. package/dist/viewer/assets/main-CxzcuU_o.js +84 -0
  345. package/dist/viewer/assets/main-j-7ULYpm.css +1 -0
  346. package/dist/viewer/index.html +2 -2
  347. package/dist/viewer/manifest.json +10 -10
  348. package/dist/viewer/version.json +1 -1
  349. package/package.json +7 -3
  350. package/dist/viewer/assets/main-B7AOQXXX.js +0 -81
  351. package/dist/viewer/assets/main-vdrFrG7q.css +0 -1
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/core/model-upgrade.ts"],
4
+ "sourcesContent": ["/**\n * Model upgrade - moves a model to a newer spec version and keeps its staged changesets\n * committable.\n *\n * A staged changeset records a hash of the model it was created on (its base snapshot), and\n * committing it fails while the model no longer has that hash. An upgrade always changes the\n * hash: it writes a new spec version into the manifest, and migrations may rewrite element\n * files. So the upgrade re-baselines each staged changeset that was in step with the model\n * before the upgrade onto the upgraded model. A changeset that had drifted before the upgrade\n * keeps its base snapshot, so that drift still blocks its commit.\n */\n\nimport { readdir } from \"fs/promises\";\nimport { Model } from \"./model.js\";\nimport { MigrationRegistry, type ApplyMigrationsResult } from \"./migration-registry.js\";\nimport { applyCatalogMigrations } from \"./catalog-migration.js\";\nimport { BaseSnapshotManager } from \"./base-snapshot-manager.js\";\nimport { StagedChangesetStorage } from \"./staged-changeset-storage.js\";\nimport { FileLock } from \"../utils/file-lock.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\n\nexport interface ModelUpgradeOptions {\n fromVersion: string;\n toVersion: string;\n /** No migration path exists between the versions: only write the new spec version */\n versionBumpOnly?: boolean;\n /**\n * The model is at the target version but still names retired types or relationships: apply\n * the catalog migration (element type renames and relationship rewrites) on its own\n */\n catalogOnly?: boolean;\n /** Passed to the migrations (default true) */\n validate?: boolean;\n}\n\n/** A staged changeset, by directory id and name */\nexport interface ChangesetRef {\n id: string;\n name: string;\n}\n\nexport interface ChangesetRebaselineResult {\n /** Staged changesets now based on the upgraded model */\n rebaselined: ChangesetRef[];\n /** Staged changesets that had drifted before the upgrade; they keep their base snapshot */\n drifted: ChangesetRef[];\n /** Changesets that could not be read or re-baselined */\n errors: string[];\n}\n\nexport interface ModelUpgradeResult {\n /** The migrations applied; absent for a version bump */\n migration?: ApplyMigrationsResult;\n changesets: ChangesetRebaselineResult;\n}\n\n/** Staged changesets sorted by whether they match the model before an upgrade */\nexport interface ChangesetBaselines {\n /** Snapshot of the model before the upgrade; null when there is no staged changeset */\n snapshot: string | null;\n inStep: ChangesetRef[];\n drifted: ChangesetRef[];\n errors: string[];\n}\n\n/**\n * Upgrade the model at startPath to options.toVersion and save it, then re-baseline its\n * staged changesets on the upgraded model.\n */\nexport async function upgradeModel(\n startPath: string,\n options: ModelUpgradeOptions\n): Promise<ModelUpgradeResult> {\n // Load every layer, as `dr changeset commit` does before it checks for drift\n const model = await Model.load(startPath, { lazyLoad: false });\n const baselines = await captureChangesetBaselines(model);\n\n let migration: ApplyMigrationsResult | undefined;\n if (options.versionBumpOnly) {\n model.manifest.specVersion = options.toVersion;\n await model.saveManifest();\n } else if (options.catalogOnly) {\n const catalog = await applyCatalogMigrations(model);\n if (catalog.errors.length > 0) {\n throw new Error(`Catalog migration failed: ${catalog.errors.join(\"; \")}`);\n }\n migration = {\n applied: [\n {\n from: options.fromVersion,\n to: options.toVersion,\n description: `Catalog changes applied (${catalog.summaries.join(\"; \")})`,\n changes: {\n migrationsApplied: 1,\n filesModified: catalog.filesModified,\n description: catalog.summaries.join(\"; \"),\n details: catalog.details,\n },\n },\n ],\n currentVersion: options.fromVersion,\n targetVersion: options.toVersion,\n totalChanges: 1,\n };\n await model.saveDirtyLayers();\n } else {\n migration = await new MigrationRegistry().applyMigrations(model, {\n fromVersion: options.fromVersion,\n toVersion: options.toVersion,\n dryRun: false,\n validate: options.validate ?? true,\n });\n await model.saveManifest();\n await model.saveDirtyLayers();\n }\n\n return { migration, changesets: await rebaselineChangesets(startPath, baselines) };\n}\n\n/**\n * Hash the model and sort its staged changesets into those whose base snapshot matches it\n * and those that had drifted from it. Call before the model changes.\n *\n * @param model - loaded with lazyLoad: false, as `dr changeset commit` loads it, because the\n * snapshot hashes every loaded layer\n */\nexport async function captureChangesetBaselines(model: Model): Promise<ChangesetBaselines> {\n if (model.lazyLoad) {\n throw new Error(\"captureChangesetBaselines needs a model loaded with lazyLoad: false\");\n }\n\n const { changesets, errors } = await listStagedChangesets(model.rootPath);\n const baselines: ChangesetBaselines = { snapshot: null, inStep: [], drifted: [], errors };\n if (changesets.length === 0) {\n return baselines;\n }\n\n const snapshot = await new BaseSnapshotManager().captureSnapshot(model);\n baselines.snapshot = snapshot;\n for (const { ref, baseSnapshot } of changesets) {\n // A changeset without a base snapshot never reports drift, so it needs no re-baseline\n if (!baseSnapshot) {\n continue;\n }\n (baseSnapshot === snapshot ? baselines.inStep : baselines.drifted).push(ref);\n }\n return baselines;\n}\n\n/**\n * Point the changesets that were in step before the upgrade at the upgraded model. Call after\n * the upgraded model is saved.\n *\n * @param startPath - the path the model was loaded from before the upgrade\n */\nexport async function rebaselineChangesets(\n startPath: string,\n baselines: ChangesetBaselines\n): Promise<ChangesetRebaselineResult> {\n const result: ChangesetRebaselineResult = {\n rebaselined: [],\n drifted: baselines.drifted,\n errors: [...baselines.errors],\n };\n if (baselines.inStep.length === 0) {\n return result;\n }\n\n // Reload rather than hash the model in memory: migrations may rewrite files on disk\n // without updating it, and the commit hashes what it loads from disk\n const upgraded = await Model.load(startPath, { lazyLoad: false });\n const snapshot = await new BaseSnapshotManager().captureSnapshot(upgraded);\n const storage = new StagedChangesetStorage(upgraded.rootPath);\n\n for (const ref of baselines.inStep) {\n try {\n await new FileLock(storage.getChangesetPath(ref.id)).withLock(async () => {\n const changeset = await storage.load(ref.id);\n // Skip a changeset whose status or base changed while the upgrade ran\n if (\n !changeset ||\n changeset.status !== \"staged\" ||\n changeset.baseSnapshot !== baselines.snapshot\n ) {\n return;\n }\n changeset.baseSnapshot = snapshot;\n changeset.updateModified();\n await storage.save(changeset);\n result.rebaselined.push(ref);\n });\n } catch (error) {\n result.errors.push(\n `Could not re-baseline changeset '${ref.name}': ${getErrorMessage(error)}; committing it needs --force`\n );\n }\n }\n return result;\n}\n\nasync function listStagedChangesets(rootPath: string): Promise<{\n changesets: Array<{ ref: ChangesetRef; baseSnapshot: string | undefined }>;\n errors: string[];\n}> {\n const storage = new StagedChangesetStorage(rootPath);\n const changesets: Array<{ ref: ChangesetRef; baseSnapshot: string | undefined }> = [];\n const errors: string[] = [];\n\n let ids: string[];\n try {\n ids = (await readdir(storage.getChangesetsDir(), { withFileTypes: true }))\n // FileLock keeps \"<changeset>.lock\" directories beside the changesets\n .filter((entry) => entry.isDirectory() && !entry.name.endsWith(\".lock\"))\n .map((entry) => entry.name)\n .sort();\n } catch (error) {\n if ((error as { code?: string }).code !== \"ENOENT\") {\n errors.push(`Could not list changesets: ${getErrorMessage(error)}`);\n }\n return { changesets, errors };\n }\n\n for (const id of ids) {\n try {\n const changeset = await storage.load(id);\n if (changeset?.status === \"staged\") {\n changesets.push({\n ref: { id, name: changeset.name || id },\n baseSnapshot: changeset.baseSnapshot,\n });\n }\n } catch (error) {\n errors.push(`Could not read changeset '${id}': ${getErrorMessage(error)}`);\n }\n }\n return { changesets, errors };\n}\n"],
5
+ "mappings": "AAYA,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,yBAAqD;AAC9D,SAAS,8BAA8B;AACvC,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,gBAAgB;AACzB,SAAS,uBAAuB;AAkDhC,eAAsB,aACpB,WACA,SAA4B;AAG5B,QAAM,QAAQ,MAAM,MAAM,KAAK,WAAW,EAAE,UAAU,GAAK,CAAE,GACvD,YAAY,MAAM,0BAA0B,KAAK;AAEvD,MAAI;AACJ,MAAI,QAAQ;AACV,UAAM,SAAS,cAAc,QAAQ,WACrC,MAAM,MAAM,aAAY;WACf,QAAQ,aAAa;AAC9B,UAAM,UAAU,MAAM,uBAAuB,KAAK;AAClD,QAAI,QAAQ,OAAO,SAAS;AAC1B,YAAM,IAAI,MAAM,6BAA6B,QAAQ,OAAO,KAAK,IAAI,CAAC,EAAE;AAE1E,gBAAY;MACV,SAAS;QACP;UACE,MAAM,QAAQ;UACd,IAAI,QAAQ;UACZ,aAAa,4BAA4B,QAAQ,UAAU,KAAK,IAAI,CAAC;UACrE,SAAS;YACP,mBAAmB;YACnB,eAAe,QAAQ;YACvB,aAAa,QAAQ,UAAU,KAAK,IAAI;YACxC,SAAS,QAAQ;;;;MAIvB,gBAAgB,QAAQ;MACxB,eAAe,QAAQ;MACvB,cAAc;OAEhB,MAAM,MAAM,gBAAe;EAC7B;AACE,gBAAY,MAAM,IAAI,kBAAiB,EAAG,gBAAgB,OAAO;MAC/D,aAAa,QAAQ;MACrB,WAAW,QAAQ;MACnB,QAAQ;MACR,UAAU,QAAQ,YAAY;KAC/B,GACD,MAAM,MAAM,aAAY,GACxB,MAAM,MAAM,gBAAe;AAG7B,SAAO,EAAE,WAAW,YAAY,MAAM,qBAAqB,WAAW,SAAS,EAAC;AAClF;AASA,eAAsB,0BAA0B,OAAY;AAC1D,MAAI,MAAM;AACR,UAAM,IAAI,MAAM,qEAAqE;AAGvF,QAAM,EAAE,YAAY,OAAM,IAAK,MAAM,qBAAqB,MAAM,QAAQ,GAClE,YAAgC,EAAE,UAAU,MAAM,QAAQ,CAAA,GAAI,SAAS,CAAA,GAAI,OAAM;AACvF,MAAI,WAAW,WAAW;AACxB,WAAO;AAGT,QAAM,WAAW,MAAM,IAAI,oBAAmB,EAAG,gBAAgB,KAAK;AACtE,YAAU,WAAW;AACrB,aAAW,EAAE,KAAK,aAAY,KAAM;AAElC,IAAK,iBAGJ,iBAAiB,WAAW,UAAU,SAAS,UAAU,SAAS,KAAK,GAAG;AAE7E,SAAO;AACT;AAQA,eAAsB,qBACpB,WACA,WAA6B;AAE7B,QAAM,SAAoC;IACxC,aAAa,CAAA;IACb,SAAS,UAAU;IACnB,QAAQ,CAAC,GAAG,UAAU,MAAM;;AAE9B,MAAI,UAAU,OAAO,WAAW;AAC9B,WAAO;AAKT,QAAM,WAAW,MAAM,MAAM,KAAK,WAAW,EAAE,UAAU,GAAK,CAAE,GAC1D,WAAW,MAAM,IAAI,oBAAmB,EAAG,gBAAgB,QAAQ,GACnE,UAAU,IAAI,uBAAuB,SAAS,QAAQ;AAE5D,aAAW,OAAO,UAAU;AAC1B,QAAI;AACF,YAAM,IAAI,SAAS,QAAQ,iBAAiB,IAAI,EAAE,CAAC,EAAE,SAAS,YAAW;AACvE,cAAM,YAAY,MAAM,QAAQ,KAAK,IAAI,EAAE;AAE3C,QACE,CAAC,aACD,UAAU,WAAW,YACrB,UAAU,iBAAiB,UAAU,aAIvC,UAAU,eAAe,UACzB,UAAU,eAAc,GACxB,MAAM,QAAQ,KAAK,SAAS,GAC5B,OAAO,YAAY,KAAK,GAAG;MAC7B,CAAC;IACH,SAAS,OAAO;AACd,aAAO,OAAO,KACZ,oCAAoC,IAAI,IAAI,MAAM,gBAAgB,KAAK,CAAC,+BAA+B;IAE3G;AAEF,SAAO;AACT;AAEA,eAAe,qBAAqB,UAAgB;AAIlD,QAAM,UAAU,IAAI,uBAAuB,QAAQ,GAC7C,aAA6E,CAAA,GAC7E,SAAmB,CAAA;AAEzB,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,QAAQ,QAAQ,iBAAgB,GAAI,EAAE,eAAe,GAAI,CAAE,GAErE,OAAO,CAAC,UAAU,MAAM,YAAW,KAAM,CAAC,MAAM,KAAK,SAAS,OAAO,CAAC,EACtE,IAAI,CAAC,UAAU,MAAM,IAAI,EACzB,KAAI;EACT,SAAS,OAAO;AACd,WAAK,MAA4B,SAAS,YACxC,OAAO,KAAK,8BAA8B,gBAAgB,KAAK,CAAC,EAAE,GAE7D,EAAE,YAAY,OAAM;EAC7B;AAEA,aAAW,MAAM;AACf,QAAI;AACF,YAAM,YAAY,MAAM,QAAQ,KAAK,EAAE;AACvC,MAAI,WAAW,WAAW,YACxB,WAAW,KAAK;QACd,KAAK,EAAE,IAAI,MAAM,UAAU,QAAQ,GAAE;QACrC,cAAc,UAAU;OACzB;IAEL,SAAS,OAAO;AACd,aAAO,KAAK,6BAA6B,EAAE,MAAM,gBAAgB,KAAK,CAAC,EAAE;IAC3E;AAEF,SAAO,EAAE,YAAY,OAAM;AAC7B;",
6
+ "names": []
7
+ }
@@ -10,6 +10,9 @@ import { ensureDir, writeFile } from "../utils/file-io.js";
10
10
  import { getCliVersion } from "../utils/spec-version.js";
11
11
  import { startSpan, endSpan, startActiveSpan } from "../telemetry/index.js";
12
12
  import { findProjectRoot } from "../utils/project-paths.js";
13
+ import { getCodebasePath } from "../utils/globals.js";
14
+ import { getErrorMessage } from "../utils/errors.js";
15
+ import { assertBaseModelWritable } from "./changeset-only-guard.js";
13
16
  import { getNodeType } from "../generated/node-types.js";
14
17
  import path from "node:path";
15
18
  import fs from "node:fs/promises";
@@ -49,19 +52,26 @@ function isPersistableAttribute(specNodeId, key) {
49
52
  const isTelemetryEnabled = !1;
50
53
  class Model {
51
54
  rootPath;
55
+ codebaseRoot;
52
56
  manifest;
53
57
  graph;
54
58
  layers;
55
59
  relationships;
56
60
  lazyLoad;
61
+ loadedLayerFilter;
57
62
  loadedLayers;
58
63
  virtualProjectionEngine;
59
64
  /** ID of the currently active changeset, or null if none is active. Set during load(). */
60
65
  _activeChangesetId = null;
61
66
  /** Layer names that exist only in staged changes (not yet committed to base model). */
62
67
  _stagedLayerNames = /* @__PURE__ */ new Set();
68
+ /**
69
+ * relationships.yaml as committed, captured at load time before the active changeset's staged
70
+ * relationship changes are merged into `relationships`. Null when no changeset was active.
71
+ */
72
+ _baseRelationships = null;
63
73
  constructor(rootPath, manifest, options = {}) {
64
- this.rootPath = rootPath, this.manifest = manifest, this.graph = new GraphModel(), this.layers = /* @__PURE__ */ new Map(), this.relationships = new Relationships(), this.lazyLoad = options.lazyLoad ?? !1, this.loadedLayers = /* @__PURE__ */ new Set();
74
+ this.rootPath = rootPath, this.codebaseRoot = options.codebaseRoot ?? rootPath, this.manifest = manifest, this.graph = new GraphModel(), this.layers = /* @__PURE__ */ new Map(), this.relationships = new Relationships(), this.lazyLoad = options.lazyLoad ?? !1, this.loadedLayerFilter = options.layers, this.loadedLayers = /* @__PURE__ */ new Set();
65
75
  }
66
76
  /**
67
77
  * Get the committed base layer (no staging projection).
@@ -79,8 +89,11 @@ class Model {
79
89
  if (this.lazyLoad && !this.loadedLayers.has(name) && await this.loadLayer(name), this._activeChangesetId)
80
90
  try {
81
91
  return await this.getVirtualProjectionEngine().projectLayer(this, this._activeChangesetId, name);
82
- } catch {
83
- return this.layers.get(name);
92
+ } catch (err) {
93
+ const errorMessage = err instanceof Error ? err.message : String(err), isChangesetNotFound = errorMessage.includes("Changeset") && errorMessage.includes("not found"), isCorrupted = errorMessage.includes("corrupted");
94
+ if (isChangesetNotFound || isCorrupted)
95
+ return this.layers.get(name);
96
+ throw err;
84
97
  }
85
98
  return this.layers.get(name);
86
99
  }
@@ -108,18 +121,34 @@ class Model {
108
121
  getActiveChangesetId() {
109
122
  return this._activeChangesetId;
110
123
  }
124
+ /**
125
+ * The committed relationships, without the active changeset's staged additions and removals
126
+ * that load() merges into `relationships` for read commands. Equal to `relationships.getAll()`
127
+ * when no changeset was active at load time.
128
+ */
129
+ getBaseRelationships() {
130
+ return this._baseRelationships ? [...this._baseRelationships] : this.relationships.getAll();
131
+ }
111
132
  /**
112
133
  * Load a layer from disk (model/XX_layername/*.yaml files)
113
134
  */
114
135
  async loadLayer(name) {
115
136
  await startActiveSpan("layer.load", async (layerSpan) => {
116
137
  const fs2 = await import("fs/promises"), yaml = await import("yaml");
117
- let layerPath = null;
118
- const modelDir = `${this.rootPath}/documentation-robotics/model`;
138
+ let layerPath = null, modelDir = `${this.rootPath}/documentation-robotics/model`;
119
139
  try {
120
140
  const layerDir = (await fs2.readdir(modelDir, { withFileTypes: !0 })).find((e) => e.isDirectory() && e.name.match(/^\d{2}_/) && e.name.replace(/^\d{2}_/, "") === name);
121
141
  layerDir && (layerPath = `${modelDir}/${layerDir.name}`);
122
- } catch {
142
+ } catch (err) {
143
+ if (!(err?.code === "ENOENT"))
144
+ throw err;
145
+ try {
146
+ const layerDir = (await fs2.readdir(this.rootPath, { withFileTypes: !0 })).find((e) => e.isDirectory() && e.name.match(/^\d{2}_/) && e.name.replace(/^\d{2}_/, "") === name);
147
+ layerDir && (layerPath = `${this.rootPath}/${layerDir.name}`);
148
+ } catch (err2) {
149
+ if (!(err2?.code === "ENOENT"))
150
+ throw err2;
151
+ }
123
152
  }
124
153
  if (!layerPath)
125
154
  return;
@@ -211,10 +240,19 @@ class Model {
211
240
  const staged = Array.from(this._stagedLayerNames).filter((l) => !this.layers.has(l));
212
241
  return [...base, ...staged];
213
242
  }
243
+ /**
244
+ * Records that the active changeset now stages an element change in `layerName`, so
245
+ * getLayerNames() (and findElementLayer()) discover a layer the base model does not have without
246
+ * a reload. StagingAreaManager calls this when it stages into this model's active changeset.
247
+ */
248
+ noteStagedLayer(changesetId, layerName) {
249
+ this._activeChangesetId && this._activeChangesetId === changesetId && layerName && this._stagedLayerNames.add(layerName);
250
+ }
214
251
  /**
215
252
  * Save a layer to disk (model/XX_layername/*.yaml)
216
253
  */
217
254
  async saveLayer(name) {
255
+ assertBaseModelWritable();
218
256
  const layer = this.layers.get(name);
219
257
  if (!layer)
220
258
  throw new Error(`Layer ${name} not found`);
@@ -224,7 +262,16 @@ class Model {
224
262
  try {
225
263
  const layerDir = (await fs2.readdir(modelDir, { withFileTypes: !0 })).find((e) => e.isDirectory() && e.name.match(/^\d{2}_/) && e.name.replace(/^\d{2}_/, "") === name);
226
264
  layerDir && (layerPath = `${modelDir}/${layerDir.name}`);
227
- } catch {
265
+ } catch (err) {
266
+ if (!(err?.code === "ENOENT"))
267
+ throw err;
268
+ try {
269
+ const layerDir = (await fs2.readdir(this.rootPath, { withFileTypes: !0 })).find((e) => e.isDirectory() && e.name.match(/^\d{2}_/) && e.name.replace(/^\d{2}_/, "") === name);
270
+ layerDir && (layerPath = `${this.rootPath}/${layerDir.name}`);
271
+ } catch (err2) {
272
+ if (!(err2?.code === "ENOENT"))
273
+ throw err2;
274
+ }
228
275
  }
229
276
  if (!layerPath) {
230
277
  const layerNameWithoutPrefix = name.replace(/^\d{2}-/, ""), index = CANONICAL_LAYER_NAMES.indexOf(layerNameWithoutPrefix);
@@ -302,7 +349,7 @@ class Model {
302
349
  * Save the manifest to disk (documentation-robotics/model/manifest.yaml)
303
350
  */
304
351
  async saveManifest() {
305
- this.manifest.updateModified();
352
+ assertBaseModelWritable(), this.manifest.updateModified();
306
353
  const yaml = await import("yaml"), manifestPath = `${this.rootPath}/documentation-robotics/model/manifest.yaml`, yamlData = {
307
354
  version: this.manifest.version,
308
355
  schema: "documentation-robotics-v1",
@@ -328,7 +375,7 @@ class Model {
328
375
  ...layer && { elements: this.getLayerElementCounts(layer) }
329
376
  };
330
377
  }
331
- this.manifest.changeset_history && this.manifest.changeset_history.length > 0 && (yamlData.changeset_history = this.manifest.changeset_history), await ensureDir(`${this.rootPath}/documentation-robotics/model`), await writeFile(manifestPath, yaml.stringify(yamlData));
378
+ this.manifest.codebase_path && (yamlData.codebase_path = this.manifest.codebase_path), this.manifest.changeset_history && this.manifest.changeset_history.length > 0 && (yamlData.changeset_history = this.manifest.changeset_history), this.manifest.models && Object.keys(this.manifest.models).length > 0 && (yamlData.models = this.manifest.models), await ensureDir(`${this.rootPath}/documentation-robotics/model`), await writeFile(manifestPath, yaml.stringify(yamlData));
332
379
  }
333
380
  getLayerElementCounts(layer) {
334
381
  const counts = {};
@@ -350,8 +397,8 @@ class Model {
350
397
  throw new Error(`Failed to load relationships.yaml: ${err instanceof Error ? err.message : String(err)}`);
351
398
  return;
352
399
  }
353
- if (!Array.isArray(data)) {
354
- console.warn(`Warning: relationships.yaml contains non-array content (received ${data === null ? "null" : typeof data}). Expected an array of relationships. Skipping relationship loading.`);
400
+ if (data === null && (data = []), !Array.isArray(data)) {
401
+ console.warn(`Warning: relationships.yaml contains non-array content (received ${typeof data}). Expected an array of relationships. Skipping relationship loading.`);
355
402
  return;
356
403
  }
357
404
  this.relationships = Relationships.fromArray(data);
@@ -372,17 +419,86 @@ class Model {
372
419
  }
373
420
  /**
374
421
  * Save relationships to relationships.yaml
422
+ *
423
+ * @param options.preserveHeader - Keep the file's existing leading comment block
424
+ * (refreshing its `# Last updated:` line) instead of writing the standard header.
375
425
  */
376
- async saveRelationships() {
426
+ async saveRelationships(options = {}) {
427
+ assertBaseModelWritable();
377
428
  const fs2 = await import("fs/promises"), yaml = await import("yaml"), relationshipsPath = `${this.rootPath}/documentation-robotics/model/relationships.yaml`;
378
429
  await ensureDir(`${this.rootPath}/documentation-robotics/model`);
379
- const data = this.relationships.toArray(), yamlContent = `# Intra-layer relationships
430
+ const data = this.relationships.toArray(), timestamp = (/* @__PURE__ */ new Date()).toISOString();
431
+ let header = `# Intra-layer relationships
380
432
  # Format: source_id -> predicate -> target_id
381
- # Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}
433
+ # Last updated: ${timestamp}
382
434
 
383
- ` + yaml.stringify(data);
435
+ `;
436
+ if (options.preserveHeader)
437
+ try {
438
+ const { extractRelationshipsHeader } = await import("./relationship-prune.js"), existing = extractRelationshipsHeader(await fs2.readFile(relationshipsPath, "utf-8"));
439
+ existing && (header = existing.replace(/^(# Last updated:).*$/m, `$1 ${timestamp}`) + `
440
+ `);
441
+ } catch {
442
+ }
443
+ const yamlContent = header + yaml.stringify(data);
384
444
  await fs2.writeFile(relationshipsPath, yamlContent, "utf-8"), this.relationships.markClean();
385
445
  }
446
+ /**
447
+ * Auto-resolve codebaseRoot from farm manifest if inside a farm
448
+ *
449
+ * @param modelRoot - The model's project root (documentation_robotics parent)
450
+ * @returns Resolved codebase root path, or null if not in farm or resolution fails
451
+ */
452
+ static async resolveFarmCodebaseRoot(modelRoot) {
453
+ const { findFarmRoot } = await import("../utils/project-paths.js"), { FarmManifest } = await import("./farm-manifest.js"), farmRoot = await findFarmRoot(modelRoot);
454
+ if (!farmRoot)
455
+ return null;
456
+ const farmYamlPath = path.join(farmRoot, "farm.yaml");
457
+ let manifest;
458
+ try {
459
+ manifest = await FarmManifest.load(farmYamlPath);
460
+ } catch (error) {
461
+ const errorMsg = getErrorMessage(error);
462
+ if (errorMsg.includes("ENOENT") || errorMsg.includes("not found"))
463
+ return null;
464
+ throw new Error(`Failed to load farm manifest at ${farmYamlPath}: ${errorMsg}`);
465
+ }
466
+ const modelName = path.basename(modelRoot);
467
+ for (const project of manifest.getAllProjects())
468
+ if (path.basename(project.model) === modelName)
469
+ return path.resolve(farmRoot, project.source);
470
+ return null;
471
+ }
472
+ /**
473
+ * Walk upward from startPath looking for documentation-robotics/model/manifest.yaml,
474
+ * stopping at the nearest .git directory (repo boundary) so this can never cross into
475
+ * an unrelated parent project or a sibling farm/detached model directory.
476
+ *
477
+ * @param startPath - Directory to start the walk from
478
+ * @returns Manifest path if found within the repo boundary, or null
479
+ */
480
+ static async findManifestUpwardBounded(startPath) {
481
+ let currentPath = startPath;
482
+ for (; ; ) {
483
+ const candidateManifestPath = path.join(currentPath, "documentation-robotics", "model", "manifest.yaml");
484
+ try {
485
+ return await fs.access(candidateManifestPath), candidateManifestPath;
486
+ } catch (err) {
487
+ if (!(err?.code === "ENOENT"))
488
+ throw err;
489
+ }
490
+ try {
491
+ return await fs.access(path.join(currentPath, ".git")), null;
492
+ } catch (err) {
493
+ if (!(err?.code === "ENOENT"))
494
+ throw err;
495
+ }
496
+ const parentPath = path.dirname(currentPath);
497
+ if (parentPath === currentPath)
498
+ return null;
499
+ currentPath = parentPath;
500
+ }
501
+ }
386
502
  /**
387
503
  * Resolve model paths (private helper)
388
504
  *
@@ -390,18 +506,52 @@ class Model {
390
506
  * @returns Project root and manifest path
391
507
  */
392
508
  static async resolveModelPaths(startPath) {
393
- const searchPath = startPath || process.cwd();
509
+ if (startPath) {
510
+ const resolvedPath = path.resolve(startPath);
511
+ let manifestPath2;
512
+ resolvedPath.endsWith("manifest.yaml") ? manifestPath2 = resolvedPath : resolvedPath.endsWith("model") ? manifestPath2 = path.join(resolvedPath, "manifest.yaml") : manifestPath2 = path.join(resolvedPath, "documentation-robotics", "model", "manifest.yaml");
513
+ try {
514
+ await fs.access(manifestPath2);
515
+ let projectRoot2;
516
+ const manifestParentDir = path.dirname(manifestPath2), standardProjectRoot = path.dirname(path.dirname(path.dirname(manifestPath2))), standardDocRobotsPath = path.join(standardProjectRoot, "documentation-robotics", "model");
517
+ try {
518
+ await fs.access(standardDocRobotsPath), projectRoot2 = standardProjectRoot;
519
+ } catch (err) {
520
+ if (!(err?.code === "ENOENT"))
521
+ throw err;
522
+ projectRoot2 = manifestParentDir;
523
+ }
524
+ return { projectRoot: projectRoot2, manifestPath: path.normalize(manifestPath2) };
525
+ } catch (err) {
526
+ if (!(err?.code === "ENOENT"))
527
+ throw err;
528
+ const fallbackManifestPath = await Model.findManifestUpwardBounded(resolvedPath);
529
+ if (fallbackManifestPath)
530
+ return { projectRoot: path.dirname(path.dirname(path.dirname(fallbackManifestPath))), manifestPath: path.normalize(fallbackManifestPath) };
531
+ throw new Error(`Model not found at ${startPath}`);
532
+ }
533
+ }
394
534
  if (process.env.DR_MODEL_PATH) {
395
535
  const envPath = path.resolve(process.env.DR_MODEL_PATH);
396
536
  let manifestPath2;
397
537
  envPath.endsWith("manifest.yaml") ? manifestPath2 = envPath : envPath.endsWith("model") ? manifestPath2 = path.join(envPath, "manifest.yaml") : manifestPath2 = path.join(envPath, "documentation-robotics", "model", "manifest.yaml");
398
538
  try {
399
- return await fs.access(manifestPath2), { projectRoot: path.dirname(path.dirname(path.dirname(manifestPath2))), manifestPath: path.normalize(manifestPath2) };
400
- } catch {
401
- throw new Error(`Model not found at DR_MODEL_PATH: ${process.env.DR_MODEL_PATH}`);
539
+ await fs.access(manifestPath2);
540
+ let projectRoot2;
541
+ const manifestParentDir = path.dirname(manifestPath2), standardProjectRoot = path.dirname(path.dirname(path.dirname(manifestPath2))), standardDocRobotsPath = path.join(standardProjectRoot, "documentation-robotics", "model");
542
+ try {
543
+ await fs.access(standardDocRobotsPath), projectRoot2 = standardProjectRoot;
544
+ } catch (err) {
545
+ if (!(err?.code === "ENOENT"))
546
+ throw err;
547
+ projectRoot2 = manifestParentDir;
548
+ }
549
+ return { projectRoot: projectRoot2, manifestPath: path.normalize(manifestPath2) };
550
+ } catch (err) {
551
+ throw err?.code === "ENOENT" ? new Error(`Model not found at DR_MODEL_PATH: ${process.env.DR_MODEL_PATH}`) : err;
402
552
  }
403
553
  }
404
- const projectRoot = await findProjectRoot(searchPath);
554
+ const searchPath = process.cwd(), projectRoot = await findProjectRoot(searchPath);
405
555
  if (!projectRoot)
406
556
  throw new Error(`No DR project found. Could not find documentation_robotics/ folder.
407
557
  Run "dr init" to create a new project, or navigate to a directory containing a DR project.`);
@@ -413,6 +563,21 @@ Run "dr init" to create a new project, or navigate to a directory containing a D
413
563
  Run "dr init" to create a model.`);
414
564
  }
415
565
  }
566
+ /**
567
+ * Validate that the manifest's models field is a plain object.
568
+ *
569
+ * @param models - The raw value from manifest YAML
570
+ * @returns Validation result with valid flag and error message if invalid
571
+ */
572
+ static validateModelsField(models) {
573
+ return models == null ? { valid: !0 } : typeof models != "object" || Array.isArray(models) ? {
574
+ valid: !1,
575
+ error: `Invalid manifest 'models' field: expected an object with model names as keys (e.g., { "auth-service": { } }), but got ${Array.isArray(models) ? "an array" : `a ${typeof models}`}. This often happens when YAML syntax is incorrect, like writing 'models: auth-service' instead of 'models: { auth-service: {} }' or 'models:\\n auth-service: {}'.`
576
+ } : Object.getPrototypeOf(models) !== Object.prototype ? {
577
+ valid: !1,
578
+ error: `Invalid manifest 'models' field: expected a plain object, but got an instance of ${models.constructor?.name || "unknown"}. Please ensure 'models' is a simple YAML object mapping model names to declarations.`
579
+ } : { valid: !0 };
580
+ }
416
581
  /**
417
582
  * Load a model from disk
418
583
  *
@@ -432,10 +597,34 @@ Run "dr init" to create a model.`);
432
597
  name: manifestYaml.project?.name || "Unnamed Model",
433
598
  description: manifestYaml.project?.description || "",
434
599
  version: manifestYaml.project?.version || manifestYaml.version || "0.1.0",
600
+ cliVersion: manifestYaml.cli_version,
435
601
  specVersion: manifestYaml.spec_version || "0.6.0",
436
602
  created: manifestYaml.created || (/* @__PURE__ */ new Date()).toISOString(),
437
603
  modified: manifestYaml.updated || (/* @__PURE__ */ new Date()).toISOString()
438
- }, manifest = new Manifest(manifestData), model = new Model(projectRoot, manifest, options);
604
+ };
605
+ if (manifestYaml.models) {
606
+ const modelsValidation = Model.validateModelsField(manifestYaml.models);
607
+ if (!modelsValidation.valid)
608
+ throw new Error(modelsValidation.error);
609
+ manifestData.models = manifestYaml.models;
610
+ }
611
+ manifestYaml.codebase_path && (manifestData.codebase_path = manifestYaml.codebase_path);
612
+ const manifest = new Manifest(manifestData);
613
+ let codebaseRoot = projectRoot;
614
+ if (options.codebaseRoot)
615
+ codebaseRoot = options.codebaseRoot;
616
+ else {
617
+ const globalCodebasePath = getCodebasePath();
618
+ if (globalCodebasePath)
619
+ codebaseRoot = path.resolve(globalCodebasePath);
620
+ else if (manifest.codebase_path)
621
+ codebaseRoot = path.resolve(projectRoot, manifest.codebase_path);
622
+ else {
623
+ const farmCodebaseRoot = await Model.resolveFarmCodebaseRoot(projectRoot);
624
+ farmCodebaseRoot && (codebaseRoot = farmCodebaseRoot);
625
+ }
626
+ }
627
+ const model = new Model(projectRoot, manifest, { ...options, codebaseRoot });
439
628
  if (!options.lazyLoad)
440
629
  try {
441
630
  if (manifestYaml.layers)
@@ -458,29 +647,37 @@ Run "dr init" to create a model.`);
458
647
  try {
459
648
  const activeId = (await fs.readFile(activePath, "utf-8")).trim();
460
649
  if (activeId) {
461
- model._activeChangesetId = activeId;
462
- const changeset = await new StagedChangesetStorage(projectRoot).load(activeId);
463
- if (changeset)
464
- for (const change of changeset.changes) {
465
- if ((change.type === "add" || change.type === "update" || change.type === "delete") && model._stagedLayerNames.add(change.layerName), change.type === "relationship-add" && change.after) {
466
- const rel = change.after;
467
- model.relationships.find(rel.source, rel.target, rel.predicate).length === 0 && model.relationships.add({
468
- source: rel.source,
469
- target: rel.target,
470
- predicate: rel.predicate,
471
- layer: rel.layer,
472
- ...rel.targetLayer ? { targetLayer: rel.targetLayer } : {},
473
- category: rel.category ?? "structural",
474
- ...rel.properties ? { properties: rel.properties } : {}
475
- });
476
- }
477
- if (change.type === "relationship-delete" && change.before) {
478
- const rel = change.before;
479
- model.relationships.delete(rel.source, rel.target, rel.predicate);
650
+ model._activeChangesetId = activeId, model._baseRelationships = model.relationships.getAll();
651
+ const storage = new StagedChangesetStorage(projectRoot);
652
+ try {
653
+ const changeset = await storage.load(activeId);
654
+ if (changeset)
655
+ for (const change of changeset.changes) {
656
+ if ((change.type === "add" || change.type === "update" || change.type === "delete") && model._stagedLayerNames.add(change.layerName), change.type === "relationship-add" && change.after) {
657
+ const rel = change.after;
658
+ model.relationships.find(rel.source, rel.target, rel.predicate).length === 0 && model.relationships.add({
659
+ source: rel.source,
660
+ target: rel.target,
661
+ predicate: rel.predicate,
662
+ layer: rel.layer,
663
+ ...rel.targetLayer ? { targetLayer: rel.targetLayer } : {},
664
+ category: rel.category ?? "structural",
665
+ ...rel.properties ? { properties: rel.properties } : {}
666
+ });
667
+ }
668
+ if (change.type === "relationship-delete" && change.before) {
669
+ const rel = change.before;
670
+ model.relationships.delete(rel.source, rel.target, rel.predicate);
671
+ }
480
672
  }
481
- }
673
+ } catch (err) {
674
+ const errorMessage = err instanceof Error ? err.message : String(err);
675
+ throw new Error(`Failed to load active changeset '${activeId}': ${errorMessage}`, { cause: err });
676
+ }
482
677
  }
483
- } catch {
678
+ } catch (err) {
679
+ if (!(err?.code === "ENOENT"))
680
+ throw err;
484
681
  model._activeChangesetId = null;
485
682
  }
486
683
  let entityCount = 0;
@@ -496,13 +693,28 @@ Run "dr init" to create a model.`);
496
693
  * Initialize a new model in a directory
497
694
  */
498
695
  static async init(rootPath, manifestData, options = {}) {
696
+ const yaml = await import("yaml");
499
697
  await ensureDir(`${rootPath}/documentation-robotics/model`);
500
698
  for (let i = 0; i < CANONICAL_LAYER_NAMES.length; i++) {
501
699
  const orderNum = String(i + 1).padStart(2, "0"), layerName = CANONICAL_LAYER_NAMES[i];
502
700
  await ensureDir(`${rootPath}/documentation-robotics/model/${orderNum}_${layerName}`);
503
701
  }
504
- const manifest = new Manifest(manifestData), model = new Model(rootPath, manifest, options);
505
- return await model.saveManifest(), model;
702
+ const manifest = new Manifest(manifestData);
703
+ let codebaseRoot = rootPath;
704
+ if (options.codebaseRoot)
705
+ codebaseRoot = options.codebaseRoot;
706
+ else {
707
+ const globalCodebasePath = getCodebasePath();
708
+ globalCodebasePath ? codebaseRoot = path.resolve(globalCodebasePath) : manifest.codebase_path && (codebaseRoot = path.resolve(rootPath, manifest.codebase_path));
709
+ }
710
+ const model = new Model(rootPath, manifest, { ...options, codebaseRoot });
711
+ await model.saveManifest();
712
+ const relationshipsPath = `${rootPath}/documentation-robotics/model/relationships.yaml`, yamlContent = `# Intra-layer relationships
713
+ # Format: source_id -> predicate -> target_id
714
+ # Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}
715
+
716
+ ` + yaml.stringify([]);
717
+ return await writeFile(relationshipsPath, yamlContent), model;
506
718
  }
507
719
  /**
508
720
  * Graph-based API methods for querying the model
@@ -592,6 +804,7 @@ Run "dr init" to create a model.`);
592
804
  * @throws Error if atomic write fails
593
805
  */
594
806
  async saveDirtyLayersAtomic() {
807
+ assertBaseModelWritable();
595
808
  const tempLayerWrites = [];
596
809
  try {
597
810
  for (const layer of this.layers.values())