@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/staged-changeset-storage.ts"],
4
- "sourcesContent": ["/**\n * StagedChangesetStorage - Handles YAML-based storage for staged changesets\n *\n * Manages the new storage structure:\n * documentation-robotics/changesets/{changeset-id}/\n * \u251C\u2500\u2500 metadata.yaml # Name, description, dates, status, base snapshot\n * \u2514\u2500\u2500 changes.yaml # Delta-only change records\n */\n\nimport { readFile, writeFile, readdir, rm } from \"fs/promises\";\nimport { fileExists, ensureDir } from \"../utils/file-io.js\";\nimport { FileLock } from \"../utils/file-lock.js\";\nimport path from \"path\";\nimport yaml from \"yaml\";\nimport ansis from \"ansis\";\nimport type { StagedChangesetData, StagedChange } from \"./changeset.js\";\nimport { Changeset } from \"./changeset.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\n\n/**\n * Storage manager for staged changesets using YAML format\n */\nexport class StagedChangesetStorage {\n private changesetsDir: string;\n\n constructor(rootPath: string) {\n // Store changesets in documentation-robotics/changesets/ directory\n this.changesetsDir = path.join(rootPath, \"documentation-robotics\", \"changesets\");\n }\n\n /**\n * Create a new staged changeset directory structure\n */\n async create(\n id: string,\n name: string,\n description: string | undefined,\n baseSnapshot: string\n ): Promise<Changeset> {\n const now = new Date().toISOString();\n const sanitizedId = this.sanitizeId(id);\n const changesetPath = path.join(this.changesetsDir, sanitizedId);\n\n // Create directory structure\n await ensureDir(changesetPath);\n\n // Create metadata\n const metadata: Omit<StagedChangesetData, \"changes\"> = {\n id: sanitizedId,\n name,\n description,\n created: now,\n modified: now,\n status: \"staged\",\n baseSnapshot,\n stats: {\n additions: 0,\n modifications: 0,\n deletions: 0,\n },\n };\n\n // Write metadata.yaml\n await writeFile(path.join(changesetPath, \"metadata.yaml\"), yaml.stringify(metadata), \"utf-8\");\n\n // Write empty changes.yaml\n await writeFile(path.join(changesetPath, \"changes.yaml\"), yaml.stringify([]), \"utf-8\");\n\n // Return constructed changeset object\n return new Changeset({\n id: sanitizedId,\n name,\n description,\n created: now,\n modified: now,\n status: \"staged\",\n baseSnapshot,\n changes: [],\n stats: metadata.stats,\n });\n }\n\n /**\n * Load a staged changeset from YAML files\n */\n async load(id: string): Promise<Changeset | null> {\n const sanitizedId = this.sanitizeId(id);\n const changesetPath = path.join(this.changesetsDir, sanitizedId);\n const metadataPath = path.join(changesetPath, \"metadata.yaml\");\n const changesPath = path.join(changesetPath, \"changes.yaml\");\n\n // Check if changeset directory exists\n if (!(await fileExists(changesetPath))) {\n return null; // Changeset doesn't exist - this is OK\n }\n\n // Changeset directory exists - files SHOULD be present\n const metadataExists = await fileExists(metadataPath);\n const changesExists = await fileExists(changesPath);\n\n if (!metadataExists || !changesExists) {\n // Changeset directory exists but required files are missing - this is corruption\n throw new Error(\n `Changeset '${id}' is corrupted (missing ${!metadataExists ? \"metadata.yaml\" : \"changes.yaml\"}). ` +\n `Location: ${changesetPath}\\n` +\n `This may indicate disk failure or incomplete write. Check disk health and restore from backup if available.`\n );\n }\n\n try {\n // Load metadata\n const metadataContent = await readFile(metadataPath, \"utf-8\");\n const metadata = yaml.parse(metadataContent);\n\n // Load changes\n const changesContent = await readFile(changesPath, \"utf-8\");\n const changes = yaml.parse(changesContent) || [];\n\n // Construct changeset object\n const data: StagedChangesetData = {\n id: metadata.id,\n name: metadata.name,\n description: metadata.description,\n created: metadata.created,\n modified: metadata.modified,\n status: metadata.status,\n baseSnapshot: metadata.baseSnapshot,\n changes: changes as StagedChange[],\n stats: metadata.stats || { additions: 0, modifications: 0, deletions: 0 },\n };\n\n return Changeset.fromJSON(data);\n } catch (error) {\n // YAML parsing or file read error - provide detailed guidance\n throw new Error(\n `Failed to load changeset '${id}': YAML parsing failed.\\n` +\n `Error: ${getErrorMessage(error)}\\n` +\n `Location: ${metadataPath}\\n` +\n `This indicates file corruption. Try:\\n` +\n ` 1. Validate YAML syntax with: yamllint ${metadataPath}\\n` +\n ` 2. Restore from backup if available\\n` +\n ` 3. Manually edit to fix syntax errors`\n );\n }\n }\n\n /**\n * Save a staged changeset to YAML files\n */\n async save(changeset: Changeset): Promise<void> {\n if (!changeset.id) {\n throw new Error(\"Changeset must have an id to be saved in YAML format\");\n }\n\n const sanitizedId = this.sanitizeId(changeset.id);\n const changesetPath = path.join(this.changesetsDir, sanitizedId);\n await ensureDir(changesetPath);\n\n // Prepare metadata (all fields except changes)\n const metadata = {\n id: changeset.id,\n name: changeset.name,\n description: changeset.description,\n created: changeset.created,\n modified: changeset.modified,\n status: changeset.status,\n baseSnapshot: changeset.baseSnapshot,\n stats: changeset.stats || { additions: 0, modifications: 0, deletions: 0 },\n };\n\n // Write metadata.yaml\n await writeFile(path.join(changesetPath, \"metadata.yaml\"), yaml.stringify(metadata), \"utf-8\");\n\n // Write changes.yaml\n await writeFile(\n path.join(changesetPath, \"changes.yaml\"),\n yaml.stringify(changeset.changes),\n \"utf-8\"\n );\n }\n\n /**\n * List all staged changesets\n */\n async list(): Promise<Changeset[]> {\n if (!(await fileExists(this.changesetsDir))) {\n return [];\n }\n\n try {\n const entries = await readdir(this.changesetsDir, { withFileTypes: true });\n const changesets: Changeset[] = [];\n let skippedCount = 0;\n\n for (const entry of entries) {\n if (entry.isDirectory()) {\n try {\n const changeset = await this.load(entry.name);\n if (changeset) {\n changesets.push(changeset);\n } else {\n // load() returned null - directory exists but no valid changeset\n console.warn(\n ansis.yellow(\n `\u26A0 Warning: Changeset directory '${entry.name}' exists but could not be loaded (may be corrupted)`\n )\n );\n skippedCount++;\n }\n } catch (loadError) {\n // load() threw an error - this is a corrupted changeset\n console.warn(\n ansis.yellow(\n `\u26A0 Warning: Failed to load changeset '${entry.name}': ${loadError instanceof Error ? loadError.message : String(loadError)}`\n )\n );\n skippedCount++;\n }\n }\n }\n\n // Show summary if any were skipped\n if (skippedCount > 0) {\n console.warn(\n ansis.yellow(`\\n\u26A0 ${skippedCount} changeset(s) could not be loaded and were skipped.`) +\n ansis.dim(`\\nRun 'dr changeset validate' to check for corruption and repair options.`)\n );\n }\n\n return changesets;\n } catch (error) {\n throw new Error(\n `Failed to list changesets: ${getErrorMessage(error)}`\n );\n }\n }\n\n /**\n * Delete a staged changeset\n */\n async delete(id: string): Promise<void> {\n const sanitizedId = this.sanitizeId(id);\n const changesetPath = path.join(this.changesetsDir, sanitizedId);\n\n if (!(await fileExists(changesetPath))) {\n // List available changesets to help user\n const available = await this.list();\n const availableIds = available.map((cs) => cs.id).join(\", \");\n\n throw new Error(\n `Changeset '${id}' not found.\\n` +\n `Available changesets: ${availableIds || \"(none)\"}\\n` +\n `Run 'dr changeset list' to see all changesets.`\n );\n }\n\n try {\n await rm(changesetPath, { recursive: true, force: true });\n } catch (error) {\n throw new Error(\n `Failed to delete changeset '${id}': ${getErrorMessage(error)}`\n );\n }\n }\n\n /**\n * Add or update a change in the changeset's changes.yaml\n *\n * Uses file locking to prevent concurrent modification race conditions\n * Assigns sequence number atomically inside the lock to prevent duplicates\n *\n * @returns The assigned sequence number for the change\n * @throws {Error} If lock acquisition, changeset load, save, or lock release fails\n */\n async addChange(id: string, change: Omit<StagedChange, \"sequenceNumber\">): Promise<number> {\n const changesetPath = this.getChangesetPath(id);\n const lock = new FileLock(changesetPath);\n\n try {\n return await lock.withLock(async () => {\n const changeset = await this.load(id);\n if (!changeset) {\n throw new Error(`Changeset '${id}' not found`);\n }\n\n // Assign sequence number atomically inside the lock\n // This prevents race conditions when multiple concurrent operations\n // try to add changes at the same time\n const sequenceNumber = changeset.changes.length;\n const stagedChange: StagedChange = {\n ...change,\n sequenceNumber,\n };\n\n changeset.changes.push(stagedChange);\n changeset.updateModified();\n\n await this.save(changeset);\n\n return sequenceNumber;\n });\n } catch (error) {\n const errorMessage = getErrorMessage(error);\n\n // Detect lock-specific errors\n if (errorMessage.includes(\"lock\") || errorMessage.includes(\"timeout\")) {\n const lockFilePath = path.join(changesetPath, \".lock\");\n\n throw new Error(\n `Failed to add change to changeset '${id}': Lock acquisition failed.\\n` +\n `Error: ${errorMessage}\\n\\n` +\n `This usually means another dr process is modifying this changeset.\\n` +\n `If no other processes are running, a stale lock file may exist.\\n\\n` +\n `To resolve:\\n` +\n ` 1. Wait for other operations to complete\\n` +\n ` 2. Check for running dr processes: ps aux | grep dr\\n` +\n ` 3. If no processes found, remove stale lock: rm \"${lockFilePath}\"\\n` +\n ` 4. Retry the operation`\n );\n }\n\n // Generic error with context\n throw new Error(\n `Failed to add change to changeset '${id}': ${errorMessage}\\n` +\n `Changeset path: ${changesetPath}`\n );\n }\n }\n\n /**\n * Remove a change by element ID\n *\n * Uses file locking to prevent concurrent modification race conditions\n *\n * @throws {Error} If lock acquisition, changeset load, save, or lock release fails\n */\n async removeChange(id: string, elementId: string): Promise<void> {\n const changesetPath = this.getChangesetPath(id);\n const lock = new FileLock(changesetPath);\n\n try {\n await lock.withLock(async () => {\n const changeset = await this.load(id);\n if (!changeset) {\n throw new Error(`Changeset '${id}' not found`);\n }\n\n // Filter out changes for this element\n const filtered = changeset.changes.filter((c) => c.elementId !== elementId);\n\n // Reconstruct changes with updated sequence numbers\n changeset.changes = filtered.map((c, idx) => ({\n ...c,\n sequenceNumber: idx,\n }));\n\n changeset.updateModified();\n\n await this.save(changeset);\n });\n } catch (error) {\n const errorMessage = getErrorMessage(error);\n\n // Detect lock-specific errors\n if (errorMessage.includes(\"lock\") || errorMessage.includes(\"timeout\")) {\n const lockFilePath = path.join(changesetPath, \".lock\");\n\n throw new Error(\n `Failed to remove change from changeset '${id}': Lock acquisition failed.\\n` +\n `Error: ${errorMessage}\\n\\n` +\n `This usually means another dr process is modifying this changeset.\\n` +\n `If no other processes are running, a stale lock file may exist.\\n\\n` +\n `To resolve:\\n` +\n ` 1. Wait for other operations to complete\\n` +\n ` 2. Check for running dr processes: ps aux | grep dr\\n` +\n ` 3. If no processes found, remove stale lock: rm \"${lockFilePath}\"\\n` +\n ` 4. Retry the operation`\n );\n }\n\n // Generic error with context\n throw new Error(\n `Failed to remove change from changeset '${id}': ${errorMessage}\\n` +\n `Changeset path: ${changesetPath}`\n );\n }\n }\n\n /**\n * Update an existing change in-place within the changeset's changes.yaml\n *\n * Atomically replaces the change for the given elementId under a single file lock,\n * preventing data loss if the process is interrupted between a remove and add.\n * The original sequence number is preserved so commit-ordering is unaffected.\n *\n * @throws {Error} If changeset not found, elementId not found in changes, or I/O error\n */\n async updateChange(\n id: string,\n elementId: string,\n updated: Omit<StagedChange, \"sequenceNumber\">\n ): Promise<void> {\n const changesetPath = this.getChangesetPath(id);\n const lock = new FileLock(changesetPath);\n\n try {\n await lock.withLock(async () => {\n const changeset = await this.load(id);\n if (!changeset) {\n throw new Error(`Changeset '${id}' not found`);\n }\n\n const index = changeset.changes.findIndex((c) => c.elementId === elementId);\n if (index === -1) {\n throw new Error(`No change for element '${elementId}' in changeset '${id}'`);\n }\n\n // Spread existing entry first so runtime fields like sequenceNumber and timestamp\n // are preserved. Then apply the updated fields. Since `updated` is\n // Omit<StagedChange, \"sequenceNumber\">, it will not overwrite sequenceNumber.\n const existing = changeset.changes[index] as any;\n changeset.changes[index] = {\n ...existing,\n ...updated,\n sequenceNumber: existing.sequenceNumber ?? index,\n } as any;\n\n changeset.updateModified();\n await this.save(changeset);\n });\n } catch (error) {\n const errorMessage = getErrorMessage(error);\n throw new Error(\n `Failed to update change in changeset '${id}': ${errorMessage}\\n` +\n `Changeset path: ${changesetPath}`\n );\n }\n }\n\n /**\n * Get the storage path for a changeset\n */\n getChangesetPath(id: string): string {\n const sanitizedId = this.sanitizeId(id);\n return path.join(this.changesetsDir, sanitizedId);\n }\n\n /**\n * Get the root changesets directory\n */\n getChangesetsDir(): string {\n return this.changesetsDir;\n }\n\n /**\n * Sanitize changeset ID for safe use in file paths\n * Prevents path traversal by removing special characters and path separators\n */\n private sanitizeId(id: string): string {\n const sanitized = id\n .replace(/\\.\\./g, \"\") // Remove .. (parent directory)\n .replace(/^\\/+/, \"\") // Remove leading slashes\n .replace(/^[A-Z]:\\\\/g, \"\") // Remove Windows drive letters\n .toLowerCase()\n .replace(/\\s+/g, \"-\") // Replace spaces with hyphens\n .replace(/[^a-z0-9-]/g, \"\"); // Keep only alphanumeric and hyphens\n\n if (!sanitized) {\n throw new Error(\n `Invalid changeset ID '${id}': results in empty string after sanitization. ` +\n `ID must contain at least one alphanumeric character.`\n );\n }\n\n return sanitized;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * StagedChangesetStorage - Handles YAML-based storage for staged changesets\n *\n * Manages the new storage structure:\n * documentation-robotics/changesets/{changeset-id}/\n * \u251C\u2500\u2500 metadata.yaml # Name, description, dates, status, base snapshot\n * \u2514\u2500\u2500 changes.yaml # Delta-only change records\n */\n\nimport { readFile, writeFile, readdir, rm } from \"fs/promises\";\nimport { fileExists, ensureDir } from \"../utils/file-io.js\";\nimport { FileLock } from \"../utils/file-lock.js\";\nimport path from \"path\";\nimport yaml from \"yaml\";\nimport ansis from \"ansis\";\nimport type { StagedChangesetData, StagedChange, UnstagedChange } from \"./changeset.js\";\nimport { Changeset, compactChange, nextSequenceNumber, sequenceOf, withSequenceNumbers } from \"./changeset.js\";\nimport { getErrorMessage } from \"../utils/errors.js\";\n\n/**\n * How many unstaged changes a changeset keeps as tombstones. Older ones are dropped (and\n * `unstagedPrunedThrough` records how far), so a delta reaching back past them says it is\n * approximate.\n */\nexport const MAX_UNSTAGED_TOMBSTONES = 200;\n\n/**\n * Reads the tombstone list from metadata.yaml, keeping the well-formed entries. Malformed entries\n * are reported, not silently dropped: the changeset still loads, and its changes are untouched.\n */\nfunction readTombstones(value: unknown, id: string): UnstagedChange[] | undefined {\n if (value === undefined || value === null) return undefined;\n if (!Array.isArray(value)) {\n console.warn(ansis.yellow(`\u26A0 Changeset '${id}': 'unstaged' in metadata.yaml is not a list; ignoring it`));\n return undefined;\n }\n const valid = value.filter(\n (t): t is UnstagedChange =>\n !!t &&\n typeof t === \"object\" &&\n typeof t.sequence === \"number\" &&\n typeof t.removedAt === \"number\" &&\n !!t.change &&\n typeof t.change === \"object\" &&\n typeof t.change.type === \"string\" &&\n typeof t.change.elementId === \"string\"\n );\n if (valid.length < value.length) {\n console.warn(\n ansis.yellow(\n `\u26A0 Changeset '${id}': ignoring ${value.length - valid.length} malformed 'unstaged' entr(ies) in metadata.yaml`\n )\n );\n }\n return valid;\n}\n\n/** Appends tombstones, keeping the newest MAX_UNSTAGED_TOMBSTONES and recording how far it trimmed. */\nfunction addTombstones(changeset: Changeset, tombstones: UnstagedChange[]): void {\n changeset.unstaged = [...changeset.unstaged, ...tombstones];\n if (changeset.unstaged.length > MAX_UNSTAGED_TOMBSTONES) {\n const dropped = changeset.unstaged.slice(0, changeset.unstaged.length - MAX_UNSTAGED_TOMBSTONES);\n changeset.unstaged = changeset.unstaged.slice(-MAX_UNSTAGED_TOMBSTONES);\n changeset.unstagedPrunedThrough = Math.max(changeset.unstagedPrunedThrough ?? -1, ...dropped.map((t) => t.removedAt));\n }\n}\n\n/**\n * Storage manager for staged changesets using YAML format\n */\nexport class StagedChangesetStorage {\n private changesetsDir: string;\n\n constructor(rootPath: string) {\n // Store changesets in documentation-robotics/changesets/ directory\n this.changesetsDir = path.join(rootPath, \"documentation-robotics\", \"changesets\");\n }\n\n /**\n * Create a new staged changeset directory structure\n */\n async create(\n id: string,\n name: string,\n description: string | undefined,\n baseSnapshot: string\n ): Promise<Changeset> {\n const now = new Date().toISOString();\n const sanitizedId = this.sanitizeId(id);\n const changesetPath = path.join(this.changesetsDir, sanitizedId);\n\n // Create directory structure\n await ensureDir(changesetPath);\n\n // Create metadata\n const metadata: Omit<StagedChangesetData, \"changes\"> = {\n id: sanitizedId,\n name,\n description,\n created: now,\n modified: now,\n status: \"staged\",\n baseSnapshot,\n stats: {\n additions: 0,\n modifications: 0,\n deletions: 0,\n },\n };\n\n // Write metadata.yaml\n await writeFile(path.join(changesetPath, \"metadata.yaml\"), yaml.stringify(metadata), \"utf-8\");\n\n // Write empty changes.yaml\n await writeFile(path.join(changesetPath, \"changes.yaml\"), yaml.stringify([]), \"utf-8\");\n\n // Return constructed changeset object\n return new Changeset({\n id: sanitizedId,\n name,\n description,\n created: now,\n modified: now,\n status: \"staged\",\n baseSnapshot,\n changes: [],\n stats: metadata.stats,\n });\n }\n\n /**\n * Load a staged changeset from YAML files\n */\n async load(id: string): Promise<Changeset | null> {\n const sanitizedId = this.sanitizeId(id);\n const changesetPath = path.join(this.changesetsDir, sanitizedId);\n const metadataPath = path.join(changesetPath, \"metadata.yaml\");\n const changesPath = path.join(changesetPath, \"changes.yaml\");\n\n // Check if changeset directory exists\n if (!(await fileExists(changesetPath))) {\n return null; // Changeset doesn't exist - this is OK\n }\n\n // Changeset directory exists - files SHOULD be present\n const metadataExists = await fileExists(metadataPath);\n const changesExists = await fileExists(changesPath);\n\n if (!metadataExists || !changesExists) {\n // Changeset directory exists but required files are missing - this is corruption\n throw new Error(\n `Changeset '${id}' is corrupted (missing ${!metadataExists ? \"metadata.yaml\" : \"changes.yaml\"}). ` +\n `Location: ${changesetPath}\\n` +\n `This may indicate disk failure or incomplete write. Check disk health and restore from backup if available.`\n );\n }\n\n try {\n // Load metadata\n const metadataContent = await readFile(metadataPath, \"utf-8\");\n const metadata = yaml.parse(metadataContent);\n\n // Load changes\n const changesContent = await readFile(changesPath, \"utf-8\");\n const changes = yaml.parse(changesContent) || [];\n\n // Construct changeset object\n const data: StagedChangesetData = {\n id: metadata.id,\n name: metadata.name,\n description: metadata.description,\n created: metadata.created,\n modified: metadata.modified,\n status: metadata.status,\n baseSnapshot: metadata.baseSnapshot,\n changes: changes as StagedChange[],\n stats: metadata.stats || { additions: 0, modifications: 0, deletions: 0 },\n ...(typeof metadata.lastSequence === \"number\" ? { lastSequence: metadata.lastSequence } : {}),\n ...(typeof metadata.unstagedPrunedThrough === \"number\"\n ? { unstagedPrunedThrough: metadata.unstagedPrunedThrough }\n : {}),\n };\n const unstaged = readTombstones(metadata.unstaged, id);\n if (unstaged) data.unstaged = unstaged;\n\n return Changeset.fromJSON(data);\n } catch (error) {\n // YAML parsing or file read error - provide detailed guidance\n throw new Error(\n `Failed to load changeset '${id}': YAML parsing failed.\\n` +\n `Error: ${getErrorMessage(error)}\\n` +\n `Location: ${metadataPath}\\n` +\n `This indicates file corruption. Try:\\n` +\n ` 1. Validate YAML syntax with: yamllint ${metadataPath}\\n` +\n ` 2. Restore from backup if available\\n` +\n ` 3. Manually edit to fix syntax errors`\n );\n }\n }\n\n /**\n * Save a staged changeset to YAML files\n */\n async save(changeset: Changeset): Promise<void> {\n if (!changeset.id) {\n throw new Error(\"Changeset must have an id to be saved in YAML format\");\n }\n\n const sanitizedId = this.sanitizeId(changeset.id);\n const changesetPath = path.join(this.changesetsDir, sanitizedId);\n await ensureDir(changesetPath);\n\n // A change staged in memory without a number gets the next unused one (never one an unstaged\n // change or unstage event took); lastSequence then covers it.\n const numbered = withSequenceNumbers(changeset.changes, nextSequenceNumber(changeset));\n const lastSequence =\n changeset.lastSequence !== undefined\n ? Math.max(changeset.lastSequence, ...numbered.map((c) => c.sequenceNumber))\n : undefined;\n\n // Prepare metadata (all fields except changes)\n const metadata = {\n id: changeset.id,\n name: changeset.name,\n description: changeset.description,\n created: changeset.created,\n modified: changeset.modified,\n status: changeset.status,\n baseSnapshot: changeset.baseSnapshot,\n stats: changeset.stats || { additions: 0, modifications: 0, deletions: 0 },\n // Sequence bookkeeping (see nextSequenceNumber); absent on changesets never unstaged from.\n ...(lastSequence !== undefined ? { lastSequence } : {}),\n ...(changeset.unstagedPrunedThrough !== undefined\n ? { unstagedPrunedThrough: changeset.unstagedPrunedThrough }\n : {}),\n ...(changeset.unstaged.length > 0 ? { unstaged: changeset.unstaged } : {}),\n };\n\n // Write metadata.yaml\n await writeFile(path.join(changesetPath, \"metadata.yaml\"), yaml.stringify(metadata), \"utf-8\");\n\n // Write changes.yaml\n await writeFile(\n path.join(changesetPath, \"changes.yaml\"),\n yaml.stringify(numbered),\n \"utf-8\"\n );\n }\n\n /**\n * List all staged changesets\n */\n async list(): Promise<Changeset[]> {\n if (!(await fileExists(this.changesetsDir))) {\n return [];\n }\n\n try {\n const entries = await readdir(this.changesetsDir, { withFileTypes: true });\n const changesets: Changeset[] = [];\n let skippedCount = 0;\n\n for (const entry of entries) {\n if (entry.isDirectory()) {\n try {\n const changeset = await this.load(entry.name);\n if (changeset) {\n changesets.push(changeset);\n } else {\n // load() returned null - directory exists but no valid changeset\n console.warn(\n ansis.yellow(\n `\u26A0 Warning: Changeset directory '${entry.name}' exists but could not be loaded (may be corrupted)`\n )\n );\n skippedCount++;\n }\n } catch (loadError) {\n // load() threw an error - this is a corrupted changeset\n console.warn(\n ansis.yellow(\n `\u26A0 Warning: Failed to load changeset '${entry.name}': ${loadError instanceof Error ? loadError.message : String(loadError)}`\n )\n );\n skippedCount++;\n }\n }\n }\n\n // Show summary if any were skipped\n if (skippedCount > 0) {\n console.warn(\n ansis.yellow(`\\n\u26A0 ${skippedCount} changeset(s) could not be loaded and were skipped.`) +\n ansis.dim(`\\nRun 'dr changeset validate' to check for corruption and repair options.`)\n );\n }\n\n return changesets;\n } catch (error) {\n throw new Error(\n `Failed to list changesets: ${getErrorMessage(error)}`\n );\n }\n }\n\n /**\n * Delete a staged changeset\n */\n async delete(id: string): Promise<void> {\n const sanitizedId = this.sanitizeId(id);\n const changesetPath = path.join(this.changesetsDir, sanitizedId);\n\n if (!(await fileExists(changesetPath))) {\n // List available changesets to help user\n const available = await this.list();\n const availableIds = available.map((cs) => cs.id).join(\", \");\n\n throw new Error(\n `Changeset '${id}' not found.\\n` +\n `Available changesets: ${availableIds || \"(none)\"}\\n` +\n `Run 'dr changeset list' to see all changesets.`\n );\n }\n\n try {\n await rm(changesetPath, { recursive: true, force: true });\n } catch (error) {\n throw new Error(\n `Failed to delete changeset '${id}': ${getErrorMessage(error)}`\n );\n }\n }\n\n /**\n * Add or update a change in the changeset's changes.yaml\n *\n * Uses file locking to prevent concurrent modification race conditions\n * Assigns sequence number atomically inside the lock to prevent duplicates\n *\n * @returns The assigned sequence number for the change\n * @throws {Error} If lock acquisition, changeset load, save, or lock release fails\n */\n async addChange(id: string, change: Omit<StagedChange, \"sequenceNumber\">): Promise<number> {\n const changesetPath = this.getChangesetPath(id);\n const lock = new FileLock(changesetPath);\n\n try {\n return await lock.withLock(async () => {\n const changeset = await this.load(id);\n if (!changeset) {\n throw new Error(`Changeset '${id}' not found`);\n }\n\n // Assign sequence number atomically inside the lock\n // This prevents race conditions when multiple concurrent operations\n // try to add changes at the same time. One past every number used so far (unstaged\n // changes and unstage events included), so a number is never reused and a revision\n // always names the same prefix of the plan.\n const sequenceNumber = nextSequenceNumber(changeset);\n const stagedChange: StagedChange = {\n ...change,\n sequenceNumber,\n };\n\n changeset.changes = withSequenceNumbers(changeset.changes);\n changeset.changes.push(stagedChange);\n changeset.lastSequence = sequenceNumber;\n changeset.updateModified();\n\n await this.save(changeset);\n\n return sequenceNumber;\n });\n } catch (error) {\n const errorMessage = getErrorMessage(error);\n\n // Detect lock-specific errors\n if (errorMessage.includes(\"lock\") || errorMessage.includes(\"timeout\")) {\n const lockFilePath = path.join(changesetPath, \".lock\");\n\n throw new Error(\n `Failed to add change to changeset '${id}': Lock acquisition failed.\\n` +\n `Error: ${errorMessage}\\n\\n` +\n `This usually means another dr process is modifying this changeset.\\n` +\n `If no other processes are running, a stale lock file may exist.\\n\\n` +\n `To resolve:\\n` +\n ` 1. Wait for other operations to complete\\n` +\n ` 2. Check for running dr processes: ps aux | grep dr\\n` +\n ` 3. If no processes found, remove stale lock: rm \"${lockFilePath}\"\\n` +\n ` 4. Retry the operation`\n );\n }\n\n // Generic error with context\n throw new Error(\n `Failed to add change to changeset '${id}': ${errorMessage}\\n` +\n `Changeset path: ${changesetPath}`\n );\n }\n }\n\n /**\n * Add several changes, and rewrite some staged ones in place, in one write: one load and one save\n * instead of one per change. Additions take the next sequence numbers in order; a rewrite keeps\n * its change's number and leaves a tombstone, as {@link updateChange} does.\n *\n * @returns The sequence numbers assigned to the additions, in order\n */\n async addChanges(\n id: string,\n changes: Array<Omit<StagedChange, \"sequenceNumber\">>,\n rewrites: Array<{ elementId: string; change: Omit<StagedChange, \"sequenceNumber\"> }> = []\n ): Promise<number[]> {\n if (changes.length === 0 && rewrites.length === 0) return [];\n const changesetPath = this.getChangesetPath(id);\n const lock = new FileLock(changesetPath);\n try {\n return await lock.withLock(async () => {\n const changeset = await this.load(id);\n if (!changeset) {\n throw new Error(`Changeset '${id}' not found`);\n }\n changeset.changes = withSequenceNumbers(changeset.changes, nextSequenceNumber(changeset));\n for (const { elementId, change } of rewrites) {\n const index = changeset.changes.findIndex((c) => c.elementId === elementId);\n if (index === -1) {\n throw new Error(`No change for element '${elementId}' in changeset '${id}'`);\n }\n const existing = changeset.changes[index] as StagedChange;\n const rewrittenAt = nextSequenceNumber(changeset);\n changeset.lastSequence = rewrittenAt;\n addTombstones(changeset, [\n { sequence: existing.sequenceNumber, removedAt: rewrittenAt, change: compactChange(existing), rewritten: true },\n ]);\n changeset.changes[index] = { ...existing, ...change, sequenceNumber: existing.sequenceNumber } as StagedChange;\n }\n const numbers: number[] = [];\n for (const change of changes) {\n const sequenceNumber = nextSequenceNumber(changeset);\n const staged: StagedChange = { ...change, sequenceNumber };\n changeset.changes.push(staged);\n changeset.lastSequence = sequenceNumber;\n numbers.push(sequenceNumber);\n }\n changeset.updateModified();\n await this.save(changeset);\n return numbers;\n });\n } catch (error) {\n const errorMessage = getErrorMessage(error);\n if (errorMessage.includes(\"lock\") || errorMessage.includes(\"timeout\")) {\n const lockFilePath = path.join(changesetPath, \".lock\");\n throw new Error(\n `Failed to add changes to changeset '${id}': Lock acquisition failed.\\n` +\n `Error: ${errorMessage}\\n\\n` +\n `This usually means another dr process is modifying this changeset.\\n` +\n `If no other processes are running, a stale lock file may exist.\\n\\n` +\n `To resolve:\\n` +\n ` 1. Wait for other operations to complete\\n` +\n ` 2. Check for running dr processes: ps aux | grep dr\\n` +\n ` 3. If no processes found, remove stale lock: rm \"${lockFilePath}\"\\n` +\n ` 4. Retry the operation`\n );\n }\n throw new Error(`Failed to add changes to changeset '${id}': ${errorMessage}\\n` + `Changeset path: ${changesetPath}`);\n }\n }\n\n /**\n * Remove a change by element ID\n *\n * Uses file locking to prevent concurrent modification race conditions\n *\n * @throws {Error} If lock acquisition, changeset load, save, or lock release fails\n */\n async removeChange(id: string, elementId: string): Promise<void> {\n await this.removeChanges(id, [elementId]);\n }\n\n /**\n * Remove every change to any of `elementIds` in one write. The removal is one revision: it takes\n * one sequence number, and each removed change is kept as a tombstone.\n *\n * Uses file locking to prevent concurrent modification race conditions\n *\n * @throws {Error} If lock acquisition, changeset load, save, or lock release fails\n */\n async removeChanges(id: string, elementIds: string[]): Promise<void> {\n const keys = new Set(elementIds);\n const changesetPath = this.getChangesetPath(id);\n const lock = new FileLock(changesetPath);\n\n try {\n await lock.withLock(async () => {\n const changeset = await this.load(id);\n if (!changeset) {\n throw new Error(`Changeset '${id}' not found`);\n }\n\n // Number any change that carries none by its position first, so the ones that stay keep\n // the numbers they had. Remaining changes are not renumbered.\n const numbered = withSequenceNumbers(changeset.changes);\n const removed = numbered.filter((c) => keys.has(c.elementId));\n changeset.changes = numbered.filter((c) => !keys.has(c.elementId));\n\n if (removed.length > 0) {\n // The unstage is a revision of its own: it takes the next sequence number, and each\n // removed change is kept as a tombstone so the plan before it can be rebuilt.\n const removedAt = nextSequenceNumber({ ...changeset, changes: numbered });\n changeset.lastSequence = removedAt;\n addTombstones(\n changeset,\n removed.map((c, index) => ({\n sequence: sequenceOf(c, index),\n removedAt,\n change: compactChange(c),\n }))\n );\n }\n\n changeset.updateModified();\n\n await this.save(changeset);\n });\n } catch (error) {\n const errorMessage = getErrorMessage(error);\n\n // Detect lock-specific errors\n if (errorMessage.includes(\"lock\") || errorMessage.includes(\"timeout\")) {\n const lockFilePath = path.join(changesetPath, \".lock\");\n\n throw new Error(\n `Failed to remove change from changeset '${id}': Lock acquisition failed.\\n` +\n `Error: ${errorMessage}\\n\\n` +\n `This usually means another dr process is modifying this changeset.\\n` +\n `If no other processes are running, a stale lock file may exist.\\n\\n` +\n `To resolve:\\n` +\n ` 1. Wait for other operations to complete\\n` +\n ` 2. Check for running dr processes: ps aux | grep dr\\n` +\n ` 3. If no processes found, remove stale lock: rm \"${lockFilePath}\"\\n` +\n ` 4. Retry the operation`\n );\n }\n\n // Generic error with context\n throw new Error(\n `Failed to remove change from changeset '${id}': ${errorMessage}\\n` +\n `Changeset path: ${changesetPath}`\n );\n }\n }\n\n /**\n * Update an existing change in-place within the changeset's changes.yaml\n *\n * Atomically replaces the change for the given elementId under a single file lock,\n * preventing data loss if the process is interrupted between a remove and add.\n * The original sequence number is preserved so commit-ordering is unaffected.\n *\n * @throws {Error} If changeset not found, elementId not found in changes, or I/O error\n */\n async updateChange(\n id: string,\n elementId: string,\n updated: Omit<StagedChange, \"sequenceNumber\">\n ): Promise<void> {\n const changesetPath = this.getChangesetPath(id);\n const lock = new FileLock(changesetPath);\n\n try {\n await lock.withLock(async () => {\n const changeset = await this.load(id);\n if (!changeset) {\n throw new Error(`Changeset '${id}' not found`);\n }\n\n const index = changeset.changes.findIndex((c) => c.elementId === elementId);\n if (index === -1) {\n throw new Error(`No change for element '${elementId}' in changeset '${id}'`);\n }\n\n // Number every change first (an older changes.yaml may carry none), so the list is never\n // left half numbered. Spread the existing entry so runtime fields like sequenceNumber and\n // timestamp are preserved; `updated` omits sequenceNumber and cannot overwrite it.\n changeset.changes = withSequenceNumbers(changeset.changes, nextSequenceNumber(changeset));\n const existing = changeset.changes[index] as StagedChange;\n // The rewrite is a revision of its own, like an unstage: it takes the next sequence number,\n // and the version the change had is kept as a tombstone so the plan before it can be rebuilt.\n const rewrittenAt = nextSequenceNumber(changeset);\n changeset.lastSequence = rewrittenAt;\n addTombstones(changeset, [\n { sequence: existing.sequenceNumber, removedAt: rewrittenAt, change: compactChange(existing), rewritten: true },\n ]);\n changeset.changes[index] = {\n ...existing,\n ...updated,\n sequenceNumber: existing.sequenceNumber,\n } as StagedChange;\n\n changeset.updateModified();\n await this.save(changeset);\n });\n } catch (error) {\n const errorMessage = getErrorMessage(error);\n throw new Error(\n `Failed to update change in changeset '${id}': ${errorMessage}\\n` +\n `Changeset path: ${changesetPath}`\n );\n }\n }\n\n /**\n * Change a changeset's name and/or description in place, under the changeset's file lock so a\n * concurrent stage cannot be lost. Staged changes are untouched.\n *\n * @returns The updated changeset\n * @throws {Error} If the changeset is not found, or on lock or I/O failure\n */\n async updateMetadata(\n id: string,\n patch: { name?: string; description?: string }\n ): Promise<Changeset> {\n const lock = new FileLock(this.getChangesetPath(id));\n return lock.withLock(async () => {\n const changeset = await this.load(id);\n if (!changeset) {\n throw new Error(`Changeset '${id}' not found`);\n }\n if (patch.name !== undefined) changeset.name = patch.name;\n if (patch.description !== undefined) changeset.description = patch.description || undefined;\n changeset.updateModified();\n await this.save(changeset);\n return changeset;\n });\n }\n\n /**\n * Get the storage path for a changeset\n */\n /**\n * Remove every file in the changeset's directory except metadata.yaml and changes.yaml (and\n * dotfiles such as a lock): e.g. the base index, once the changeset is discarded and nothing\n * reads it again.\n *\n * @returns The names of the files removed\n */\n async removeSideFiles(id: string): Promise<string[]> {\n const changesetPath = this.getChangesetPath(id);\n if (!(await fileExists(changesetPath))) return [];\n const keep = new Set([\"metadata.yaml\", \"changes.yaml\"]);\n const removed: string[] = [];\n for (const entry of await readdir(changesetPath, { withFileTypes: true })) {\n if (keep.has(entry.name) || entry.name.startsWith(\".\") || !entry.isFile()) continue;\n await rm(path.join(changesetPath, entry.name), { force: true });\n removed.push(entry.name);\n }\n return removed;\n }\n\n getChangesetPath(id: string): string {\n const sanitizedId = this.sanitizeId(id);\n return path.join(this.changesetsDir, sanitizedId);\n }\n\n /**\n * Get the root changesets directory\n */\n getChangesetsDir(): string {\n return this.changesetsDir;\n }\n\n /**\n * Sanitize changeset ID for safe use in file paths\n * Prevents path traversal by removing special characters and path separators\n */\n private sanitizeId(id: string): string {\n const sanitized = id\n .replace(/\\.\\./g, \"\") // Remove .. (parent directory)\n .replace(/^\\/+/, \"\") // Remove leading slashes\n .replace(/^[A-Z]:\\\\/g, \"\") // Remove Windows drive letters\n .toLowerCase()\n .replace(/\\s+/g, \"-\") // Replace spaces with hyphens\n .replace(/[^a-z0-9-]/g, \"\"); // Keep only alphanumeric and hyphens\n\n if (!sanitized) {\n throw new Error(\n `Invalid changeset ID '${id}': results in empty string after sanitization. ` +\n `ID must contain at least one alphanumeric character.`\n );\n }\n\n return sanitized;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,28 @@
1
+ const LEGACY_KEY = "sourceReference", KEY = "source_reference";
2
+ function setStagedSourceReference(snapshot, ref) {
3
+ delete snapshot[LEGACY_KEY], snapshot[KEY] = ref ?? null;
4
+ }
5
+ function readStagedSourceReference(snapshot) {
6
+ if (!snapshot)
7
+ return { present: !1, value: void 0 };
8
+ for (const key of [LEGACY_KEY, KEY])
9
+ if (Object.prototype.hasOwnProperty.call(snapshot, key)) {
10
+ const raw = snapshot[key];
11
+ return { present: !0, value: raw && typeof raw == "object" ? raw : void 0 };
12
+ }
13
+ return { present: !1, value: void 0 };
14
+ }
15
+ function stagedSourceReference(snapshot) {
16
+ return readStagedSourceReference(snapshot).value;
17
+ }
18
+ function canonicalStagedSnapshot(snapshot) {
19
+ const sourceReference = readStagedSourceReference(snapshot), data = { ...snapshot };
20
+ return delete data[LEGACY_KEY], delete data[KEY], sourceReference.value && (data[KEY] = sourceReference.value), { data, sourceReference };
21
+ }
22
+ export {
23
+ canonicalStagedSnapshot,
24
+ readStagedSourceReference,
25
+ setStagedSourceReference,
26
+ stagedSourceReference
27
+ };
28
+ //# sourceMappingURL=staged-source-reference.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/core/staged-source-reference.ts"],
4
+ "sourcesContent": ["/**\n * Source references inside staged element snapshots.\n *\n * A staged change's `before`/`after` is an element snapshot in the on-disk shape\n * (`Element.toJSON()`), so its source reference lives under the snake_case key\n * `source_reference`. A cleared reference is written as `source_reference: null`:\n * an update's `after` starts as a copy of `before`, so leaving the key out would\n * keep the old reference instead of clearing it.\n *\n * Earlier builds of `dr update` and the MCP `model_update` tool wrote the new\n * reference under a camelCase `sourceReference` key instead, next to the stale\n * snake_case copy. Those changes.yaml files exist, so readers go through\n * {@link readStagedSourceReference}, which honours the camelCase key when present.\n */\nimport type { SourceReference } from \"../types/source-reference.js\";\n\nconst LEGACY_KEY = \"sourceReference\";\nconst KEY = \"source_reference\";\n\n/**\n * Record a source-reference change on a staged element snapshot.\n * `undefined` clears the reference.\n */\nexport function setStagedSourceReference(\n snapshot: Record<string, unknown>,\n ref: SourceReference | undefined\n): void {\n delete snapshot[LEGACY_KEY];\n snapshot[KEY] = ref ?? null;\n}\n\n/**\n * Read a staged snapshot's source reference.\n *\n * `present` is false when the snapshot says nothing about the reference (neither\n * key is set), so a caller can keep whatever the element already has; when it is\n * true, `value` is the reference to use, `undefined` meaning cleared.\n */\nexport function readStagedSourceReference(\n snapshot: Record<string, unknown> | undefined | null\n): { present: boolean; value: SourceReference | undefined } {\n if (!snapshot) return { present: false, value: undefined };\n // The legacy camelCase key carries the intended new value; its snake_case\n // neighbour in such files is the stale copy of `before`.\n for (const key of [LEGACY_KEY, KEY]) {\n if (Object.prototype.hasOwnProperty.call(snapshot, key)) {\n const raw = snapshot[key];\n const value = raw && typeof raw === \"object\" ? (raw as SourceReference) : undefined;\n return { present: true, value };\n }\n }\n return { present: false, value: undefined };\n}\n\n/** The snapshot's source reference, or undefined when it has none or it is cleared. */\nexport function stagedSourceReference(\n snapshot: Record<string, unknown> | undefined | null\n): SourceReference | undefined {\n return readStagedSourceReference(snapshot).value;\n}\n\n/**\n * A copy of the snapshot in the canonical shape: the legacy camelCase key folded\n * into `source_reference`, and a cleared reference left out. Used where a\n * snapshot becomes an element (commit), so no stray keys reach the model.\n */\nexport function canonicalStagedSnapshot(snapshot: Record<string, unknown>): {\n data: Record<string, unknown>;\n sourceReference: { present: boolean; value: SourceReference | undefined };\n} {\n const sourceReference = readStagedSourceReference(snapshot);\n const data = { ...snapshot };\n delete data[LEGACY_KEY];\n delete data[KEY];\n if (sourceReference.value) data[KEY] = sourceReference.value;\n return { data, sourceReference };\n}\n"],
5
+ "mappings": "AAgBA,MAAM,aAAa,mBACb,MAAM;AAMN,SAAU,yBACd,UACA,KAAgC;AAEhC,SAAO,SAAS,UAAU,GAC1B,SAAS,GAAG,IAAI,OAAO;AACzB;AASM,SAAU,0BACd,UAAoD;AAEpD,MAAI,CAAC;AAAU,WAAO,EAAE,SAAS,IAAO,OAAO,OAAS;AAGxD,aAAW,OAAO,CAAC,YAAY,GAAG;AAChC,QAAI,OAAO,UAAU,eAAe,KAAK,UAAU,GAAG,GAAG;AACvD,YAAM,MAAM,SAAS,GAAG;AAExB,aAAO,EAAE,SAAS,IAAM,OADV,OAAO,OAAO,OAAQ,WAAY,MAA0B,OAC7C;IAC/B;AAEF,SAAO,EAAE,SAAS,IAAO,OAAO,OAAS;AAC3C;AAGM,SAAU,sBACd,UAAoD;AAEpD,SAAO,0BAA0B,QAAQ,EAAE;AAC7C;AAOM,SAAU,wBAAwB,UAAiC;AAIvE,QAAM,kBAAkB,0BAA0B,QAAQ,GACpD,OAAO,EAAE,GAAG,SAAQ;AAC1B,gBAAO,KAAK,UAAU,GACtB,OAAO,KAAK,GAAG,GACX,gBAAgB,UAAO,KAAK,GAAG,IAAI,gBAAgB,QAChD,EAAE,MAAM,gBAAe;AAChC;",
6
+ "names": []
7
+ }
@@ -2,11 +2,14 @@ import path from "path";
2
2
  import ansis from "ansis";
