@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
@@ -8,7 +8,12 @@ import { getValidRelationships, getValidPredicatesForSource, getValidDestination
8
8
  import { normalizeNodeType } from "../generated/node-types.js";
9
9
  import { ModelReportOrchestrator } from "../reports/model-report-orchestrator.js";
10
10
  import { emitLog, SeverityNumber } from "../telemetry/index.js";
11
+ import { elementIdentifiers } from "../core/relationships.js";
11
12
  import { RelationshipCatalog } from "../core/relationship-catalog.js";
13
+ import { collectKnownIdentifiers, findDanglingRelationships } from "../core/relationship-prune.js";
14
+ import { isJson } from "../utils/globals.js";
15
+ import { ChangesetGraph } from "../core/changeset-graph.js";
16
+ import { liveRelationshipsBetween, planRelationshipAdd, stageRelationshipDeletes } from "../core/relationship-staging.js";
12
17
  function constructSpecNodeId(element, elementId) {
13
18
  const layerId = element.layer_id, type = element.type;
14
19
  if (typeof layerId != "string" || !layerId)
@@ -39,6 +44,13 @@ function getRelationshipConstraints(sourceSpecNodeId, predicate, destSpecNodeId)
39
44
  };
40
45
  }
41
46
  async function addRelationshipHandler(model, source, target, predicate, properties, preResolved) {
47
+ return await addRelationship(model, source, target, predicate, properties, preResolved) !== "duplicate";
48
+ }
49
+ async function activeStagedChangeset(manager) {
50
+ const active = await manager.getActive();
51
+ return active && active.status === "staged" ? active : null;
52
+ }
53
+ async function addRelationship(model, source, target, predicate, properties, preResolved) {
42
54
  let resolvedSourceLayerName = preResolved?.source?.layerName, resolvedSourceElement = preResolved?.source?.element, resolvedTargetLayerName = preResolved?.target?.layerName, resolvedTargetElement = preResolved?.target?.element;
43
55
  if (!resolvedSourceLayerName && (resolvedSourceLayerName = await findElementLayer(model, source), !resolvedSourceLayerName))
44
56
  throw new CLIError(`Source element ${source} not found`, ErrorCategory.USER);
@@ -68,9 +80,19 @@ async function addRelationshipHandler(model, source, target, predicate, properti
68
80
  if (relType && !relType.semantics.reflexivity && source === target)
69
81
  throw new CLIError(`Invalid relationship: predicate "${predicate}" does not allow reflexive relationships (${source})`, ErrorCategory.USER);
70
82
  }
71
- if (model.relationships.find(source, target, predicate).length > 0)
72
- return !1;
73
- const stagingManager = new StagingAreaManager(model.rootPath, model), activeChangesetId = await stagingManager.getActiveId(), relData = {
83
+ const stagingManager = new StagingAreaManager(model.rootPath, model), activeChangesetId = await stagingManager.getActiveId(), activeChangeset = activeChangesetId ? await activeStagedChangeset(stagingManager) : null;
84
+ if (activeChangeset) {
85
+ const graph = await ChangesetGraph.build(model, activeChangeset), plan = planRelationshipAdd(graph, activeChangeset, { source, predicate, target });
86
+ if (plan.action === "duplicate")
87
+ return "duplicate";
88
+ if (plan.action === "restore") {
89
+ for (const key of plan.keys)
90
+ await stagingManager.unstage(activeChangeset.id, key);
91
+ return "restored";
92
+ }
93
+ } else if (model.relationships.find(source, target, predicate).length > 0)
94
+ return "duplicate";
95
+ const relData = {
74
96
  source,
75
97
  target,
76
98
  predicate,
@@ -109,15 +131,35 @@ async function addRelationshipHandler(model, source, target, predicate, properti
109
131
  });
110
132
  }
111
133
  }
112
- return !0;
134
+ return activeChangesetId ? "staged" : "added";
135
+ }
136
+ async function findRelationshipsByEndpoints(model, source, target, predicate) {
137
+ const sourceIds = new Set(elementIdentifiers(model.getElementById(source) ?? {}, source)), targetIds = new Set(elementIdentifiers(model.getElementById(target) ?? {}, target));
138
+ return model.relationships.getAll().filter((rel) => sourceIds.has(rel.source) && targetIds.has(rel.target) && (!predicate || rel.predicate === predicate));
139
+ }
140
+ function noMatchingRelationshipHints(surface, source, inChangeset) {
141
+ return surface === "mcp" ? [
142
+ `Use "model_show" on ${source} to see its relationships`,
143
+ inChangeset ? 'Use "changeset_brief" to see what this changeset already stages' : 'Use "model_search" to check the source and target element IDs'
144
+ ] : [
145
+ `Use "dr relationship list ${source}" to see the relationships recorded for ${source}`,
146
+ inChangeset ? 'Use "dr changeset staged" to see what the active changeset already stages' : 'Use "dr relationship prune --dry-run" to list rows whose endpoints no longer resolve'
147
+ ];
113
148
  }
