@documentation-robotics/cli 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -16
- package/dist/analysis/relationship-classifier.js +2 -9
- package/dist/analysis/relationship-classifier.js.map +2 -2
- package/dist/analyzers/active-analyzer.js +62 -0
- package/dist/analyzers/active-analyzer.js.map +7 -0
- 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 +191 -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/extract.js +138 -0
- package/dist/analyzers/cards/extract.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/routes.js +96 -0
- package/dist/analyzers/cards/routes.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 +270 -174
- package/dist/analyzers/cbm-analyzer.js.map +2 -2
- package/dist/analyzers/cbm-responses.js +342 -0
- package/dist/analyzers/cbm-responses.js.map +7 -0
- package/dist/analyzers/code-changes.js +159 -0
- package/dist/analyzers/code-changes.js.map +7 -0
- package/dist/analyzers/edge-relationships.js +49 -0
- package/dist/analyzers/edge-relationships.js.map +7 -0
- package/dist/analyzers/extraction-plan.js +541 -0
- package/dist/analyzers/extraction-plan.js.map +7 -0
- package/dist/analyzers/index-job.js +229 -0
- package/dist/analyzers/index-job.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 +23 -11
- 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 +269 -0
- package/dist/coding-agents/drbot-mcp.js.map +7 -0
- package/dist/commands/add.js +7 -4
- package/dist/commands/add.js.map +2 -2
- package/dist/commands/analyzer.js +143 -40
- package/dist/commands/analyzer.js.map +2 -2
- package/dist/commands/changeset-verify.js +63 -0
- package/dist/commands/changeset-verify.js.map +7 -0
- package/dist/commands/changeset.js +80 -29
- 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/init.js +9 -26
- package/dist/commands/init.js.map +2 -2
- package/dist/commands/mcp.js +40 -21
- package/dist/commands/mcp.js.map +3 -3
- package/dist/commands/relationship.js +208 -37
- package/dist/commands/relationship.js.map +2 -2
- package/dist/commands/reports.js +3 -3
- package/dist/commands/reports.js.map +2 -2
- package/dist/commands/update.js +52 -32
- package/dist/commands/update.js.map +2 -2
- package/dist/commands/upgrade.js +99 -103
- package/dist/commands/upgrade.js.map +3 -3
- package/dist/commands/validate.js +19 -3
- package/dist/commands/validate.js.map +2 -2
- package/dist/commands/version.js +1 -1
- package/dist/commands/visualize.js +34 -15
- package/dist/commands/visualize.js.map +2 -2
- package/dist/core/annotation-store.js +15 -5
- package/dist/core/annotation-store.js.map +2 -2
- package/dist/core/catalog-migration.js +34 -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 +160 -0
- package/dist/core/changeset-base-index.js.map +7 -0
- package/dist/core/changeset-brief-compact.js +256 -0
- package/dist/core/changeset-brief-compact.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 +848 -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/extraction-observability.js +161 -0
- package/dist/core/extraction-observability.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 +88 -47
- 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/project-init.js +53 -0
- package/dist/core/project-init.js.map +7 -0
- 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 +161 -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 +145 -18
- 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 +166 -24
- package/dist/core/staging-area.js.map +2 -2
- package/dist/core/upgrade-plan.js +117 -0
- package/dist/core/upgrade-plan.js.map +7 -0
- 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 +2468 -7913
- package/dist/generated/relationship-index.js.map +2 -2
- package/dist/integrations/claude_code/.claude-plugin/plugin.json +24 -0
- package/dist/integrations/claude_code/agents/dr-architect.md +30 -3
- package/dist/integrations/claude_code/agents/dr-extractor.md +8 -2
- package/dist/integrations/claude_code/commands/dr-analyzer.md +565 -0
- package/dist/integrations/claude_code/commands/dr-changeset.md +2 -1
- package/dist/integrations/claude_code/commands/dr-map.md +20 -1
- package/dist/integrations/claude_code/commands/dr-plan.md +66 -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 +47 -26
- package/dist/integrations/claude_code/docs/QUICK_START.md +37 -8
- package/dist/integrations/claude_code/docs/README.md +68 -2
- package/dist/integrations/claude_code/plugin-agents/dr-advisor.md +255 -0
- package/dist/integrations/claude_code/plugin-agents/dr-architect.md +306 -0
- 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 +95 -15
- package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +178 -0
- package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +8 -0
- package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +6 -1
- package/dist/integrations/github_copilot/agents/dr-architect.md +3 -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 +225 -102
- package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +177 -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 +45 -0
- package/dist/mcp/changeset-only.js.map +7 -0
- package/dist/mcp/project-root.js +85 -0
- package/dist/mcp/project-root.js.map +7 -0
- package/dist/mcp/server-instructions.js +27 -0
- package/dist/mcp/server-instructions.js.map +7 -0
- package/dist/mcp/strict-schema.js +52 -0
- package/dist/mcp/strict-schema.js.map +7 -0
- package/dist/mcp/tool-registry.js +55 -5
- package/dist/mcp/tool-registry.js.map +2 -2
- package/dist/mcp/tools/changeset-analysis-tools.js +98 -0
- package/dist/mcp/tools/changeset-analysis-tools.js.map +7 -0
- package/dist/mcp/tools/changeset-commit.js +224 -0
- package/dist/mcp/tools/changeset-commit.js.map +7 -0
- package/dist/mcp/tools/changeset-editing.js +260 -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 +49 -0
- package/dist/mcp/tools/changeset-verify.js.map +7 -0
- package/dist/mcp/tools/codebase-extract.js +836 -0
- package/dist/mcp/tools/codebase-extract.js.map +7 -0
- package/dist/mcp/tools/codebase-index.js +125 -0
- package/dist/mcp/tools/codebase-index.js.map +7 -0
- package/dist/mcp/tools/codebase-shared.js +265 -0
- package/dist/mcp/tools/codebase-shared.js.map +7 -0
- package/dist/mcp/tools/codebase-verify.js +48 -0
- package/dist/mcp/tools/codebase-verify.js.map +7 -0
- package/dist/mcp/tools/index.js +34 -1
- package/dist/mcp/tools/index.js.map +2 -2
- package/dist/mcp/tools/model-add.js +191 -74
- 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 +46 -26
- package/dist/mcp/tools/model-update.js.map +2 -2
- package/dist/mcp/tools/model-validate.js +46 -4
- package/dist/mcp/tools/model-validate.js.map +2 -2
- package/dist/mcp/tools/project-status-collectors.js +381 -0
- package/dist/mcp/tools/project-status-collectors.js.map +7 -0
- package/dist/mcp/tools/project-status.js +237 -0
- package/dist/mcp/tools/project-status.js.map +7 -0
- package/dist/mcp/tools/project-tools.js +505 -0
- package/dist/mcp/tools/project-tools.js.map +7 -0
- package/dist/mcp/tools/relationship-tools.js +167 -0
- package/dist/mcp/tools/relationship-tools.js.map +7 -0
- package/dist/mcp/tools/review-open.js +210 -0
- package/dist/mcp/tools/review-open.js.map +7 -0
- package/dist/mcp/tools/shared.js +35 -4
- 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/reports/model-report-orchestrator.js +31 -2
- package/dist/reports/model-report-orchestrator.js.map +2 -2
- package/dist/schemas/bundled/analyzers/cbm.json +1200 -70
- 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 +865 -1646
- 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 +686 -231
- package/dist/server/server.js.map +3 -3
- package/dist/server/viewer-registry.js +264 -0
- package/dist/server/viewer-registry.js.map +7 -0
- 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/id-generator.js +5 -2
- package/dist/utils/id-generator.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 +64 -31
- 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/model-warnings.js +28 -0
- package/dist/validators/model-warnings.js.map +7 -0
- 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-CUOQGGRA.css +1 -0
- package/dist/viewer/assets/main-DvH3gYrI.js +84 -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,21 +2,25 @@ 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
|
-
import { buildSourceReference,
|
|
8
|
-
import {
|
|
8
|
+
import { buildSourceReference, precheckUpdateSourceReferenceOptions, resolveSourceReferenceOptions } from "../../utils/source-reference.js";
|
|
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
|
-
sourceSymbol: z.string().optional(),
|
|
19
|
-
sourceProvenance: z.enum(["extracted", "manual", "inferred", "generated"]).optional(),
|
|
22
|
+
sourceSymbol: z.string().optional().describe("Sent without sourceFile, goes on the element's source file and keeps its provenance: on its one location when that has no symbol, else added beside the symbols the file already has (never twice). An element with no source file, or several, needs sourceFile with it."),
|
|
23
|
+
sourceProvenance: z.enum(["extracted", "manual", "inferred", "generated"]).optional().describe("Sent alone, changes the provenance and keeps the element's file. A sourceFile sent alone keeps the current provenance, or gets 'manual'. 'extracted' and 'manual' need a file (on the element or sent): without one they are ignored with a warning."),
|
|
20
24
|
sourceRepoRemote: z.string().optional(),
|
|
21
25
|
sourceRepoCommit: z.string().optional().describe("Full 40-character Git commit SHA."),
|
|
22
26
|
clearSourceReference: z.boolean().optional().describe("Remove the element's source reference. Cannot be combined with other source options."),
|
|
@@ -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 &&
|
|
35
|
+
hasSourceOptions && precheckUpdateSourceReferenceOptions(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,8 +47,14 @@ 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
|
-
|
|
47
|
-
|
|
50
|
+
const currentSource = element.getSourceReference(), source = resolveSourceReferenceOptions(args, "mcp", currentSource), warnings = [...source.warnings];
|
|
51
|
+
if (!(args.name || args.description !== void 0 || args.type || args.attributes || attributePatch || source.options.sourceProvenance || args.clearSourceReference))
|
|
52
|
+
return jsonResult({
|
|
53
|
+
status: "no-op",
|
|
54
|
+
id: args.id,
|
|
55
|
+
message: warnings.length > 0 ? "Nothing left to update" : "No fields specified for update",
|
|
56
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
57
|
+
});
|
|
48
58
|
const mutationHandler = new MutationHandler(model, args.id, layerName);
|
|
49
59
|
await mutationHandler.executeUpdate(element, async (elem, after) => {
|
|
50
60
|
let resolvedNewType;
|
|
@@ -55,46 +65,56 @@ async function modelUpdateHandler(args) {
|
|
|
55
65
|
}
|
|
56
66
|
resolvedNewType = normalizeNodeType(layerName, args.type);
|
|
57
67
|
}
|
|
58
|
-
|
|
68
|
+
const newAttributes = resolveAttributeUpdate(elem.attributes, {
|
|
69
|
+
attributes: args.attributes,
|
|
70
|
+
attributePatch
|
|
71
|
+
});
|
|
72
|
+
if (resolvedNewType || newAttributes) {
|
|
59
73
|
const candidateType = resolvedNewType ?? elem.type, tempElem = new Element({
|
|
60
74
|
...elem.toJSON(),
|
|
61
75
|
type: candidateType,
|
|
62
76
|
spec_node_id: `${layerName}.${candidateType}`,
|
|
63
|
-
attributes:
|
|
77
|
+
attributes: newAttributes ?? elem.attributes
|
|
64
78
|
}), schemaValidator = new SchemaValidator(), tempLayer = new Layer(layerName);
|
|
65
79
|
tempLayer.addElement(tempElem);
|
|
66
|
-
const attrValidation = await schemaValidator.validateLayer(tempLayer);
|
|
67
|
-
if (
|
|
68
|
-
const
|
|
80
|
+
const attrValidation = await schemaValidator.validateLayer(tempLayer), undefinedType = !isValidNodeType(layerName, candidateType), blocking = undefinedType ? attrValidation.errors.filter((e) => e.location !== "/type") : attrValidation.errors;
|
|
81
|
+
if (undefinedType) {
|
|
82
|
+
const { message, hint } = undefinedTypeWarning("mcp", layerName, args.id, candidateType, suggestNodeTypes(layerName, candidateType));
|
|
83
|
+
warnings.push(`${message}. ${hint}`);
|
|
84
|
+
}
|
|
85
|
+
if (blocking.length > 0) {
|
|
86
|
+
const errorMessages = blocking.map((e) => ` ${e.message}`).join(`
|
|
69
87
|
`);
|
|
70
88
|
throw new CLIError(`Updated attributes fail schema validation:
|
|
71
89
|
${errorMessages}`, ErrorCategory.USER, [
|
|
72
|
-
|
|
90
|
+
...undefinedType ? warnings : [schemaLookupHint("mcp", layerName, candidateType)]
|
|
73
91
|
]);
|
|
74
92
|
}
|
|
75
93
|
}
|
|
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),
|
|
77
|
-
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const newRef = buildSourceReference(args);
|
|
82
|
-
newRef && (elem.setSourceReference(newRef), after.sourceReference = newRef);
|
|
94
|
+
if (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), args.clearSourceReference)
|
|
95
|
+
elem.setSourceReference(void 0), setStagedSourceReference(after, void 0);
|
|
96
|
+
else if (source.options.sourceProvenance) {
|
|
97
|
+
const newRef = buildSourceReference(source.options, currentSource);
|
|
98
|
+
newRef && (elem.setSourceReference(newRef), setStagedSourceReference(after, newRef));
|
|
83
99
|
}
|
|
84
100
|
layer.updateElement(elem);
|
|
85
|
-
});
|
|
101
|
+
}), await reloadModel(args.rootPath);
|
|
86
102
|
const activeChangeset = await mutationHandler.getStagingManager().getActive(), staged = !!(activeChangeset && activeChangeset.status === "staged");
|
|
87
103
|
return jsonResult({
|
|
88
104
|
status: staged ? "staged" : "updated",
|
|
89
105
|
changeset: staged ? activeChangeset.name : void 0,
|
|
90
106
|
id: args.id,
|
|
91
|
-
layer: layerName
|
|
107
|
+
layer: layerName,
|
|
108
|
+
changedFields: mutationHandler.getChangedFields(),
|
|
109
|
+
// The changeset adds this element: the update was folded into that add (no separate change).
|
|
110
|
+
...mutationHandler.wasFoldedIntoAdd() ? { foldedIntoAdd: !0 } : {},
|
|
111
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
92
112
|
});
|
|
93
113
|
});
|
|
94
114
|
}
|
|
95
115
|
const modelUpdateTool = {
|
|
96
116
|
name: "model_update",
|
|
97
|
-
description: "Update an existing element's name, description, type, attributes, or source reference.",
|
|
117
|
+
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
118
|
inputSchema,
|
|
99
119
|
handler: modelUpdateHandler
|
|
100
120
|
};
|
|
@@ -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,
|
|
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 {\n buildSourceReference,\n precheckUpdateSourceReferenceOptions,\n resolveSourceReferenceOptions,\n} 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\n .string()\n .optional()\n .describe(\n \"Sent without sourceFile, goes on the element's source file and keeps its provenance: on its one location \" +\n \"when that has no symbol, else added beside the symbols the file already has (never twice). An element \" +\n \"with no source file, or several, needs sourceFile with it.\"\n ),\n sourceProvenance: z\n .enum([\"extracted\", \"manual\", \"inferred\", \"generated\"])\n .optional()\n .describe(\n \"Sent alone, changes the provenance and keeps the element's file. A sourceFile sent alone keeps the current \" +\n \"provenance, or gets 'manual'. 'extracted' and 'manual' need a file (on the element or sent): without one \" +\n \"they are ignored with a warning.\"\n ),\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 // Refuse what cannot be settled before touching the model; the partial forms are settled\n // against the element's current reference once it is loaded.\n precheckUpdateSourceReferenceOptions(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 // Settle partial source fields first: one that is ignored (with a warning) is not an update.\n const currentSource = element.getSourceReference();\n const source = resolveSourceReferenceOptions(args, \"mcp\", currentSource);\n const warnings: string[] = [...source.warnings];\n\n const hasUpdates =\n args.name ||\n args.description !== undefined ||\n args.type ||\n args.attributes ||\n attributePatch ||\n source.options.sourceProvenance ||\n args.clearSourceReference;\n\n if (!hasUpdates) {\n return jsonResult({\n status: \"no-op\",\n id: args.id,\n message: warnings.length > 0 ? \"Nothing left to update\" : \"No fields specified for update\",\n ...(warnings.length > 0 ? { warnings } : {}),\n });\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 // 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 if (args.clearSourceReference) {\n elem.setSourceReference(undefined);\n setStagedSourceReference(after, undefined);\n } else if (source.options.sourceProvenance) {\n const newRef = buildSourceReference(source.options, currentSource);\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": "AAKA,SAAS,SAAS;AAElB,SAAS,eAAe;AACxB,SAAS,aAAa;AACtB,SAAS,uBAAuB;AAChC,SAAS,gCAAgC;AACzC,SAAS,sBAAsB,iBAAiB,yBAAyB;AACzE,SAAS,wBAAwB;AACjC,SACE,sBACA,sCACA,qCACK;AACP,SAAS,kBAAkB,4BAA4B;AACvD,SAAS,iBAAiB,wBAAwB;AAClD,SAAS,UAAU,eAAe,aAAa,0BAA0B;AACzE,SAAS,yBAAyB,8BAA8B;AAChE,SAAS,YAAY,WAAW,aAAa,gBAAgB,eAAuC;AAEpG,MAAM,cAAc;EAClB,IAAI,EAAE,OAAM,EAAG,SAAS,+BAA+B;EACvD,MAAM,EAAE,OAAM,EAAG,SAAQ;EACzB,aAAa,EAAE,OAAM,EAAG,SAAQ;EAChC,MAAM,EAAE,OAAM,EAAG,SAAQ,EAAG,SAAS,4CAA4C;EACjF,YAAY,EAAE,OAAO,EAAE,OAAM,GAAI,EAAE,QAAO,CAAE,EAAE,SAAQ,EAAG,SAAS,uEAAuE;EACzI,gBAAgB,EACb,OAAO,EAAE,OAAM,GAAI,EAAE,QAAO,CAAE,EAC9B,SAAQ,EACR,SACC,6PAE+C;EAEnD,YAAY,EAAE,OAAM,EAAG,SAAQ;EAC/B,cAAc,EACX,OAAM,EACN,SAAQ,EACR,SACC,2QAE8D;EAElE,kBAAkB,EACf,KAAK,CAAC,aAAa,UAAU,YAAY,WAAW,CAAC,EACrD,SAAQ,EACR,SACC,sPAEoC;EAExC,kBAAkB,EAAE,OAAM,EAAG,SAAQ;EACrC,kBAAkB,EAAE,OAAM,EAAG,SAAQ,EAAG,SAAS,mCAAmC;EACpF,sBAAsB,EAAE,QAAO,EAAG,SAAQ,EAAG,SAAS,sFAAsF;EAC5I,UAAU;;AAKZ,SAAS,qCAAqC,MAAqB;AACjE,QAAM,mBACJ,KAAK,cAAc,KAAK,gBAAgB,KAAK,oBAAoB,KAAK,oBAAoB,KAAK;AAEjG,MAAI,KAAK,wBAAwB;AAC/B,UAAM,IAAI,SAAS,uEAAuE,cAAc,MAAM;MAC5G;KACD;AAGH,EAAI,oBAGF,qCAAqC,MAAM,KAAK;AAEpD;AAEA,eAAsB,mBAAmB,MAAqB;AAC5D,SAAO,QAAQ,YAAW;AACxB,yCAAqC,IAAI,GAEzC,uBAAuB,QAAW,EAAE,YAAY,KAAK,YAAY,gBAAgB,KAAK,eAAc,CAAE;AAEtG,UAAM,iBAAiB,wBAAwB,KAAK,cAAc,GAE5D,QAAQ,MAAM,UAAU,KAAK,QAAQ,GAErC,YAAY,MAAM,iBAAiB,OAAO,KAAK,EAAE;AACvD,QAAI,CAAC;AACH,YAAM,IAAI,SAAS,WAAW,KAAK,EAAE,cAAc,cAAc,WAAW;QAC1E;OACD;AAGH,UAAM,QAAQ,MAAM,MAAM,SAAS,SAAS,GACtC,UAAU,OAAO,WAAW,KAAK,EAAE;AACzC,QAAI,CAAC,SAAS,CAAC;AACb,YAAM,IAAI,SAAS,WAAW,KAAK,EAAE,cAAc,cAAc,WAAW;QAC1E;OACD;AAIH,UAAM,gBAAgB,QAAQ,mBAAkB,GAC1C,SAAS,8BAA8B,MAAM,OAAO,aAAa,GACjE,WAAqB,CAAC,GAAG,OAAO,QAAQ;AAW9C,QAAI,EARF,KAAK,QACL,KAAK,gBAAgB,UACrB,KAAK,QACL,KAAK,cACL,kBACA,OAAO,QAAQ,oBACf,KAAK;AAGL,aAAO,WAAW;QAChB,QAAQ;QACR,IAAI,KAAK;QACT,SAAS,SAAS,SAAS,IAAI,2BAA2B;QAC1D,GAAI,SAAS,SAAS,IAAI,EAAE,SAAQ,IAAK,CAAA;OAC1C;AAGH,UAAM,kBAAkB,IAAI,gBAAgB,OAAO,KAAK,IAAI,SAAS;AAErE,UAAM,gBAAgB,cAAc,SAAS,OAAO,MAAM,UAAS;AACjE,UAAI;AACJ,UAAI,KAAK,MAAM;AACb,YAAI,CAAC,gBAAgB,WAAW,KAAK,IAAI,GAAG;AAE1C,gBAAM,YADiB,qBAAqB,SAAS,EACpB,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAI,GAClD,UAAU,YAAY,KAAK,MAAO,WAAW,CAAC,GAC9C,cAAwB,CAAC,mBAAmB,SAAS,KAAK,mBAAmB,SAAS,CAAC,EAAE;AAC/F,gBAAI,QAAQ,SAAS,KAAG,YAAY,QAAQ,iBAAiB,QAAQ,KAAK,MAAM,CAAC,GAAG,GAC9E,IAAI,SAAS,yBAAyB,KAAK,IAAI,gBAAgB,SAAS,KAAK,cAAc,MAAM,WAAW;QACpH;AACA,0BAAkB,kBAAkB,WAAW,KAAK,IAAI;MAC1D;AAGA,YAAM,gBAAgB,uBAAuB,KAAK,YAAY;QAC5D,YAAY,KAAK;QACjB;OACD;AAED,UAAI,mBAAmB,eAAe;AACpC,cAAM,gBAAgB,mBAAmB,KAAK,MACxC,WAAW,IAAI,QAAQ;UAC3B,GAAG,KAAK,OAAM;UACd,MAAM;UACN,cAAc,GAAG,SAAS,IAAI,aAAa;UAC3C,YAAY,iBAAiB,KAAK;SACnC,GACK,kBAAkB,IAAI,gBAAe,GACrC,YAAY,IAAI,MAAM,SAAS;AACrC,kBAAU,WAAW,QAAQ;AAC7B,cAAM,iBAAiB,MAAM,gBAAgB,cAAc,SAAS,GAI9D,gBAAgB,CAAC,gBAAgB,WAAW,aAAa,GACzD,WAAW,gBACb,eAAe,OAAO,OAAO,CAAC,MAAM,EAAE,aAAa,OAAO,IAC1D,eAAe;AACnB,YAAI,eAAe;AACjB,gBAAM,EAAE,SAAS,KAAI,IAAK,qBACxB,OACA,WACA,KAAK,IACL,eACA,iBAAiB,WAAW,aAAa,CAAC;AAE5C,mBAAS,KAAK,GAAG,OAAO,KAAK,IAAI,EAAE;QACrC;AACA,YAAI,SAAS,SAAS,GAAG;AACvB,gBAAM,gBAAgB,SAAS,IAAI,CAAC,MAAM,KAAK,EAAE,OAAO,EAAE,EAAE,KAAK;CAAI;AACrE,gBAAM,IAAI,SAAS;EAA+C,aAAa,IAAI,cAAc,MAAM;YACrG,GAAI,gBAAgB,WAAW,CAAC,iBAAiB,OAAO,WAAW,aAAa,CAAC;WAClF;QACH;MACF;AAwBA,UAtBI,oBACF,KAAK,OAAO,iBACZ,MAAM,OAAO,iBACb,KAAK,eAAe,GAAG,SAAS,IAAI,eAAe,IACnD,MAAM,eAAe,GAAG,SAAS,IAAI,eAAe,KAGlD,KAAK,SACP,KAAK,OAAO,KAAK,MACjB,MAAM,OAAO,KAAK,OAGhB,KAAK,gBAAgB,WACvB,KAAK,cAAc,KAAK,eAAe,QACvC,MAAM,cAAc,KAAK,eAAe,SAGtC,kBACF,KAAK,aAAa,eAClB,MAAM,aAAa,gBAGjB,KAAK;AACP,aAAK,mBAAmB,MAAS,GACjC,yBAAyB,OAAO,MAAS;eAChC,OAAO,QAAQ,kBAAkB;AAC1C,cAAM,SAAS,qBAAqB,OAAO,SAAS,aAAa;AACjE,QAAI,WACF,KAAK,mBAAmB,MAAM,GAC9B,yBAAyB,OAAO,MAAM;MAE1C;AAEA,YAAM,cAAc,IAAI;IAC1B,CAAC,GAID,MAAM,YAAY,KAAK,QAAQ;AAG/B,UAAM,kBAAkB,MADD,gBAAgB,kBAAiB,EACX,UAAS,GAChD,SAAS,CAAC,EAAE,mBAAmB,gBAAgB,WAAW;AAEhE,WAAO,WAAW;MAChB,QAAQ,SAAS,WAAW;MAC5B,WAAW,SAAS,gBAAiB,OAAO;MAC5C,IAAI,KAAK;MACT,OAAO;MACP,eAAe,gBAAgB,iBAAgB;;MAE/C,GAAI,gBAAgB,iBAAgB,IAAK,EAAE,eAAe,GAAI,IAAK,CAAA;MACnE,GAAI,SAAS,SAAS,IAAI,EAAE,SAAQ,IAAK,CAAA;KAC1C;EACH,CAAC;AACH;AAEO,MAAM,kBAAyD;EACpE,MAAM;EACN,aACE;EAEF;EACA,SAAS;;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -13,7 +13,7 @@ const inputSchema = {
|
|
|
13
13
|
async function modelValidateHandler(args) {
|
|
14
14
|
return runTool(async () => {
|
|
15
15
|
const model = await loadModel(args.rootPath), activeChangesetId = model.getActiveChangesetId();
|
|
16
|
-
let modelToValidate = activeChangesetId ? { ...await model.getVirtualProjectionEngine().projectModel(model, activeChangesetId), rootPath: model.rootPath } : model;
|
|
16
|
+
let modelToValidate = activeChangesetId ? { ...await model.getVirtualProjectionEngine().projectModel(model, activeChangesetId), rootPath: model.rootPath } : model, outOfScopeIssues = [];
|
|
17
17
|
if (args.layers && args.layers.length > 0) {
|
|
18
18
|
const unknownLayers = args.layers.filter((name) => !isValidLayer(name));
|
|
19
19
|
if (unknownLayers.length > 0) {
|
|
@@ -25,9 +25,10 @@ async function modelValidateHandler(args) {
|
|
|
25
25
|
throw new CLIError(`Unknown layer(s): ${unknownLayers.join(", ")}`, ErrorCategory.USER, suggestions);
|
|
26
26
|
}
|
|
27
27
|
const requestedLayers = new Set(args.layers);
|
|
28
|
-
modelToValidate = {
|
|
28
|
+
outOfScopeIssues = danglingOutOfScopeEnds(modelToValidate, requestedLayers), modelToValidate = {
|
|
29
29
|
...modelToValidate,
|
|
30
|
-
layers: new Map(Array.from(modelToValidate.layers.entries()).filter(([name]) => requestedLayers.has(name)))
|
|
30
|
+
layers: new Map(Array.from(modelToValidate.layers.entries()).filter(([name]) => requestedLayers.has(name))),
|
|
31
|
+
loadedLayerFilter: [...requestedLayers]
|
|
31
32
|
};
|
|
32
33
|
}
|
|
33
34
|
if (args.orphans) {
|
|
@@ -37,7 +38,10 @@ async function modelValidateHandler(args) {
|
|
|
37
38
|
orphanedElements: stats2.orphanedElements
|
|
38
39
|
});
|
|
39
40
|
}
|
|
40
|
-
const result = await new Validator().validateModel(modelToValidate)
|
|
41
|
+
const result = await new Validator().validateModel(modelToValidate);
|
|
42
|
+
for (const issue of outOfScopeIssues)
|
|
43
|
+
result.addError(issue);
|
|
44
|
+
const stats = ValidationFormatter.calculateStats(modelToValidate);
|
|
41
45
|
for (const orphanId of stats.orphanedElements)
|
|
42
46
|
result.addWarning({
|
|
43
47
|
message: `Element '${orphanId}' is orphaned (no cross-layer references or intra-layer relationships)`,
|
|
@@ -65,6 +69,44 @@ async function modelValidateHandler(args) {
|
|
|
65
69
|
return jsonResult({ ...formatted, valid });
|
|
66
70
|
});
|
|
67
71
|
}
|
|
72
|
+
function danglingOutOfScopeEnds(model, scope) {
|
|
73
|
+
const known = /* @__PURE__ */ new Set();
|
|
74
|
+
for (const layer of model.layers.values())
|
|
75
|
+
for (const element of layer.listElements())
|
|
76
|
+
known.add(element.id), element.path && known.add(element.path);
|
|
77
|
+
const layerOf = (id) => id.split(".")[0] ?? "", referenceLayerOf = (id) => {
|
|
78
|
+
const separator = id.includes(".") ? "." : "-";
|
|
79
|
+
return [...getAllLayerIds()].sort((a, b) => b.length - a.length).find((name) => id.startsWith(name + separator)) ?? id.split(separator)[0] ?? "";
|
|
80
|
+
}, issues = [];
|
|
81
|
+
for (const rel of model.relationships.getAll()) {
|
|
82
|
+
if (!rel.source.includes(".") || !rel.target.includes("."))
|
|
83
|
+
continue;
|
|
84
|
+
const sourceIn = scope.has(layerOf(rel.source)), targetIn = scope.has(layerOf(rel.target));
|
|
85
|
+
if (sourceIn === targetIn)
|
|
86
|
+
continue;
|
|
87
|
+
const [end, role] = sourceIn ? [rel.target, "target"] : [rel.source, "source"];
|
|
88
|
+
known.has(end) || issues.push({
|
|
89
|
+
layer: rel.layer || layerOf(rel.source),
|
|
90
|
+
elementId: end,
|
|
91
|
+
message: `[Relationship Schema]: Relationship ${role} element '${end}' not found (outside the validated layers, and not in the model)`,
|
|
92
|
+
category: "reference"
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
for (const [layerName, layer] of model.layers)
|
|
96
|
+
if (scope.has(layerName))
|
|
97
|
+
for (const element of layer.listElements())
|
|
98
|
+
for (const ref of element.references ?? []) {
|
|
99
|
+
const destination = ref.target;
|
|
100
|
+
!destination || destination.startsWith("@") || scope.has(referenceLayerOf(destination)) || known.has(destination) || issues.push({
|
|
101
|
+
layer: layerName,
|
|
102
|
+
elementId: element.path || element.id,
|
|
103
|
+
message: `Broken reference: target '${destination}' does not exist (outside the validated layers, and not in the model)`,
|
|
104
|
+
fixSuggestion: `Remove reference or create element '${destination}'`,
|
|
105
|
+
category: "reference"
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return issues;
|
|
109
|
+
}
|
|
68
110
|
const modelValidateTool = {
|
|
69
111
|
name: "model_validate",
|
|
70
112
|
description: "Run the full schema/naming/reference/semantic/relationship validation pipeline against the model.",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/mcp/tools/model-validate.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * model_validate \u2014 run the 5-stage validation pipeline against the model.\n * Mirrors `dr validate`, reusing the shared Validator and ValidationFormatter.\n */\n\nimport { z } from \"zod\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Model } from \"../../core/model.js\";\nimport { getAllLayerIds, isValidLayer } from \"../../generated/layer-registry.js\";\nimport { CLIError, ErrorCategory, findSimilar, formatValidOptions } from \"../../utils/errors.js\";\nimport { ValidationFormatter } from \"../../validators/validation-formatter.js\";\nimport { Validator } from \"../../validators/validator.js\";\nimport { jsonResult, loadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\n\nconst inputSchema = {\n layers: z.array(z.string()).optional().describe(\"Restrict validation to these layers. Omit to validate the whole model.\"),\n strict: z.boolean().optional().describe(\"Also flag elements with no description as warnings.\"),\n orphans: z\n .boolean()\n .optional()\n .describe(\"Return only the orphaned-elements report instead of running full validation.\"),\n rootPath: rootPathSchema,\n};\n\nexport type ModelValidateArgs = z.infer<z.ZodObject<typeof inputSchema>>;\n\nexport async function modelValidateHandler(args: ModelValidateArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n\n // `Validator` is typed to accept `Model`, but a projected changeset is a\n // `ProjectedModel` (manifest/layers/relationships only, no `rootPath` or the\n // mutation methods `Model` exposes). The validation pipeline only reads\n // `rootPath` for telemetry (validator.ts), so it's carried over explicitly\n // rather than left undefined. This mirrors the identical cast in\n // `cli/src/commands/validate.ts`; a proper fix would give `Validator` a\n // narrower \"validatable model\" interface shared by both types.\n const activeChangesetId = model.getActiveChangesetId();\n let modelToValidate = (\n activeChangesetId\n ? { ...(await model.getVirtualProjectionEngine().projectModel(model, activeChangesetId)), rootPath: model.rootPath }\n : model\n ) as unknown as Model;\n\n // `loadModel()` returns the full cached model (all layers), so a `layers` scope\n // is applied here rather than at load time. Copies the layers Map onto a new\n // object instead of mutating `model.layers` directly, since that Map is the\n // shared cached instance other tool calls read from.\n if (args.layers && args.layers.length > 0) {\n const unknownLayers = args.layers.filter((name) => !isValidLayer(name));\n if (unknownLayers.length > 0) {\n const validLayers = getAllLayerIds();\n const suggestions: string[] = [`Use a valid layer name: ${formatValidOptions(validLayers)}`];\n for (const unknown of unknownLayers) {\n const similar = findSimilar(unknown, validLayers, 3);\n if (similar.length > 0) suggestions.unshift(`Did you mean \"${similar.join(\" or \")}\" instead of \"${unknown}\"?`);\n }\n throw new CLIError(`Unknown layer(s): ${unknownLayers.join(\", \")}`, ErrorCategory.USER, suggestions);\n }\n\n const requestedLayers = new Set(args.layers);\n modelToValidate = {\n ...modelToValidate,\n layers: new Map(\n Array.from(modelToValidate.layers.entries()).filter(([name]) => requestedLayers.has(name))\n ),\n } as unknown as Model;\n }\n\n if (args.orphans) {\n const stats = ValidationFormatter.calculateStats(modelToValidate);\n return jsonResult({\n orphanCount: stats.orphanedElements.length,\n orphanedElements: stats.orphanedElements,\n });\n }\n\n const validator = new Validator();\n const result = await validator.validateModel(modelToValidate);\n\n const stats = ValidationFormatter.calculateStats(modelToValidate);\n for (const orphanId of stats.orphanedElements) {\n result.addWarning({\n message: `Element '${orphanId}' is orphaned (no cross-layer references or intra-layer relationships)`,\n layer: orphanId.split(\".\")[0] ?? \"\",\n elementId: orphanId,\n category: \"orphan\",\n fixSuggestion: \"Add cross-layer references or relationships to connect this element to the rest of the model\",\n });\n }\n\n for (const [layerName, layer] of modelToValidate.layers) {\n for (const element of layer.listElements()) {\n const elementId = element.path || element.id;\n if (!element.source_reference) {\n result.addWarning({\n message: `Element '${elementId}' has no source reference`,\n layer: layerName,\n elementId,\n fixSuggestion: \"Add a source_reference with provenance to link this element to its implementation\",\n });\n }\n if (args.strict && (!element.description || element.description.trim() === \"\")) {\n result.addWarning({\n message: `Element '${elementId}' has no description`,\n layer: layerName,\n elementId,\n fixSuggestion: \"Add a description to document what this element represents\",\n });\n }\n }\n }\n\n const formatted = ValidationFormatter.toJSON(result, modelToValidate);\n const valid = result.isValid() && !(args.strict && result.warnings.length > 0);\n\n return jsonResult({ ...formatted, valid });\n });\n}\n\nexport const modelValidateTool: McpToolDefinition<typeof inputSchema> = {\n name: \"model_validate\",\n description: \"Run the full schema/naming/reference/semantic/relationship validation pipeline against the model.\",\n inputSchema,\n handler: modelValidateHandler,\n};\n"],
|
|
5
|
-
"mappings": "AAKA,SAAS,SAAS;AAGlB,SAAS,gBAAgB,oBAAoB;AAC7C,SAAS,UAAU,eAAe,aAAa,0BAA0B;AACzE,SAAS,2BAA2B;AACpC,SAAS,iBAAiB;
|
|
4
|
+
"sourcesContent": ["/**\n * model_validate \u2014 run the 5-stage validation pipeline against the model.\n * Mirrors `dr validate`, reusing the shared Validator and ValidationFormatter.\n */\n\nimport { z } from \"zod\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Model } from \"../../core/model.js\";\nimport { getAllLayerIds, isValidLayer } from \"../../generated/layer-registry.js\";\nimport { CLIError, ErrorCategory, findSimilar, formatValidOptions } from \"../../utils/errors.js\";\nimport { ValidationFormatter } from \"../../validators/validation-formatter.js\";\nimport { Validator } from \"../../validators/validator.js\";\nimport type { ValidationIssue } from \"../../validators/types.js\";\nimport { jsonResult, loadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\n\nconst inputSchema = {\n layers: z.array(z.string()).optional().describe(\"Restrict validation to these layers. Omit to validate the whole model.\"),\n strict: z.boolean().optional().describe(\"Also flag elements with no description as warnings.\"),\n orphans: z\n .boolean()\n .optional()\n .describe(\"Return only the orphaned-elements report instead of running full validation.\"),\n rootPath: rootPathSchema,\n};\n\nexport type ModelValidateArgs = z.infer<z.ZodObject<typeof inputSchema>>;\n\nexport async function modelValidateHandler(args: ModelValidateArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n\n // `Validator` is typed to accept `Model`, but a projected changeset is a\n // `ProjectedModel` (manifest/layers/relationships only, no `rootPath` or the\n // mutation methods `Model` exposes). The validation pipeline only reads\n // `rootPath` for telemetry (validator.ts), so it's carried over explicitly\n // rather than left undefined. This mirrors the identical cast in\n // `cli/src/commands/validate.ts`; a proper fix would give `Validator` a\n // narrower \"validatable model\" interface shared by both types.\n const activeChangesetId = model.getActiveChangesetId();\n let modelToValidate = (\n activeChangesetId\n ? { ...(await model.getVirtualProjectionEngine().projectModel(model, activeChangesetId)), rootPath: model.rootPath }\n : model\n ) as unknown as Model;\n\n // Ends outside a `layers` scope that exist nowhere in the full model (see danglingOutOfScopeEnds).\n let outOfScopeIssues: Array<Omit<ValidationIssue, \"severity\">> = [];\n\n // `loadModel()` returns the full cached model (all layers), so a `layers` scope\n // is applied here rather than at load time. Copies the layers Map onto a new\n // object instead of mutating `model.layers` directly, since that Map is the\n // shared cached instance other tool calls read from.\n if (args.layers && args.layers.length > 0) {\n const unknownLayers = args.layers.filter((name) => !isValidLayer(name));\n if (unknownLayers.length > 0) {\n const validLayers = getAllLayerIds();\n const suggestions: string[] = [`Use a valid layer name: ${formatValidOptions(validLayers)}`];\n for (const unknown of unknownLayers) {\n const similar = findSimilar(unknown, validLayers, 3);\n if (similar.length > 0) suggestions.unshift(`Did you mean \"${similar.join(\" or \")}\" instead of \"${unknown}\"?`);\n }\n throw new CLIError(`Unknown layer(s): ${unknownLayers.join(\", \")}`, ErrorCategory.USER, suggestions);\n }\n\n const requestedLayers = new Set(args.layers);\n outOfScopeIssues = danglingOutOfScopeEnds(modelToValidate, requestedLayers);\n // Relationships stay whole, as in `dr validate --layers`, which loads only the requested\n // layers. `loadedLayerFilter` is what tells the validators that elements outside it were\n // left out on purpose: a relationship or reference whose other end lies outside the scope\n // is then skipped instead of reported as pointing at a missing element.\n modelToValidate = {\n ...modelToValidate,\n layers: new Map(\n Array.from(modelToValidate.layers.entries()).filter(([name]) => requestedLayers.has(name))\n ),\n loadedLayerFilter: [...requestedLayers],\n } as unknown as Model;\n }\n\n if (args.orphans) {\n const stats = ValidationFormatter.calculateStats(modelToValidate);\n return jsonResult({\n orphanCount: stats.orphanedElements.length,\n orphanedElements: stats.orphanedElements,\n });\n }\n\n const validator = new Validator();\n const result = await validator.validateModel(modelToValidate);\n for (const issue of outOfScopeIssues) result.addError(issue);\n\n const stats = ValidationFormatter.calculateStats(modelToValidate);\n for (const orphanId of stats.orphanedElements) {\n result.addWarning({\n message: `Element '${orphanId}' is orphaned (no cross-layer references or intra-layer relationships)`,\n layer: orphanId.split(\".\")[0] ?? \"\",\n elementId: orphanId,\n category: \"orphan\",\n fixSuggestion: \"Add cross-layer references or relationships to connect this element to the rest of the model\",\n });\n }\n\n for (const [layerName, layer] of modelToValidate.layers) {\n for (const element of layer.listElements()) {\n const elementId = element.path || element.id;\n if (!element.source_reference) {\n result.addWarning({\n message: `Element '${elementId}' has no source reference`,\n layer: layerName,\n elementId,\n fixSuggestion: \"Add a source_reference with provenance to link this element to its implementation\",\n });\n }\n if (args.strict && (!element.description || element.description.trim() === \"\")) {\n result.addWarning({\n message: `Element '${elementId}' has no description`,\n layer: layerName,\n elementId,\n fixSuggestion: \"Add a description to document what this element represents\",\n });\n }\n }\n }\n\n const formatted = ValidationFormatter.toJSON(result, modelToValidate);\n const valid = result.isValid() && !(args.strict && result.warnings.length > 0);\n\n return jsonResult({ ...formatted, valid });\n });\n}\n\n/**\n * The relationships and references that leave a `layers` scope and point at an element that\n * exists nowhere in the model. The validators skip an end outside the scope (as `dr validate\n * --layers` does, having loaded only those layers); the MCP server holds the whole model, so it\n * can still tell a real dangling end from one that is merely out of scope. A relationship with\n * both ends outside the scope isn't looked at, as in the CLI.\n */\nfunction danglingOutOfScopeEnds(model: Model, scope: Set<string>): Array<Omit<ValidationIssue, \"severity\">> {\n const known = new Set<string>();\n for (const layer of model.layers.values()) {\n for (const element of layer.listElements()) {\n known.add(element.id);\n if (element.path) known.add(element.path);\n }\n }\n const layerOf = (id: string) => id.split(\".\")[0] ?? \"\";\n // The layer the reference validator reads from a target (reference-validator.ts\n // extractLayerFromId): a known layer the id starts with, else its first segment.\n const referenceLayerOf = (id: string) => {\n const separator = id.includes(\".\") ? \".\" : \"-\";\n const layer = [...getAllLayerIds()]\n .sort((a, b) => b.length - a.length)\n .find((name) => id.startsWith(name + separator));\n return layer ?? id.split(separator)[0] ?? \"\";\n };\n const issues: Array<Omit<ValidationIssue, \"severity\">> = [];\n\n for (const rel of model.relationships.getAll()) {\n // An id with no layer segment is looked up, and reported, by the relationship validator\n // whatever the scope (relationship-schema-validator.ts extractLayerFromElementId).\n if (!rel.source.includes(\".\") || !rel.target.includes(\".\")) continue;\n const sourceIn = scope.has(layerOf(rel.source));\n const targetIn = scope.has(layerOf(rel.target));\n if (sourceIn === targetIn) continue; // both in scope: the validators check it; neither: skipped\n const [end, role] = sourceIn ? [rel.target, \"target\"] : [rel.source, \"source\"];\n if (known.has(end)) continue;\n issues.push({\n layer: rel.layer || layerOf(rel.source),\n elementId: end,\n message: `[Relationship Schema]: Relationship ${role} element '${end}' not found (outside the validated layers, and not in the model)`,\n category: \"reference\",\n });\n }\n\n for (const [layerName, layer] of model.layers) {\n if (!scope.has(layerName)) continue;\n for (const element of layer.listElements()) {\n for (const ref of element.references ?? []) {\n const destination = ref.target;\n if (!destination || destination.startsWith(\"@\") || scope.has(referenceLayerOf(destination)) || known.has(destination)) continue;\n issues.push({\n layer: layerName,\n elementId: element.path || element.id,\n message: `Broken reference: target '${destination}' does not exist (outside the validated layers, and not in the model)`,\n fixSuggestion: `Remove reference or create element '${destination}'`,\n category: \"reference\",\n });\n }\n }\n }\n return issues;\n}\n\nexport const modelValidateTool: McpToolDefinition<typeof inputSchema> = {\n name: \"model_validate\",\n description: \"Run the full schema/naming/reference/semantic/relationship validation pipeline against the model.\",\n inputSchema,\n handler: modelValidateHandler,\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["stats"]
|
|
7
7
|
}
|