3
3
  import { StagedChangesetStorage } from "./staged-changeset-storage.js";
4
4
  import { BaseSnapshotManager } from "./base-snapshot-manager.js";
5
+ import { withSequenceNumbers } from "./changeset.js";
5
6
  import { ChangesetValidator } from "./changeset-validator.js";
6
7
  import { fileExists } from "../utils/file-io.js";
7
8
  import { createSha256Hash } from "../types/index.js";
8
9
  import { getErrorMessage } from "../utils/errors.js";
9
10
  import { Layer } from "./layer.js";
11
+ import { canonicalStagedSnapshot } from "./staged-source-reference.js";
12
+ import { elementIdentifiers, Relationships } from "./relationships.js";
10
13
  import { isValidLayerName } from "./layers.js";
11
14
  import { isTelemetryEnabled, startSpan, startActiveSpan, endSpan, emitLog, SeverityNumber } from "../telemetry/index.js";
12
15
  import { ModelReportOrchestrator } from "../reports/model-report-orchestrator.js";
@@ -49,7 +52,7 @@ class StagingAreaManager {
49
52
  * @returns VirtualProjectionEngine instance for computing merged model views
50
53
  */
51
54
  async getProjectionEngine() {
52
- if (!this.projectionEngine) {
55
+ if (!this.projectionEngine && typeof this.model?.getVirtualProjectionEngine == "function" && (this.projectionEngine = this.model.getVirtualProjectionEngine()), !this.projectionEngine) {
53
56
  const { VirtualProjectionEngine } = await import("./virtual-projection.js");
54
57
  this.projectionEngine = new VirtualProjectionEngine(this.rootPath);
55
58
  }
@@ -62,7 +65,14 @@ class StagingAreaManager {
62
65
  if (!this.model)
63
66
  throw new Error("Model required for creating changesets");
64
67
  const id = this.generateChangesetId(), baseSnapshotId = await this.snapshotManager.captureSnapshot(this.model), changeset = await this.storage.create(id, name, description, baseSnapshotId);
65
- return changeset.id = id, changeset.markStaged(), await this.storage.save(changeset), changeset;
68
+ changeset.id = id, changeset.markStaged(), await this.storage.save(changeset);
69
+ try {
70
+ const { captureBaseIndex, writeBaseIndex } = await import("./changeset-base-index.js");
71
+ await writeBaseIndex(this.rootPath, changeset.id, await captureBaseIndex(this.model));
72
+ } catch (error) {
73
+ console.warn(`Warning: could not record the base index for changeset '${name}': ${getErrorMessage(error)}. Verify and brief will fall back to what they can tell without it.`);
74
+ }
75
+ return changeset;
66
76
  }
67
77
  /**
68
78
  * Load a changeset by name or ID
@@ -91,6 +101,30 @@ class StagingAreaManager {
91
101
  const activeChangeset = await this.getActive();
92
102
  activeChangeset && activeChangeset.id === changeset.id && await this.clearActive(), await this.storage.delete(changeset.id);
93
103
  }
104
+ /**
105
+ * Rename a changeset and/or change its description (its stated intent). Staged changes,
106
+ * status and the active marker are untouched. Names stay unique: renaming to a name another
107
+ * changeset already has is refused.
108
+ *
109
+ * @param nameOrId - Changeset ID or current name
110
+ * @param patch - Fields to change; an empty description clears it
111
+ * @throws Error if the changeset is not found, or the new name is taken
112
+ */
113
+ async update(nameOrId, patch) {
114
+ const changeset = await this.load(nameOrId);
115
+ if (!changeset || !changeset.id)
116
+ throw new Error(`Changeset '${nameOrId}' not found`);
117
+ if (patch.name !== void 0) {
118
+ const name = patch.name.trim();
119
+ if (!name)
120
+ throw new Error("Changeset name cannot be empty");
121
+ const clash = (await this.storage.list()).find((cs) => cs.name === name && cs.id !== changeset.id);
122
+ if (clash)
123
+ throw new Error(`Another changeset (${clash.id}) is already named '${name}'`);
124
+ patch = { ...patch, name };
125
+ }
126
+ return this.storage.updateMetadata(changeset.id, patch);
127
+ }
94
128
  /**
95
129
  * Stage a change in the changeset.
96
130
  * Records the change in the staging area without applying to the base model.
@@ -111,7 +145,48 @@ class StagingAreaManager {
111
145
  ...change,
112
146
  timestamp: change.timestamp || (/* @__PURE__ */ new Date()).toISOString()
113
147
  };
114
- await this.storage.addChange(changesetId, stagedChange), (await this.getProjectionEngine()).invalidateOnStage(changesetId, change.layerName);
148
+ await this.storage.addChange(changesetId, stagedChange), !change.type.startsWith("relationship-") && typeof this.model?.noteStagedLayer == "function" && this.model.noteStagedLayer(changesetId, change.layerName), (await this.getProjectionEngine()).invalidateOnStage(changesetId, change.layerName);
149
+ }
150
+ /**
151
+ * Stage several changes in one write, in order (see {@link stage}), and rewrite staged ones in
152
+ * place (see {@link restage}) in the same write. The changeset must be in 'staged' status.
153
+ */
154
+ async stageMany(changesetId, changes, rewrites = []) {
155
+ if (changes.length === 0 && rewrites.length === 0)
156
+ return;
157
+ this.validateChangesetId(changesetId);
158
+ const changeset = await this.load(changesetId);
159
+ if (!changeset)
160
+ throw new Error(`Changeset '${changesetId}' not found`);
161
+ if (changeset.status !== "staged")
162
+ throw new Error(`Cannot stage changes on changeset with status '${changeset.status}'. Changeset must have status 'staged' to accept new changes.`);
163
+ const now = (/* @__PURE__ */ new Date()).toISOString();
164
+ await this.storage.addChanges(changesetId, changes.map((change) => ({ ...change, timestamp: change.timestamp || now })), rewrites.map((r) => ({ elementId: r.elementId, change: { ...r.change, timestamp: r.change.timestamp || now } })));
165
+ const engine = await this.getProjectionEngine();
166
+ for (const layerName of new Set([...changes, ...rewrites.map((r) => r.change)].map((c) => c.layerName)))
167
+ engine.invalidateOnStage(changesetId, layerName);
168
+ if (typeof this.model?.noteStagedLayer == "function")
169
+ for (const change of changes)
170
+ change.type.startsWith("relationship-") || this.model.noteStagedLayer(changesetId, change.layerName);
171
+ }
172
+ /**
173
+ * Rewrite a staged change in place, keeping its sequence number: e.g. to fold an update of an
174
+ * element into the changeset's add of it. The changeset must be in 'staged' status.
175
+ *
176
+ * @param elementId - The key (`elementId`) of the change to rewrite
177
+ * @throws Error if the changeset is not found, not staged, or has no change with that key
178
+ */
179
+ async restage(changesetId, elementId, change) {
180
+ this.validateChangesetId(changesetId);
181
+ const changeset = await this.load(changesetId);
182
+ if (!changeset)
183
+ throw new Error(`Changeset '${changesetId}' not found`);
184
+ if (changeset.status !== "staged")
185
+ throw new Error(`Cannot change staged changes on changeset with status '${changeset.status}'. Changeset must have status 'staged'.`);
186
+ await this.storage.updateChange(changesetId, elementId, {
187
+ ...change,
188
+ timestamp: change.timestamp || (/* @__PURE__ */ new Date()).toISOString()
189
+ }), (await this.getProjectionEngine()).invalidateOnStage(changesetId, change.layerName);
115
190
  }
