@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
|
@@ -1,50 +1,113 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { ChangesetGraph } from "../../core/changeset-graph.js";
|
|
2
3
|
import { DependencyTracker, TraceDirection } from "../../core/dependency-tracker.js";
|
|
3
4
|
import { ReferenceRegistry } from "../../core/reference-registry.js";
|
|
5
|
+
import { StagingAreaManager } from "../../core/staging-area.js";
|
|
4
6
|
import { findElementLayer } from "../../utils/element-utils.js";
|
|
5
|
-
import { CLIError, ErrorCategory } from "../../utils/errors.js";
|
|
7
|
+
import { CLIError, ErrorCategory, getErrorMessage } from "../../utils/errors.js";
|
|
6
8
|
import { jsonResult, loadModel, rootPathSchema, runTool } from "./shared.js";
|
|
9
|
+
import { activeStagedChangeset, resolveChangeset } from "./changeset-shared.js";
|
|
7
10
|
const inputSchema = {
|
|
8
11
|
id: z.string().describe("Element ID or path to trace."),
|
|
9
12
|
direction: z.enum(["up", "down", "both"]).optional().describe("'up' = what depends on this element (dependents), 'down' = what this element depends on (dependencies), 'both' (default)."),
|
|
10
13
|
depth: z.number().int().positive().optional().describe("Maximum trace depth. Omit for unlimited."),
|
|
14
|
+
changesetId: z.string().optional().describe("Trace through this changeset's projected graph (ID or name). Defaults to the active changeset, if any."),
|
|
15
|
+
base: z.boolean().optional().describe("Trace the committed base model only, ignoring any active changeset."),
|
|
11
16
|
rootPath: rootPathSchema
|
|
12
17
|
};
|
|
13
18
|
async function modelTraceHandler(args) {
|
|
14
19
|
return runTool(async () => {
|
|
15
|
-
const model = await loadModel(args.rootPath)
|
|
16
|
-
if (
|
|
17
|
-
throw new CLIError(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const registry = new ReferenceRegistry();
|
|
21
|
-
for (const layer of model.layers.values())
|
|
22
|
-
for (const element of layer.listElements())
|
|
23
|
-
registry.registerElement(element);
|
|
24
|
-
const graph = registry.getDependencyGraph();
|
|
25
|
-
for (const rel of model.relationships.getAll())
|
|
26
|
-
graph.hasNode(rel.source) || graph.addNode(rel.source), graph.hasNode(rel.target) || graph.addNode(rel.target), graph.hasEdge(rel.source, rel.target) || graph.addEdge(rel.source, rel.target, { type: rel.predicate });
|
|
27
|
-
const tracker = new DependencyTracker(graph, model), direction = args.direction ?? "both", maxDepth = args.depth ?? null, result = { id: args.id, layer: layerName, direction };
|
|
28
|
-
if (direction === "up" || direction === "both") {
|
|
29
|
-
const direct = tracker.traceDependencies(args.id, TraceDirection.DOWN, 1), transitiveAll = tracker.traceDependencies(args.id, TraceDirection.DOWN, maxDepth);
|
|
30
|
-
result.dependents = {
|
|
31
|
-
direct,
|
|
32
|
-
transitive: transitiveAll.filter((d) => !direct.includes(d))
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
if (direction === "down" || direction === "both") {
|
|
36
|
-
const direct = tracker.traceDependencies(args.id, TraceDirection.UP, 1), transitiveAll = tracker.traceDependencies(args.id, TraceDirection.UP, maxDepth);
|
|
37
|
-
result.dependencies = {
|
|
38
|
-
direct,
|
|
39
|
-
transitive: transitiveAll.filter((d) => !direct.includes(d))
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
return jsonResult(result);
|
|
20
|
+
const model = await loadModel(args.rootPath);
|
|
21
|
+
if (args.base && args.changesetId)
|
|
22
|
+
throw new CLIError('Pass either "base" or "changesetId", not both', ErrorCategory.USER);
|
|
23
|
+
const manager = new StagingAreaManager(model.rootPath, model), changeset = args.base ? null : args.changesetId ? await resolveChangeset(manager, args.changesetId) : await activeStagedChangeset(manager);
|
|
24
|
+
return jsonResult(changeset ? await traceChangeset(model, changeset, args) : await traceBase(model, args));
|
|
43
25
|
});
|
|
44
26
|
}
|
|
27
|
+
async function traceBase(model, args) {
|
|
28
|
+
const layerName = await findElementLayer(model, args.id);
|
|
29
|
+
if (!layerName)
|
|
30
|
+
throw new CLIError(`Element ${args.id} not found`, ErrorCategory.NOT_FOUND, [
|
|
31
|
+
'Use "model_search" to find similar elements'
|
|
32
|
+
]);
|
|
33
|
+
const graph = referenceGraph(model.layers.values());
|
|
34
|
+
for (const rel of model.getBaseRelationships())
|
|
35
|
+
addEdge(graph, rel.source, rel.target, rel.predicate);
|
|
36
|
+
return { id: args.id, layer: layerName, ...runTrace(graph, model, args.id, args) };
|
|
37
|
+
}
|
|
38
|
+
async function traceChangeset(model, changeset, args) {
|
|
39
|
+
const projected = await ChangesetGraph.build(model, changeset), root = projected.getElement(args.id);
|
|
40
|
+
if (!root)
|
|
41
|
+
throw new CLIError(`Element ${args.id} not found in changeset '${changeset.name}' or the base model`, ErrorCategory.NOT_FOUND, [
|
|
42
|
+
'Use "model_search" to find similar elements',
|
|
43
|
+
'Pass "base": true to trace the committed model only'
|
|
44
|
+
]);
|
|
45
|
+
const warnings = [], followDeleted = root.status === "deleted";
|
|
46
|
+
followDeleted && warnings.push(`${root.id} is deleted by this changeset; the trace follows the relationships it has today`);
|
|
47
|
+
const layers = await projectedLayers(model, changeset, warnings), graph = referenceGraph(layers);
|
|
48
|
+
for (const rel of projected.relationships())
|
|
49
|
+
rel.status === "deleted" && !(followDeleted && (rel.source === root.id || rel.target === root.id)) || addEdge(graph, rel.source, rel.target, rel.predicate);
|
|
50
|
+
const trace = runTrace(graph, model, root.id, args), reached = /* @__PURE__ */ new Set([root.id]);
|
|
51
|
+
for (const side of [trace.dependents, trace.dependencies])
|
|
52
|
+
for (const id of [...side?.direct ?? [], ...side?.transitive ?? []])
|
|
53
|
+
reached.add(id);
|
|
54
|
+
const changedElements = {};
|
|
55
|
+
for (const id of reached) {
|
|
56
|
+
const status = projected.getElement(id)?.status;
|
|
57
|
+
status && status !== "unchanged" && (changedElements[id] = status);
|
|
58
|
+
}
|
|
59
|
+
const changedEdges = projected.changedRelationships().filter((rel) => reached.has(rel.source) || reached.has(rel.target)).map((rel) => ({ source: rel.source, predicate: rel.predicate, target: rel.target, status: rel.status }));
|
|
60
|
+
return {
|
|
61
|
+
id: root.id,
|
|
62
|
+
layer: root.layer,
|
|
63
|
+
...trace,
|
|
64
|
+
changeset: { id: changeset.id, name: changeset.name, elementStatus: root.status },
|
|
65
|
+
changedElements,
|
|
66
|
+
changedEdges,
|
|
67
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function projectedLayers(model, changeset, warnings) {
|
|
71
|
+
const names = new Set(model.getLayerNames());
|
|
72
|
+
for (const change of changeset.changes)
|
|
73
|
+
change.type.startsWith("relationship-") || names.add(change.layerName);
|
|
74
|
+
const isActive = model.getActiveChangesetId() === changeset.id, layers = [];
|
|
75
|
+
for (const name of names)
|
|
76
|
+
try {
|
|
77
|
+
const layer = isActive ? await model.getLayer(name) : await model.getVirtualProjectionEngine().projectLayer(model, changeset.id, name);
|
|
78
|
+
layer && layers.push(layer);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
warnings.push(`Layer ${name} could not be projected (${getErrorMessage(error)}); its base references were used`);
|
|
81
|
+
const base = await model.getBaseLayer(name);
|
|
82
|
+
base && layers.push(base);
|
|
83
|
+
}
|
|
84
|
+
return layers;
|
|
85
|
+
}
|
|
86
|
+
function referenceGraph(layers) {
|
|
87
|
+
const registry = new ReferenceRegistry();
|
|
88
|
+
for (const layer of layers)
|
|
89
|
+
for (const element of layer.listElements())
|
|
90
|
+
registry.registerElement(element);
|
|
91
|
+
return registry.getDependencyGraph();
|
|
92
|
+
}
|
|
93
|
+
function addEdge(graph, source, target, predicate) {
|
|
94
|
+
graph.hasNode(source) || graph.addNode(source), graph.hasNode(target) || graph.addNode(target), graph.hasEdge(source, target) || graph.addEdge(source, target, { type: predicate });
|
|
95
|
+
}
|
|
96
|
+
function runTrace(graph, model, id, args) {
|
|
97
|
+
const tracker = new DependencyTracker(graph, model), direction = args.direction ?? "both", maxDepth = args.depth ?? null, result = { direction };
|
|
98
|
+
if (direction === "up" || direction === "both") {
|
|
99
|
+
const direct = tracker.traceDependencies(id, TraceDirection.DOWN, 1), transitiveAll = tracker.traceDependencies(id, TraceDirection.DOWN, maxDepth);
|
|
100
|
+
result.dependents = { direct, transitive: transitiveAll.filter((d) => !direct.includes(d)) };
|
|
101
|
+
}
|
|
102
|
+
if (direction === "down" || direction === "both") {
|
|
103
|
+
const direct = tracker.traceDependencies(id, TraceDirection.UP, 1), transitiveAll = tracker.traceDependencies(id, TraceDirection.UP, maxDepth);
|
|
104
|
+
result.dependencies = { direct, transitive: transitiveAll.filter((d) => !direct.includes(d)) };
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
45
108
|
const modelTraceTool = {
|
|
46
109
|
name: "model_trace",
|
|
47
|
-
description: "Trace dependents and/or dependencies of an element across cross-layer references and relationships.",
|
|
110
|
+
description: "Trace dependents and/or dependencies of an element across cross-layer references and relationships. With an active changeset (or changesetId) it traces the changeset's projected graph: staged relationships are followed, staged removals are not, and changedEdges / changedElements mark what the changeset adds or deletes.",
|
|
48
111
|
inputSchema,
|
|
49
112
|
handler: modelTraceHandler
|
|
50
113
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/mcp/tools/model-trace.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * model_trace \u2014 dependency trace for an element. Mirrors `dr trace`.\n */\n\nimport { z } from \"zod\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { DependencyTracker, TraceDirection } from \"../../core/dependency-tracker.js\";\nimport { ReferenceRegistry } from \"../../core/reference-registry.js\";\nimport { findElementLayer } from \"../../utils/element-utils.js\";\nimport { CLIError, ErrorCategory } from \"../../utils/errors.js\";\nimport { jsonResult, loadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\n\nconst inputSchema = {\n id: z.string().describe(\"Element ID or path to trace.\"),\n direction: z\n .enum([\"up\", \"down\", \"both\"])\n .optional()\n .describe(\"'up' = what depends on this element (dependents), 'down' = what this element depends on (dependencies), 'both' (default).\"),\n depth: z.number().int().positive().optional().describe(\"Maximum trace depth. Omit for unlimited.\"),\n rootPath: rootPathSchema,\n};\n\nexport type ModelTraceArgs = z.infer<z.ZodObject<typeof inputSchema>>;\n\nexport async function modelTraceHandler(args: ModelTraceArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n\n const layerName = await findElementLayer(model, args.id);\n
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["/**\n * model_trace \u2014 dependency trace for an element. Mirrors `dr trace`.\n *\n * Changeset-aware: when a changeset is active (or one is named with `changesetId`), the trace runs\n * over that changeset's projected graph (`ChangesetGraph`): relationships it stages are followed,\n * relationships it deletes are not, and the result lists the added and deleted edges that touch\n * the traced elements. Pass `base: true` to trace the committed model only.\n */\n\nimport { z } from \"zod\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport type Graph from \"graphology\";\nimport { ChangesetGraph } from \"../../core/changeset-graph.js\";\nimport type { Changeset } from \"../../core/changeset.js\";\nimport { DependencyTracker, TraceDirection } from \"../../core/dependency-tracker.js\";\nimport type { Layer } from \"../../core/layer.js\";\nimport type { Model } from \"../../core/model.js\";\nimport { ReferenceRegistry } from \"../../core/reference-registry.js\";\nimport { StagingAreaManager } from \"../../core/staging-area.js\";\nimport { findElementLayer } from \"../../utils/element-utils.js\";\nimport { CLIError, ErrorCategory, getErrorMessage } from \"../../utils/errors.js\";\nimport { jsonResult, loadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\nimport { activeStagedChangeset, resolveChangeset } from \"./changeset-shared.js\";\n\nconst inputSchema = {\n id: z.string().describe(\"Element ID or path to trace.\"),\n direction: z\n .enum([\"up\", \"down\", \"both\"])\n .optional()\n .describe(\"'up' = what depends on this element (dependents), 'down' = what this element depends on (dependencies), 'both' (default).\"),\n depth: z.number().int().positive().optional().describe(\"Maximum trace depth. Omit for unlimited.\"),\n changesetId: z\n .string()\n .optional()\n .describe(\"Trace through this changeset's projected graph (ID or name). Defaults to the active changeset, if any.\"),\n base: z.boolean().optional().describe(\"Trace the committed base model only, ignoring any active changeset.\"),\n rootPath: rootPathSchema,\n};\n\nexport type ModelTraceArgs = z.infer<z.ZodObject<typeof inputSchema>>;\n\ninterface TraceResult {\n id: string;\n layer: string;\n direction: string;\n dependents?: { direct: string[]; transitive: string[] };\n dependencies?: { direct: string[]; transitive: string[] };\n changeset?: { id: string; name: string; elementStatus: string };\n changedElements?: Record<string, string>;\n changedEdges?: Array<{ source: string; predicate: string; target: string; status: string }>;\n warnings?: string[];\n}\n\nexport async function modelTraceHandler(args: ModelTraceArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n if (args.base && args.changesetId) {\n throw new CLIError('Pass either \"base\" or \"changesetId\", not both', ErrorCategory.USER);\n }\n\n const manager = new StagingAreaManager(model.rootPath, model);\n const changeset = args.base\n ? null\n : args.changesetId\n ? await resolveChangeset(manager, args.changesetId)\n : await activeStagedChangeset(manager);\n\n return jsonResult(changeset ? await traceChangeset(model, changeset, args) : await traceBase(model, args));\n });\n}\n\n/** The committed model: cross-layer references plus relationships.yaml. */\nasync function traceBase(model: Model, args: ModelTraceArgs): Promise<TraceResult> {\n const layerName = await findElementLayer(model, args.id);\n if (!layerName) {\n throw new CLIError(`Element ${args.id} not found`, ErrorCategory.NOT_FOUND, [\n 'Use \"model_search\" to find similar elements',\n ]);\n }\n\n // Build a reference registry from cross-layer references, then extend the\n // dependency graph with intra-layer relationships from relationships.yaml.\n const graph = referenceGraph(model.layers.values());\n for (const rel of model.getBaseRelationships()) {\n addEdge(graph, rel.source, rel.target, rel.predicate);\n }\n\n return { id: args.id, layer: layerName, ...runTrace(graph, model, args.id, args) };\n}\n\n/** A changeset's projection: its elements' references plus base and staged relationships, minus staged removals. */\nasync function traceChangeset(model: Model, changeset: Changeset, args: ModelTraceArgs): Promise<TraceResult> {\n const projected = await ChangesetGraph.build(model, changeset);\n const root = projected.getElement(args.id);\n if (!root) {\n throw new CLIError(`Element ${args.id} not found in changeset '${changeset.name}' or the base model`, ErrorCategory.NOT_FOUND, [\n 'Use \"model_search\" to find similar elements',\n 'Pass \"base\": true to trace the committed model only',\n ]);\n }\n\n const warnings: string[] = [];\n // Tracing an element the changeset deletes follows the edges it had: that is what the delete breaks.\n const followDeleted = root.status === \"deleted\";\n if (followDeleted) {\n warnings.push(`${root.id} is deleted by this changeset; the trace follows the relationships it has today`);\n }\n\n const layers = await projectedLayers(model, changeset, warnings);\n const graph = referenceGraph(layers);\n for (const rel of projected.relationships()) {\n // Only the root's own removed edges come back; removals elsewhere in the changeset stay removed.\n if (rel.status === \"deleted\" && !(followDeleted && (rel.source === root.id || rel.target === root.id))) continue;\n addEdge(graph, rel.source, rel.target, rel.predicate);\n }\n\n const trace = runTrace(graph, model, root.id, args);\n const reached = new Set<string>([root.id]);\n for (const side of [trace.dependents, trace.dependencies]) {\n for (const id of [...(side?.direct ?? []), ...(side?.transitive ?? [])]) reached.add(id);\n }\n\n const changedElements: Record<string, string> = {};\n for (const id of reached) {\n const status = projected.getElement(id)?.status;\n if (status && status !== \"unchanged\") changedElements[id] = status;\n }\n const changedEdges = projected\n .changedRelationships()\n .filter((rel) => reached.has(rel.source) || reached.has(rel.target))\n .map((rel) => ({ source: rel.source, predicate: rel.predicate, target: rel.target, status: rel.status }));\n\n return {\n id: root.id,\n layer: root.layer,\n ...trace,\n changeset: { id: changeset.id!, name: changeset.name, elementStatus: root.status },\n changedElements,\n changedEdges,\n ...(warnings.length > 0 ? { warnings } : {}),\n };\n}\n\n/**\n * The changeset's projected layers, for the cross-layer references its elements carry. A layer\n * that fails to project falls back to its base version, with a warning.\n */\nasync function projectedLayers(model: Model, changeset: Changeset, warnings: string[]): Promise<Layer[]> {\n const names = new Set<string>(model.getLayerNames());\n for (const change of changeset.changes) {\n if (!change.type.startsWith(\"relationship-\")) names.add(change.layerName);\n }\n const isActive = model.getActiveChangesetId() === changeset.id;\n const layers: Layer[] = [];\n for (const name of names) {\n try {\n const layer = isActive\n ? await model.getLayer(name)\n : await model.getVirtualProjectionEngine().projectLayer(model, changeset.id!, name);\n if (layer) layers.push(layer);\n } catch (error) {\n warnings.push(`Layer ${name} could not be projected (${getErrorMessage(error)}); its base references were used`);\n const base = await model.getBaseLayer(name);\n if (base) layers.push(base);\n }\n }\n return layers;\n}\n\nfunction referenceGraph(layers: Iterable<Layer>): Graph {\n const registry = new ReferenceRegistry();\n for (const layer of layers) {\n for (const element of layer.listElements()) {\n registry.registerElement(element);\n }\n }\n return registry.getDependencyGraph();\n}\n\nfunction addEdge(graph: Graph, source: string, target: string, predicate: string): void {\n if (!graph.hasNode(source)) graph.addNode(source);\n if (!graph.hasNode(target)) graph.addNode(target);\n if (!graph.hasEdge(source, target)) {\n graph.addEdge(source, target, { type: predicate });\n }\n}\n\nfunction runTrace(\n graph: Graph,\n model: Model,\n id: string,\n args: ModelTraceArgs\n): Pick<TraceResult, \"direction\" | \"dependents\" | \"dependencies\"> {\n const tracker = new DependencyTracker(graph, model);\n const direction = args.direction ?? \"both\";\n const maxDepth = args.depth ?? null;\n const result: Pick<TraceResult, \"direction\" | \"dependents\" | \"dependencies\"> = { direction };\n\n if (direction === \"up\" || direction === \"both\") {\n const direct = tracker.traceDependencies(id, TraceDirection.DOWN, 1);\n const transitiveAll = tracker.traceDependencies(id, TraceDirection.DOWN, maxDepth);\n result.dependents = { direct, transitive: transitiveAll.filter((d) => !direct.includes(d)) };\n }\n\n if (direction === \"down\" || direction === \"both\") {\n const direct = tracker.traceDependencies(id, TraceDirection.UP, 1);\n const transitiveAll = tracker.traceDependencies(id, TraceDirection.UP, maxDepth);\n result.dependencies = { direct, transitive: transitiveAll.filter((d) => !direct.includes(d)) };\n }\n\n return result;\n}\n\nexport const modelTraceTool: McpToolDefinition<typeof inputSchema> = {\n name: \"model_trace\",\n description:\n \"Trace dependents and/or dependencies of an element across cross-layer references and relationships. \" +\n \"With an active changeset (or changesetId) it traces the changeset's projected graph: staged relationships are \" +\n \"followed, staged removals are not, and changedEdges / changedElements mark what the changeset adds or deletes.\",\n inputSchema,\n handler: modelTraceHandler,\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,18 +2,22 @@ import { z } from "zod";
|
|
|
2
2
|
import { Element } from "../../core/element.js";
|
|
3
3
|
import { Layer } from "../../core/layer.js";
|
|
4
4
|
import { MutationHandler } from "../../core/mutation-handler.js";
|
|
5
|
+
import { setStagedSourceReference } from "../../core/staged-source-reference.js";
|
|
5
6
|
import { getNodeTypesForLayer, isValidNodeType, normalizeNodeType } from "../../generated/node-types.js";
|
|
6
7
|
import { findElementLayer } from "../../utils/element-utils.js";
|
|
7
8
|
import { buildSourceReference, validateSourceReferenceOptions } from "../../utils/source-reference.js";
|
|
8
|
-
import {
|
|
9
|
+
import { schemaLookupHint, undefinedTypeWarning } from "../../utils/error-hints.js";
|
|
10
|
+
import { SchemaValidator, suggestNodeTypes } from "../../validators/schema-validator.js";
|
|
9
11
|
import { CLIError, ErrorCategory, findSimilar, formatValidOptions } from "../../utils/errors.js";
|
|
10
|
-
import {
|
|
12
|
+
import { effectiveAttributePatch, resolveAttributeUpdate } from "../../utils/attribute-patch.js";
|
|
13
|
+
import { jsonResult, loadModel, reloadModel, rootPathSchema, runTool } from "./shared.js";
|
|
11
14
|
const inputSchema = {
|
|
12
15
|
id: z.string().describe("Element ID or path to update."),
|
|
13
16
|
name: z.string().optional(),
|
|
14
17
|
description: z.string().optional(),
|
|
15
18
|
type: z.string().optional().describe("New element type within the current layer."),
|
|
16
19
|
attributes: z.record(z.string(), z.unknown()).optional().describe("Replaces the element's attributes, validated against its spec schema."),
|
|
20
|
+
attributePatch: z.record(z.string(), z.unknown()).optional().describe("Merged into the element's current attributes (as the active changeset leaves them), so required attributes need not be repeated; a null value removes that key. The merged result is validated against the spec schema. Cannot be combined with attributes."),
|
|
17
21
|
sourceFile: z.string().optional(),
|
|
18
22
|
sourceSymbol: z.string().optional(),
|
|
19
23
|
sourceProvenance: z.enum(["extracted", "manual", "inferred", "generated"]).optional(),
|
|
@@ -28,12 +32,12 @@ function validateUpdateSourceReferenceOptions(args) {
|
|
|
28
32
|
throw new CLIError("Cannot use clearSourceReference with other source reference options", ErrorCategory.USER, [
|
|
29
33
|
"Specify either clearSourceReference or other source options, not both"
|
|
30
34
|
]);
|
|
31
|
-
hasSourceOptions && validateSourceReferenceOptions(args);
|
|
35
|
+
hasSourceOptions && validateSourceReferenceOptions(args, "mcp");
|
|
32
36
|
}
|
|
33
37
|
async function modelUpdateHandler(args) {
|
|
34
38
|
return runTool(async () => {
|
|
35
|
-
validateUpdateSourceReferenceOptions(args);
|
|
36
|
-
const model = await loadModel(args.rootPath), layerName = await findElementLayer(model, args.id);
|
|
39
|
+
validateUpdateSourceReferenceOptions(args), resolveAttributeUpdate(void 0, { attributes: args.attributes, attributePatch: args.attributePatch });
|
|
40
|
+
const attributePatch = effectiveAttributePatch(args.attributePatch), model = await loadModel(args.rootPath), layerName = await findElementLayer(model, args.id);
|
|
37
41
|
if (!layerName)
|
|
38
42
|
throw new CLIError(`Element ${args.id} not found`, ErrorCategory.NOT_FOUND, [
|
|
39
43
|
'Use "model_search" to find similar elements'
|
|
@@ -43,9 +47,9 @@ async function modelUpdateHandler(args) {
|
|
|
43
47
|
throw new CLIError(`Element ${args.id} not found`, ErrorCategory.NOT_FOUND, [
|
|
44
48
|
'Use "model_search" to find similar elements'
|
|
45
49
|
]);
|
|
46
|
-
if (!(args.name || args.description !== void 0 || args.type || args.attributes || args.sourceFile || args.sourceSymbol || args.sourceProvenance || args.sourceRepoRemote || args.sourceRepoCommit || args.clearSourceReference))
|
|
50
|
+
if (!(args.name || args.description !== void 0 || args.type || args.attributes || attributePatch || args.sourceFile || args.sourceSymbol || args.sourceProvenance || args.sourceRepoRemote || args.sourceRepoCommit || args.clearSourceReference))
|
|
47
51
|
return jsonResult({ status: "no-op", id: args.id, message: "No fields specified for update" });
|
|
48
|
-
const mutationHandler = new MutationHandler(model, args.id, layerName);
|
|
52
|
+
const mutationHandler = new MutationHandler(model, args.id, layerName), warnings = [];
|
|
49
53
|
await mutationHandler.executeUpdate(element, async (elem, after) => {
|
|
50
54
|
let resolvedNewType;
|
|
51
55
|
if (args.type) {
|
|
@@ -55,46 +59,58 @@ async function modelUpdateHandler(args) {
|
|
|
55
59
|
}
|
|
56
60
|
resolvedNewType = normalizeNodeType(layerName, args.type);
|
|
57
61
|
}
|
|
58
|
-
|
|
62
|
+
const newAttributes = resolveAttributeUpdate(elem.attributes, {
|
|
63
|
+
attributes: args.attributes,
|
|
64
|
+
attributePatch
|
|
65
|
+
});
|
|
66
|
+
if (resolvedNewType || newAttributes) {
|
|
59
67
|
const candidateType = resolvedNewType ?? elem.type, tempElem = new Element({
|
|
60
68
|
...elem.toJSON(),
|
|
61
69
|
type: candidateType,
|
|
62
70
|
spec_node_id: `${layerName}.${candidateType}`,
|
|
63
|
-
attributes:
|
|
71
|
+
attributes: newAttributes ?? elem.attributes
|
|
64
72
|
}), schemaValidator = new SchemaValidator(), tempLayer = new Layer(layerName);
|
|
65
73
|
tempLayer.addElement(tempElem);
|
|
66
|
-
const attrValidation = await schemaValidator.validateLayer(tempLayer);
|
|
67
|
-
if (
|
|
68
|
-
const
|
|
74
|
+
const attrValidation = await schemaValidator.validateLayer(tempLayer), undefinedType = !isValidNodeType(layerName, candidateType), blocking = undefinedType ? attrValidation.errors.filter((e) => e.location !== "/type") : attrValidation.errors;
|
|
75
|
+
if (undefinedType) {
|
|
76
|
+
const { message, hint } = undefinedTypeWarning("mcp", layerName, args.id, candidateType, suggestNodeTypes(layerName, candidateType));
|
|
77
|
+
warnings.push(`${message}. ${hint}`);
|
|
78
|
+
}
|
|
79
|
+
if (blocking.length > 0) {
|
|
80
|
+
const errorMessages = blocking.map((e) => ` ${e.message}`).join(`
|
|
69
81
|
`);
|
|
70
82
|
throw new CLIError(`Updated attributes fail schema validation:
|
|
71
83
|
${errorMessages}`, ErrorCategory.USER, [
|
|
72
|
-
|
|
84
|
+
...undefinedType ? warnings : [schemaLookupHint("mcp", layerName, candidateType)]
|
|
73
85
|
]);
|
|
74
86
|
}
|
|
75
87
|
}
|
|
76
|
-
resolvedNewType && (elem.type = resolvedNewType, after.type = resolvedNewType, elem.spec_node_id = `${layerName}.${resolvedNewType}`, after.spec_node_id = `${layerName}.${resolvedNewType}`), args.name && (elem.name = args.name, after.name = args.name), args.description !== void 0 && (elem.description = args.description || void 0, after.description = args.description || void 0),
|
|
88
|
+
resolvedNewType && (elem.type = resolvedNewType, after.type = resolvedNewType, elem.spec_node_id = `${layerName}.${resolvedNewType}`, after.spec_node_id = `${layerName}.${resolvedNewType}`), args.name && (elem.name = args.name, after.name = args.name), args.description !== void 0 && (elem.description = args.description || void 0, after.description = args.description || void 0), newAttributes && (elem.attributes = newAttributes, after.attributes = newAttributes);
|
|
77
89
|
const hasSourceReferenceUpdate = args.sourceFile || args.sourceSymbol || args.sourceProvenance || args.sourceRepoRemote || args.sourceRepoCommit;
|
|
78
90
|
if (args.clearSourceReference)
|
|
79
|
-
elem.setSourceReference(void 0), after
|
|
91
|
+
elem.setSourceReference(void 0), setStagedSourceReference(after, void 0);
|
|
80
92
|
else if (hasSourceReferenceUpdate) {
|
|
81
93
|
const newRef = buildSourceReference(args);
|
|
82
|
-
newRef && (elem.setSourceReference(newRef), after
|
|
94
|
+
newRef && (elem.setSourceReference(newRef), setStagedSourceReference(after, newRef));
|
|
83
95
|
}
|
|
84
96
|
layer.updateElement(elem);
|
|
85
|
-
});
|
|
97
|
+
}), await reloadModel(args.rootPath);
|
|
86
98
|
const activeChangeset = await mutationHandler.getStagingManager().getActive(), staged = !!(activeChangeset && activeChangeset.status === "staged");
|
|
87
99
|
return jsonResult({
|
|
88
100
|
status: staged ? "staged" : "updated",
|
|
89
101
|
changeset: staged ? activeChangeset.name : void 0,
|
|
90
102
|
id: args.id,
|
|
91
|
-
layer: layerName
|
|
103
|
+
layer: layerName,
|
|
104
|
+
changedFields: mutationHandler.getChangedFields(),
|
|
105
|
+
// The changeset adds this element: the update was folded into that add (no separate change).
|
|
106
|
+
...mutationHandler.wasFoldedIntoAdd() ? { foldedIntoAdd: !0 } : {},
|
|
107
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
92
108
|
});
|
|
93
109
|
});
|
|
94
110
|
}
|
|
95
111
|
const modelUpdateTool = {
|
|
96
112
|
name: "model_update",
|
|
97
|
-
description: "Update an existing element's name, description, type, attributes, or source reference.",
|
|
113
|
+
description: "Update an existing element's name, description, type, attributes, or source reference. attributes replaces all attributes; attributePatch changes only the keys it names (null removes one).",
|
|
98
114
|
inputSchema,
|
|
99
115
|
handler: modelUpdateHandler
|
|
100
116
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/mcp/tools/model-update.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * model_update \u2014 update an existing element. Mirrors `dr update`, operating\n * through the shared MutationHandler.\n */\n\nimport { z } from \"zod\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { Element } from \"../../core/element.js\";\nimport { Layer } from \"../../core/layer.js\";\nimport { MutationHandler } from \"../../core/mutation-handler.js\";\nimport { getNodeTypesForLayer, isValidNodeType, normalizeNodeType } from \"../../generated/node-types.js\";\nimport { findElementLayer } from \"../../utils/element-utils.js\";\nimport { buildSourceReference, validateSourceReferenceOptions } from \"../../utils/source-reference.js\";\nimport { SchemaValidator } from \"../../validators/schema-validator.js\";\nimport { CLIError, ErrorCategory, findSimilar, formatValidOptions } from \"../../utils/errors.js\";\nimport { jsonResult, loadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\n\nconst inputSchema = {\n id: z.string().describe(\"Element ID or path to update.\"),\n name: z.string().optional(),\n description: z.string().optional(),\n type: z.string().optional().describe(\"New element type within the current layer.\"),\n attributes: z.record(z.string(), z.unknown()).optional().describe(\"Replaces the element's attributes, validated against its spec schema.\"),\n sourceFile: z.string().optional(),\n sourceSymbol: z.string().optional(),\n sourceProvenance: z.enum([\"extracted\", \"manual\", \"inferred\", \"generated\"]).optional(),\n sourceRepoRemote: z.string().optional(),\n sourceRepoCommit: z.string().optional().describe(\"Full 40-character Git commit SHA.\"),\n clearSourceReference: z.boolean().optional().describe(\"Remove the element's source reference. Cannot be combined with other source options.\"),\n rootPath: rootPathSchema,\n};\n\nexport type ModelUpdateArgs = z.infer<z.ZodObject<typeof inputSchema>>;\n\nfunction validateUpdateSourceReferenceOptions(args: ModelUpdateArgs): void {\n const hasSourceOptions =\n args.sourceFile || args.sourceSymbol || args.sourceProvenance || args.sourceRepoRemote || args.sourceRepoCommit;\n\n if (args.clearSourceReference && hasSourceOptions) {\n throw new CLIError(\"Cannot use clearSourceReference with other source reference options\", ErrorCategory.USER, [\n \"Specify either clearSourceReference or other source options, not both\",\n ]);\n }\n\n if (hasSourceOptions) {\n validateSourceReferenceOptions(args);\n }\n}\n\nexport async function modelUpdateHandler(args: ModelUpdateArgs): Promise<CallToolResult> {\n return runTool(async () => {\n validateUpdateSourceReferenceOptions(args);\n\n const model = await loadModel(args.rootPath);\n\n const layerName = await findElementLayer(model, args.id);\n if (!layerName) {\n throw new CLIError(`Element ${args.id} not found`, ErrorCategory.NOT_FOUND, [\n 'Use \"model_search\" to find similar elements',\n ]);\n }\n\n const layer = await model.getLayer(layerName);\n const element = layer?.getElement(args.id);\n if (!layer || !element) {\n throw new CLIError(`Element ${args.id} not found`, ErrorCategory.NOT_FOUND, [\n 'Use \"model_search\" to find similar elements',\n ]);\n }\n\n const hasUpdates =\n args.name ||\n args.description !== undefined ||\n args.type ||\n args.attributes ||\n args.sourceFile ||\n args.sourceSymbol ||\n args.sourceProvenance ||\n args.sourceRepoRemote ||\n args.sourceRepoCommit ||\n args.clearSourceReference;\n\n if (!hasUpdates) {\n return jsonResult({ status: \"no-op\", id: args.id, message: \"No fields specified for update\" });\n }\n\n const mutationHandler = new MutationHandler(model, args.id, layerName);\n\n await mutationHandler.executeUpdate(element, async (elem, after) => {\n let resolvedNewType: string | undefined;\n if (args.type) {\n if (!isValidNodeType(layerName, args.type)) {\n const validNodeTypes = getNodeTypesForLayer(layerName);\n const typeNames = validNodeTypes.map((t) => t.type).sort();\n const similar = findSimilar(args.type!, typeNames, 3);\n const suggestions: string[] = [`Valid types for ${layerName}: ${formatValidOptions(typeNames)}`];\n if (similar.length > 0) suggestions.unshift(`Did you mean: ${similar.join(\" or \")}?`);\n throw new CLIError(`Invalid element type \"${args.type}\" for layer \"${layerName}\"`, ErrorCategory.USER, suggestions);\n }\n resolvedNewType = normalizeNodeType(layerName, args.type);\n }\n\n if (resolvedNewType || args.attributes) {\n const candidateType = resolvedNewType ?? elem.type;\n const tempElem = new Element({\n ...elem.toJSON(),\n type: candidateType,\n spec_node_id: `${layerName}.${candidateType}`,\n attributes: args.attributes ?? elem.attributes,\n });\n const schemaValidator = new SchemaValidator();\n const tempLayer = new Layer(layerName);\n tempLayer.addElement(tempElem);\n const attrValidation = await schemaValidator.validateLayer(tempLayer);\n if (!attrValidation.isValid()) {\n const errorMessages = attrValidation.errors.map((e) => ` ${e.message}`).join(\"\\n\");\n throw new CLIError(`Updated attributes fail schema validation:\\n${errorMessages}`, ErrorCategory.USER, [\n `Use the spec resource for \"${candidateType}\" to see the required attributes`,\n ]);\n }\n }\n\n if (resolvedNewType) {\n elem.type = resolvedNewType;\n after.type = resolvedNewType;\n elem.spec_node_id = `${layerName}.${resolvedNewType}`;\n after.spec_node_id = `${layerName}.${resolvedNewType}`;\n }\n\n if (args.name) {\n elem.name = args.name;\n after.name = args.name;\n }\n\n if (args.description !== undefined) {\n elem.description = args.description || undefined;\n after.description = args.description || undefined;\n }\n\n if (args.attributes) {\n elem.attributes = args.attributes;\n after.attributes = args.attributes;\n }\n\n const hasSourceReferenceUpdate =\n args.sourceFile || args.sourceSymbol || args.sourceProvenance || args.sourceRepoRemote || args.sourceRepoCommit;\n\n if (args.clearSourceReference) {\n elem.setSourceReference(undefined);\n after.sourceReference = undefined;\n } else if (hasSourceReferenceUpdate) {\n const newRef = buildSourceReference(args);\n if (newRef) {\n elem.setSourceReference(newRef);\n after.sourceReference = newRef;\n }\n }\n\n layer.updateElement(elem);\n });\n\n const stagingManager = mutationHandler.getStagingManager();\n const activeChangeset = await stagingManager.getActive();\n const staged = !!(activeChangeset && activeChangeset.status === \"staged\");\n\n return jsonResult({\n status: staged ? \"staged\" : \"updated\",\n changeset: staged ? activeChangeset!.name : undefined,\n id: args.id,\n layer: layerName,\n });\n });\n}\n\nexport const modelUpdateTool: McpToolDefinition<typeof inputSchema> = {\n name: \"model_update\",\n description: \"Update an existing element's name, description, type, attributes, or source reference.\",\n inputSchema,\n handler: modelUpdateHandler,\n};\n"],
|
|
5
|
-
"mappings": "AAKA,SAAS,SAAS;AAElB,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,uBAAuB;AAChC,SAAS,sBAAsB,iBAAiB,yBAAyB;AACzE,SAAS,wBAAwB;AACjC,SAAS,sBAAsB,sCAAsC;AACrE,SAAS,
|
|
4
|
+
"sourcesContent": ["/**\n * model_update \u2014 update an existing element. Mirrors `dr update`, operating\n * through the shared MutationHandler.\n */\n\nimport { z } from \"zod\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { Element } from \"../../core/element.js\";\nimport { Layer } from \"../../core/layer.js\";\nimport { MutationHandler } from \"../../core/mutation-handler.js\";\nimport { setStagedSourceReference } from \"../../core/staged-source-reference.js\";\nimport { getNodeTypesForLayer, isValidNodeType, normalizeNodeType } from \"../../generated/node-types.js\";\nimport { findElementLayer } from \"../../utils/element-utils.js\";\nimport { buildSourceReference, validateSourceReferenceOptions } from \"../../utils/source-reference.js\";\nimport { schemaLookupHint, undefinedTypeWarning } from \"../../utils/error-hints.js\";\nimport { SchemaValidator, suggestNodeTypes } from \"../../validators/schema-validator.js\";\nimport { CLIError, ErrorCategory, findSimilar, formatValidOptions } from \"../../utils/errors.js\";\nimport { effectiveAttributePatch, resolveAttributeUpdate } from \"../../utils/attribute-patch.js\";\nimport { jsonResult, loadModel, reloadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\n\nconst inputSchema = {\n id: z.string().describe(\"Element ID or path to update.\"),\n name: z.string().optional(),\n description: z.string().optional(),\n type: z.string().optional().describe(\"New element type within the current layer.\"),\n attributes: z.record(z.string(), z.unknown()).optional().describe(\"Replaces the element's attributes, validated against its spec schema.\"),\n attributePatch: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\n \"Merged into the element's current attributes (as the active changeset leaves them), so required attributes \" +\n \"need not be repeated; a null value removes that key. The merged result is validated against the spec \" +\n \"schema. Cannot be combined with attributes.\"\n ),\n sourceFile: z.string().optional(),\n sourceSymbol: z.string().optional(),\n sourceProvenance: z.enum([\"extracted\", \"manual\", \"inferred\", \"generated\"]).optional(),\n sourceRepoRemote: z.string().optional(),\n sourceRepoCommit: z.string().optional().describe(\"Full 40-character Git commit SHA.\"),\n clearSourceReference: z.boolean().optional().describe(\"Remove the element's source reference. Cannot be combined with other source options.\"),\n rootPath: rootPathSchema,\n};\n\nexport type ModelUpdateArgs = z.infer<z.ZodObject<typeof inputSchema>>;\n\nfunction validateUpdateSourceReferenceOptions(args: ModelUpdateArgs): void {\n const hasSourceOptions =\n args.sourceFile || args.sourceSymbol || args.sourceProvenance || args.sourceRepoRemote || args.sourceRepoCommit;\n\n if (args.clearSourceReference && hasSourceOptions) {\n throw new CLIError(\"Cannot use clearSourceReference with other source reference options\", ErrorCategory.USER, [\n \"Specify either clearSourceReference or other source options, not both\",\n ]);\n }\n\n if (hasSourceOptions) {\n validateSourceReferenceOptions(args, \"mcp\");\n }\n}\n\nexport async function modelUpdateHandler(args: ModelUpdateArgs): Promise<CallToolResult> {\n return runTool(async () => {\n validateUpdateSourceReferenceOptions(args);\n // Refuse attributes + attributePatch before touching the model.\n resolveAttributeUpdate(undefined, { attributes: args.attributes, attributePatch: args.attributePatch });\n // An empty patch names no key: it is not an update.\n const attributePatch = effectiveAttributePatch(args.attributePatch);\n\n const model = await loadModel(args.rootPath);\n\n const layerName = await findElementLayer(model, args.id);\n if (!layerName) {\n throw new CLIError(`Element ${args.id} not found`, ErrorCategory.NOT_FOUND, [\n 'Use \"model_search\" to find similar elements',\n ]);\n }\n\n const layer = await model.getLayer(layerName);\n const element = layer?.getElement(args.id);\n if (!layer || !element) {\n throw new CLIError(`Element ${args.id} not found`, ErrorCategory.NOT_FOUND, [\n 'Use \"model_search\" to find similar elements',\n ]);\n }\n\n const hasUpdates =\n args.name ||\n args.description !== undefined ||\n args.type ||\n args.attributes ||\n attributePatch ||\n args.sourceFile ||\n args.sourceSymbol ||\n args.sourceProvenance ||\n args.sourceRepoRemote ||\n args.sourceRepoCommit ||\n args.clearSourceReference;\n\n if (!hasUpdates) {\n return jsonResult({ status: \"no-op\", id: args.id, message: \"No fields specified for update\" });\n }\n\n const mutationHandler = new MutationHandler(model, args.id, layerName);\n const warnings: string[] = [];\n\n await mutationHandler.executeUpdate(element, async (elem, after) => {\n let resolvedNewType: string | undefined;\n if (args.type) {\n if (!isValidNodeType(layerName, args.type)) {\n const validNodeTypes = getNodeTypesForLayer(layerName);\n const typeNames = validNodeTypes.map((t) => t.type).sort();\n const similar = findSimilar(args.type!, typeNames, 3);\n const suggestions: string[] = [`Valid types for ${layerName}: ${formatValidOptions(typeNames)}`];\n if (similar.length > 0) suggestions.unshift(`Did you mean: ${similar.join(\" or \")}?`);\n throw new CLIError(`Invalid element type \"${args.type}\" for layer \"${layerName}\"`, ErrorCategory.USER, suggestions);\n }\n resolvedNewType = normalizeNodeType(layerName, args.type);\n }\n\n // The element is the projected one, so a patch merges over attributes the changeset already staged.\n const newAttributes = resolveAttributeUpdate(elem.attributes, {\n attributes: args.attributes,\n attributePatch,\n });\n\n if (resolvedNewType || newAttributes) {\n const candidateType = resolvedNewType ?? elem.type;\n const tempElem = new Element({\n ...elem.toJSON(),\n type: candidateType,\n spec_node_id: `${layerName}.${candidateType}`,\n attributes: newAttributes ?? elem.attributes,\n });\n const schemaValidator = new SchemaValidator();\n const tempLayer = new Layer(layerName);\n tempLayer.addElement(tempElem);\n const attrValidation = await schemaValidator.validateLayer(tempLayer);\n // An element already of a type the spec no longer defines (no type change requested):\n // its attributes have no type schema to fail, so let the update through and say how to\n // retype it, rather than refusing with a spec_type hint that cannot work.\n const undefinedType = !isValidNodeType(layerName, candidateType);\n const blocking = undefinedType\n ? attrValidation.errors.filter((e) => e.location !== \"/type\")\n : attrValidation.errors;\n if (undefinedType) {\n const { message, hint } = undefinedTypeWarning(\n \"mcp\",\n layerName,\n args.id,\n candidateType,\n suggestNodeTypes(layerName, candidateType)\n );\n warnings.push(`${message}. ${hint}`);\n }\n if (blocking.length > 0) {\n const errorMessages = blocking.map((e) => ` ${e.message}`).join(\"\\n\");\n throw new CLIError(`Updated attributes fail schema validation:\\n${errorMessages}`, ErrorCategory.USER, [\n ...(undefinedType ? warnings : [schemaLookupHint(\"mcp\", layerName, candidateType)]),\n ]);\n }\n }\n\n if (resolvedNewType) {\n elem.type = resolvedNewType;\n after.type = resolvedNewType;\n elem.spec_node_id = `${layerName}.${resolvedNewType}`;\n after.spec_node_id = `${layerName}.${resolvedNewType}`;\n }\n\n if (args.name) {\n elem.name = args.name;\n after.name = args.name;\n }\n\n if (args.description !== undefined) {\n elem.description = args.description || undefined;\n after.description = args.description || undefined;\n }\n\n if (newAttributes) {\n elem.attributes = newAttributes;\n after.attributes = newAttributes;\n }\n\n const hasSourceReferenceUpdate =\n args.sourceFile || args.sourceSymbol || args.sourceProvenance || args.sourceRepoRemote || args.sourceRepoCommit;\n\n if (args.clearSourceReference) {\n elem.setSourceReference(undefined);\n setStagedSourceReference(after, undefined);\n } else if (hasSourceReferenceUpdate) {\n const newRef = buildSourceReference(args);\n if (newRef) {\n elem.setSourceReference(newRef);\n setStagedSourceReference(after, newRef);\n }\n }\n\n layer.updateElement(elem);\n });\n\n // The handler mutated the cached (projected) element in place; reread so the next call projects\n // from what is on disk, staged or written.\n await reloadModel(args.rootPath);\n\n const stagingManager = mutationHandler.getStagingManager();\n const activeChangeset = await stagingManager.getActive();\n const staged = !!(activeChangeset && activeChangeset.status === \"staged\");\n\n return jsonResult({\n status: staged ? \"staged\" : \"updated\",\n changeset: staged ? activeChangeset!.name : undefined,\n id: args.id,\n layer: layerName,\n changedFields: mutationHandler.getChangedFields(),\n // The changeset adds this element: the update was folded into that add (no separate change).\n ...(mutationHandler.wasFoldedIntoAdd() ? { foldedIntoAdd: true } : {}),\n ...(warnings.length > 0 ? { warnings } : {}),\n });\n });\n}\n\nexport const modelUpdateTool: McpToolDefinition<typeof inputSchema> = {\n name: \"model_update\",\n description:\n \"Update an existing element's name, description, type, attributes, or source reference. attributes replaces \" +\n \"all attributes; attributePatch changes only the keys it names (null removes one).\",\n inputSchema,\n handler: modelUpdateHandler,\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ChangesetGraph } from "../../core/changeset-graph.js";
|
|
3
|
+
import { Element } from "../../core/element.js";
|
|
4
|
+
import { StagingAreaManager } from "../../core/staging-area.js";
|
|
5
|
+
import { liveRelationshipsBetween, stageRelationshipDeletes } from "../../core/relationship-staging.js";
|
|
6
|
+
import { addRelationship, deleteRelationshipHandler } from "../../commands/relationship.js";
|
|
7
|
+
import { CLIError, ErrorCategory } from "../../utils/errors.js";
|
|
8
|
+
import { jsonResult, loadModel, reloadModel, rootPathSchema, runTool } from "./shared.js";
|
|
9
|
+
import { activeStagedChangeset } from "./changeset-shared.js";
|
|
10
|
+
const addInputSchema = {
|
|
11
|
+
source: z.string().describe("Source element ID (layer.type.name)."),
|
|
12
|
+
predicate: z.string().describe("Relationship predicate, e.g. 'realizes', 'serves', 'accesses'. Use spec_type to see what the catalog allows."),
|
|
13
|
+
target: z.string().describe("Target element ID (layer.type.name)."),
|
|
14
|
+
properties: z.record(z.string(), z.unknown()).optional().describe("Optional relationship properties."),
|
|
15
|
+
rootPath: rootPathSchema
|
|
16
|
+
};
|
|
17
|
+
async function relationshipAddHandler(args) {
|
|
18
|
+
return runTool(async () => {
|
|
19
|
+
const model = await loadModel(args.rootPath), manager = new StagingAreaManager(model.rootPath, model), active = await activeStagedChangeset(manager);
|
|
20
|
+
if (!active) {
|
|
21
|
+
const outcome2 = await addRelationship(model, args.source, args.target, args.predicate, args.properties);
|
|
22
|
+
return jsonResult({
|
|
23
|
+
status: outcome2,
|
|
24
|
+
source: args.source,
|
|
25
|
+
predicate: args.predicate,
|
|
26
|
+
target: args.target
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const graph = await ChangesetGraph.build(model, active), sourceElement = liveElement(graph, args.source, "Source"), targetElement = liveElement(graph, args.target, "Target"), source = sourceElement.id, target = targetElement.id, triple = { source, predicate: args.predicate, target }, outcome = await addRelationship(model, source, target, args.predicate, args.properties, {
|
|
30
|
+
source: preResolved(sourceElement),
|
|
31
|
+
target: preResolved(targetElement)
|
|
32
|
+
});
|
|
33
|
+
return outcome !== "duplicate" && await reloadModel(args.rootPath), jsonResult({ status: outcome, changeset: active.name, ...triple });
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const relationshipAddTool = {
|
|
37
|
+
name: "relationship_add",
|
|
38
|
+
description: "Add a relationship between two elements, validated against the relationship catalog. Stages it in the active changeset when there is one (including between elements the changeset itself adds), else writes the model. Status: 'staged' or 'added'; 'duplicate' when the model or the changeset already has it (nothing staged); 'restored' when the changeset had staged its deletion (that deletion is unstaged).",
|
|
39
|
+
inputSchema: addInputSchema,
|
|
40
|
+
handler: relationshipAddHandler
|
|
41
|
+
}, deleteInputSchema = {
|
|
42
|
+
source: z.string().describe("Source element ID."),
|
|
43
|
+
target: z.string().describe("Target element ID."),
|
|
44
|
+
predicate: z.string().optional().describe("Only this predicate. Omit to remove every relationship from source to target."),
|
|
45
|
+
rootPath: rootPathSchema
|
|
46
|
+
};
|
|
47
|
+
async function relationshipDeleteHandler(args) {
|
|
48
|
+
return runTool(async () => {
|
|
49
|
+
const model = await loadModel(args.rootPath), manager = new StagingAreaManager(model.rootPath, model), active = await activeStagedChangeset(manager);
|
|
50
|
+
if (!active) {
|
|
51
|
+
const { deletedCount } = await deleteRelationshipHandler(model, args.source, args.target, args.predicate, void 0, "mcp");
|
|
52
|
+
return jsonResult({ status: "deleted", deletedCount, source: args.source, target: args.target });
|
|
53
|
+
}
|
|
54
|
+
const graph = await ChangesetGraph.build(model, active), source = graph.resolve(args.source), target = graph.resolve(args.target), matches = liveRelationshipsBetween(graph, source, target, args.predicate);
|
|
55
|
+
if (matches.length === 0)
|
|
56
|
+
throw new CLIError("No matching relationships found", ErrorCategory.USER, [
|
|
57
|
+
`Use "model_show" on ${args.source} to see its relationships`,
|
|
58
|
+
'Use "changeset_brief" to see what this changeset already stages'
|
|
59
|
+
]);
|
|
60
|
+
const results = await stageRelationshipDeletes(manager, graph, active, matches);
|
|
61
|
+
return await reloadModel(args.rootPath), jsonResult({ changeset: active.name, source, target, relationships: results });
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const relationshipDeleteTool = {
|
|
65
|
+
name: "relationship_delete",
|
|
66
|
+
description: "Remove relationships from source to target. In an active changeset, stages the removal, or unstages the relationship if this changeset added it; else writes the model.",
|
|
67
|
+
inputSchema: deleteInputSchema,
|
|
68
|
+
handler: relationshipDeleteHandler
|
|
69
|
+
};
|
|
70
|
+
function liveElement(graph, id, role) {
|
|
71
|
+
const element = graph.getElement(id);
|
|
72
|
+
if (!element || element.status === "deleted")
|
|
73
|
+
throw new CLIError(`${role} element ${id} not found${element ? " (this changeset deletes it)" : ""}`, ErrorCategory.USER, ['Use "model_search" to find elements, or "changeset_brief" to see what this changeset adds']);
|
|
74
|
+
return element;
|
|
75
|
+
}
|
|
76
|
+
function preResolved(element) {
|
|
77
|
+
return {
|
|
78
|
+
layerName: element.layer,
|
|
79
|
+
element: new Element({
|
|
80
|
+
id: element.uuid ?? element.id,
|
|
81
|
+
path: element.id,
|
|
82
|
+
spec_node_id: `${element.layer}.${element.type}`,
|
|
83
|
+
layer_id: element.layer,
|
|
84
|
+
type: element.type,
|
|
85
|
+
name: element.name,
|
|
86
|
+
description: element.description,
|
|
87
|
+
attributes: element.attributes,
|
|
88
|
+
layer: element.layer
|
|
89
|
+
})
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
relationshipAddHandler,
|
|
94
|
+
relationshipAddTool,
|
|
95
|
+
relationshipDeleteHandler,
|
|
96
|
+
relationshipDeleteTool
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=relationship-tools.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/mcp/tools/relationship-tools.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * relationship_add, relationship_delete \u2014 stage (or write) relationships. Mirror\n * `dr relationship add|delete`, validated against the relationship catalog.\n *\n * Inside an active changeset both tools read the changeset's projected graph, so they work between\n * elements that exist only in the changeset, report a repeated add as a duplicate rather than\n * staging it twice, and undo instead of stacking: deleting a relationship this changeset added\n * unstages the add, and re-adding one it deleted unstages the delete. The projected-graph logic is\n * shared with the CLI (`cli/src/core/relationship-staging.ts`).\n */\n\nimport { z } from \"zod\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { ChangesetGraph, type GraphElement } from \"../../core/changeset-graph.js\";\nimport { Element } from \"../../core/element.js\";\nimport { StagingAreaManager } from \"../../core/staging-area.js\";\nimport { liveRelationshipsBetween, stageRelationshipDeletes } from \"../../core/relationship-staging.js\";\nimport { addRelationship, deleteRelationshipHandler } from \"../../commands/relationship.js\";\nimport { CLIError, ErrorCategory } from \"../../utils/errors.js\";\nimport { jsonResult, loadModel, reloadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\nimport { activeStagedChangeset } from \"./changeset-shared.js\";\n\n// ---------------------------------------------------------------------------\n// relationship_add\n// ---------------------------------------------------------------------------\n\nconst addInputSchema = {\n source: z.string().describe(\"Source element ID (layer.type.name).\"),\n predicate: z\n .string()\n .describe(\"Relationship predicate, e.g. 'realizes', 'serves', 'accesses'. Use spec_type to see what the catalog allows.\"),\n target: z.string().describe(\"Target element ID (layer.type.name).\"),\n properties: z.record(z.string(), z.unknown()).optional().describe(\"Optional relationship properties.\"),\n rootPath: rootPathSchema,\n};\n\nexport type RelationshipAddArgs = z.infer<z.ZodObject<typeof addInputSchema>>;\n\nexport async function relationshipAddHandler(args: RelationshipAddArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const active = await activeStagedChangeset(manager);\n\n if (!active) {\n const outcome = await addRelationship(model, args.source, args.target, args.predicate, args.properties);\n return jsonResult({\n status: outcome,\n source: args.source,\n predicate: args.predicate,\n target: args.target,\n });\n }\n\n // Resolve the ends in the projection first, for a clear error when the changeset deletes one.\n const graph = await ChangesetGraph.build(model, active);\n const sourceElement = liveElement(graph, args.source, \"Source\");\n const targetElement = liveElement(graph, args.target, \"Target\");\n const source = sourceElement.id;\n const target = targetElement.id;\n const triple = { source, predicate: args.predicate, target };\n\n // Hand addRelationship the projection's resolution: its own lookup reads the cached model's\n // layers, which need not know a layer only this changeset adds elements to.\n const outcome = await addRelationship(model, source, target, args.predicate, args.properties, {\n source: preResolved(sourceElement),\n target: preResolved(targetElement),\n });\n // Model.load merges the active changeset's relationships into model.relationships, and other tools\n // (model_validate, model_export, model_delete's cascade) read that merged view: reload after any staging.\n if (outcome !== \"duplicate\") {\n await reloadModel(args.rootPath);\n }\n return jsonResult({ status: outcome, changeset: active.name, ...triple });\n });\n}\n\nexport const relationshipAddTool: McpToolDefinition<typeof addInputSchema> = {\n name: \"relationship_add\",\n description:\n \"Add a relationship between two elements, validated against the relationship catalog. Stages it in the \" +\n \"active changeset when there is one (including between elements the changeset itself adds), else writes the model. \" +\n \"Status: 'staged' or 'added'; 'duplicate' when the model or the changeset already has it (nothing staged); \" +\n \"'restored' when the changeset had staged its deletion (that deletion is unstaged).\",\n inputSchema: addInputSchema,\n handler: relationshipAddHandler,\n};\n\n// ---------------------------------------------------------------------------\n// relationship_delete\n// ---------------------------------------------------------------------------\n\nconst deleteInputSchema = {\n source: z.string().describe(\"Source element ID.\"),\n target: z.string().describe(\"Target element ID.\"),\n predicate: z.string().optional().describe(\"Only this predicate. Omit to remove every relationship from source to target.\"),\n rootPath: rootPathSchema,\n};\n\nexport type RelationshipDeleteArgs = z.infer<z.ZodObject<typeof deleteInputSchema>>;\n\nexport async function relationshipDeleteHandler(args: RelationshipDeleteArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const active = await activeStagedChangeset(manager);\n\n if (!active) {\n const { deletedCount } = await deleteRelationshipHandler(\n model,\n args.source,\n args.target,\n args.predicate,\n undefined,\n \"mcp\"\n );\n return jsonResult({ status: \"deleted\", deletedCount, source: args.source, target: args.target });\n }\n\n const graph = await ChangesetGraph.build(model, active);\n const source = graph.resolve(args.source);\n const target = graph.resolve(args.target);\n const matches = liveRelationshipsBetween(graph, source, target, args.predicate);\n\n if (matches.length === 0) {\n throw new CLIError(\"No matching relationships found\", ErrorCategory.USER, [\n `Use \"model_show\" on ${args.source} to see its relationships`,\n 'Use \"changeset_brief\" to see what this changeset already stages',\n ]);\n }\n\n const results = await stageRelationshipDeletes(manager, graph, active, matches);\n // Staged or unstaged, the cached model's merged relationships are now stale.\n await reloadModel(args.rootPath);\n return jsonResult({ changeset: active.name, source, target, relationships: results });\n });\n}\n\nexport const relationshipDeleteTool: McpToolDefinition<typeof deleteInputSchema> = {\n name: \"relationship_delete\",\n description:\n \"Remove relationships from source to target. In an active changeset, stages the removal, or unstages the \" +\n \"relationship if this changeset added it; else writes the model.\",\n inputSchema: deleteInputSchema,\n handler: relationshipDeleteHandler,\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction liveElement(graph: ChangesetGraph, id: string, role: \"Source\" | \"Target\"): GraphElement {\n const element = graph.getElement(id);\n if (!element || element.status === \"deleted\") {\n throw new CLIError(\n `${role} element ${id} not found${element ? \" (this changeset deletes it)\" : \"\"}`,\n ErrorCategory.USER,\n ['Use \"model_search\" to find elements, or \"changeset_brief\" to see what this changeset adds']\n );\n }\n return element;\n}\n\n/** A projected element in the shape addRelationship's catalog check reads. */\nfunction preResolved(element: GraphElement): { element: Element; layerName: string } {\n return {\n layerName: element.layer,\n element: new Element({\n id: element.uuid ?? element.id,\n path: element.id,\n spec_node_id: `${element.layer}.${element.type}`,\n layer_id: element.layer,\n type: element.type,\n name: element.name,\n description: element.description,\n attributes: element.attributes,\n layer: element.layer,\n }),\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["outcome"]
|
|
7
|
+
}
|
package/dist/mcp/tools/shared.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { Model } from "../../core/model.js";
|
|
3
3
|
import { CLIError, ModelNotFoundError, getErrorMessage } from "../../utils/errors.js";
|
|
4
|
+
import { ChangesetOnlyError } from "../../core/changeset-only-guard.js";
|
|
5
|
+
import { changesetOnlyError } from "../changeset-only.js";
|
|
4
6
|
const rootPathSchema = z.string().optional().describe("Absolute path to the project root containing documentation-robotics/. Defaults to the MCP server's working directory."), modelCache = /* @__PURE__ */ new Map();
|
|
5
7
|
function modelCacheKey(rootPath) {
|
|
6
8
|
return rootPath ?? "";
|
|
@@ -36,10 +38,30 @@ function evictOnFailure(key, promise) {
|
|
|
36
38
|
}
|
|
37
39
|
function jsonResult(data, options) {
|
|
38
40
|
return {
|
|
39
|
-
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
41
|
+
content: [{ type: "text", text: options?.compact ? compactJson(data) : JSON.stringify(data, null, 2) }],
|
|
40
42
|
...options?.isError ? { isError: !0 } : {}
|
|
41
43
|
};
|
|
42
44
|
}
|
|
45
|
+
const COMPACT_LINE = 140;
|
|
46
|
+
function compactJson(data) {
|
|
47
|
+
const write = (raw, indent) => {
|
|
48
|
+
const value = raw !== null && typeof raw == "object" && typeof raw.toJSON == "function" ? raw.toJSON() : raw, inline = JSON.stringify(value);
|
|
49
|
+
if (inline === void 0 || value === null || typeof value != "object" || inline.length + indent.length <= COMPACT_LINE)
|
|
50
|
+
return inline;
|
|
51
|
+
const inner = indent + " ";
|
|
52
|
+
return Array.isArray(value) ? `[
|
|
53
|
+
${value.map((item) => inner + (write(item, inner) ?? "null")).join(`,
|
|
54
|
+
`)}
|
|
55
|
+
${indent}]` : `{
|
|
56
|
+
${Object.entries(value).map(([key, item]) => {
|
|
57
|
+
const text = write(item, inner);
|
|
58
|
+
return text === void 0 ? void 0 : `${inner}${JSON.stringify(key)}: ${text}`;
|
|
59
|
+
}).filter((line) => line !== void 0).join(`,
|
|
60
|
+
`)}
|
|
61
|
+
${indent}}`;
|
|
62
|
+
};
|
|
63
|
+
return write(data, "") ?? "null";
|
|
64
|
+
}
|
|
43
65
|
function errorResult(error) {
|
|
44
66
|
if (error instanceof CLIError) {
|
|
45
67
|
const context = error.context;
|
|
@@ -85,7 +107,7 @@ async function runTool(fn) {
|
|
|
85
107
|
try {
|
|
86
108
|
return await fn();
|
|
87
109
|
} catch (error) {
|
|
88
|
-
return logToolError(error), errorResult(error);
|
|
110
|
+
return logToolError(error), error instanceof ChangesetOnlyError ? (clearModelCache(), errorResult(changesetOnlyError())) : errorResult(error);
|
|
89
111
|
}
|
|
90
112
|
}
|
|
91
113
|
function logToolError(error) {
|
|
@@ -93,6 +115,7 @@ function logToolError(error) {
|
|
|
93
115
|
}
|
|
94
116
|
export {
|
|
95
117
|
clearModelCache,
|
|
118
|
+
compactJson,
|
|
96
119
|
errorResult,
|
|
97
120
|
jsonResult,
|
|
98
121
|
loadModel,
|