@documentation-robotics/cli 0.2.0 → 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 (269) hide show
  1. package/README.md +36 -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/audit/relationships/ai/prompts.js +10 -10
  34. package/dist/audit/relationships/ai/prompts.js.map +2 -2
  35. package/dist/audit/relationships/analysis/layer-templates.js +52 -52
  36. package/dist/audit/relationships/analysis/layer-templates.js.map +1 -1
  37. package/dist/cli.js +12 -2
  38. package/dist/cli.js.map +2 -2
  39. package/dist/coding-agents/default-permissions.js +31 -1
  40. package/dist/coding-agents/default-permissions.js.map +2 -2
  41. package/dist/coding-agents/drbot-mcp.js +167 -0
  42. package/dist/coding-agents/drbot-mcp.js.map +7 -0
  43. package/dist/commands/add.js +3 -2
  44. package/dist/commands/add.js.map +2 -2
  45. package/dist/commands/analyzer.js +117 -14
  46. package/dist/commands/analyzer.js.map +2 -2
  47. package/dist/commands/changeset-verify.js +55 -0
  48. package/dist/commands/changeset-verify.js.map +7 -0
  49. package/dist/commands/changeset.js +43 -22
  50. package/dist/commands/changeset.js.map +2 -2
  51. package/dist/commands/delete.js +10 -4
  52. package/dist/commands/delete.js.map +2 -2
  53. package/dist/commands/farm.js +176 -60
  54. package/dist/commands/farm.js.map +2 -2
  55. package/dist/commands/mcp.js +19 -4
  56. package/dist/commands/mcp.js.map +2 -2
  57. package/dist/commands/relationship.js +207 -26
  58. package/dist/commands/relationship.js.map +2 -2
  59. package/dist/commands/update.js +42 -23
  60. package/dist/commands/update.js.map +2 -2
  61. package/dist/commands/upgrade.js +99 -11
  62. package/dist/commands/upgrade.js.map +3 -3
  63. package/dist/commands/version.js +1 -1
  64. package/dist/commands/visualize.js +4 -3
  65. package/dist/commands/visualize.js.map +2 -2
  66. package/dist/core/catalog-migration.js +33 -0
  67. package/dist/core/catalog-migration.js.map +7 -0
  68. package/dist/core/changeset-analysis.js +421 -0
  69. package/dist/core/changeset-analysis.js.map +7 -0
  70. package/dist/core/changeset-base-index.js +153 -0
  71. package/dist/core/changeset-base-index.js.map +7 -0
  72. package/dist/core/changeset-brief-delta.js +102 -0
  73. package/dist/core/changeset-brief-delta.js.map +7 -0
  74. package/dist/core/changeset-graph.js +281 -0
  75. package/dist/core/changeset-graph.js.map +7 -0
  76. package/dist/core/changeset-only-guard.js +32 -0
  77. package/dist/core/changeset-only-guard.js.map +7 -0
  78. package/dist/core/changeset-summary.js +68 -0
  79. package/dist/core/changeset-summary.js.map +7 -0
  80. package/dist/core/changeset-validator.js +66 -20
  81. package/dist/core/changeset-validator.js.map +2 -2
  82. package/dist/core/changeset-verify.js +518 -0
  83. package/dist/core/changeset-verify.js.map +7 -0
  84. package/dist/core/changeset.js +56 -9
  85. package/dist/core/changeset.js.map +2 -2
  86. package/dist/core/element-type-migration.js +145 -0
  87. package/dist/core/element-type-migration.js.map +7 -0
  88. package/dist/core/farm-manifest.js +12 -6
  89. package/dist/core/farm-manifest.js.map +2 -2
  90. package/dist/core/farm-sync-engine.js +1 -1
  91. package/dist/core/farm-sync-engine.js.map +2 -2
  92. package/dist/core/farm-sync-state.js +10 -2
  93. package/dist/core/farm-sync-state.js.map +2 -2
  94. package/dist/core/migration-registry.js +75 -46
  95. package/dist/core/migration-registry.js.map +2 -2
  96. package/dist/core/model-upgrade.js +102 -0
  97. package/dist/core/model-upgrade.js.map +7 -0
  98. package/dist/core/model.js +82 -30
  99. package/dist/core/model.js.map +2 -2
  100. package/dist/core/mutation-handler.js +71 -6
  101. package/dist/core/mutation-handler.js.map +2 -2
  102. package/dist/core/relationship-migration-table.js +6639 -0
  103. package/dist/core/relationship-migration-table.js.map +7 -0
  104. package/dist/core/relationship-migration.js +272 -0
  105. package/dist/core/relationship-migration.js.map +7 -0
  106. package/dist/core/relationship-prune.js +49 -0
  107. package/dist/core/relationship-prune.js.map +7 -0
  108. package/dist/core/relationship-staging.js +68 -0
  109. package/dist/core/relationship-staging.js.map +7 -0
  110. package/dist/core/relationships.js +26 -4
  111. package/dist/core/relationships.js.map +2 -2
  112. package/dist/core/report-data-model.js +7 -13
  113. package/dist/core/report-data-model.js.map +2 -2
  114. package/dist/core/staged-changeset-storage.js +64 -17
  115. package/dist/core/staged-changeset-storage.js.map +2 -2
  116. package/dist/core/staged-source-reference.js +28 -0
  117. package/dist/core/staged-source-reference.js.map +7 -0
  118. package/dist/core/staging-area.js +83 -18
  119. package/dist/core/staging-area.js.map +2 -2
  120. package/dist/core/virtual-projection.js +13 -5
  121. package/dist/core/virtual-projection.js.map +2 -2
  122. package/dist/export/archimate-exporter.js +1 -0
  123. package/dist/export/archimate-exporter.js.map +2 -2
  124. package/dist/generated/compiled-validators.js +2 -2
  125. package/dist/generated/compiled-validators.js.map +2 -2
  126. package/dist/generated/layer-registry.js +16 -16
  127. package/dist/generated/layer-registry.js.map +2 -2
  128. package/dist/generated/node-types.js +186 -168
  129. package/dist/generated/node-types.js.map +2 -2
  130. package/dist/generated/relationship-index.js +2236 -7790
  131. package/dist/generated/relationship-index.js.map +2 -2
  132. package/dist/integrations/claude_code/agents/dr-architect.md +16 -3
  133. package/dist/integrations/claude_code/agents/dr-extractor.md +1 -1
  134. package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
  135. package/dist/integrations/claude_code/commands/dr-map.md +13 -0
  136. package/dist/integrations/claude_code/commands/dr-plan.md +40 -0
  137. package/dist/integrations/claude_code/commands/dr-relate.md +106 -96
  138. package/dist/integrations/claude_code/commands/dr-verify.md +45 -2
  139. package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +14 -0
  140. package/dist/integrations/claude_code/docs/MCP_SERVER.md +26 -2
  141. package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
  142. package/dist/integrations/claude_code/docs/README.md +48 -1
  143. package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +93 -72
  144. package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +163 -126
  145. package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +161 -126
  146. package/dist/integrations/claude_code/skills/dr_04_security_layer/SKILL.md +153 -133
  147. package/dist/integrations/claude_code/skills/dr_05_application_layer/SKILL.md +129 -100
  148. package/dist/integrations/claude_code/skills/dr_06_technology_layer/SKILL.md +111 -94
  149. package/dist/integrations/claude_code/skills/dr_07_api_layer/SKILL.md +143 -157
  150. package/dist/integrations/claude_code/skills/dr_08_data_model_layer/SKILL.md +117 -33
  151. package/dist/integrations/claude_code/skills/dr_09_dataset_layer/SKILL.md +74 -25
  152. package/dist/integrations/claude_code/skills/dr_10_ux_layer/SKILL.md +88 -12
  153. package/dist/integrations/claude_code/skills/dr_11_navigation_layer/SKILL.md +83 -24
  154. package/dist/integrations/claude_code/skills/dr_12_apm_layer/SKILL.md +57 -12
  155. package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +92 -15
  156. package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +119 -0
  157. package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +3 -0
  158. package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +1 -1
  159. package/dist/integrations/github_copilot/agents/dr-architect.md +1 -1
  160. package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +182 -102
  161. package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +266 -148
  162. package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +547 -86
  163. package/dist/integrations/github_copilot/skills/dr_04_security_layer/SKILL.md +164 -169
  164. package/dist/integrations/github_copilot/skills/dr_05_application_layer/SKILL.md +420 -150
  165. package/dist/integrations/github_copilot/skills/dr_06_technology_layer/SKILL.md +331 -138
  166. package/dist/integrations/github_copilot/skills/dr_07_api_layer/SKILL.md +265 -201
  167. package/dist/integrations/github_copilot/skills/dr_08_data_model_layer/SKILL.md +181 -58
  168. package/dist/integrations/github_copilot/skills/dr_09_dataset_layer/SKILL.md +209 -57
  169. package/dist/integrations/github_copilot/skills/dr_10_ux_layer/SKILL.md +277 -112
  170. package/dist/integrations/github_copilot/skills/dr_11_navigation_layer/SKILL.md +219 -70
  171. package/dist/integrations/github_copilot/skills/dr_12_apm_layer/SKILL.md +189 -105
  172. package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +222 -102
  173. package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +119 -0
  174. package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +3 -0
  175. package/dist/integrations/hash-utils.js.map +2 -2
  176. package/dist/mcp/api-key-manager.js +14 -1
  177. package/dist/mcp/api-key-manager.js.map +2 -2
  178. package/dist/mcp/changeset-only.js +34 -0
  179. package/dist/mcp/changeset-only.js.map +7 -0
  180. package/dist/mcp/tool-registry.js +19 -3
  181. package/dist/mcp/tool-registry.js.map +2 -2
  182. package/dist/mcp/tools/changeset-analysis-tools.js +78 -0
  183. package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
  184. package/dist/mcp/tools/changeset-editing.js +184 -0
  185. package/dist/mcp/tools/changeset-editing.js.map +7 -0
  186. package/dist/mcp/tools/changeset-lifecycle.js +83 -0
  187. package/dist/mcp/tools/changeset-lifecycle.js.map +7 -0
  188. package/dist/mcp/tools/changeset-shared.js +31 -0
  189. package/dist/mcp/tools/changeset-shared.js.map +7 -0
  190. package/dist/mcp/tools/changeset-show.js +17 -1
  191. package/dist/mcp/tools/changeset-show.js.map +2 -2
  192. package/dist/mcp/tools/changeset-verify.js +43 -0
  193. package/dist/mcp/tools/changeset-verify.js.map +7 -0
  194. package/dist/mcp/tools/index.js +19 -1
  195. package/dist/mcp/tools/index.js.map +2 -2
  196. package/dist/mcp/tools/model-add.js +6 -5
  197. package/dist/mcp/tools/model-add.js.map +2 -2
  198. package/dist/mcp/tools/model-delete.js +2 -2
  199. package/dist/mcp/tools/model-delete.js.map +2 -2
  200. package/dist/mcp/tools/model-search.js +14 -0
  201. package/dist/mcp/tools/model-search.js.map +2 -2
  202. package/dist/mcp/tools/model-show.js +29 -2
  203. package/dist/mcp/tools/model-show.js.map +2 -2
  204. package/dist/mcp/tools/model-trace.js +93 -30
  205. package/dist/mcp/tools/model-trace.js.map +2 -2
  206. package/dist/mcp/tools/model-update.js +35 -19
  207. package/dist/mcp/tools/model-update.js.map +2 -2
  208. package/dist/mcp/tools/relationship-tools.js +98 -0
  209. package/dist/mcp/tools/relationship-tools.js.map +7 -0
  210. package/dist/mcp/tools/shared.js +25 -2
  211. package/dist/mcp/tools/shared.js.map +2 -2
  212. package/dist/mcp/tools/spec-type.js +90 -0
  213. package/dist/mcp/tools/spec-type.js.map +7 -0
  214. package/dist/schemas/bundled/analyzers/cbm.json +1186 -68
  215. package/dist/schemas/bundled/analyzers/manifest.json +2 -2
  216. package/dist/schemas/bundled/api.json +1029 -1658
  217. package/dist/schemas/bundled/apm.json +499 -734
  218. package/dist/schemas/bundled/application.json +735 -371
  219. package/dist/schemas/bundled/base.json +3 -220
  220. package/dist/schemas/bundled/business.json +452 -366
  221. package/dist/schemas/bundled/data-model.json +395 -411
  222. package/dist/schemas/bundled/data-store.json +334 -558
  223. package/dist/schemas/bundled/manifest.json +18 -18
  224. package/dist/schemas/bundled/motivation.json +349 -368
  225. package/dist/schemas/bundled/navigation.json +415 -731
  226. package/dist/schemas/bundled/product.json +325 -159
  227. package/dist/schemas/bundled/security.json +864 -1232
  228. package/dist/schemas/bundled/technology.json +574 -703
  229. package/dist/schemas/bundled/testing.json +718 -1643
  230. package/dist/schemas/bundled/ux.json +739 -1352
  231. package/dist/server/schemas.js +8 -0
  232. package/dist/server/schemas.js.map +2 -2
  233. package/dist/server/server.js +218 -34
  234. package/dist/server/server.js.map +2 -2
  235. package/dist/server-entry.js +2 -1
  236. package/dist/server-entry.js.map +2 -2
  237. package/dist/telemetry/index.js +1 -1
  238. package/dist/utils/ansi-suppressor.js +6 -8
  239. package/dist/utils/ansi-suppressor.js.map +2 -2
  240. package/dist/utils/attribute-patch.js +41 -0
  241. package/dist/utils/attribute-patch.js.map +7 -0
  242. package/dist/utils/error-hints.js +29 -0
  243. package/dist/utils/error-hints.js.map +7 -0
  244. package/dist/utils/errors.js +9 -3
  245. package/dist/utils/errors.js.map +2 -2
  246. package/dist/utils/project-paths.js +26 -1
  247. package/dist/utils/project-paths.js.map +2 -2
  248. package/dist/utils/source-reference.js +14 -12
  249. package/dist/utils/source-reference.js.map +2 -2
  250. package/dist/utils/spec-version.js +2 -2
  251. package/dist/utils/spec-version.js.map +1 -1
  252. package/dist/validators/composed-reference-validator.js +5 -27
  253. package/dist/validators/composed-reference-validator.js.map +2 -2
  254. package/dist/validators/reference-validator.js +0 -7
  255. package/dist/validators/reference-validator.js.map +2 -2
  256. package/dist/validators/relationship-schema-validator.js +3 -3
  257. package/dist/validators/relationship-schema-validator.js.map +2 -2
  258. package/dist/validators/schema-validator.js +27 -9
  259. package/dist/validators/schema-validator.js.map +2 -2
  260. package/dist/validators/types.js.map +1 -1
  261. package/dist/viewer/README.md +6 -6
  262. package/dist/viewer/assets/main-CxzcuU_o.js +84 -0
  263. package/dist/viewer/assets/main-j-7ULYpm.css +1 -0
  264. package/dist/viewer/index.html +2 -2
  265. package/dist/viewer/manifest.json +10 -10
  266. package/dist/viewer/version.json +1 -1
  267. package/package.json +2 -1
  268. package/dist/viewer/assets/main-B7AOQXXX.js +0 -81
  269. package/dist/viewer/assets/main-vdrFrG7q.css +0 -1
@@ -0,0 +1,102 @@
1
+ import { readdir } from "fs/promises";
2
+ import { Model } from "./model.js";
3
+ import { MigrationRegistry } from "./migration-registry.js";
4
+ import { applyCatalogMigrations } from "./catalog-migration.js";
5
+ import { BaseSnapshotManager } from "./base-snapshot-manager.js";
6
+ import { StagedChangesetStorage } from "./staged-changeset-storage.js";
7
+ import { FileLock } from "../utils/file-lock.js";
8
+ import { getErrorMessage } from "../utils/errors.js";
9
+ async function upgradeModel(startPath, options) {
10
+ const model = await Model.load(startPath, { lazyLoad: !1 }), baselines = await captureChangesetBaselines(model);
11
+ let migration;
12
+ if (options.versionBumpOnly)
13
+ model.manifest.specVersion = options.toVersion, await model.saveManifest();
14
+ else if (options.catalogOnly) {
15
+ const catalog = await applyCatalogMigrations(model);
16
+ if (catalog.errors.length > 0)
17
+ throw new Error(`Catalog migration failed: ${catalog.errors.join("; ")}`);
18
+ migration = {
19
+ applied: [
20
+ {
21
+ from: options.fromVersion,
22
+ to: options.toVersion,
23
+ description: `Catalog changes applied (${catalog.summaries.join("; ")})`,
24
+ changes: {
25
+ migrationsApplied: 1,
26
+ filesModified: catalog.filesModified,
27
+ description: catalog.summaries.join("; "),
28
+ details: catalog.details
29
+ }
30
+ }
31
+ ],
32
+ currentVersion: options.fromVersion,
33
+ targetVersion: options.toVersion,
34
+ totalChanges: 1
35
+ }, await model.saveDirtyLayers();
36
+ } else
37
+ migration = await new MigrationRegistry().applyMigrations(model, {
38
+ fromVersion: options.fromVersion,
39
+ toVersion: options.toVersion,
40
+ dryRun: !1,
41
+ validate: options.validate ?? !0
42
+ }), await model.saveManifest(), await model.saveDirtyLayers();
43
+ return { migration, changesets: await rebaselineChangesets(startPath, baselines) };
44
+ }
45
+ async function captureChangesetBaselines(model) {
46
+ if (model.lazyLoad)
47
+ throw new Error("captureChangesetBaselines needs a model loaded with lazyLoad: false");
48
+ const { changesets, errors } = await listStagedChangesets(model.rootPath), baselines = { snapshot: null, inStep: [], drifted: [], errors };
49
+ if (changesets.length === 0)
50
+ return baselines;
51
+ const snapshot = await new BaseSnapshotManager().captureSnapshot(model);
52
+ baselines.snapshot = snapshot;
53
+ for (const { ref, baseSnapshot } of changesets)
54
+ baseSnapshot && (baseSnapshot === snapshot ? baselines.inStep : baselines.drifted).push(ref);
55
+ return baselines;
56
+ }
57
+ async function rebaselineChangesets(startPath, baselines) {
58
+ const result = {
59
+ rebaselined: [],
60
+ drifted: baselines.drifted,
61
+ errors: [...baselines.errors]
62
+ };
63
+ if (baselines.inStep.length === 0)
64
+ return result;
65
+ const upgraded = await Model.load(startPath, { lazyLoad: !1 }), snapshot = await new BaseSnapshotManager().captureSnapshot(upgraded), storage = new StagedChangesetStorage(upgraded.rootPath);
66
+ for (const ref of baselines.inStep)
67
+ try {
68
+ await new FileLock(storage.getChangesetPath(ref.id)).withLock(async () => {
69
+ const changeset = await storage.load(ref.id);
70
+ !changeset || changeset.status !== "staged" || changeset.baseSnapshot !== baselines.snapshot || (changeset.baseSnapshot = snapshot, changeset.updateModified(), await storage.save(changeset), result.rebaselined.push(ref));
71
+ });
72
+ } catch (error) {
73
+ result.errors.push(`Could not re-baseline changeset '${ref.name}': ${getErrorMessage(error)}; committing it needs --force`);
74
+ }
75
+ return result;
76
+ }
77
+ async function listStagedChangesets(rootPath) {
78
+ const storage = new StagedChangesetStorage(rootPath), changesets = [], errors = [];
79
+ let ids;
80
+ try {
81
+ ids = (await readdir(storage.getChangesetsDir(), { withFileTypes: !0 })).filter((entry) => entry.isDirectory() && !entry.name.endsWith(".lock")).map((entry) => entry.name).sort();
82
+ } catch (error) {
83
+ return error.code !== "ENOENT" && errors.push(`Could not list changesets: ${getErrorMessage(error)}`), { changesets, errors };
84
+ }
85
+ for (const id of ids)
86
+ try {
87
+ const changeset = await storage.load(id);
88
+ changeset?.status === "staged" && changesets.push({
89
+ ref: { id, name: changeset.name || id },
90
+ baseSnapshot: changeset.baseSnapshot
91
+ });
92
+ } catch (error) {
93
+ errors.push(`Could not read changeset '${id}': ${getErrorMessage(error)}`);
94
+ }
95
+ return { changesets, errors };
96
+ }
97
+ export {
98
+ captureChangesetBaselines,
99
+ rebaselineChangesets,
100
+ upgradeModel
101
+ };
102
+ //# sourceMappingURL=model-upgrade.js.map
@@ -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": "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",
6
+ "names": []
7
+ }
@@ -12,6 +12,7 @@ import { startSpan, endSpan, startActiveSpan } from "../telemetry/index.js";
12
12
  import { findProjectRoot } from "../utils/project-paths.js";