116
191
  /**
117
192
  * Unstage a specific element from the changeset.
@@ -131,6 +206,27 @@ class StagingAreaManager {
131
206
  throw new Error(`Cannot unstage changes on changeset with status '${changeset.status}'. Changeset must have status 'staged'.`);
132
207
  await this.storage.removeChange(changesetId, elementId), await (await this.getProjectionEngine()).invalidateOnUnstage(changesetId, elementId);
133
208
  }
209
+ /**
210
+ * Unstage several elements' changes in one write (one revision), e.g. a batch unstage.
211
+ * The changeset must be in 'staged' status.
212
+ *
213
+ * @param changesetId - ID of the changeset to unstage from
214
+ * @param elementIds - change keys (element IDs, or relationship change keys) to remove
215
+ * @throws Error if changeset ID is invalid, changeset not found, or not in 'staged' status
216
+ */
217
+ async unstageMany(changesetId, elementIds) {
218
+ if (elementIds.length === 0)
219
+ return;
220
+ if (elementIds.length === 1)
221
+ return this.unstage(changesetId, elementIds[0]);
222
+ this.validateChangesetId(changesetId);
223
+ const changeset = await this.load(changesetId);
224
+ if (!changeset)
225
+ throw new Error(`Changeset '${changesetId}' not found`);
226
+ if (changeset.status !== "staged")
227
+ throw new Error(`Cannot unstage changes on changeset with status '${changeset.status}'. Changeset must have status 'staged'.`);
228
+ await this.storage.removeChanges(changesetId, elementIds), await (await this.getProjectionEngine()).invalidateOnUnstage(changesetId);
229
+ }
134
230
  /**
135
231
  * Discard all changes in the changeset.
136
232
  * Clears all staged changes and marks the changeset as 'discarded'.
@@ -144,7 +240,7 @@ class StagingAreaManager {
144
240
  const changeset = await this.load(changesetId);
145
241
  if (!changeset)
146
242
  throw new Error(`Changeset '${changesetId}' not found`);
147
- changeset.changes = [], changeset.status = "discarded", changeset.updateModified(), await this.storage.save(changeset), (await this.getProjectionEngine()).invalidateOnDiscard(changesetId);
243
+ changeset.changes = [], changeset.unstaged = [], changeset.status = "discarded", changeset.updateModified(), await this.storage.save(changeset), await this.storage.removeSideFiles(changesetId), (await this.getProjectionEngine()).invalidateOnDiscard(changesetId);
148
244
  }
149
245
  /**
150
246
  * Capture a base snapshot of the current model state for drift detection.
@@ -251,21 +347,32 @@ class StagingAreaManager {
251
347
  failed: 0,
252
348
  validation: {
253
349
  passed: !0,
254
- errors: []
350
+ errors: [],
351
+ ran: shouldValidate
255
352
  }
256
353
  };
257
354
  if (shouldValidate && await startActiveSpan("staging.validate", async (validationSpan) => {
258
- const validation = await this.validator.validateChangeset(model, changesetId);
259
- if (validationSpan.setAttribute("validation.passed", validation.isValid()), validationSpan.setAttribute("validation.errorCount", validation.errors.length), validationSpan.setStatus({ code: 0 }), !validation.isValid())
260
- throw result.validation.passed = !1, result.validation.errors = validation.errors.map((e) => e.message), emitLog(SeverityNumber.ERROR, "Validation failed", {
355
+ const report = await this.validator.validateChangesetReport(model, changesetId), validation = report.result;
356
+ if (result.validation.preExistingErrors = report.preExistingErrors.length, report.preExistingErrors.length > 0 && (result.validation.preExistingSample = report.preExistingErrors.slice(0, 10).map((e) => e.message), emitLog(SeverityNumber.WARN, "Pre-existing validation errors not counted against the changeset", {
357
+ "validation.preExistingErrorCount": report.preExistingErrors.length
358
+ })), report.baseValidationFailure && (result.validation.baseValidationFailure = report.baseValidationFailure, emitLog(SeverityNumber.WARN, "Base model could not be validated; every error counts", {
359
+ "validation.baseValidationFailure": report.baseValidationFailure
360
+ })), validationSpan.setAttribute("validation.passed", validation.isValid()), validationSpan.setAttribute("validation.errorCount", validation.errors.length), validationSpan.setAttribute("validation.preExistingErrorCount", report.preExistingErrors.length), validationSpan.setStatus({ code: 0 }), !validation.isValid()) {
361
+ result.validation.passed = !1, result.validation.errors = validation.errors.map((e) => e.message), emitLog(SeverityNumber.ERROR, "Validation failed", {
261
362
  "validation.errorCount": result.validation.errors.length,
262
363
  "validation.errors": result.validation.errors.slice(0, 10)
263
364
  // First 10 errors
264
- }), new Error(`Validation failed with ${result.validation.errors.length} error(s):
365
+ });
366
+ const notes = [];
367
+ throw report.preExistingErrors.length > 0 && notes.push(`${report.preExistingErrors.length} pre-existing error(s) in the base model were not counted against this changeset.`), report.baseValidationFailure && notes.push(`The base model could not be validated (${report.baseValidationFailure}), so every error was counted.`), new Error(`Validation failed with ${result.validation.errors.length} error(s) introduced by this changeset:
265
368
  ` + result.validation.errors.map((e) => ` - ${e}`).join(`
266
- `) + `
369
+ `) + (notes.length > 0 ? `
267
370
 
268
- Fix these validation errors before committing.`);
371
+ ${notes.join(`
372
+ `)}` : "") + `
373
+
374
+ Fix these validation errors before committing, or skip validation with --no-validate.`);
375
+ }
269
376
  }), isDryRun)
270
377
  return result.committed = changeset.changes.length, drift.isDrifted && (result.driftWarning = drift), result;
271
378
  let backup;
@@ -274,7 +381,18 @@ Fix these validation errors before committing.`);
274
381
  } catch (backupError) {
275
382
  throw new Error(`Failed to create backup before commit. No changes have been applied. ${backupError instanceof Error ? backupError.message : String(backupError)}`);
276
383
  }
277
- const sortedChanges = [...changeset.changes].sort((a, b) => a.sequenceNumber - b.sequenceNumber);
384
+ const sortedChanges = withSequenceNumbers(changeset.changes).sort((a, b) => a.sequenceNumber - b.sequenceNumber);
385
+ let relationshipsPurged = !1;
386
+ model.getActiveChangesetId() !== null && (model.relationships = new Relationships(model.getBaseRelationships()));
387
+ let preCommitReportLayers = /* @__PURE__ */ new Set();
388
+ try {
389
+ preCommitReportLayers = reportLayersForChanges(new ModelReportOrchestrator(model, model.rootPath), model, sortedChanges);
390
+ } catch (error) {
391
+ emitLog(SeverityNumber.WARN, "Could not read report layers before changeset commit", {
392
+ "changeset.id": changesetId,
393
+ "error.message": getErrorMessage(error)
394
+ });
395
+ }
278
396
  try {
279
397
  const applySpan = isTelemetryEnabled ? startSpan("staging.apply", {
280
398
  "apply.changeCount": changeset.changes.length
@@ -309,19 +427,19 @@ Fix these validation errors before committing.`);
309
427
  "change.layer": change.layerName
310
428
  });
311
429
  else {
312
- const element = new Element(elementData);
430
+ const element = new Element(canonicalStagedSnapshot(elementData).data);
313
431
  layer.addElement(element), result.committed++;
314
432
  }
315
433
  } else if (change.type === "update" && change.after) {
316
434
  const element = layer.getElement(change.elementId);
317
435
  if (element) {
318
- const elementData = change.after;
319
- Object.assign(element, elementData), layer.updateElement(element), result.committed++;
436
+ const { data: elementData, sourceReference } = canonicalStagedSnapshot(change.after);
437
+ Object.assign(element, elementData), sourceReference.present && element.setSourceReference(sourceReference.value), layer.updateElement(element), result.committed++;
320
438
  } else
321
439
  result.failed++, result.validation.passed = !1, result.validation.errors.push(`Cannot update '${change.elementId}' in '${change.layerName}': element not found in base model`);
322
440
  } else if (change.type === "delete") {
323
- const element = layer.getElement(change.elementId);
324
- element ? (layer.deleteElement(element.path || element.id), result.committed++) : (result.skipped = (result.skipped ?? 0) + 1, result.skippedDetails = result.skippedDetails ?? [], result.skippedDetails.push(`'${change.elementId}' in '${change.layerName}' not found \u2014 already deleted`));
441
+ const element = layer.getElement(change.elementId), before = change.before ?? {};
442
+ model.relationships.deleteForElement(elementIdentifiers(element ?? before, before.id, before.path, change.elementId)) > 0 && (relationshipsPurged = !0), element ? (layer.deleteElement(element.path || element.id), result.committed++) : (result.skipped = (result.skipped ?? 0) + 1, result.skippedDetails = result.skippedDetails ?? [], result.skippedDetails.push(`'${change.elementId}' in '${change.layerName}' not found \u2014 already deleted`));
325
443
  } else
326
444
  result.failed++, result.validation.passed = !1, result.validation.errors.push(`Cannot apply change '${change.type}' for '${change.elementId}': missing payload or unrecognised type`);
327
445
  } catch (error) {
@@ -346,7 +464,7 @@ Fix these validation errors before committing.`);
346
464
  } catch (error) {
347
465
  throw new Error(`Failed to save layer '${layerName}': ${getErrorMessage(error)}`);
348
466
  }
349
- if (hasRelationshipChanges)
467
+ if (hasRelationshipChanges || relationshipsPurged)
350
468
  try {
351
469
  await model.saveRelationships();
352
470
  } catch (error) {
@@ -369,12 +487,10 @@ Fix these validation errors before committing.`);
369
487
  throw new Error(`Failed to save manifest with changeset history: ${manifestError instanceof Error ? manifestError.message : String(manifestError)}. Model will be rolled back to maintain consistency.`);
370
488
  }
371
489
  try {
372
- const orchestrator = new ModelReportOrchestrator(model, model.rootPath), affectedLayers = /* @__PURE__ */ new Set();
373
- for (const change of sortedChanges)
374
- if (isValidLayerName(change.layerName))
375
- for (const layer of orchestrator.computeAffectedLayers(change.layerName))
376
- affectedLayers.add(layer);
377
- await orchestrator.regenerate(affectedLayers);
490
+ const orchestrator = new ModelReportOrchestrator(model, model.rootPath), reportLayers = reportLayersForChanges(orchestrator, model, sortedChanges);
491
+ for (const layer of preCommitReportLayers)
492
+ reportLayers.add(layer);
493
+ await orchestrator.regenerate(reportLayers);
378
494
  } catch (error) {
379
495
  emitLog(SeverityNumber.WARN, "Failed to regenerate layer reports after changeset commit", {
380
496
  "changeset.id": changesetId,
@@ -996,6 +1112,32 @@ This indicates a filesystem or permissions issue. Check:
996
1112
  return `changeset-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
997
1113
  }
998
1114
  }
