@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.
- package/README.md +36 -2
- package/dist/analysis/relationship-classifier.js +2 -9
- package/dist/analysis/relationship-classifier.js.map +2 -2
- package/dist/analyzers/call-graph-utils.js +2 -2
- package/dist/analyzers/call-graph-utils.js.map +2 -2
- package/dist/analyzers/cards/build.js +171 -0
- package/dist/analyzers/cards/build.js.map +7 -0
- package/dist/analyzers/cards/classify.js +42 -0
- package/dist/analyzers/cards/classify.js.map +7 -0
- package/dist/analyzers/cards/edge-index.js +44 -0
- package/dist/analyzers/cards/edge-index.js.map +7 -0
- package/dist/analyzers/cards/facts.js +46 -0
- package/dist/analyzers/cards/facts.js.map +7 -0
- package/dist/analyzers/cards/graph.js +42 -0
- package/dist/analyzers/cards/graph.js.map +7 -0
- package/dist/analyzers/cards/index.js +8 -0
- package/dist/analyzers/cards/index.js.map +7 -0
- package/dist/analyzers/cards/tier0.js +18 -0
- package/dist/analyzers/cards/tier0.js.map +7 -0
- package/dist/analyzers/cards/types.js +1 -0
- package/dist/analyzers/cards/types.js.map +7 -0
- package/dist/analyzers/cbm-analyzer.js +195 -171
- package/dist/analyzers/cbm-analyzer.js.map +2 -2
- package/dist/analyzers/cbm-responses.js +336 -0
- package/dist/analyzers/cbm-responses.js.map +7 -0
- package/dist/analyzers/edge-relationships.js +45 -0
- package/dist/analyzers/edge-relationships.js.map +7 -0
- package/dist/analyzers/mapping-loader.js +9 -0
- package/dist/analyzers/mapping-loader.js.map +2 -2
- package/dist/analyzers/tier0-rules.js +153 -0
- package/dist/analyzers/tier0-rules.js.map +7 -0
- package/dist/analyzers/types.js.map +1 -1
- package/dist/audit/relationships/ai/prompts.js +10 -10
- package/dist/audit/relationships/ai/prompts.js.map +2 -2
- package/dist/audit/relationships/analysis/layer-templates.js +52 -52
- package/dist/audit/relationships/analysis/layer-templates.js.map +1 -1
- package/dist/cli.js +12 -2
- package/dist/cli.js.map +2 -2
- package/dist/coding-agents/default-permissions.js +31 -1
- package/dist/coding-agents/default-permissions.js.map +2 -2
- package/dist/coding-agents/drbot-mcp.js +167 -0
- package/dist/coding-agents/drbot-mcp.js.map +7 -0
- package/dist/commands/add.js +3 -2
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +117 -14
- package/dist/commands/analyzer.js.map +2 -2
- package/dist/commands/changeset-verify.js +55 -0
- package/dist/commands/changeset-verify.js.map +7 -0
- package/dist/commands/changeset.js +43 -22
- package/dist/commands/changeset.js.map +2 -2
- package/dist/commands/delete.js +10 -4
- package/dist/commands/delete.js.map +2 -2
- package/dist/commands/farm.js +176 -60
- package/dist/commands/farm.js.map +2 -2
- package/dist/commands/mcp.js +19 -4
- package/dist/commands/mcp.js.map +2 -2
- package/dist/commands/relationship.js +207 -26
- package/dist/commands/relationship.js.map +2 -2
- package/dist/commands/update.js +42 -23
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/upgrade.js +99 -11
- package/dist/commands/upgrade.js.map +3 -3
- package/dist/commands/version.js +1 -1
- package/dist/commands/visualize.js +4 -3
- package/dist/commands/visualize.js.map +2 -2
- package/dist/core/catalog-migration.js +33 -0
- package/dist/core/catalog-migration.js.map +7 -0
- package/dist/core/changeset-analysis.js +421 -0
- package/dist/core/changeset-analysis.js.map +7 -0
- package/dist/core/changeset-base-index.js +153 -0
- package/dist/core/changeset-base-index.js.map +7 -0
- package/dist/core/changeset-brief-delta.js +102 -0
- package/dist/core/changeset-brief-delta.js.map +7 -0
- package/dist/core/changeset-graph.js +281 -0
- package/dist/core/changeset-graph.js.map +7 -0
- package/dist/core/changeset-only-guard.js +32 -0
- package/dist/core/changeset-only-guard.js.map +7 -0
- package/dist/core/changeset-summary.js +68 -0
- package/dist/core/changeset-summary.js.map +7 -0
- package/dist/core/changeset-validator.js +66 -20
- package/dist/core/changeset-validator.js.map +2 -2
- package/dist/core/changeset-verify.js +518 -0
- package/dist/core/changeset-verify.js.map +7 -0
- package/dist/core/changeset.js +56 -9
- package/dist/core/changeset.js.map +2 -2
- package/dist/core/element-type-migration.js +145 -0
- package/dist/core/element-type-migration.js.map +7 -0
- package/dist/core/farm-manifest.js +12 -6
- package/dist/core/farm-manifest.js.map +2 -2
- package/dist/core/farm-sync-engine.js +1 -1
- package/dist/core/farm-sync-engine.js.map +2 -2
- package/dist/core/farm-sync-state.js +10 -2
- package/dist/core/farm-sync-state.js.map +2 -2
- package/dist/core/migration-registry.js +75 -46
- package/dist/core/migration-registry.js.map +2 -2
- package/dist/core/model-upgrade.js +102 -0
- package/dist/core/model-upgrade.js.map +7 -0
- package/dist/core/model.js +82 -30
- package/dist/core/model.js.map +2 -2
- package/dist/core/mutation-handler.js +71 -6
- package/dist/core/mutation-handler.js.map +2 -2
- package/dist/core/relationship-migration-table.js +6639 -0
- package/dist/core/relationship-migration-table.js.map +7 -0
- package/dist/core/relationship-migration.js +272 -0
- package/dist/core/relationship-migration.js.map +7 -0
- package/dist/core/relationship-prune.js +49 -0
- package/dist/core/relationship-prune.js.map +7 -0
- package/dist/core/relationship-staging.js +68 -0
- package/dist/core/relationship-staging.js.map +7 -0
- package/dist/core/relationships.js +26 -4
- package/dist/core/relationships.js.map +2 -2
- package/dist/core/report-data-model.js +7 -13
- package/dist/core/report-data-model.js.map +2 -2
- package/dist/core/staged-changeset-storage.js +64 -17
- package/dist/core/staged-changeset-storage.js.map +2 -2
- package/dist/core/staged-source-reference.js +28 -0
- package/dist/core/staged-source-reference.js.map +7 -0
- package/dist/core/staging-area.js +83 -18
- package/dist/core/staging-area.js.map +2 -2
- package/dist/core/virtual-projection.js +13 -5
- package/dist/core/virtual-projection.js.map +2 -2
- package/dist/export/archimate-exporter.js +1 -0
- package/dist/export/archimate-exporter.js.map +2 -2
- package/dist/generated/compiled-validators.js +2 -2
- package/dist/generated/compiled-validators.js.map +2 -2
- package/dist/generated/layer-registry.js +16 -16
- package/dist/generated/layer-registry.js.map +2 -2
- package/dist/generated/node-types.js +186 -168
- package/dist/generated/node-types.js.map +2 -2
- package/dist/generated/relationship-index.js +2236 -7790
- package/dist/generated/relationship-index.js.map +2 -2
- package/dist/integrations/claude_code/agents/dr-architect.md +16 -3
- package/dist/integrations/claude_code/agents/dr-extractor.md +1 -1
- package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
- package/dist/integrations/claude_code/commands/dr-map.md +13 -0
- package/dist/integrations/claude_code/commands/dr-plan.md +40 -0
- package/dist/integrations/claude_code/commands/dr-relate.md +106 -96
- package/dist/integrations/claude_code/commands/dr-verify.md +45 -2
- package/dist/integrations/claude_code/docs/END_TO_END_WALKTHROUGH.md +14 -0
- package/dist/integrations/claude_code/docs/MCP_SERVER.md +26 -2
- package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
- package/dist/integrations/claude_code/docs/README.md +48 -1
- package/dist/integrations/claude_code/skills/dr_01_motivation_layer/SKILL.md +93 -72
- package/dist/integrations/claude_code/skills/dr_02_business_layer/SKILL.md +163 -126
- package/dist/integrations/claude_code/skills/dr_03_product_layer/SKILL.md +161 -126
- package/dist/integrations/claude_code/skills/dr_04_security_layer/SKILL.md +153 -133
- package/dist/integrations/claude_code/skills/dr_05_application_layer/SKILL.md +129 -100
- package/dist/integrations/claude_code/skills/dr_06_technology_layer/SKILL.md +111 -94
- package/dist/integrations/claude_code/skills/dr_07_api_layer/SKILL.md +143 -157
- package/dist/integrations/claude_code/skills/dr_08_data_model_layer/SKILL.md +117 -33
- package/dist/integrations/claude_code/skills/dr_09_dataset_layer/SKILL.md +74 -25
- package/dist/integrations/claude_code/skills/dr_10_ux_layer/SKILL.md +88 -12
- package/dist/integrations/claude_code/skills/dr_11_navigation_layer/SKILL.md +83 -24
- package/dist/integrations/claude_code/skills/dr_12_apm_layer/SKILL.md +57 -12
- package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +92 -15
- package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +119 -0
- package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +3 -0
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +1 -1
- package/dist/integrations/github_copilot/agents/dr-architect.md +1 -1
- package/dist/integrations/github_copilot/skills/dr_01_motivation_layer/SKILL.md +182 -102
- package/dist/integrations/github_copilot/skills/dr_02_business_layer/SKILL.md +266 -148
- package/dist/integrations/github_copilot/skills/dr_03_product_layer/SKILL.md +547 -86
- package/dist/integrations/github_copilot/skills/dr_04_security_layer/SKILL.md +164 -169
- package/dist/integrations/github_copilot/skills/dr_05_application_layer/SKILL.md +420 -150
- package/dist/integrations/github_copilot/skills/dr_06_technology_layer/SKILL.md +331 -138
- package/dist/integrations/github_copilot/skills/dr_07_api_layer/SKILL.md +265 -201
- package/dist/integrations/github_copilot/skills/dr_08_data_model_layer/SKILL.md +181 -58
- package/dist/integrations/github_copilot/skills/dr_09_dataset_layer/SKILL.md +209 -57
- package/dist/integrations/github_copilot/skills/dr_10_ux_layer/SKILL.md +277 -112
- package/dist/integrations/github_copilot/skills/dr_11_navigation_layer/SKILL.md +219 -70
- package/dist/integrations/github_copilot/skills/dr_12_apm_layer/SKILL.md +189 -105
- package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +222 -102
- package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +119 -0
- package/dist/integrations/github_copilot/skills/dr_changeset_reviewer/SKILL.md +3 -0
- package/dist/integrations/hash-utils.js.map +2 -2
- package/dist/mcp/api-key-manager.js +14 -1
- package/dist/mcp/api-key-manager.js.map +2 -2
- package/dist/mcp/changeset-only.js +34 -0
- package/dist/mcp/changeset-only.js.map +7 -0
- package/dist/mcp/tool-registry.js +19 -3
- package/dist/mcp/tool-registry.js.map +2 -2
- package/dist/mcp/tools/changeset-analysis-tools.js +78 -0
- package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
- package/dist/mcp/tools/changeset-editing.js +184 -0
- package/dist/mcp/tools/changeset-editing.js.map +7 -0
- package/dist/mcp/tools/changeset-lifecycle.js +83 -0
- package/dist/mcp/tools/changeset-lifecycle.js.map +7 -0
- package/dist/mcp/tools/changeset-shared.js +31 -0
- package/dist/mcp/tools/changeset-shared.js.map +7 -0
- package/dist/mcp/tools/changeset-show.js +17 -1
- package/dist/mcp/tools/changeset-show.js.map +2 -2
- package/dist/mcp/tools/changeset-verify.js +43 -0
- package/dist/mcp/tools/changeset-verify.js.map +7 -0
- package/dist/mcp/tools/index.js +19 -1
- package/dist/mcp/tools/index.js.map +2 -2
- package/dist/mcp/tools/model-add.js +6 -5
- package/dist/mcp/tools/model-add.js.map +2 -2
- package/dist/mcp/tools/model-delete.js +2 -2
- package/dist/mcp/tools/model-delete.js.map +2 -2
- package/dist/mcp/tools/model-search.js +14 -0
- package/dist/mcp/tools/model-search.js.map +2 -2
- package/dist/mcp/tools/model-show.js +29 -2
- package/dist/mcp/tools/model-show.js.map +2 -2
- package/dist/mcp/tools/model-trace.js +93 -30
- package/dist/mcp/tools/model-trace.js.map +2 -2
- package/dist/mcp/tools/model-update.js +35 -19
- package/dist/mcp/tools/model-update.js.map +2 -2
- package/dist/mcp/tools/relationship-tools.js +98 -0
- package/dist/mcp/tools/relationship-tools.js.map +7 -0
- package/dist/mcp/tools/shared.js +25 -2
- package/dist/mcp/tools/shared.js.map +2 -2
- package/dist/mcp/tools/spec-type.js +90 -0
- package/dist/mcp/tools/spec-type.js.map +7 -0
- package/dist/schemas/bundled/analyzers/cbm.json +1186 -68
- package/dist/schemas/bundled/analyzers/manifest.json +2 -2
- package/dist/schemas/bundled/api.json +1029 -1658
- package/dist/schemas/bundled/apm.json +499 -734
- package/dist/schemas/bundled/application.json +735 -371
- package/dist/schemas/bundled/base.json +3 -220
- package/dist/schemas/bundled/business.json +452 -366
- package/dist/schemas/bundled/data-model.json +395 -411
- package/dist/schemas/bundled/data-store.json +334 -558
- package/dist/schemas/bundled/manifest.json +18 -18
- package/dist/schemas/bundled/motivation.json +349 -368
- package/dist/schemas/bundled/navigation.json +415 -731
- package/dist/schemas/bundled/product.json +325 -159
- package/dist/schemas/bundled/security.json +864 -1232
- package/dist/schemas/bundled/technology.json +574 -703
- package/dist/schemas/bundled/testing.json +718 -1643
- package/dist/schemas/bundled/ux.json +739 -1352
- package/dist/server/schemas.js +8 -0
- package/dist/server/schemas.js.map +2 -2
- package/dist/server/server.js +218 -34
- package/dist/server/server.js.map +2 -2
- package/dist/server-entry.js +2 -1
- package/dist/server-entry.js.map +2 -2
- package/dist/telemetry/index.js +1 -1
- package/dist/utils/ansi-suppressor.js +6 -8
- package/dist/utils/ansi-suppressor.js.map +2 -2
- package/dist/utils/attribute-patch.js +41 -0
- package/dist/utils/attribute-patch.js.map +7 -0
- package/dist/utils/error-hints.js +29 -0
- package/dist/utils/error-hints.js.map +7 -0
- package/dist/utils/errors.js +9 -3
- package/dist/utils/errors.js.map +2 -2
- package/dist/utils/project-paths.js +26 -1
- package/dist/utils/project-paths.js.map +2 -2
- package/dist/utils/source-reference.js +14 -12
- package/dist/utils/source-reference.js.map +2 -2
- package/dist/utils/spec-version.js +2 -2
- package/dist/utils/spec-version.js.map +1 -1
- package/dist/validators/composed-reference-validator.js +5 -27
- package/dist/validators/composed-reference-validator.js.map +2 -2
- package/dist/validators/reference-validator.js +0 -7
- package/dist/validators/reference-validator.js.map +2 -2
- package/dist/validators/relationship-schema-validator.js +3 -3
- package/dist/validators/relationship-schema-validator.js.map +2 -2
- package/dist/validators/schema-validator.js +27 -9
- package/dist/validators/schema-validator.js.map +2 -2
- package/dist/validators/types.js.map +1 -1
- package/dist/viewer/README.md +6 -6
- package/dist/viewer/assets/main-CxzcuU_o.js +84 -0
- package/dist/viewer/assets/main-j-7ULYpm.css +1 -0
- package/dist/viewer/index.html +2 -2
- package/dist/viewer/manifest.json +10 -10
- package/dist/viewer/version.json +1 -1
- package/package.json +2 -1
- package/dist/viewer/assets/main-B7AOQXXX.js +0 -81
- 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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
}
|