13
13
  import { getCodebasePath } from "../utils/globals.js";
14
14
  import { getErrorMessage } from "../utils/errors.js";
15
+ import { assertBaseModelWritable } from "./changeset-only-guard.js";
15
16
  import { getNodeType } from "../generated/node-types.js";
16
17
  import path from "node:path";
17
18
  import fs from "node:fs/promises";
@@ -64,6 +65,11 @@ class Model {
64
65
  _activeChangesetId = null;
65
66
  /** Layer names that exist only in staged changes (not yet committed to base model). */
66
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;
67
73
  constructor(rootPath, manifest, options = {}) {
68
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();
69
75
  }
@@ -83,8 +89,11 @@ class Model {
83
89
  if (this.lazyLoad && !this.loadedLayers.has(name) && await this.loadLayer(name), this._activeChangesetId)
84
90
  try {
85
91
  return await this.getVirtualProjectionEngine().projectLayer(this, this._activeChangesetId, name);
86
- } catch {
87
- 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;
88
97
  }
89
98
  return this.layers.get(name);
90
99
  }
@@ -112,6 +121,14 @@ class Model {
112
121
  getActiveChangesetId() {
113
122
  return this._activeChangesetId;
114
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
+ }
115
132
  /**
116
133
  * Load a layer from disk (model/XX_layername/*.yaml files)
117
134
  */
@@ -223,10 +240,19 @@ class Model {
223
240
  const staged = Array.from(this._stagedLayerNames).filter((l) => !this.layers.has(l));
224
241
  return [...base, ...staged];
225
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
+ }
226
251
  /**
227
252
  * Save a layer to disk (model/XX_layername/*.yaml)
228
253
  */
229
254
  async saveLayer(name) {
255
+ assertBaseModelWritable();
230
256
  const layer = this.layers.get(name);
231
257
  if (!layer)
232
258
  throw new Error(`Layer ${name} not found`);
@@ -323,7 +349,7 @@ class Model {
323
349
  * Save the manifest to disk (documentation-robotics/model/manifest.yaml)
324
350
  */
325
351
  async saveManifest() {
326
- this.manifest.updateModified();
352
+ assertBaseModelWritable(), this.manifest.updateModified();
327
353
  const yaml = await import("yaml"), manifestPath = `${this.rootPath}/documentation-robotics/model/manifest.yaml`, yamlData = {
328
354
  version: this.manifest.version,
329
355
  schema: "documentation-robotics-v1",
@@ -393,15 +419,28 @@ class Model {
393
419
  }
394
420
  /**
395
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.
396
425
  */
397
- async saveRelationships() {
426
+ async saveRelationships(options = {}) {
427
+ assertBaseModelWritable();
398
428
  const fs2 = await import("fs/promises"), yaml = await import("yaml"), relationshipsPath = `${this.rootPath}/documentation-robotics/model/relationships.yaml`;
399
429
  await ensureDir(`${this.rootPath}/documentation-robotics/model`);
400
- 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
401
432
  # Format: source_id -> predicate -> target_id
402
- # Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}
433
+ # Last updated: ${timestamp}
403
434
 
404
- ` + 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);
405
444
  await fs2.writeFile(relationshipsPath, yamlContent, "utf-8"), this.relationships.markClean();
406
445
  }
407
446
  /**
@@ -444,11 +483,15 @@ class Model {
444
483
  const candidateManifestPath = path.join(currentPath, "documentation-robotics", "model", "manifest.yaml");
445
484
  try {
446
485
  return await fs.access(candidateManifestPath), candidateManifestPath;
447
- } catch {
486
+ } catch (err) {
487
+ if (!(err?.code === "ENOENT"))
488
+ throw err;
448
489
  }
449
490
  try {
450
491
  return await fs.access(path.join(currentPath, ".git")), null;
451
- } catch {
492
+ } catch (err) {
493
+ if (!(err?.code === "ENOENT"))
494
+ throw err;
452
495
  }
453
496
  const parentPath = path.dirname(currentPath);
454
497
  if (parentPath === currentPath)
@@ -604,29 +647,37 @@ Run "dr init" to create a model.`);
604
647
  try {
605
648
  const activeId = (await fs.readFile(activePath, "utf-8")).trim();
606
649
  if (activeId) {
607
- model._activeChangesetId = activeId;
608
- const changeset = await new StagedChangesetStorage(projectRoot).load(activeId);
609
- if (changeset)
610
- for (const change of changeset.changes) {
611
- if ((change.type === "add" || change.type === "update" || change.type === "delete") && model._stagedLayerNames.add(change.layerName), change.type === "relationship-add" && change.after) {
612
- const rel = change.after;
613
- model.relationships.find(rel.source, rel.target, rel.predicate).length === 0 && model.relationships.add({
614
- source: rel.source,
615
- target: rel.target,
616
- predicate: rel.predicate,
617
- layer: rel.layer,
618
- ...rel.targetLayer ? { targetLayer: rel.targetLayer } : {},
619
- category: rel.category ?? "structural",
620
- ...rel.properties ? { properties: rel.properties } : {}
621
- });
622
- }
623
- if (change.type === "relationship-delete" && change.before) {
624
- const rel = change.before;
625
- 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
+ }
626
672
  }
627
- }
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
+ }
628
677
  }
629
- } catch {
678
+ } catch (err) {
679
+ if (!(err?.code === "ENOENT"))
680
+ throw err;
630
681
  model._activeChangesetId = null;
631
682
  }
632
683
  let entityCount = 0;
@@ -753,6 +804,7 @@ Run "dr init" to create a model.`);
753
804
  * @throws Error if atomic write fails
754
805
  */
755
806
  async saveDirtyLayersAtomic() {
807
+ assertBaseModelWritable();
756
808
  const tempLayerWrites = [];
757
809
  try {
758
810
  for (const layer of this.layers.values())