1115
+ function layerOfId(id) {
1116
+ const layer = id?.split(".")[0];
1117
+ return layer && isValidLayerName(layer) ? layer : void 0;
1118
+ }
1119
+ function layerOfElement(model, id) {
1120
+ if (!id)
1121
+ return;
1122
+ const element = model.getElementById(id);
1123
+ return element?.layer_id || element?.layer || layerOfId(id);
1124
+ }
1125
+ function reportLayersForChanges(orchestrator, model, changes) {
1126
+ const layers = /* @__PURE__ */ new Set(), add = (layer) => {
1127
+ typeof layer == "string" && isValidLayerName(layer) && layers.add(layer);
1128
+ };
1129
+ for (const change of changes) {
1130
+ if (change.type === "relationship-add" || change.type === "relationship-delete") {
1131
+ const data = change.after ?? change.before ?? {}, [source, , target] = typeof data.source == "string" ? [data.source, data.predicate, data.target] : change.elementId.split("::");
1132
+ add(change.layerName), add(data.layer), add(data.targetLayer), add(layerOfElement(model, source)), add(layerOfElement(model, target));
1133
+ continue;
1134
+ }
1135
+ const snapshot = change.after ?? change.before ?? {}, element = model.getElementById(change.elementId), ids = elementIdentifiers(element ?? snapshot, change.elementId, element?.path || void 0);
1136
+ for (const layer of orchestrator.computeElementAffectedLayers(change.layerName, ids))
1137
+ layers.add(layer);
1138
+ }
1139
+ return layers;
1140
+ }
999
1141
  export {
1000
1142
  StagingAreaManager
1001
1143
  };