114
- async function deleteRelationshipHandler(model, source, target, predicate, preResolved) {
149
+ async function deleteRelationshipHandler(model, source, target, predicate, preResolved, surface = "cli") {
150
+ const changesetManager = new StagingAreaManager(model.rootPath, model), changeset = await changesetManager.getActiveId() ? await activeStagedChangeset(changesetManager) : null;
151
+ if (changeset) {
152
+ const graph = await ChangesetGraph.build(model, changeset), matches = liveRelationshipsBetween(graph, source, target, predicate);
153
+ if (matches.length === 0)
154
+ throw new CLIError("No matching relationships found", ErrorCategory.USER, noMatchingRelationshipHints(surface, source, !0));
155
+ const staged = await stageRelationshipDeletes(changesetManager, graph, changeset, matches);
156
+ return { deletedCount: staged.length, staged };
157
+ }
115
158
  let resolvedSourceLayerName = preResolved?.sourceLayerName;
116
- if (!resolvedSourceLayerName && (resolvedSourceLayerName = await findElementLayer(model, source), !resolvedSourceLayerName))
117
- throw new CLIError(`Source element ${source} not found`, ErrorCategory.USER);
159
+ resolvedSourceLayerName || (resolvedSourceLayerName = await findElementLayer(model, source));
118
160
  let resolvedToDelete = preResolved?.toDelete;
119
- if (resolvedToDelete || (resolvedToDelete = model.relationships.find(source, target, predicate)), resolvedToDelete.length === 0)
120
- throw new CLIError("No matching relationships found", ErrorCategory.USER);
161
+ if (resolvedToDelete || (resolvedToDelete = await findRelationshipsByEndpoints(model, source, target, predicate)), resolvedToDelete.length === 0)
162
+ throw new CLIError("No matching relationships found", ErrorCategory.USER, noMatchingRelationshipHints(surface, source, !1));
121
163
  const stagingManager = new StagingAreaManager(model.rootPath, model), activeChangesetId = await stagingManager.getActiveId();
122
164
  if (activeChangesetId)
123
165
  for (const rel of resolvedToDelete) {
@@ -137,7 +179,9 @@ async function deleteRelationshipHandler(model, source, target, predicate, preRe
137
179
  });
138
180
  }
139
181
  else {
140
- model.relationships.delete(source, target, predicate), await model.saveRelationships(), await model.saveManifest();
182
+ for (const rel of resolvedToDelete)
183
+ model.relationships.delete(rel.source, rel.target, rel.predicate);
184
+ await model.saveRelationships(), await model.saveManifest();
141
185
  try {
142
186
  const orchestrator = new ModelReportOrchestrator(model, model.rootPath), affectedLayers = /* @__PURE__ */ new Set();
143
187
  for (const rel of resolvedToDelete) {
@@ -161,6 +205,106 @@ async function deleteRelationshipHandler(model, source, target, predicate, preRe
161
205
  }
162
206
  return { deletedCount: resolvedToDelete.length };
163
207
  }
208
+ async function pruneRelationshipsCommand(options) {
209
+ const format = options.format ?? "text";
210
+ if (format !== "text" && format !== "json")
211
+ throw new CLIError(`Invalid format: ${format}`, ErrorCategory.USER, [
212
+ "Use --format text or --format json"
213
+ ]);
214
+ const jsonOutput = format === "json" || isJson(), layers = options.layers?.length ? options.layers : void 0;
215
+ if (layers) {
216
+ const invalid = layers.filter((l) => !isValidLayerName(l));
217
+ if (invalid.length > 0)
218
+ throw new CLIError(`Invalid layer name(s): ${invalid.join(", ")}`, ErrorCategory.USER, ['Use canonical layer names, e.g. "api", "data-model", "data-store"']);
219
+ }
220
+ const model = await Model.load(options.model || process.cwd()), elements = [];
221
+ for (const layerName of model.getLayerNames()) {
222
+ const layer = await model.getLayer(layerName);
223
+ layer && elements.push(...layer.listElements());
224
+ }
225
+ const known = collectKnownIdentifiers(elements), analysis = findDanglingRelationships(model.relationships.getAll(), known, { layers }), stagingManager = new StagingAreaManager(model.rootPath, model), activeChangeset = await stagingManager.getActive(), staging = !!activeChangeset && activeChangeset.status === "staged", result = {
226
+ scanned: analysis.scanned,
227
+ dangling: analysis.dangling.length,
228
+ removed: 0,
229
+ dryRun: !!options.dryRun,
230
+ staged: !1,
231
+ ...staging ? { changeset: activeChangeset.name } : {},
232
+ ...layers ? { layers } : {},
233
+ relationships: analysis.dangling.map(({ relationship: rel, unresolved }) => ({
234
+ source: rel.source,
235
+ predicate: rel.predicate,
236
+ target: rel.target,
237
+ layer: rel.layer,
238
+ ...rel.targetLayer ? { targetLayer: rel.targetLayer } : {},
239
+ unresolved
240
+ }))
241
+ };
242
+ if (!options.dryRun && analysis.dangling.length > 0) {
243
+ if (staging) {
244
+ for (const { relationship: rel } of analysis.dangling)
245
+ await stagingManager.stage(activeChangeset.id, {
246
+ type: "relationship-delete",
247
+ elementId: `${rel.source}::${rel.predicate}::${rel.target}`,
248
+ layerName: rel.layer,
249
+ before: {
250
+ source: rel.source,
251
+ target: rel.target,
252
+ predicate: rel.predicate,
253
+ layer: rel.layer,
254
+ ...rel.targetLayer ? { targetLayer: rel.targetLayer } : {},
255
+ ...rel.properties ? { properties: rel.properties } : {}
256
+ }
257
+ });
258
+ result.staged = !0;
259
+ } else {
260
+ const danglingRows = new Set(analysis.dangling.map((d) => d.relationship));
261
+ model.relationships.retain((rel) => !danglingRows.has(rel)), await model.saveRelationships({ preserveHeader: !0 }), await model.saveManifest();
262
+ try {
263
+ const orchestrator = new ModelReportOrchestrator(model, model.rootPath), affectedLayers = /* @__PURE__ */ new Set();
264
+ for (const { relationship: rel } of analysis.dangling)
265
+ for (const name of [rel.layer, rel.targetLayer])
266
+ if (name && isValidLayerName(name))
267
+ for (const affected of orchestrator.computeAffectedLayers(name))
268
+ affectedLayers.add(affected);
269
+ await orchestrator.regenerate(affectedLayers);
270
+ } catch (error) {
271
+ emitLog(SeverityNumber.WARN, "Failed to regenerate layer reports after relationship prune", {
272
+ "relationship.pruned": analysis.dangling.length,
273
+ "error.message": getErrorMessage(error)
274
+ });
275
+ }
276
+ }
277
+ result.removed = analysis.dangling.length;
278
+ }
279
+ if (jsonOutput)
280
+ return console.log(JSON.stringify({ status: "ok", ...result }, null, 2)), result;
281
+ const scope = layers ? ` in layer(s) ${layers.join(", ")}` : "";
282
+ handleInfo(`Scanned ${result.scanned} relationship(s)${scope}: ${result.dangling} dangling`);
283
+ for (const row of result.relationships) {
284
+ const side = row.unresolved === "both" ? "source and target not found" : `${row.unresolved} not found`;
285
+ handleInfo(` - ${ansis.yellow(row.source)} --[${ansis.magenta(row.predicate)}]--> ${ansis.yellow(row.target)} ${ansis.dim(`(${side})`)}`);
286
+ }
287
+ return result.dangling === 0 ? handleSuccess("No dangling relationships found", {
288
+ scanned: result.scanned,
289
+ dangling: 0,
290
+ removed: 0
291
+ }) : result.dryRun ? handleSuccess(`Dry run: would remove ${result.dangling} dangling relationship(s)`, {
292
+ scanned: result.scanned,
293
+ dangling: result.dangling,
294
+ removed: 0,
295
+ dryRun: !0
296
+ }) : result.staged ? handleSuccess(`Staged removal of ${result.removed} dangling relationship(s) to ${ansis.bold(result.changeset)}`, {
297
+ scanned: result.scanned,
298
+ dangling: result.dangling,
299
+ removed: result.removed,
300
+ changesetStatus: "staged",
301
+ changeset: result.changeset
302
+ }) : handleSuccess(`Removed ${result.removed} dangling relationship(s)`, {
303
+ scanned: result.scanned,
304
+ dangling: result.dangling,
305
+ removed: result.removed
306
+ }), result;
307
+ }
164
308
  function relationshipCommands(program, rootProgram) {
165
309
  program.command("add <source> <target>").description("Add a relationship between elements (intra-layer or cross-layer)").option("--model <path>", "Path to model root (contains model/manifest.yaml)").requiredOption("--predicate <predicate>", "Relationship predicate (e.g., depends-on, realizes, exposes)").option("--properties <json>", "Relationship properties (JSON)").addHelpText("after", `
166
310
  Examples:
@@ -169,7 +313,7 @@ Examples:
169
313
  $ dr relationship add business.businessprocess.checkout application.applicationprocess.checkout --predicate aggregates
170
314
  $ dr relationship add element-1 element-2 --predicate realizes --properties '{"strength":"high"}'`).action(async (source, target, options) => {
171
315
  try {
172
- const model = await Model.load(options.model || process.cwd()), sourceLayerName = await findElementLayer(model, source);
316
+ const model = await Model.load(options.model), sourceLayerName = await findElementLayer(model, source);
173
317
  if (!sourceLayerName)
174
318
  throw new CLIError(`Source element ${source} not found`, ErrorCategory.USER);
175
319
  const sourceLayer = await model.getLayer(sourceLayerName);
@@ -195,17 +339,29 @@ Examples:
195
339
  } catch {
196
340
  throw new CLIError("Invalid JSON in --properties", ErrorCategory.USER);
197
341
  }
198
- const activeChangesetId = await new StagingAreaManager(model.rootPath, model).getActiveId();
199
- if (!await addRelationshipHandler(model, source, target, options.predicate, properties, {
342
+ const activeChangesetId = await new StagingAreaManager(model.rootPath, model).getActiveId(), outcome = await addRelationship(model, source, target, options.predicate, properties, {
200
343
  source: { element: sourceElement, layerName: sourceLayerName },
201
344
  target: { element: targetElement, layerName: targetLayerName }
202
- })) {
203
- handleSuccess(`Relationship ${source} --[${options.predicate}]--> ${target} already exists`, {
345
+ });
346
+ if (outcome === "duplicate") {
347
+ handleSuccess(`Relationship ${source} --[${options.predicate}]--> ${target} already exists` + (activeChangesetId ? " (in the model or the active changeset); nothing staged" : ""), {
348
+ source,
349
+ target,
350
+ predicate: options.predicate,
351
+ layer: sourceLayerName,
352
+ duplicate: !0,
353
+ ...activeChangesetId ? { changesetStatus: "unchanged" } : {}
354
+ }, { verbose: options.verbose });
355
+ return;
356
+ }
357
+ if (outcome === "restored") {
358
+ handleSuccess(`Restored relationship: ${ansis.bold(source)} ${options.predicate} ${ansis.bold(target)}` + ansis.dim(" [the active changeset's staged deletion of it was unstaged]"), {
204
359
  source,
205
360
  target,
206
361
  predicate: options.predicate,
207
362
  layer: sourceLayerName,
208
- duplicate: !0
363
+ restored: !0,
364
+ changesetStatus: "unstaged"
209
365
  }, { verbose: options.verbose });
210
366
  return;
211
367
  }
@@ -225,13 +381,13 @@ Examples:
225
381
  $ dr relationship delete element-1 element-2 --predicate depends-on
226
382
  $ dr relationship delete element-1 element-2 --force`).action(async (source, target, options) => {
227
383
  try {
228
- const model = await Model.load(options.model || process.cwd()), sourceLayerName = await findElementLayer(model, source);
229
- if (!sourceLayerName)
230
- throw new CLIError(`Source element ${source} not found`, ErrorCategory.USER);
231
- const toDelete = model.relationships.find(source, target, options.predicate);
384
+ const model = await Model.load(options.model), activeForDelete = await activeStagedChangeset(new StagingAreaManager(model.rootPath, model)), toDelete = activeForDelete ? liveRelationshipsBetween(await ChangesetGraph.build(model, activeForDelete), source, target, options.predicate) : await findRelationshipsByEndpoints(model, source, target, options.predicate);
232
385
  if (toDelete.length === 0)
233
- throw new CLIError("No matching relationships found", ErrorCategory.USER);
234
- const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
386
+ throw new CLIError("No matching relationships found", ErrorCategory.USER, [
387
+ `Use "dr relationship list ${source}" to see the relationships recorded for ${source}`,
388
+ 'Use "dr relationship prune --dry-run" to list rows whose endpoints no longer resolve'
389
+ ]);
390
+ const sourceLayerName = await findElementLayer(model, source) ?? toDelete[0].layer, isInteractive = process.stdin.isTTY && process.stdout.isTTY;
235
391
  if (!options.force && isInteractive) {
236
392
  const { confirm } = await import("@clack/prompts");
237
393
  if (!await confirm({
@@ -248,24 +404,46 @@ Examples:
248
404
  }
249
405
  const activeChangesetId = await new StagingAreaManager(model.rootPath, model).getActiveId(), result = await deleteRelationshipHandler(model, source, target, options.predicate, {
250
406
  sourceLayerName,
251
- toDelete
407
+ // In a changeset the handler re-reads the projected graph itself.
408
+ toDelete: activeForDelete ? void 0 : toDelete
252
409
  }), stagedSuffix = activeChangesetId ? " [staged]" : "", deleteDetails = {
253
410
  source,
254
411
  target,
255
412
  layer: sourceLayerName,
256
- deletedCount: result.deletedCount
413
+ deletedCount: result.deletedCount,
414
+ ...result.staged ? { relationships: result.staged } : {}
257
415
  };
258
416
  options.predicate && (deleteDetails.predicate = options.predicate), activeChangesetId && (deleteDetails.changesetStatus = "staged"), handleSuccess(`Deleted ${result.deletedCount} relationship(s) from ${ansis.bold(source)} to ${ansis.bold(target)}${stagedSuffix}`, deleteDetails, { verbose: options.verbose });
259
417
  } catch (error) {
260
418
  handleError(error);
261
419
  }
420
+ }), program.command("prune").description("Remove relationship rows whose source or target no longer resolves to an element").option("--model <path>", "Path to model root (contains model/manifest.yaml)").option("--dry-run", "List the rows that would be removed without writing").option("--layers <layers...>", "Only scan rows whose source or target layer is one of these canonical layer names").option("--format <format>", "Output format: text, json", "text").addHelpText("after", `
421
+ A row is dangling when its source or target matches neither the UUID id nor the
422
+ dotted path of any current element. Kept rows keep their order and the file's
423
+ header comment is preserved. Inside an active changeset the removals are staged
424
+ as relationship deletions instead of being written to relationships.yaml.
425
+
426
+ Examples:
427
+ $ dr relationship prune --dry-run
428
+ $ dr relationship prune
429
+ $ dr relationship prune --layers api application --format json`).action(async (options) => {
430
+ try {
431
+ await pruneRelationshipsCommand({
432
+ model: options.model,
433
+ dryRun: !!options.dryRun,
434
+ layers: options.layers,
435
+ format: options.format
436
+ });
437
+ } catch (error) {
438
+ handleError(error);
439
+ }
262
440
  }), program.command("list <id>").description("List relationships for an element").option("--model <path>", "Path to model root (contains model/manifest.yaml)").option("--direction <dir>", "Filter by direction (incoming/outgoing/all)", "all").option("--json", "Output as JSON").addHelpText("after", `
263
441
  Examples:
264
442
  $ dr relationship list api-endpoint-create-customer
265
443
  $ dr relationship list business-service-order --direction outgoing
266
444
  $ dr relationship list element-1 --json`).action(async (id, options) => {
267
445
  try {
268
- const model = await Model.load(options.model || process.cwd());
446
+ const model = await Model.load(options.model);
269
447
  if (!await findElementLayer(model, id))
270
448
  throw new CLIError(`Element ${id} not found`, ErrorCategory.USER);
271
449
  const { outgoing, incoming } = model.relationships.getForElement(id);
@@ -295,7 +473,7 @@ Examples:
295
473
  $ dr relationship show api-endpoint-create-customer business-service-customer-mgmt
296
474
  $ dr relationship show application-component-api motivation-goal-sales-efficiency`).action(async (source, target, options) => {
297
475
  try {
298
- const model = await Model.load(options.model || process.cwd());
476
+ const model = await Model.load(options.model);
299
477
  if (!await findElementLayer(model, source))
300
478
  throw new CLIError(`Source element ${source} not found`, ErrorCategory.USER);
301
479
  const relationships = model.relationships.find(source, target);
@@ -320,9 +498,12 @@ Examples:
320
498
  });
321
499
  }
322
500
  export {
501
+ addRelationship,
323
502
  addRelationshipHandler,
324
503
  deleteRelationshipHandler,
504
+ findRelationshipsByEndpoints,
325
505
  getRelationshipConstraints,
506
+ pruneRelationshipsCommand,
326
507
  relationshipCommands,
327
508
  validateRelationshipCombination
328
509
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/commands/relationship.ts"],
4
- "sourcesContent": ["/**\n * Relationship subcommands for managing intra-layer and cross-layer relationships\n */\n\nimport { Command } from \"commander\";\nimport ansis from \"ansis\";\nimport { Model } from \"../core/model.js\";\nimport { StagingAreaManager } from \"../core/staging-area.js\";\nimport { findElementLayer } from \"../utils/element-utils.js\";\nimport { isValidLayerName } from \"../core/layers.js\";\nimport {\n CLIError,\n ErrorCategory,\n handleError,\n handleSuccess,\n handleInfo,\n getErrorMessage\n} from \"../utils/errors.js\";\nimport {\n getValidRelationships,\n getValidPredicatesForSource,\n getValidDestinationsForSourceAndPredicate\n} from \"../generated/relationship-index.js\";\nimport { normalizeNodeType } from \"../generated/node-types.js\";\nimport { ModelReportOrchestrator } from \"../reports/model-report-orchestrator.js\";\nimport { emitLog, SeverityNumber } from \"../telemetry/index.js\";\nimport { Element } from \"../core/element.js\";\nimport { Relationship } from \"../core/relationships.js\";\nimport { RelationshipCatalog } from \"../core/relationship-catalog.js\";\n\n/**\n * Pre-resolved element and layer information to avoid redundant lookups.\n * Ensures source and target pairs are consistent: if source is provided, both element and layerName must be present.\n * This prevents invalid states like {sourceElement: ..., sourceLayerName: undefined}.\n */\ninterface PreResolvedElements {\n source?: {\n element: Element;\n layerName: string;\n };\n target?: {\n element: Element;\n layerName: string;\n };\n}\n\n/**\n * Validate element properties and construct a SpecNodeId\n * @throws {CLIError} if element properties are invalid\n */\nfunction constructSpecNodeId(\n element: { layer_id?: unknown; type?: unknown },\n elementId: string\n): string {\n const layerId = element.layer_id;\n const type = element.type;\n\n if (typeof layerId !== \"string\" || !layerId) {\n throw new CLIError(\n `Invalid element ${elementId}: missing or invalid layer_id`,\n ErrorCategory.SYSTEM\n );\n }\n\n if (typeof type !== \"string\" || !type) {\n throw new CLIError(\n `Invalid element ${elementId}: missing or invalid type`,\n ErrorCategory.SYSTEM\n );\n }\n\n // Normalize type name for schema validation (e.g., \"process\" -> \"businessprocess\")\n const normalizedType = normalizeNodeType(layerId, type);\n\n return `${layerId}.${normalizedType}`;\n}\n\n/**\n * Validate a relationship combination using schema-driven registry\n * @returns {valid, suggestions} where suggestions are provided if invalid\n */\nexport function validateRelationshipCombination(\n sourceSpecNodeId: string,\n predicate: string,\n destSpecNodeId: string\n): { valid: boolean; suggestions?: string[] } {\n const valid =\n getValidRelationships(sourceSpecNodeId, predicate, destSpecNodeId).length >\n 0;\n\n if (valid) {\n return { valid: true };\n }\n\n // Generate helpful suggestions\n const suggestions: string[] = [];\n const validPredicates = getValidPredicatesForSource(sourceSpecNodeId);\n\n if (validPredicates.length > 0) {\n suggestions.push(\n `Valid predicates for ${sourceSpecNodeId}: ${validPredicates.join(\", \")}`\n );\n } else {\n suggestions.push(`No valid relationships defined for ${sourceSpecNodeId}`);\n }\n\n // If predicate is provided, show valid destinations\n if (predicate) {\n const validDests = getValidDestinationsForSourceAndPredicate(\n sourceSpecNodeId,\n predicate\n );\n if (validDests.length > 0) {\n suggestions.push(\n `Valid destinations for --[${predicate}]-->: ${validDests.join(\", \")}`\n );\n } else {\n suggestions.push(\n `No valid destinations for ${sourceSpecNodeId} --[${predicate}]-->`\n );\n }\n }\n\n return { valid: false, suggestions };\n}\n\n/**\n * Get cardinality and strength info for a relationship\n */\nexport function getRelationshipConstraints(\n sourceSpecNodeId: string,\n predicate: string,\n destSpecNodeId: string\n): { cardinality: string; strength: string } | null {\n const rels = getValidRelationships(\n sourceSpecNodeId,\n predicate,\n destSpecNodeId\n );\n if (rels.length === 0) return null;\n\n const rel = rels[0];\n return {\n cardinality: rel.cardinality,\n strength: rel.strength\n };\n}\n\n/**\n * Handler function for adding a relationship\n * Encapsulates the core logic of the add-relationship command\n * @internal Exported for testing purposes\n *\n * @param model - The architecture model\n * @param source - Source element ID\n * @param target - Target element ID\n * @param predicate - Relationship predicate\n * @param properties - Optional relationship properties\n * @param preResolved - Pre-resolved elements and layer names (optional, for performance)\n */\nexport async function addRelationshipHandler(\n model: Model,\n source: string,\n target: string,\n predicate: string,\n properties?: Record<string, unknown>,\n preResolved?: PreResolvedElements\n): Promise<boolean> {\n // Use pre-resolved elements if provided, otherwise resolve them\n let resolvedSourceLayerName = preResolved?.source?.layerName;\n let resolvedSourceElement = preResolved?.source?.element;\n let resolvedTargetLayerName = preResolved?.target?.layerName;\n let resolvedTargetElement = preResolved?.target?.element;\n\n if (!resolvedSourceLayerName) {\n resolvedSourceLayerName = await findElementLayer(model, source);\n if (!resolvedSourceLayerName) {\n throw new CLIError(\n `Source element ${source} not found`,\n ErrorCategory.USER\n );\n }\n }\n\n if (!resolvedSourceElement) {\n const sourceLayer = await model.getLayer(resolvedSourceLayerName);\n if (!sourceLayer) {\n throw new CLIError(\n `Source element ${source} not found`,\n ErrorCategory.USER\n );\n }\n resolvedSourceElement = sourceLayer.getElement(source);\n if (!resolvedSourceElement) {\n throw new CLIError(\n `Source element ${source} not found`,\n ErrorCategory.USER\n );\n }\n }\n\n if (!resolvedTargetLayerName) {\n resolvedTargetLayerName = await findElementLayer(model, target);\n if (!resolvedTargetLayerName) {\n throw new CLIError(\n `Target element ${target} not found`,\n ErrorCategory.USER\n );\n }\n }\n\n if (!resolvedTargetElement) {\n const targetLayer = await model.getLayer(resolvedTargetLayerName);\n if (!targetLayer) {\n throw new CLIError(\n `Target element ${target} not found`,\n ErrorCategory.USER\n );\n }\n resolvedTargetElement = targetLayer.getElement(target);\n if (!resolvedTargetElement) {\n throw new CLIError(\n `Target element ${target} not found`,\n ErrorCategory.USER\n );\n }\n }\n\n // Schema-driven validation\n const sourceSpecNodeId = constructSpecNodeId(resolvedSourceElement, source);\n const targetSpecNodeId = constructSpecNodeId(resolvedTargetElement, target);\n\n const validation = validateRelationshipCombination(\n sourceSpecNodeId,\n predicate,\n targetSpecNodeId\n );\n\n if (!validation.valid) {\n throw new CLIError(\n `Invalid relationship: ${source} --[${predicate}]--> ${target}`,\n ErrorCategory.USER,\n validation.suggestions\n );\n }\n\n {\n const catalog = new RelationshipCatalog();\n await catalog.load();\n const relType = catalog.getTypeByPredicate(predicate);\n if (relType && !relType.semantics.reflexivity && source === target) {\n throw new CLIError(\n `Invalid relationship: predicate \"${predicate}\" does not allow reflexive relationships (${source})`,\n ErrorCategory.USER\n );\n }\n }\n\n // Guard against duplicate relationships before staging or writing\n if (model.relationships.find(source, target, predicate).length > 0) {\n // Return false to signal duplicate; caller will emit appropriate output\n return false;\n }\n\n // Check for active changeset \u2014 stage relationship instead of writing directly\n const stagingManager = new StagingAreaManager(model.rootPath, model);\n const activeChangesetId = await stagingManager.getActiveId();\n\n const relData = {\n source,\n target,\n predicate,\n layer: resolvedSourceLayerName,\n ...(resolvedTargetLayerName !== resolvedSourceLayerName\n ? { targetLayer: resolvedTargetLayerName }\n : {}),\n category: \"structural\" as const,\n ...(properties ? { properties } : {})\n };\n\n if (activeChangesetId) {\n // Stage the relationship change in the active changeset\n const compositeKey = `${source}::${predicate}::${target}`;\n await stagingManager.stage(activeChangesetId, {\n type: \"relationship-add\",\n elementId: compositeKey,\n layerName: resolvedSourceLayerName,\n after: relData\n });\n } else {\n // No active changeset \u2014 write directly to relationships.yaml\n model.relationships.add(relData);\n await model.saveRelationships();\n await model.saveManifest();\n\n // Regenerate reports for all affected layers (including transitively related)\n try {\n const orchestrator = new ModelReportOrchestrator(model, model.rootPath);\n const affectedLayers = new Set<string>();\n\n // Compute affected layers for source layer\n if (isValidLayerName(resolvedSourceLayerName)) {\n for (const layer of orchestrator.computeAffectedLayers(\n resolvedSourceLayerName\n )) {\n affectedLayers.add(layer);\n }\n }\n\n // Compute affected layers for target layer\n if (isValidLayerName(resolvedTargetLayerName)) {\n for (const layer of orchestrator.computeAffectedLayers(\n resolvedTargetLayerName\n )) {\n affectedLayers.add(layer);\n }\n }\n\n await orchestrator.regenerate(affectedLayers);\n } catch (error) {\n emitLog(\n SeverityNumber.WARN,\n \"Failed to regenerate layer reports after relationship add\",\n {\n \"relationship.predicate\": predicate,\n \"relationship.sourceLayer\": resolvedSourceLayerName,\n \"relationship.targetLayer\": resolvedTargetLayerName,\n \"relationship.source\": source,\n \"relationship.target\": target,\n \"error.message\": getErrorMessage(error)\n }\n );\n }\n }\n\n return true;\n}\n\n/**\n * Handler function for deleting relationships\n * Encapsulates the core logic of the delete-relationship command\n * @internal Exported for testing purposes\n *\n * @param model - The architecture model\n * @param source - Source element ID\n * @param target - Target element ID\n * @param predicate - Optional relationship predicate\n * @param preResolved - Pre-resolved source layer name and relationships (optional, for performance)\n */\ninterface DeletePreResolved {\n sourceLayerName?: string;\n toDelete?: Relationship[];\n}\n\nexport async function deleteRelationshipHandler(\n model: Model,\n source: string,\n target: string,\n predicate?: string,\n preResolved?: DeletePreResolved\n): Promise<{ deletedCount: number }> {\n // Use pre-resolved source layer if provided, otherwise resolve it\n let resolvedSourceLayerName = preResolved?.sourceLayerName;\n if (!resolvedSourceLayerName) {\n resolvedSourceLayerName = await findElementLayer(model, source);\n if (!resolvedSourceLayerName) {\n throw new CLIError(\n `Source element ${source} not found`,\n ErrorCategory.USER\n );\n }\n }\n\n // Use pre-found relationships if provided, otherwise find them\n let resolvedToDelete = preResolved?.toDelete;\n if (!resolvedToDelete) {\n resolvedToDelete = model.relationships.find(source, target, predicate);\n }\n\n if (resolvedToDelete.length === 0) {\n throw new CLIError(\"No matching relationships found\", ErrorCategory.USER);\n }\n\n // Check for active changeset \u2014 stage deletion instead of writing directly\n const stagingManager = new StagingAreaManager(model.rootPath, model);\n const activeChangesetId = await stagingManager.getActiveId();\n\n if (activeChangesetId) {\n // Stage each relationship deletion in the active changeset\n for (const rel of resolvedToDelete) {\n const compositeKey = `${rel.source}::${rel.predicate}::${rel.target}`;\n await stagingManager.stage(activeChangesetId, {\n type: \"relationship-delete\",\n elementId: compositeKey,\n layerName: rel.layer,\n before: {\n source: rel.source,\n target: rel.target,\n predicate: rel.predicate,\n layer: rel.layer,\n ...(rel.targetLayer ? { targetLayer: rel.targetLayer } : {}),\n ...(rel.properties ? { properties: rel.properties } : {})\n }\n });\n }\n } else {\n // No active changeset \u2014 write directly\n model.relationships.delete(source, target, predicate);\n await model.saveRelationships();\n await model.saveManifest();\n\n // Regenerate reports for all affected layers (including transitively related)\n try {\n const orchestrator = new ModelReportOrchestrator(model, model.rootPath);\n const affectedLayers = new Set<string>();\n\n // Compute affected layers for each deleted relationship's source and target\n for (const rel of resolvedToDelete) {\n // Compute affected layers for source layer\n if (rel.layer && isValidLayerName(rel.layer)) {\n for (const layer of orchestrator.computeAffectedLayers(rel.layer)) {\n affectedLayers.add(layer);\n }\n }\n\n // Compute affected layers for target layer\n if (rel.targetLayer && isValidLayerName(rel.targetLayer)) {\n for (const layer of orchestrator.computeAffectedLayers(\n rel.targetLayer\n )) {\n affectedLayers.add(layer);\n }\n }\n }\n\n await orchestrator.regenerate(affectedLayers);\n } catch (error) {\n emitLog(\n SeverityNumber.WARN,\n \"Failed to regenerate layer reports after relationship delete\",\n {\n \"relationship.predicate\": predicate,\n \"relationship.source\": source,\n \"relationship.target\": target,\n \"relationship.deleteCount\": resolvedToDelete.length,\n \"error.message\": getErrorMessage(error)\n }\n );\n }\n }\n\n return { deletedCount: resolvedToDelete.length };\n}\n\nexport function relationshipCommands(program: Command, rootProgram?: Command): void {\n program\n .command(\"add <source> <target>\")\n .description(\n \"Add a relationship between elements (intra-layer or cross-layer)\"\n )\n .option(\n \"--model <path>\",\n \"Path to model root (contains model/manifest.yaml)\"\n )\n .requiredOption(\n \"--predicate <predicate>\",\n \"Relationship predicate (e.g., depends-on, realizes, exposes)\"\n )\n .option(\"--properties <json>\", \"Relationship properties (JSON)\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ dr relationship add motivation.goal.goal-a motivation.goal.goal-b --predicate aggregates\n $ dr relationship add api.operation.create-order application.service.order-service --predicate exposes\n $ dr relationship add business.businessprocess.checkout application.applicationprocess.checkout --predicate aggregates\n $ dr relationship add element-1 element-2 --predicate realizes --properties '{\"strength\":\"high\"}'`\n )\n .action(async (source, target, options) => {\n try {\n // Load model\n const model = await Model.load(options.model || process.cwd());\n\n // Find source element for constraints info\n const sourceLayerName = await findElementLayer(model, source);\n if (!sourceLayerName) {\n throw new CLIError(\n `Source element ${source} not found`,\n ErrorCategory.USER\n );\n }\n const sourceLayer = await model.getLayer(sourceLayerName);\n if (!sourceLayer) {\n throw new CLIError(\n `Source element ${source} not found`,\n ErrorCategory.USER\n );\n }\n const sourceElement = sourceLayer.getElement(source);\n if (!sourceElement) {\n throw new CLIError(\n `Source element ${source} not found`,\n ErrorCategory.USER\n );\n }\n\n // Find target element for constraints info\n const targetLayerName = await findElementLayer(model, target);\n if (!targetLayerName) {\n throw new CLIError(\n `Target element ${target} not found`,\n ErrorCategory.USER\n );\n }\n const targetLayer = await model.getLayer(targetLayerName);\n if (!targetLayer) {\n throw new CLIError(\n `Target element ${target} not found`,\n ErrorCategory.USER\n );\n }\n const targetElement = targetLayer.getElement(target);\n if (!targetElement) {\n throw new CLIError(\n `Target element ${target} not found`,\n ErrorCategory.USER\n );\n }\n\n // Get cardinality and strength info (before calling handler for CLI output)\n const sourceSpecNodeId = constructSpecNodeId(sourceElement, source);\n const targetSpecNodeId = constructSpecNodeId(targetElement, target);\n const constraints = getRelationshipConstraints(\n sourceSpecNodeId,\n options.predicate,\n targetSpecNodeId\n );\n\n // Parse properties if provided\n let properties: Record<string, unknown> | undefined;\n if (options.properties) {\n try {\n properties = JSON.parse(options.properties);\n } catch (e) {\n throw new CLIError(\n \"Invalid JSON in --properties\",\n ErrorCategory.USER\n );\n }\n }\n\n // Check for active changeset\n const stagingManager = new StagingAreaManager(model.rootPath, model);\n const activeChangesetId = await stagingManager.getActiveId();\n\n // Call the handler function with pre-resolved elements to avoid duplicate lookups\n const added = await addRelationshipHandler(\n model,\n source,\n target,\n options.predicate,\n properties,\n {\n source: { element: sourceElement, layerName: sourceLayerName },\n target: { element: targetElement, layerName: targetLayerName }\n }\n );\n\n if (!added) {\n // duplicate \u2014 emit JSON response indicating relationship already exists\n handleSuccess(\n `Relationship ${source} --[${options.predicate}]--> ${target} already exists`,\n {\n source,\n target,\n predicate: options.predicate,\n layer: sourceLayerName,\n duplicate: true,\n },\n { verbose: options.verbose }\n );\n return;\n }\n\n // Show cardinality and strength in output\n let message = `Added relationship: ${ansis.bold(source)} ${options.predicate} ${ansis.bold(target)}`;\n const details: Record<string, unknown> = {\n source,\n target,\n predicate: options.predicate,\n layer: sourceLayerName,\n };\n if (constraints) {\n message += ansis.dim(\n ` (${constraints.cardinality}, ${constraints.strength} strength)`\n );\n details.cardinality = constraints.cardinality;\n details.strength = constraints.strength;\n }\n if (activeChangesetId) {\n message += ansis.dim(` [staged]`);\n details.changesetStatus = \"staged\";\n }\n\n handleSuccess(message, details, { verbose: options.verbose });\n } catch (error) {\n handleError(error);\n }\n });\n\n program\n .command(\"delete <source> <target>\")\n .description(\"Delete a relationship\")\n .option(\n \"--model <path>\",\n \"Path to model root (contains model/manifest.yaml)\"\n )\n .option(\n \"--predicate <predicate>\",\n \"Specific predicate to delete (optional, delete all if not specified)\"\n )\n .option(\"--force\", \"Skip confirmation prompt\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ dr relationship delete element-1 element-2 --predicate depends-on\n $ dr relationship delete element-1 element-2 --force`\n )\n .action(async (source, target, options) => {\n try {\n // Load model\n const model = await Model.load(options.model || process.cwd());\n\n // Find source element and relationships to delete (for confirmation)\n const sourceLayerName = await findElementLayer(model, source);\n if (!sourceLayerName) {\n throw new CLIError(\n `Source element ${source} not found`,\n ErrorCategory.USER\n );\n }\n\n // Find relationships to delete (to show confirmation count)\n const toDelete = model.relationships.find(\n source,\n target,\n options.predicate\n );\n\n if (toDelete.length === 0) {\n throw new CLIError(\n \"No matching relationships found\",\n ErrorCategory.USER\n );\n }\n\n // Confirm deletion unless --force or non-interactive environment\n // In non-interactive environments (CI, tests), skip confirmation\n const isInteractive = process.stdin.isTTY && process.stdout.isTTY;\n if (!options.force && isInteractive) {\n // Dynamic import of prompts is safe here: caught by outer try-catch\n // This allows lazy loading of the prompt library only when needed\n const { confirm } = await import(\"@clack/prompts\");\n const confirmed = await confirm({\n message: `Delete ${toDelete.length} relationship(s)? This cannot be undone.`,\n initialValue: false\n });\n\n if (!confirmed) {\n handleSuccess(\"Deletion cancelled\", {\n source,\n target,\n status: \"cancelled\",\n });\n return;\n }\n }\n\n // Check for active changeset\n const stagingManager = new StagingAreaManager(model.rootPath, model);\n const activeChangesetId = await stagingManager.getActiveId();\n\n // Call the handler function with pre-resolved values to avoid duplicate lookups\n const result = await deleteRelationshipHandler(\n model,\n source,\n target,\n options.predicate,\n {\n sourceLayerName,\n toDelete\n }\n );\n\n const stagedSuffix = activeChangesetId ? \" [staged]\" : \"\";\n const deleteDetails: Record<string, unknown> = {\n source,\n target,\n layer: sourceLayerName,\n deletedCount: result.deletedCount,\n };\n if (options.predicate) {\n deleteDetails.predicate = options.predicate;\n }\n if (activeChangesetId) {\n deleteDetails.changesetStatus = \"staged\";\n }\n handleSuccess(\n `Deleted ${result.deletedCount} relationship(s) from ${ansis.bold(source)} to ${ansis.bold(target)}${stagedSuffix}`,\n deleteDetails,\n { verbose: options.verbose }\n );\n } catch (error) {\n handleError(error);\n }\n });\n\n program\n .command(\"list <id>\")\n .description(\"List relationships for an element\")\n .option(\n \"--model <path>\",\n \"Path to model root (contains model/manifest.yaml)\"\n )\n .option(\n \"--direction <dir>\",\n \"Filter by direction (incoming/outgoing/all)\",\n \"all\"\n )\n .option(\"--json\", \"Output as JSON\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ dr relationship list api-endpoint-create-customer\n $ dr relationship list business-service-order --direction outgoing\n $ dr relationship list element-1 --json`\n )\n .action(async (id, options) => {\n try {\n // Load model\n const model = await Model.load(options.model || process.cwd());\n const layerName = await findElementLayer(model, id);\n\n if (!layerName) {\n throw new CLIError(`Element ${id} not found`, ErrorCategory.USER);\n }\n\n // Get relationships from centralized store\n const { outgoing, incoming } = model.relationships.getForElement(id);\n\n // Filter by direction\n let relationships: typeof outgoing = [];\n if (options.direction === \"outgoing\" || options.direction === \"all\") {\n relationships.push(...outgoing);\n }\n if (options.direction === \"incoming\" || options.direction === \"all\") {\n relationships.push(...incoming);\n }\n\n const shouldOutputJson = options.json ?? (rootProgram?.opts().json);\n if (shouldOutputJson) {\n console.log(JSON.stringify(relationships, null, 2));\n return;\n }\n\n if (relationships.length === 0) {\n handleInfo(\n ansis.yellow(`No ${options.direction} relationships for ${id}`)\n );\n return;\n }\n\n handleInfo(\"\");\n handleInfo(\n ansis.bold(\n `${options.direction === \"all\" ? \"\" : options.direction + \" \"}relationships for ${ansis.cyan(id)}:`\n )\n );\n handleInfo(ansis.dim(\"\u2500\".repeat(80)));\n\n for (const rel of relationships) {\n const isOutgoing = rel.source === id;\n const direction = isOutgoing ? \"\u2192\" : \"\u2190\";\n const otherElement = isOutgoing ? rel.target : rel.source;\n const crossLayer =\n rel.targetLayer && rel.targetLayer !== rel.layer\n ? ansis.dim(` [${rel.layer} \u2192 ${rel.targetLayer}]`)\n : \"\";\n\n handleInfo(\n ` ${direction} ${ansis.magenta(rel.predicate)}: ${ansis.yellow(otherElement)}${crossLayer}`\n );\n\n if (rel.properties) {\n const propStr = Object.entries(rel.properties)\n .map(([k, v]) => `${k}=${v}`)\n .join(\", \");\n handleInfo(` ${ansis.dim(propStr)}`);\n }\n }\n\n handleInfo(ansis.dim(\"\u2500\".repeat(80)));\n handleInfo(ansis.dim(`Total: ${relationships.length} relationship(s)`));\n handleInfo(\"\");\n } catch (error) {\n handleError(error);\n }\n });\n\n program\n .command(\"show <source> <target>\")\n .description(\"Show relationship details\")\n .option(\n \"--model <path>\",\n \"Path to model root (contains model/manifest.yaml)\"\n )\n .addHelpText(\n \"after\",\n `\nExamples:\n $ dr relationship show api-endpoint-create-customer business-service-customer-mgmt\n $ dr relationship show application-component-api motivation-goal-sales-efficiency`\n )\n .action(async (source, target, options) => {\n try {\n // Load model\n const model = await Model.load(options.model || process.cwd());\n const sourceLayerName = await findElementLayer(model, source);\n\n if (!sourceLayerName) {\n throw new CLIError(\n `Source element ${source} not found`,\n ErrorCategory.USER\n );\n }\n\n // Find relationships from centralized store\n const relationships = model.relationships.find(source, target);\n\n if (relationships.length === 0) {\n throw new CLIError(\n `No relationships from ${source} to ${target}`,\n ErrorCategory.USER\n );\n }\n\n handleInfo(\"\");\n handleInfo(\n ansis.bold(\n `Relationship${relationships.length > 1 ? \"s\" : \"\"} from ${ansis.cyan(source)} to ${ansis.yellow(target)}:`\n )\n );\n handleInfo(ansis.dim(\"\u2500\".repeat(60)));\n\n for (let i = 0; i < relationships.length; i++) {\n const rel = relationships[i];\n\n if (i > 0) {\n handleInfo(\"\");\n }\n\n handleInfo(`Predicate: ${ansis.magenta(rel.predicate)}`);\n handleInfo(`Layer: ${ansis.cyan(rel.layer)}`);\n if (rel.targetLayer && rel.targetLayer !== rel.layer) {\n handleInfo(`Target Layer: ${ansis.cyan(rel.targetLayer)}`);\n }\n\n if (rel.properties && Object.keys(rel.properties).length > 0) {\n handleInfo(\"Properties:\");\n for (const [key, value] of Object.entries(rel.properties)) {\n const displayValue =\n typeof value === \"string\"\n ? value\n : JSON.stringify(value, null, 2);\n handleInfo(` ${ansis.cyan(key)}: ${displayValue}`);\n }\n } else {\n handleInfo(\"Properties: none\");\n }\n }\n\n handleInfo(\"\");\n } catch (error) {\n handleError(error);\n }\n });\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Relationship subcommands for managing intra-layer and cross-layer relationships\n */\n\nimport { Command } from \"commander\";\nimport ansis from \"ansis\";\nimport { Model } from \"../core/model.js\";\nimport { StagingAreaManager } from \"../core/staging-area.js\";\nimport { findElementLayer } from \"../utils/element-utils.js\";\nimport { isValidLayerName } from \"../core/layers.js\";\nimport {\n CLIError,\n ErrorCategory,\n handleError,\n handleSuccess,\n handleInfo,\n getErrorMessage\n} from \"../utils/errors.js\";\nimport {\n getValidRelationships,\n getValidPredicatesForSource,\n getValidDestinationsForSourceAndPredicate\n} from \"../generated/relationship-index.js\";\nimport { normalizeNodeType } from \"../generated/node-types.js\";\nimport { ModelReportOrchestrator } from \"../reports/model-report-orchestrator.js\";\nimport { emitLog, SeverityNumber } from \"../telemetry/index.js\";\nimport { Element } from \"../core/element.js\";\nimport { Relationship, elementIdentifiers } from \"../core/relationships.js\";\nimport { RelationshipCatalog } from \"../core/relationship-catalog.js\";\nimport {\n collectKnownIdentifiers,\n findDanglingRelationships,\n type UnresolvedSide,\n} from \"../core/relationship-prune.js\";\nimport { isJson } from \"../utils/globals.js\";\nimport type { HintSurface } from \"../utils/error-hints.js\";\nimport { ChangesetGraph } from \"../core/changeset-graph.js\";\nimport type { Changeset } from \"../core/changeset.js\";\nimport {\n liveRelationshipsBetween,\n planRelationshipAdd,\n stageRelationshipDeletes,\n type StagedRelationshipDeleteResult,\n} from \"../core/relationship-staging.js\";\n\n/**\n * Pre-resolved element and layer information to avoid redundant lookups.\n * Ensures source and target pairs are consistent: if source is provided, both element and layerName must be present.\n * This prevents invalid states like {sourceElement: ..., sourceLayerName: undefined}.\n */\nexport interface PreResolvedElements {\n source?: {\n element: Element;\n layerName: string;\n };\n target?: {\n element: Element;\n layerName: string;\n };\n}\n\n/**\n * Validate element properties and construct a SpecNodeId\n * @throws {CLIError} if element properties are invalid\n */\nfunction constructSpecNodeId(\n element: { layer_id?: unknown; type?: unknown },\n elementId: string\n): string {\n const layerId = element.layer_id;\n const type = element.type;\n\n if (typeof layerId !== \"string\" || !layerId) {\n throw new CLIError(\n `Invalid element ${elementId}: missing or invalid layer_id`,\n ErrorCategory.SYSTEM\n );\n }\n\n if (typeof type !== \"string\" || !type) {\n throw new CLIError(\n `Invalid element ${elementId}: missing or invalid type`,\n ErrorCategory.SYSTEM\n );\n }\n\n // Normalize type name for schema validation (e.g., \"process\" -> \"businessprocess\")\n const normalizedType = normalizeNodeType(layerId, type);\n\n return `${layerId}.${normalizedType}`;\n}\n\n/**\n * Validate a relationship combination using schema-driven registry\n * @returns {valid, suggestions} where suggestions are provided if invalid\n */\nexport function validateRelationshipCombination(\n sourceSpecNodeId: string,\n predicate: string,\n destSpecNodeId: string\n): { valid: boolean; suggestions?: string[] } {\n const valid =\n getValidRelationships(sourceSpecNodeId, predicate, destSpecNodeId).length >\n 0;\n\n if (valid) {\n return { valid: true };\n }\n\n // Generate helpful suggestions\n const suggestions: string[] = [];\n const validPredicates = getValidPredicatesForSource(sourceSpecNodeId);\n\n if (validPredicates.length > 0) {\n suggestions.push(\n `Valid predicates for ${sourceSpecNodeId}: ${validPredicates.join(\", \")}`\n );\n } else {\n suggestions.push(`No valid relationships defined for ${sourceSpecNodeId}`);\n }\n\n // If predicate is provided, show valid destinations\n if (predicate) {\n const validDests = getValidDestinationsForSourceAndPredicate(\n sourceSpecNodeId,\n predicate\n );\n if (validDests.length > 0) {\n suggestions.push(\n `Valid destinations for --[${predicate}]-->: ${validDests.join(\", \")}`\n );\n } else {\n suggestions.push(\n `No valid destinations for ${sourceSpecNodeId} --[${predicate}]-->`\n );\n }\n }\n\n return { valid: false, suggestions };\n}\n\n/**\n * Get cardinality and strength info for a relationship\n */\nexport function getRelationshipConstraints(\n sourceSpecNodeId: string,\n predicate: string,\n destSpecNodeId: string\n): { cardinality: string; strength: string } | null {\n const rels = getValidRelationships(\n sourceSpecNodeId,\n predicate,\n destSpecNodeId\n );\n if (rels.length === 0) return null;\n\n const rel = rels[0];\n return {\n cardinality: rel.cardinality,\n strength: rel.strength\n };\n}\n\n/**\n * Handler function for adding a relationship\n * Encapsulates the core logic of the add-relationship command\n * @internal Exported for testing purposes\n *\n * @param model - The architecture model\n * @param source - Source element ID\n * @param target - Target element ID\n * @param predicate - Relationship predicate\n * @param properties - Optional relationship properties\n * @param preResolved - Pre-resolved elements and layer names (optional, for performance)\n */\nexport async function addRelationshipHandler(\n model: Model,\n source: string,\n target: string,\n predicate: string,\n properties?: Record<string, unknown>,\n preResolved?: PreResolvedElements\n): Promise<boolean> {\n const outcome = await addRelationship(model, source, target, predicate, properties, preResolved);\n return outcome !== \"duplicate\";\n}\n\n/**\n * What adding a relationship did:\n * - \"added\": written to relationships.yaml (no active changeset)\n * - \"staged\": staged in the active changeset\n * - \"restored\": the active changeset had staged its deletion; that deletion was unstaged\n * - \"duplicate\": already present (in the base model, or in the active changeset's projection); nothing written\n */\nexport type AddRelationshipOutcome = \"added\" | \"staged\" | \"restored\" | \"duplicate\";\n\n/** The active changeset when one is set and can accept changes, else null. */\nasync function activeStagedChangeset(manager: StagingAreaManager): Promise<Changeset | null> {\n const active = await manager.getActive();\n return active && active.status === \"staged\" ? active : null;\n}\n\n/**\n * Adds a relationship, validated against the catalog. Inside an active changeset the duplicate\n * check reads the changeset's projected graph (base plus staged relationships), so adding the same\n * relationship twice stages it once, and re-adding one the changeset deletes unstages the delete.\n */\nexport async function addRelationship(\n model: Model,\n source: string,\n target: string,\n predicate: string,\n properties?: Record<string, unknown>,\n preResolved?: PreResolvedElements\n): Promise<AddRelationshipOutcome> {\n // Use pre-resolved elements if provided, otherwise resolve them\n let resolvedSourceLayerName = preResolved?.source?.layerName;\n let resolvedSourceElement = preResolved?.source?.element;\n let resolvedTargetLayerName = preResolved?.target?.layerName;\n let resolvedTargetElement = preResolved?.target?.element;\n\n if (!resolvedSourceLayerName) {\n resolvedSourceLayerName = await findElementLayer(model, source);\n if (!resolvedSourceLayerName) {\n throw new CLIError(\n `Source element ${source} not found`,\n ErrorCategory.USER\n );\n }\n }\n\n if (!resolvedSourceElement) {\n const sourceLayer = await model.getLayer(resolvedSourceLayerName);\n if (!sourceLayer) {\n throw new CLIError(\n `Source element ${source} not found`,\n ErrorCategory.USER\n );\n }\n resolvedSourceElement = sourceLayer.getElement(source);\n if (!resolvedSourceElement) {\n throw new CLIError(\n `Source element ${source} not found`,\n ErrorCategory.USER\n );\n }\n }\n\n if (!resolvedTargetLayerName) {\n resolvedTargetLayerName = await findElementLayer(model, target);\n if (!resolvedTargetLayerName) {\n throw new CLIError(\n `Target element ${target} not found`,\n ErrorCategory.USER\n );\n }\n }\n\n if (!resolvedTargetElement) {\n const targetLayer = await model.getLayer(resolvedTargetLayerName);\n if (!targetLayer) {\n throw new CLIError(\n `Target element ${target} not found`,\n ErrorCategory.USER\n );\n }\n resolvedTargetElement = targetLayer.getElement(target);\n if (!resolvedTargetElement) {\n throw new CLIError(\n `Target element ${target} not found`,\n ErrorCategory.USER\n );\n }\n }\n\n // Schema-driven validation\n const sourceSpecNodeId = constructSpecNodeId(resolvedSourceElement, source);\n const targetSpecNodeId = constructSpecNodeId(resolvedTargetElement, target);\n\n const validation = validateRelationshipCombination(\n sourceSpecNodeId,\n predicate,\n targetSpecNodeId\n );\n\n if (!validation.valid) {\n throw new CLIError(\n `Invalid relationship: ${source} --[${predicate}]--> ${target}`,\n ErrorCategory.USER,\n validation.suggestions\n );\n }\n\n {\n const catalog = new RelationshipCatalog();\n await catalog.load();\n const relType = catalog.getTypeByPredicate(predicate);\n if (relType && !relType.semantics.reflexivity && source === target) {\n throw new CLIError(\n `Invalid relationship: predicate \"${predicate}\" does not allow reflexive relationships (${source})`,\n ErrorCategory.USER\n );\n }\n }\n\n // Check for active changeset \u2014 stage relationship instead of writing directly\n const stagingManager = new StagingAreaManager(model.rootPath, model);\n const activeChangesetId = await stagingManager.getActiveId();\n const activeChangeset = activeChangesetId ? await activeStagedChangeset(stagingManager) : null;\n\n // Guard against duplicate relationships before staging or writing. Inside a changeset the\n // projected graph is the reference: it holds what this changeset already staged, and drops\n // what it deletes.\n if (activeChangeset) {\n const graph = await ChangesetGraph.build(model, activeChangeset);\n const plan = planRelationshipAdd(graph, activeChangeset, { source, predicate, target });\n if (plan.action === \"duplicate\") {\n return \"duplicate\";\n }\n if (plan.action === \"restore\") {\n for (const key of plan.keys) {\n await stagingManager.unstage(activeChangeset.id!, key);\n }\n return \"restored\";\n }\n } else if (model.relationships.find(source, target, predicate).length > 0) {\n return \"duplicate\";\n }\n\n const relData = {\n source,\n target,\n predicate,\n layer: resolvedSourceLayerName,\n ...(resolvedTargetLayerName !== resolvedSourceLayerName\n ? { targetLayer: resolvedTargetLayerName }\n : {}),\n category: \"structural\" as const,\n ...(properties ? { properties } : {})\n };\n\n if (activeChangesetId) {\n // Stage the relationship change in the active changeset\n const compositeKey = `${source}::${predicate}::${target}`;\n await stagingManager.stage(activeChangesetId, {\n type: \"relationship-add\",\n elementId: compositeKey,\n layerName: resolvedSourceLayerName,\n after: relData\n });\n } else {\n // No active changeset \u2014 write directly to relationships.yaml\n model.relationships.add(relData);\n await model.saveRelationships();\n await model.saveManifest();\n\n // Regenerate reports for all affected layers (including transitively related)\n try {\n const orchestrator = new ModelReportOrchestrator(model, model.rootPath);\n const affectedLayers = new Set<string>();\n\n // Compute affected layers for source layer\n if (isValidLayerName(resolvedSourceLayerName)) {\n for (const layer of orchestrator.computeAffectedLayers(\n resolvedSourceLayerName\n )) {\n affectedLayers.add(layer);\n }\n }\n\n // Compute affected layers for target layer\n if (isValidLayerName(resolvedTargetLayerName)) {\n for (const layer of orchestrator.computeAffectedLayers(\n resolvedTargetLayerName\n )) {\n affectedLayers.add(layer);\n }\n }\n\n await orchestrator.regenerate(affectedLayers);\n } catch (error) {\n emitLog(\n SeverityNumber.WARN,\n \"Failed to regenerate layer reports after relationship add\",\n {\n \"relationship.predicate\": predicate,\n \"relationship.sourceLayer\": resolvedSourceLayerName,\n \"relationship.targetLayer\": resolvedTargetLayerName,\n \"relationship.source\": source,\n \"relationship.target\": target,\n \"error.message\": getErrorMessage(error)\n }\n );\n }\n }\n\n return activeChangesetId ? \"staged\" : \"added\";\n}\n\n/**\n * Find relationships between two endpoints as they are recorded in relationships.yaml.\n *\n * Rows may name an element by UUID `id` or by dotted `path`, and the user may type\n * either form. Each endpoint matches a row when the row's value is the typed\n * identifier or any identifier of the element it resolves to. An endpoint that no\n * longer resolves to an element still matches rows that store the typed value.\n */\nexport async function findRelationshipsByEndpoints(\n model: Model,\n source: string,\n target: string,\n predicate?: string\n): Promise<Relationship[]> {\n const sourceIds = new Set(elementIdentifiers(model.getElementById(source) ?? {}, source));\n const targetIds = new Set(elementIdentifiers(model.getElementById(target) ?? {}, target));\n return model.relationships\n .getAll()\n .filter(\n (rel) =>\n sourceIds.has(rel.source) &&\n targetIds.has(rel.target) &&\n (!predicate || rel.predicate === predicate)\n );\n}\n\n/** Next steps when a relationship delete matches nothing, named for the caller's surface. */\nfunction noMatchingRelationshipHints(surface: HintSurface, source: string, inChangeset: boolean): string[] {\n if (surface === \"mcp\") {\n return [\n `Use \"model_show\" on ${source} to see its relationships`,\n inChangeset\n ? 'Use \"changeset_brief\" to see what this changeset already stages'\n : 'Use \"model_search\" to check the source and target element IDs',\n ];\n }\n return [\n `Use \"dr relationship list ${source}\" to see the relationships recorded for ${source}`,\n inChangeset\n ? 'Use \"dr changeset staged\" to see what the active changeset already stages'\n : 'Use \"dr relationship prune --dry-run\" to list rows whose endpoints no longer resolve',\n ];\n}\n\n/**\n * Handler function for deleting relationships\n * Encapsulates the core logic of the delete-relationship command\n * @internal Exported for testing purposes\n *\n * @param model - The architecture model\n * @param source - Source element ID\n * @param target - Target element ID\n * @param predicate - Optional relationship predicate\n * @param preResolved - Pre-resolved source layer name and relationships (optional, for performance)\n */\ninterface DeletePreResolved {\n sourceLayerName?: string;\n toDelete?: Relationship[];\n}\n\nexport async function deleteRelationshipHandler(\n model: Model,\n source: string,\n target: string,\n predicate?: string,\n preResolved?: DeletePreResolved,\n surface: HintSurface = \"cli\"\n): Promise<{ deletedCount: number; staged?: StagedRelationshipDeleteResult[] }> {\n // Inside a changeset, match against its projected graph: a relationship this changeset added is\n // unstaged (not deleted on top of itself), and one it already deletes is not matched again.\n const changesetManager = new StagingAreaManager(model.rootPath, model);\n const changeset = (await changesetManager.getActiveId())\n ? await activeStagedChangeset(changesetManager)\n : null;\n if (changeset) {\n const graph = await ChangesetGraph.build(model, changeset);\n const matches = liveRelationshipsBetween(graph, source, target, predicate);\n if (matches.length === 0) {\n throw new CLIError(\n \"No matching relationships found\",\n ErrorCategory.USER,\n noMatchingRelationshipHints(surface, source, true)\n );\n }\n const staged = await stageRelationshipDeletes(changesetManager, graph, changeset, matches);\n return { deletedCount: staged.length, staged };\n }\n\n // The source element need not resolve: a row whose endpoint was deleted is\n // exactly the row a user needs to remove (#963). The layer is informational only.\n let resolvedSourceLayerName = preResolved?.sourceLayerName;\n if (!resolvedSourceLayerName) {\n resolvedSourceLayerName = await findElementLayer(model, source);\n }\n\n // Use pre-found relationships if provided, otherwise find them\n let resolvedToDelete = preResolved?.toDelete;\n if (!resolvedToDelete) {\n resolvedToDelete = await findRelationshipsByEndpoints(model, source, target, predicate);\n }\n\n if (resolvedToDelete.length === 0) {\n throw new CLIError(\n \"No matching relationships found\",\n ErrorCategory.USER,\n noMatchingRelationshipHints(surface, source, false)\n );\n }\n\n // Check for active changeset \u2014 stage deletion instead of writing directly\n const stagingManager = new StagingAreaManager(model.rootPath, model);\n const activeChangesetId = await stagingManager.getActiveId();\n\n if (activeChangesetId) {\n // Stage each relationship deletion in the active changeset\n for (const rel of resolvedToDelete) {\n const compositeKey = `${rel.source}::${rel.predicate}::${rel.target}`;\n await stagingManager.stage(activeChangesetId, {\n type: \"relationship-delete\",\n elementId: compositeKey,\n layerName: rel.layer,\n before: {\n source: rel.source,\n target: rel.target,\n predicate: rel.predicate,\n layer: rel.layer,\n ...(rel.targetLayer ? { targetLayer: rel.targetLayer } : {}),\n ...(rel.properties ? { properties: rel.properties } : {})\n }\n });\n }\n } else {\n // No active changeset \u2014 write directly\n for (const rel of resolvedToDelete) {\n model.relationships.delete(rel.source, rel.target, rel.predicate);\n }\n await model.saveRelationships();\n await model.saveManifest();\n\n // Regenerate reports for all affected layers (including transitively related)\n try {\n const orchestrator = new ModelReportOrchestrator(model, model.rootPath);\n const affectedLayers = new Set<string>();\n\n // Compute affected layers for each deleted relationship's source and target\n for (const rel of resolvedToDelete) {\n // Compute affected layers for source layer\n if (rel.layer && isValidLayerName(rel.layer)) {\n for (const layer of orchestrator.computeAffectedLayers(rel.layer)) {\n affectedLayers.add(layer);\n }\n }\n\n // Compute affected layers for target layer\n if (rel.targetLayer && isValidLayerName(rel.targetLayer)) {\n for (const layer of orchestrator.computeAffectedLayers(\n rel.targetLayer\n )) {\n affectedLayers.add(layer);\n }\n }\n }\n\n await orchestrator.regenerate(affectedLayers);\n } catch (error) {\n emitLog(\n SeverityNumber.WARN,\n \"Failed to regenerate layer reports after relationship delete\",\n {\n \"relationship.predicate\": predicate,\n \"relationship.source\": source,\n \"relationship.target\": target,\n \"relationship.deleteCount\": resolvedToDelete.length,\n \"error.message\": getErrorMessage(error)\n }\n );\n }\n }\n\n return { deletedCount: resolvedToDelete.length };\n}\n\nexport interface PruneRelationshipsOptions {\n model?: string;\n dryRun?: boolean;\n layers?: string[];\n format?: string;\n}\n\nexport interface PruneRelationshipsResult {\n scanned: number;\n dangling: number;\n removed: number;\n dryRun: boolean;\n staged: boolean;\n changeset?: string;\n layers?: string[];\n relationships: Array<{\n source: string;\n predicate: string;\n target: string;\n layer: string;\n targetLayer?: string;\n unresolved: UnresolvedSide;\n }>;\n}\n\n/**\n * `dr relationship prune`: remove rows whose source or target resolves to no element.\n *\n * Elements are matched by UUID id and by dotted path (both are legal in\n * relationships.yaml). Inside an active changeset the removals are staged as\n * relationship-delete changes; otherwise relationships.yaml is rewritten with the\n * surviving rows in their original order and its header comment preserved.\n *\n * @internal Exported for testing purposes\n */\nexport async function pruneRelationshipsCommand(\n options: PruneRelationshipsOptions\n): Promise<PruneRelationshipsResult> {\n const format = options.format ?? \"text\";\n if (format !== \"text\" && format !== \"json\") {\n throw new CLIError(`Invalid format: ${format}`, ErrorCategory.USER, [\n \"Use --format text or --format json\",\n ]);\n }\n const jsonOutput = format === \"json\" || isJson();\n\n const layers = options.layers?.length ? options.layers : undefined;\n if (layers) {\n const invalid = layers.filter((l) => !isValidLayerName(l));\n if (invalid.length > 0) {\n throw new CLIError(\n `Invalid layer name(s): ${invalid.join(\", \")}`,\n ErrorCategory.USER,\n ['Use canonical layer names, e.g. \"api\", \"data-model\", \"data-store\"']\n );\n }\n }\n\n const model = await Model.load(options.model || process.cwd());\n\n // Every identifier a current element is known by. getLayer() returns the projected\n // view, so staged adds count as present and staged deletes as absent.\n const elements: Element[] = [];\n for (const layerName of model.getLayerNames()) {\n const layer = await model.getLayer(layerName);\n if (layer) {\n elements.push(...layer.listElements());\n }\n }\n const known = collectKnownIdentifiers(elements);\n\n const analysis = findDanglingRelationships(model.relationships.getAll(), known, { layers });\n\n const stagingManager = new StagingAreaManager(model.rootPath, model);\n const activeChangeset = await stagingManager.getActive();\n const staging = !!activeChangeset && activeChangeset.status === \"staged\";\n\n const result: PruneRelationshipsResult = {\n scanned: analysis.scanned,\n dangling: analysis.dangling.length,\n removed: 0,\n dryRun: !!options.dryRun,\n staged: false,\n ...(staging ? { changeset: activeChangeset!.name } : {}),\n ...(layers ? { layers } : {}),\n relationships: analysis.dangling.map(({ relationship: rel, unresolved }) => ({\n source: rel.source,\n predicate: rel.predicate,\n target: rel.target,\n layer: rel.layer,\n ...(rel.targetLayer ? { targetLayer: rel.targetLayer } : {}),\n unresolved,\n })),\n };\n\n if (!options.dryRun && analysis.dangling.length > 0) {\n if (staging) {\n for (const { relationship: rel } of analysis.dangling) {\n await stagingManager.stage(activeChangeset!.id, {\n type: \"relationship-delete\",\n elementId: `${rel.source}::${rel.predicate}::${rel.target}`,\n layerName: rel.layer,\n before: {\n source: rel.source,\n target: rel.target,\n predicate: rel.predicate,\n layer: rel.layer,\n ...(rel.targetLayer ? { targetLayer: rel.targetLayer } : {}),\n ...(rel.properties ? { properties: rel.properties } : {}),\n },\n });\n }\n result.staged = true;\n } else {\n const danglingRows = new Set(analysis.dangling.map((d) => d.relationship));\n model.relationships.retain((rel) => !danglingRows.has(rel));\n await model.saveRelationships({ preserveHeader: true });\n await model.saveManifest();\n\n try {\n const orchestrator = new ModelReportOrchestrator(model, model.rootPath);\n const affectedLayers = new Set<string>();\n for (const { relationship: rel } of analysis.dangling) {\n for (const name of [rel.layer, rel.targetLayer]) {\n if (name && isValidLayerName(name)) {\n for (const affected of orchestrator.computeAffectedLayers(name)) {\n affectedLayers.add(affected);\n }\n }\n }\n }\n await orchestrator.regenerate(affectedLayers);\n } catch (error) {\n emitLog(SeverityNumber.WARN, \"Failed to regenerate layer reports after relationship prune\", {\n \"relationship.pruned\": analysis.dangling.length,\n \"error.message\": getErrorMessage(error),\n });\n }\n }\n result.removed = analysis.dangling.length;\n }\n\n if (jsonOutput) {\n console.log(JSON.stringify({ status: \"ok\", ...result }, null, 2));\n return result;\n }\n\n const scope = layers ? ` in layer(s) ${layers.join(\", \")}` : \"\";\n handleInfo(\n `Scanned ${result.scanned} relationship(s)${scope}: ${result.dangling} dangling`\n );\n for (const row of result.relationships) {\n const side =\n row.unresolved === \"both\"\n ? \"source and target not found\"\n : `${row.unresolved} not found`;\n handleInfo(\n ` - ${ansis.yellow(row.source)} --[${ansis.magenta(row.predicate)}]--> ${ansis.yellow(row.target)} ${ansis.dim(`(${side})`)}`\n );\n }\n\n if (result.dangling === 0) {\n handleSuccess(\"No dangling relationships found\", {\n scanned: result.scanned,\n dangling: 0,\n removed: 0,\n });\n } else if (result.dryRun) {\n handleSuccess(`Dry run: would remove ${result.dangling} dangling relationship(s)`, {\n scanned: result.scanned,\n dangling: result.dangling,\n removed: 0,\n dryRun: true,\n });\n } else if (result.staged) {\n handleSuccess(\n `Staged removal of ${result.removed} dangling relationship(s) to ${ansis.bold(result.changeset!)}`,\n {\n scanned: result.scanned,\n dangling: result.dangling,\n removed: result.removed,\n changesetStatus: \"staged\",\n changeset: result.changeset,\n }\n );\n } else {\n handleSuccess(`Removed ${result.removed} dangling relationship(s)`, {\n scanned: result.scanned,\n dangling: result.dangling,\n removed: result.removed,\n });\n }\n\n return result;\n}\n\nexport function relationshipCommands(program: Command, rootProgram?: Command): void {\n program\n .command(\"add <source> <target>\")\n .description(\n \"Add a relationship between elements (intra-layer or cross-layer)\"\n )\n .option(\n \"--model <path>\",\n \"Path to model root (contains model/manifest.yaml)\"\n )\n .requiredOption(\n \"--predicate <predicate>\",\n \"Relationship predicate (e.g., depends-on, realizes, exposes)\"\n )\n .option(\"--properties <json>\", \"Relationship properties (JSON)\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ dr relationship add motivation.goal.goal-a motivation.goal.goal-b --predicate aggregates\n $ dr relationship add api.operation.create-order application.service.order-service --predicate exposes\n $ dr relationship add business.businessprocess.checkout application.applicationprocess.checkout --predicate aggregates\n $ dr relationship add element-1 element-2 --predicate realizes --properties '{\"strength\":\"high\"}'`\n )\n .action(async (source, target, options) => {\n try {\n // Load model\n const model = await Model.load(options.model);\n\n // Find source element for constraints info\n const sourceLayerName = await findElementLayer(model, source);\n if (!sourceLayerName) {\n throw new CLIError(\n `Source element ${source} not found`,\n ErrorCategory.USER\n );\n }\n const sourceLayer = await model.getLayer(sourceLayerName);\n if (!sourceLayer) {\n throw new CLIError(\n `Source element ${source} not found`,\n ErrorCategory.USER\n );\n }\n const sourceElement = sourceLayer.getElement(source);\n if (!sourceElement) {\n throw new CLIError(\n `Source element ${source} not found`,\n ErrorCategory.USER\n );\n }\n\n // Find target element for constraints info\n const targetLayerName = await findElementLayer(model, target);\n if (!targetLayerName) {\n throw new CLIError(\n `Target element ${target} not found`,\n ErrorCategory.USER\n );\n }\n const targetLayer = await model.getLayer(targetLayerName);\n if (!targetLayer) {\n throw new CLIError(\n `Target element ${target} not found`,\n ErrorCategory.USER\n );\n }\n const targetElement = targetLayer.getElement(target);\n if (!targetElement) {\n throw new CLIError(\n `Target element ${target} not found`,\n ErrorCategory.USER\n );\n }\n\n // Get cardinality and strength info (before calling handler for CLI output)\n const sourceSpecNodeId = constructSpecNodeId(sourceElement, source);\n const targetSpecNodeId = constructSpecNodeId(targetElement, target);\n const constraints = getRelationshipConstraints(\n sourceSpecNodeId,\n options.predicate,\n targetSpecNodeId\n );\n\n // Parse properties if provided\n let properties: Record<string, unknown> | undefined;\n if (options.properties) {\n try {\n properties = JSON.parse(options.properties);\n } catch (e) {\n throw new CLIError(\n \"Invalid JSON in --properties\",\n ErrorCategory.USER\n );\n }\n }\n\n // Check for active changeset\n const stagingManager = new StagingAreaManager(model.rootPath, model);\n const activeChangesetId = await stagingManager.getActiveId();\n\n // Call the handler function with pre-resolved elements to avoid duplicate lookups\n const outcome = await addRelationship(\n model,\n source,\n target,\n options.predicate,\n properties,\n {\n source: { element: sourceElement, layerName: sourceLayerName },\n target: { element: targetElement, layerName: targetLayerName }\n }\n );\n\n if (outcome === \"duplicate\") {\n // duplicate \u2014 emit JSON response indicating relationship already exists\n // (in the base model, or already staged by the active changeset)\n handleSuccess(\n `Relationship ${source} --[${options.predicate}]--> ${target} already exists` +\n (activeChangesetId ? \" (in the model or the active changeset); nothing staged\" : \"\"),\n {\n source,\n target,\n predicate: options.predicate,\n layer: sourceLayerName,\n duplicate: true,\n ...(activeChangesetId ? { changesetStatus: \"unchanged\" } : {}),\n },\n { verbose: options.verbose }\n );\n return;\n }\n\n if (outcome === \"restored\") {\n handleSuccess(\n `Restored relationship: ${ansis.bold(source)} ${options.predicate} ${ansis.bold(target)}` +\n ansis.dim(\" [the active changeset's staged deletion of it was unstaged]\"),\n {\n source,\n target,\n predicate: options.predicate,\n layer: sourceLayerName,\n restored: true,\n changesetStatus: \"unstaged\",\n },\n { verbose: options.verbose }\n );\n return;\n }\n\n // Show cardinality and strength in output\n let message = `Added relationship: ${ansis.bold(source)} ${options.predicate} ${ansis.bold(target)}`;\n const details: Record<string, unknown> = {\n source,\n target,\n predicate: options.predicate,\n layer: sourceLayerName,\n };\n if (constraints) {\n message += ansis.dim(\n ` (${constraints.cardinality}, ${constraints.strength} strength)`\n );\n details.cardinality = constraints.cardinality;\n details.strength = constraints.strength;\n }\n if (activeChangesetId) {\n message += ansis.dim(` [staged]`);\n details.changesetStatus = \"staged\";\n }\n\n handleSuccess(message, details, { verbose: options.verbose });\n } catch (error) {\n handleError(error);\n }\n });\n\n program\n .command(\"delete <source> <target>\")\n .description(\"Delete a relationship\")\n .option(\n \"--model <path>\",\n \"Path to model root (contains model/manifest.yaml)\"\n )\n .option(\n \"--predicate <predicate>\",\n \"Specific predicate to delete (optional, delete all if not specified)\"\n )\n .option(\"--force\", \"Skip confirmation prompt\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ dr relationship delete element-1 element-2 --predicate depends-on\n $ dr relationship delete element-1 element-2 --force`\n )\n .action(async (source, target, options) => {\n try {\n // Load model\n const model = await Model.load(options.model);\n\n // Find relationships to delete (to show confirmation count). Endpoints are\n // matched by the value recorded in the row, so a row whose source or target\n // element has already been deleted can still be removed.\n // Inside a changeset the candidates come from its projected graph, so a relationship the\n // changeset itself added can be removed (which unstages it).\n const activeForDelete = await activeStagedChangeset(\n new StagingAreaManager(model.rootPath, model)\n );\n const toDelete = activeForDelete\n ? liveRelationshipsBetween(\n await ChangesetGraph.build(model, activeForDelete),\n source,\n target,\n options.predicate\n )\n : await findRelationshipsByEndpoints(model, source, target, options.predicate);\n\n if (toDelete.length === 0) {\n throw new CLIError(\n \"No matching relationships found\",\n ErrorCategory.USER,\n [\n `Use \"dr relationship list ${source}\" to see the relationships recorded for ${source}`,\n 'Use \"dr relationship prune --dry-run\" to list rows whose endpoints no longer resolve',\n ]\n );\n }\n\n // The source layer is informational; fall back to the row's layer when the\n // source element no longer exists.\n const sourceLayerName =\n (await findElementLayer(model, source)) ?? toDelete[0].layer;\n\n // Confirm deletion unless --force or non-interactive environment\n // In non-interactive environments (CI, tests), skip confirmation\n const isInteractive = process.stdin.isTTY && process.stdout.isTTY;\n if (!options.force && isInteractive) {\n // Dynamic import of prompts is safe here: caught by outer try-catch\n // This allows lazy loading of the prompt library only when needed\n const { confirm } = await import(\"@clack/prompts\");\n const confirmed = await confirm({\n message: `Delete ${toDelete.length} relationship(s)? This cannot be undone.`,\n initialValue: false\n });\n\n if (!confirmed) {\n handleSuccess(\"Deletion cancelled\", {\n source,\n target,\n status: \"cancelled\",\n });\n return;\n }\n }\n\n // Check for active changeset\n const stagingManager = new StagingAreaManager(model.rootPath, model);\n const activeChangesetId = await stagingManager.getActiveId();\n\n // Call the handler function with pre-resolved values to avoid duplicate lookups\n const result = await deleteRelationshipHandler(\n model,\n source,\n target,\n options.predicate,\n {\n sourceLayerName,\n // In a changeset the handler re-reads the projected graph itself.\n toDelete: activeForDelete ? undefined : (toDelete as Relationship[])\n }\n );\n\n const stagedSuffix = activeChangesetId ? \" [staged]\" : \"\";\n const deleteDetails: Record<string, unknown> = {\n source,\n target,\n layer: sourceLayerName,\n deletedCount: result.deletedCount,\n ...(result.staged ? { relationships: result.staged } : {}),\n };\n if (options.predicate) {\n deleteDetails.predicate = options.predicate;\n }\n if (activeChangesetId) {\n deleteDetails.changesetStatus = \"staged\";\n }\n handleSuccess(\n `Deleted ${result.deletedCount} relationship(s) from ${ansis.bold(source)} to ${ansis.bold(target)}${stagedSuffix}`,\n deleteDetails,\n { verbose: options.verbose }\n );\n } catch (error) {\n handleError(error);\n }\n });\n\n program\n .command(\"prune\")\n .description(\n \"Remove relationship rows whose source or target no longer resolves to an element\"\n )\n .option(\n \"--model <path>\",\n \"Path to model root (contains model/manifest.yaml)\"\n )\n .option(\"--dry-run\", \"List the rows that would be removed without writing\")\n .option(\n \"--layers <layers...>\",\n \"Only scan rows whose source or target layer is one of these canonical layer names\"\n )\n .option(\"--format <format>\", \"Output format: text, json\", \"text\")\n .addHelpText(\n \"after\",\n `\nA row is dangling when its source or target matches neither the UUID id nor the\ndotted path of any current element. Kept rows keep their order and the file's\nheader comment is preserved. Inside an active changeset the removals are staged\nas relationship deletions instead of being written to relationships.yaml.\n\nExamples:\n $ dr relationship prune --dry-run\n $ dr relationship prune\n $ dr relationship prune --layers api application --format json`\n )\n .action(async (options) => {\n try {\n await pruneRelationshipsCommand({\n model: options.model,\n dryRun: !!options.dryRun,\n layers: options.layers,\n format: options.format,\n });\n } catch (error) {\n handleError(error);\n }\n });\n\n program\n .command(\"list <id>\")\n .description(\"List relationships for an element\")\n .option(\n \"--model <path>\",\n \"Path to model root (contains model/manifest.yaml)\"\n )\n .option(\n \"--direction <dir>\",\n \"Filter by direction (incoming/outgoing/all)\",\n \"all\"\n )\n .option(\"--json\", \"Output as JSON\")\n .addHelpText(\n \"after\",\n `\nExamples:\n $ dr relationship list api-endpoint-create-customer\n $ dr relationship list business-service-order --direction outgoing\n $ dr relationship list element-1 --json`\n )\n .action(async (id, options) => {\n try {\n // Load model\n const model = await Model.load(options.model);\n const layerName = await findElementLayer(model, id);\n\n if (!layerName) {\n throw new CLIError(`Element ${id} not found`, ErrorCategory.USER);\n }\n\n // Get relationships from centralized store\n const { outgoing, incoming } = model.relationships.getForElement(id);\n\n // Filter by direction\n let relationships: typeof outgoing = [];\n if (options.direction === \"outgoing\" || options.direction === \"all\") {\n relationships.push(...outgoing);\n }\n if (options.direction === \"incoming\" || options.direction === \"all\") {\n relationships.push(...incoming);\n }\n\n const shouldOutputJson = options.json ?? (rootProgram?.opts().json);\n if (shouldOutputJson) {\n console.log(JSON.stringify(relationships, null, 2));\n return;\n }\n\n if (relationships.length === 0) {\n handleInfo(\n ansis.yellow(`No ${options.direction} relationships for ${id}`)\n );\n return;\n }\n\n handleInfo(\"\");\n handleInfo(\n ansis.bold(\n `${options.direction === \"all\" ? \"\" : options.direction + \" \"}relationships for ${ansis.cyan(id)}:`\n )\n );\n handleInfo(ansis.dim(\"\u2500\".repeat(80)));\n\n for (const rel of relationships) {\n const isOutgoing = rel.source === id;\n const direction = isOutgoing ? \"\u2192\" : \"\u2190\";\n const otherElement = isOutgoing ? rel.target : rel.source;\n const crossLayer =\n rel.targetLayer && rel.targetLayer !== rel.layer\n ? ansis.dim(` [${rel.layer} \u2192 ${rel.targetLayer}]`)\n : \"\";\n\n handleInfo(\n ` ${direction} ${ansis.magenta(rel.predicate)}: ${ansis.yellow(otherElement)}${crossLayer}`\n );\n\n if (rel.properties) {\n const propStr = Object.entries(rel.properties)\n .map(([k, v]) => `${k}=${v}`)\n .join(\", \");\n handleInfo(` ${ansis.dim(propStr)}`);\n }\n }\n\n handleInfo(ansis.dim(\"\u2500\".repeat(80)));\n handleInfo(ansis.dim(`Total: ${relationships.length} relationship(s)`));\n handleInfo(\"\");\n } catch (error) {\n handleError(error);\n }\n });\n\n program\n .command(\"show <source> <target>\")\n .description(\"Show relationship details\")\n .option(\n \"--model <path>\",\n \"Path to model root (contains model/manifest.yaml)\"\n )\n .addHelpText(\n \"after\",\n `\nExamples:\n $ dr relationship show api-endpoint-create-customer business-service-customer-mgmt\n $ dr relationship show application-component-api motivation-goal-sales-efficiency`\n )\n .action(async (source, target, options) => {\n try {\n // Load model\n const model = await Model.load(options.model);\n const sourceLayerName = await findElementLayer(model, source);\n\n if (!sourceLayerName) {\n throw new CLIError(\n `Source element ${source} not found`,\n ErrorCategory.USER\n );\n }\n\n // Find relationships from centralized store\n const relationships = model.relationships.find(source, target);\n\n if (relationships.length === 0) {\n throw new CLIError(\n `No relationships from ${source} to ${target}`,\n ErrorCategory.USER\n );\n }\n\n handleInfo(\"\");\n handleInfo(\n ansis.bold(\n `Relationship${relationships.length > 1 ? \"s\" : \"\"} from ${ansis.cyan(source)} to ${ansis.yellow(target)}:`\n )\n );\n handleInfo(ansis.dim(\"\u2500\".repeat(60)));\n\n for (let i = 0; i < relationships.length; i++) {\n const rel = relationships[i];\n\n if (i > 0) {\n handleInfo(\"\");\n }\n\n handleInfo(`Predicate: ${ansis.magenta(rel.predicate)}`);\n handleInfo(`Layer: ${ansis.cyan(rel.layer)}`);\n if (rel.targetLayer && rel.targetLayer !== rel.layer) {\n handleInfo(`Target Layer: ${ansis.cyan(rel.targetLayer)}`);\n }\n\n if (rel.properties && Object.keys(rel.properties).length > 0) {\n handleInfo(\"Properties:\");\n for (const [key, value] of Object.entries(rel.properties)) {\n const displayValue =\n typeof value === \"string\"\n ? value\n : JSON.stringify(value, null, 2);\n handleInfo(` ${ansis.cyan(key)}: ${displayValue}`);\n }\n } else {\n handleInfo(\"Properties: none\");\n }\n }\n\n handleInfo(\"\");\n } catch (error) {\n handleError(error);\n }\n });\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }