@documentation-robotics/cli 0.3.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.
Files changed (182) hide show
  1. package/README.md +10 -14
  2. package/dist/analyzers/active-analyzer.js +62 -0
  3. package/dist/analyzers/active-analyzer.js.map +7 -0
  4. package/dist/analyzers/cards/build.js +21 -1
  5. package/dist/analyzers/cards/build.js.map +2 -2
  6. package/dist/analyzers/cards/extract.js +138 -0
  7. package/dist/analyzers/cards/extract.js.map +7 -0
  8. package/dist/analyzers/cards/routes.js +96 -0
  9. package/dist/analyzers/cards/routes.js.map +7 -0
  10. package/dist/analyzers/cbm-analyzer.js +76 -4
  11. package/dist/analyzers/cbm-analyzer.js.map +2 -2
  12. package/dist/analyzers/cbm-responses.js +12 -6
  13. package/dist/analyzers/cbm-responses.js.map +2 -2
  14. package/dist/analyzers/code-changes.js +159 -0
  15. package/dist/analyzers/code-changes.js.map +7 -0
  16. package/dist/analyzers/edge-relationships.js +6 -2
  17. package/dist/analyzers/edge-relationships.js.map +2 -2
  18. package/dist/analyzers/extraction-plan.js +541 -0
  19. package/dist/analyzers/extraction-plan.js.map +7 -0
  20. package/dist/analyzers/index-job.js +229 -0
  21. package/dist/analyzers/index-job.js.map +7 -0
  22. package/dist/cli.js +12 -10
  23. package/dist/cli.js.map +2 -2
  24. package/dist/coding-agents/drbot-mcp.js +120 -18
  25. package/dist/coding-agents/drbot-mcp.js.map +2 -2
  26. package/dist/commands/add.js +4 -2
  27. package/dist/commands/add.js.map +2 -2
  28. package/dist/commands/analyzer.js +35 -35
  29. package/dist/commands/analyzer.js.map +2 -2
  30. package/dist/commands/changeset-verify.js +16 -8
  31. package/dist/commands/changeset-verify.js.map +2 -2
  32. package/dist/commands/changeset.js +41 -11
  33. package/dist/commands/changeset.js.map +2 -2
  34. package/dist/commands/init.js +9 -26
  35. package/dist/commands/init.js.map +2 -2
  36. package/dist/commands/mcp.js +35 -31
  37. package/dist/commands/mcp.js.map +3 -3
  38. package/dist/commands/relationship.js +2 -12
  39. package/dist/commands/relationship.js.map +2 -2
  40. package/dist/commands/reports.js +3 -3
  41. package/dist/commands/reports.js.map +2 -2
  42. package/dist/commands/update.js +14 -13
  43. package/dist/commands/update.js.map +2 -2
  44. package/dist/commands/upgrade.js +28 -120
  45. package/dist/commands/upgrade.js.map +2 -2
  46. package/dist/commands/validate.js +19 -3
  47. package/dist/commands/validate.js.map +2 -2
  48. package/dist/commands/version.js +1 -1
  49. package/dist/commands/visualize.js +30 -12
  50. package/dist/commands/visualize.js.map +2 -2
  51. package/dist/core/annotation-store.js +15 -5
  52. package/dist/core/annotation-store.js.map +2 -2
  53. package/dist/core/catalog-migration.js +2 -1
  54. package/dist/core/catalog-migration.js.map +2 -2
  55. package/dist/core/changeset-base-index.js +7 -0
  56. package/dist/core/changeset-base-index.js.map +2 -2
  57. package/dist/core/changeset-brief-compact.js +256 -0
  58. package/dist/core/changeset-brief-compact.js.map +7 -0
  59. package/dist/core/changeset-verify.js +356 -26
  60. package/dist/core/changeset-verify.js.map +3 -3
  61. package/dist/core/extraction-observability.js +161 -0
  62. package/dist/core/extraction-observability.js.map +7 -0
  63. package/dist/core/migration-registry.js +13 -1
  64. package/dist/core/migration-registry.js.map +2 -2
  65. package/dist/core/project-init.js +53 -0
  66. package/dist/core/project-init.js.map +7 -0
  67. package/dist/core/relationship-staging.js +94 -1
  68. package/dist/core/relationship-staging.js.map +2 -2
  69. package/dist/core/staged-changeset-storage.js +83 -3
  70. package/dist/core/staged-changeset-storage.js.map +2 -2
  71. package/dist/core/staging-area.js +85 -8
  72. package/dist/core/staging-area.js.map +2 -2
  73. package/dist/core/upgrade-plan.js +117 -0
  74. package/dist/core/upgrade-plan.js.map +7 -0
  75. package/dist/generated/relationship-index.js +124 -15
  76. package/dist/generated/relationship-index.js.map +2 -2
  77. package/dist/integrations/claude_code/.claude-plugin/plugin.json +24 -0
  78. package/dist/integrations/claude_code/agents/dr-architect.md +19 -5
  79. package/dist/integrations/claude_code/agents/dr-extractor.md +7 -1
  80. package/dist/integrations/claude_code/commands/dr-changeset.md +2 -1
  81. package/dist/integrations/claude_code/commands/dr-map.md +7 -1
  82. package/dist/integrations/claude_code/commands/dr-plan.md +42 -16
  83. package/dist/integrations/claude_code/docs/MCP_SERVER.md +28 -31
  84. package/dist/integrations/claude_code/docs/README.md +20 -1
  85. package/dist/integrations/claude_code/plugin-agents/dr-advisor.md +255 -0
  86. package/dist/integrations/claude_code/plugin-agents/dr-architect.md +306 -0
  87. package/dist/integrations/claude_code/skills/dr_13_testing_layer/SKILL.md +3 -0
  88. package/dist/integrations/claude_code/skills/dr_change_planning/SKILL.md +90 -31
  89. package/dist/integrations/claude_code/skills/dr_changeset_reviewer/SKILL.md +5 -0
  90. package/dist/integrations/claude_code/skills/dr_engineering_guide/SKILL.md +5 -0
  91. package/dist/integrations/github_copilot/agents/dr-architect.md +2 -0
  92. package/dist/integrations/github_copilot/skills/dr_13_testing_layer/SKILL.md +3 -0
  93. package/dist/integrations/github_copilot/skills/dr_change_planning/SKILL.md +88 -30
  94. package/dist/mcp/changeset-only.js +14 -3
  95. package/dist/mcp/changeset-only.js.map +2 -2
  96. package/dist/mcp/project-root.js +85 -0
  97. package/dist/mcp/project-root.js.map +7 -0
  98. package/dist/mcp/server-instructions.js +27 -0
  99. package/dist/mcp/server-instructions.js.map +7 -0
  100. package/dist/mcp/strict-schema.js +52 -0
  101. package/dist/mcp/strict-schema.js.map +7 -0
  102. package/dist/mcp/tool-registry.js +39 -5
  103. package/dist/mcp/tool-registry.js.map +2 -2
  104. package/dist/mcp/tools/changeset-analysis-tools.js +25 -5
  105. package/dist/mcp/tools/changeset-analysis-tools.js.map +3 -3
  106. package/dist/mcp/tools/changeset-commit.js +224 -0
  107. package/dist/mcp/tools/changeset-commit.js.map +7 -0
  108. package/dist/mcp/tools/changeset-editing.js +133 -57
  109. package/dist/mcp/tools/changeset-editing.js.map +3 -3
  110. package/dist/mcp/tools/changeset-lifecycle.js +1 -1
  111. package/dist/mcp/tools/changeset-lifecycle.js.map +2 -2
  112. package/dist/mcp/tools/changeset-verify.js +12 -6
  113. package/dist/mcp/tools/changeset-verify.js.map +2 -2
  114. package/dist/mcp/tools/codebase-extract.js +836 -0
  115. package/dist/mcp/tools/codebase-extract.js.map +7 -0
  116. package/dist/mcp/tools/codebase-index.js +125 -0
  117. package/dist/mcp/tools/codebase-index.js.map +7 -0
  118. package/dist/mcp/tools/codebase-shared.js +265 -0
  119. package/dist/mcp/tools/codebase-shared.js.map +7 -0
  120. package/dist/mcp/tools/codebase-verify.js +48 -0
  121. package/dist/mcp/tools/codebase-verify.js.map +7 -0
  122. package/dist/mcp/tools/index.js +15 -0
  123. package/dist/mcp/tools/index.js.map +2 -2
  124. package/dist/mcp/tools/model-add.js +189 -73
  125. package/dist/mcp/tools/model-add.js.map +2 -2
  126. package/dist/mcp/tools/model-update.js +16 -12
  127. package/dist/mcp/tools/model-update.js.map +2 -2
  128. package/dist/mcp/tools/model-validate.js +46 -4
  129. package/dist/mcp/tools/model-validate.js.map +2 -2
  130. package/dist/mcp/tools/project-status-collectors.js +381 -0
  131. package/dist/mcp/tools/project-status-collectors.js.map +7 -0
  132. package/dist/mcp/tools/project-status.js +237 -0
  133. package/dist/mcp/tools/project-status.js.map +7 -0
  134. package/dist/mcp/tools/project-tools.js +505 -0
  135. package/dist/mcp/tools/project-tools.js.map +7 -0
  136. package/dist/mcp/tools/relationship-tools.js +89 -20
  137. package/dist/mcp/tools/relationship-tools.js.map +2 -2
  138. package/dist/mcp/tools/review-open.js +210 -0
  139. package/dist/mcp/tools/review-open.js.map +7 -0
  140. package/dist/mcp/tools/shared.js +10 -2
  141. package/dist/mcp/tools/shared.js.map +2 -2
  142. package/dist/reports/model-report-orchestrator.js +31 -2
  143. package/dist/reports/model-report-orchestrator.js.map +2 -2
  144. package/dist/schemas/bundled/analyzers/cbm.json +22 -10
  145. package/dist/schemas/bundled/analyzers/manifest.json +2 -2
  146. package/dist/schemas/bundled/api.json +1 -1
  147. package/dist/schemas/bundled/apm.json +1 -1
  148. package/dist/schemas/bundled/application.json +1 -1
  149. package/dist/schemas/bundled/base.json +1 -1
  150. package/dist/schemas/bundled/business.json +1 -1
  151. package/dist/schemas/bundled/data-model.json +1 -1
  152. package/dist/schemas/bundled/data-store.json +1 -1
  153. package/dist/schemas/bundled/manifest.json +3 -3
  154. package/dist/schemas/bundled/motivation.json +1 -1
  155. package/dist/schemas/bundled/navigation.json +1 -1
  156. package/dist/schemas/bundled/product.json +1 -1
  157. package/dist/schemas/bundled/security.json +1 -1
  158. package/dist/schemas/bundled/technology.json +1 -1
  159. package/dist/schemas/bundled/testing.json +145 -1
  160. package/dist/schemas/bundled/ux.json +1 -1
  161. package/dist/server/server.js +508 -237
  162. package/dist/server/server.js.map +3 -3
  163. package/dist/server/viewer-registry.js +264 -0
  164. package/dist/server/viewer-registry.js.map +7 -0
  165. package/dist/telemetry/index.js +1 -1
  166. package/dist/utils/id-generator.js +5 -2
  167. package/dist/utils/id-generator.js.map +2 -2
  168. package/dist/utils/source-reference.js +58 -27
  169. package/dist/utils/source-reference.js.map +2 -2
  170. package/dist/utils/spec-version.js +2 -2
  171. package/dist/utils/spec-version.js.map +1 -1
  172. package/dist/validators/model-warnings.js +28 -0
  173. package/dist/validators/model-warnings.js.map +7 -0
  174. package/dist/viewer/README.md +6 -6
  175. package/dist/viewer/assets/main-CUOQGGRA.css +1 -0
  176. package/dist/viewer/assets/main-DvH3gYrI.js +84 -0
  177. package/dist/viewer/index.html +2 -2
  178. package/dist/viewer/manifest.json +10 -10
  179. package/dist/viewer/version.json +1 -1
  180. package/package.json +1 -1
  181. package/dist/viewer/assets/main-CxzcuU_o.js +0 -84
  182. package/dist/viewer/assets/main-j-7ULYpm.css +0 -1
@@ -1,102 +1,218 @@
1
1
  import { z } from "zod";
2
+ import { strictItem } from "../strict-schema.js";
3
+ import { ChangesetGraph } from "../../core/changeset-graph.js";
2
4
  import { Element } from "../../core/element.js";
3
5
  import { Layer } from "../../core/layer.js";
4
6
  import { MutationHandler } from "../../core/mutation-handler.js";
7
+ import { countByStatus, planRelationshipBatch } from "../../core/relationship-staging.js";
8
+ import { StagingAreaManager } from "../../core/staging-area.js";
5
9
  import { getAllLayerIds, isValidLayer } from "../../generated/layer-registry.js";
6
10
  import { getNodeTypesForLayer, isValidNodeType, normalizeNodeType } from "../../generated/node-types.js";
7
11
  import { generateElementId, generateUUID } from "../../utils/id-generator.js";
8
- import { buildSourceReference, validateSourceReferenceOptions } from "../../utils/source-reference.js";
12
+ import { buildSourceReference, resolveSourceReferenceOptions } from "../../utils/source-reference.js";
9
13
  import { schemaLookupHint } from "../../utils/error-hints.js";
10
14
  import { SchemaValidator } from "../../validators/schema-validator.js";
11
- import { CLIError, ErrorCategory, findSimilar, formatValidOptions } from "../../utils/errors.js";
15
+ import { CLIError, ErrorCategory, findSimilar, formatValidOptions, getErrorMessage } from "../../utils/errors.js";
12
16
  import { jsonResult, loadModel, reloadModel, rootPathSchema, runTool } from "./shared.js";
13
- const inputSchema = {
14
- layer: z.string().describe("Canonical layer name (e.g. 'api', 'data-model')."),
15
- type: z.string().describe("Element type within the layer (e.g. 'operation', 'entity')."),
16
- name: z.string().describe("Human-readable element name; used to derive the element ID."),
17
+ import { activeStagedChangeset } from "./changeset-shared.js";
18
+ import { applyRelationshipPlan, previewChangeset, relationshipItemSchema } from "./relationship-tools.js";
19
+ const provenanceSchema = z.enum(["extracted", "manual", "inferred", "generated"]).optional().describe("How the source reference was made. Defaults to 'manual' when sourceFile is sent alone. 'extracted' and 'manual' need a sourceFile (sent without one, it is ignored with a warning). For a planned element with no code yet, send no source fields."), elementFields = {
17
20
  description: z.string().optional(),
18
21
  attributes: z.record(z.string(), z.unknown()).optional().describe("Type-specific attributes, validated against the element's spec schema."),
19
22
  sourceFile: z.string().optional().describe("Source file path relative to the repository root."),
20
23
  sourceSymbol: z.string().optional(),
21
- sourceProvenance: z.enum(["extracted", "manual", "inferred", "generated"]).optional(),
24
+ sourceProvenance: provenanceSchema,
22
25
  sourceRepoRemote: z.string().optional(),
23
- sourceRepoCommit: z.string().optional().describe("Full 40-character Git commit SHA."),
26
+ sourceRepoCommit: z.string().optional().describe("Full 40-character Git commit SHA.")
27
+ }, elementItemSchema = strictItem("An elements[] item", {
28
+ layer: z.string().describe("Canonical layer name."),
29
+ type: z.string().describe("Element type within the layer."),
30
+ name: z.string().describe("Human-readable element name; used to derive the element ID."),
31
+ ...elementFields
32
+ }), inputSchema = {
33
+ layer: z.string().optional().describe("Canonical layer name (e.g. 'api', 'data-model'). Single form."),
34
+ type: z.string().optional().describe("Element type within the layer (e.g. 'operation', 'entity'). Single form."),
35
+ name: z.string().optional().describe("Human-readable element name; used to derive the element ID. Single form."),
36
+ ...elementFields,
37
+ elements: z.array(elementItemSchema).optional().describe("Batch form, instead of layer/type/name: a list of elements ({layer, type, name, description?, attributes?, source fields?}), staged in one write. Each is validated on its own; an invalid one is skipped and reported."),
38
+ relationships: z.array(relationshipItemSchema).optional().describe("Batch form only: relationships to stage in the same write, as relationship_add takes them. They may name elements this call adds (by the id layer.type.kebab-name)."),
24
39
  rootPath: rootPathSchema
25
- };
40
+ }, SINGLE_FORM_FIELDS = [
41
+ "layer",
42
+ "type",
43
+ "name",
44
+ ...Object.keys(elementFields)
45
+ ];
46
+ async function prepareElement(spec) {
47
+ if (!isValidLayer(spec.layer)) {
48
+ const validLayers = getAllLayerIds(), similar = findSimilar(spec.layer, validLayers, 3), suggestions = [`Use a valid layer name: ${formatValidOptions(validLayers)}`];
49
+ throw similar.length > 0 && suggestions.unshift(`Did you mean: ${similar.join(" or ")}?`), new CLIError(`Unknown layer "${spec.layer}"`, ErrorCategory.USER, suggestions);
50
+ }
51
+ if (!isValidNodeType(spec.layer, spec.type)) {
52
+ const typeNames = getNodeTypesForLayer(spec.layer).map((t) => t.type).sort(), similar = findSimilar(spec.type, typeNames, 3), suggestions = [`Valid types for ${spec.layer}: ${formatValidOptions(typeNames)}`];
53
+ throw similar.length > 0 && suggestions.unshift(`Did you mean: ${similar.join(" or ")}?`), new CLIError(`Invalid element type "${spec.type}" for layer "${spec.layer}"`, ErrorCategory.USER, suggestions);
54
+ }
55
+ const resolvedType = normalizeNodeType(spec.layer, spec.type), elementPath = generateElementId(spec.layer, resolvedType, spec.name);
56
+ if (!elementPath.split(".")[2])
57
+ throw new CLIError(`Cannot generate a valid element ID from name "${spec.name}"`, ErrorCategory.USER, [
58
+ "Element names must contain at least one letter or digit"
59
+ ]);
60
+ const { options: sourceOptions, warnings } = resolveSourceReferenceOptions(spec, "mcp"), now = (/* @__PURE__ */ new Date()).toISOString(), element = new Element({
61
+ id: generateUUID(),
62
+ path: elementPath,
63
+ spec_node_id: `${spec.layer}.${resolvedType}`,
64
+ layer_id: spec.layer,
65
+ type: resolvedType,
66
+ name: spec.name,
67
+ description: spec.description,
68
+ attributes: spec.attributes ?? {},
69
+ metadata: {
70
+ created_at: now,
71
+ updated_at: now,
72
+ version: 1
73
+ },
74
+ layer: spec.layer
75
+ }), sourceRef = buildSourceReference(sourceOptions);
76
+ sourceRef && element.setSourceReference(sourceRef);
77
+ const schemaValidator = new SchemaValidator(), tempLayer = new Layer(spec.layer);
78
+ tempLayer.addElement(element);
79
+ const attrValidation = await schemaValidator.validateLayer(tempLayer);
80
+ if (!attrValidation.isValid()) {
81
+ const errorMessages = attrValidation.errors.map((e) => ` ${e.message}`).join(`
82
+ `);
83
+ throw new CLIError(`Element ${elementPath} has invalid attributes:
84
+ ${errorMessages}`, ErrorCategory.USER, [
85
+ schemaLookupHint("mcp", spec.layer, resolvedType)
86
+ ]);
87
+ }
88
+ return { element, id: elementPath, layer: spec.layer, type: resolvedType, warnings };
89
+ }
26
90
  async function modelAddHandler(args) {
27
91
  return runTool(async () => {
28
- if (!isValidLayer(args.layer)) {
29
- const validLayers = getAllLayerIds(), similar = findSimilar(args.layer, validLayers, 3), suggestions = [`Use a valid layer name: ${formatValidOptions(validLayers)}`];
30
- throw similar.length > 0 && suggestions.unshift(`Did you mean: ${similar.join(" or ")}?`), new CLIError(`Unknown layer "${args.layer}"`, ErrorCategory.USER, suggestions);
92
+ if (args.elements !== void 0) {
93
+ const singleFields = SINGLE_FORM_FIELDS.filter((field) => args[field] !== void 0);
94
+ if (singleFields.length > 0)
95
+ throw new CLIError(`Send either a single element or elements, not both (got ${singleFields.join(", ")} beside elements)`, ErrorCategory.USER, ["Put those fields on the items of the elements list"]);
96
+ return jsonResult(await addElementBatch(args.elements, args.relationships ?? [], args.rootPath));
31
97
  }
32
- if (!isValidNodeType(args.layer, args.type)) {
33
- const typeNames = getNodeTypesForLayer(args.layer).map((t) => t.type).sort(), similar = findSimilar(args.type, typeNames, 3), suggestions = [`Valid types for ${args.layer}: ${formatValidOptions(typeNames)}`];
34
- throw similar.length > 0 && suggestions.unshift(`Did you mean: ${similar.join(" or ")}?`), new CLIError(`Invalid element type "${args.type}" for layer "${args.layer}"`, ErrorCategory.USER, suggestions);
35
- }
36
- const resolvedType = normalizeNodeType(args.layer, args.type), elementPath = generateElementId(args.layer, resolvedType, args.name), elementUUID = generateUUID();
37
- if (!elementPath.split(".")[2])
38
- throw new CLIError(`Cannot generate a valid element ID from name "${args.name}"`, ErrorCategory.USER, [
39
- "Element names must contain at least one letter or digit"
98
+ if (args.relationships !== void 0)
99
+ throw new CLIError("relationships goes with the elements list", ErrorCategory.USER, [
100
+ "Send {elements: [...], relationships: [...]}, or use relationship_add for relationships alone"
40
101
  ]);
41
- validateSourceReferenceOptions(args, "mcp");
42
- const model = await loadModel(args.rootPath), layerKnown = model.getLayerNames().includes(args.layer);
43
- let layerObj = await model.getLayer(args.layer), isNewLayer = !1;
44
- layerObj || (layerObj = new Layer(args.layer), isNewLayer = !0);
45
- const now = (/* @__PURE__ */ new Date()).toISOString(), element = new Element({
46
- id: elementUUID,
47
- path: elementPath,
48
- spec_node_id: `${args.layer}.${resolvedType}`,
49
- layer_id: args.layer,
50
- type: resolvedType,
51
- name: args.name,
52
- description: args.description,
53
- attributes: args.attributes ?? {},
54
- metadata: {
55
- created_at: now,
56
- updated_at: now,
57
- version: 1
58
- },
59
- layer: args.layer
60
- }), sourceRef = buildSourceReference(args);
61
- sourceRef && element.setSourceReference(sourceRef);
62
- {
63
- const schemaValidator = new SchemaValidator(), tempLayer = new Layer(args.layer);
64
- tempLayer.addElement(element);
65
- const attrValidation = await schemaValidator.validateLayer(tempLayer);
66
- if (!attrValidation.isValid()) {
67
- const errorMessages = attrValidation.errors.map((e) => ` ${e.message}`).join(`
68
- `);
69
- throw new CLIError(`Element ${elementPath} has invalid attributes:
70
- ${errorMessages}`, ErrorCategory.USER, [
71
- schemaLookupHint("mcp", args.layer, resolvedType)
72
- ]);
73
- }
74
- }
75
- if (layerObj.getElement(elementPath))
76
- throw new CLIError(`Element ${elementPath} already exists in ${args.layer} layer`, ErrorCategory.USER, [
77
- 'Use "model_show" to view the existing element',
78
- 'Use "model_update" to modify it',
79
- 'Use "model_delete" to remove it first if you want to recreate it'
102
+ if (!args.layer || !args.type || !args.name)
103
+ throw new CLIError("model_add needs layer, type and name, or an elements list", ErrorCategory.USER, [
104
+ 'Single: {layer: "api", type: "operation", name: "Create Order"}',
105
+ "Batch: {elements: [{layer, type, name}, ...], relationships: [{source, predicate, target}, ...]}"
80
106
  ]);
81
- isNewLayer && model.addLayer(layerObj);
82
- const mutationHandler = new MutationHandler(model, elementPath, args.layer);
83
- await mutationHandler.executeAdd(element, (elem) => {
84
- layerObj.addElement(elem);
85
- });
86
- const activeChangeset = await mutationHandler.getStagingManager().getActive(), staged = !!(activeChangeset && activeChangeset.status === "staged");
87
- return staged && !layerKnown && await reloadModel(args.rootPath), jsonResult({
88
- status: staged ? "staged" : "added",
89
- changeset: staged ? activeChangeset.name : void 0,
90
- id: elementPath,
91
- layer: args.layer,
92
- type: resolvedType,
93
- name: args.name
107
+ return addSingleElement({ ...args, layer: args.layer, type: args.type, name: args.name }, args.rootPath);
108
+ });
109
+ }
110
+ async function addSingleElement(spec, rootPath) {
111
+ const prepared = await prepareElement(spec), { element, id: elementPath, layer } = prepared, model = await loadModel(rootPath), layerKnown = model.getLayerNames().includes(layer);
112
+ let layerObj = await model.getLayer(layer), isNewLayer = !1;
113
+ if (layerObj || (layerObj = new Layer(layer), isNewLayer = !0), layerObj.getElement(elementPath))
114
+ throw new CLIError(`Element ${elementPath} already exists in ${layer} layer`, ErrorCategory.USER, [
115
+ 'Use "model_show" to view the existing element',
116
+ 'Use "model_update" to modify it',
117
+ 'Use "model_delete" to remove it first if you want to recreate it'
118
+ ]);
119
+ isNewLayer && model.addLayer(layerObj);
120
+ const mutationHandler = new MutationHandler(model, elementPath, layer);
121
+ await mutationHandler.executeAdd(element, (elem) => {
122
+ layerObj.addElement(elem);
123
+ });
124
+ const activeChangeset = await mutationHandler.getStagingManager().getActive(), staged = !!(activeChangeset && activeChangeset.status === "staged");
125
+ return staged && !layerKnown && await reloadModel(rootPath), jsonResult({
126
+ status: staged ? "staged" : "added",
127
+ changeset: staged ? activeChangeset.name : void 0,
128
+ id: elementPath,
129
+ layer,
130
+ type: prepared.type,
131
+ name: spec.name,
132
+ ...prepared.warnings.length > 0 ? { warnings: prepared.warnings } : {}
133
+ });
134
+ }
135
+ async function addElementBatch(specs, relationships, rootPath) {
136
+ const model = await loadModel(rootPath), manager = new StagingAreaManager(model.rootPath, model), active = await activeStagedChangeset(manager), changeset = active ?? previewChangeset(), graph = await ChangesetGraph.build(model, changeset), prepared = [], pending = /* @__PURE__ */ new Map(), rejected = /* @__PURE__ */ new Map(), results = [];
137
+ for (const [index, spec] of specs.entries()) {
138
+ const asSent = { index, name: spec.name };
139
+ let item;
140
+ try {
141
+ item = await prepareElement(spec);
142
+ } catch (error) {
143
+ const id = idFor(spec);
144
+ id && rejected.set(id, getErrorMessage(error)), results.push({
145
+ ...asSent,
146
+ ...id ? { id } : {},
147
+ status: "invalid",
148
+ reason: getErrorMessage(error),
149
+ ...error instanceof CLIError && error.suggestions?.length ? { suggestions: error.suggestions } : {}
150
+ });
151
+ continue;
152
+ }
153
+ const existing = graph.getElement(item.id);
154
+ if (existing && existing.status !== "deleted") {
155
+ results.push({
156
+ ...asSent,
157
+ id: item.id,
158
+ status: "invalid",
159
+ reason: `Element ${item.id} already exists`,
160
+ suggestions: ['Use "model_update" to change it']
161
+ });
162
+ continue;
163
+ }
164
+ if (pending.has(item.id)) {
165
+ results.push({ ...asSent, id: item.id, status: "invalid", reason: "Repeated earlier in this batch" });
166
+ continue;
167
+ }
168
+ pending.set(item.id, { id: item.id, layer: item.layer, type: item.type }), prepared.push(item), results.push({
169
+ ...asSent,
170
+ id: item.id,
171
+ status: active ? "staged" : "added",
172
+ ...item.warnings.length > 0 ? { warnings: item.warnings } : {}
94
173
  });
174
+ }
175
+ const plan = await planRelationshipBatch(graph, changeset, relationships, {
176
+ pending,
177
+ rejected,
178
+ addStatus: active ? "staged" : "added"
95
179
  });
180
+ if (active) {
181
+ const elementChanges = prepared.map((p) => ({
182
+ type: "add",
183
+ elementId: p.id,
184
+ layerName: p.layer,
185
+ after: p.element.toJSON()
186
+ }));
187
+ await applyRelationshipPlan(model, manager, active, plan, elementChanges);
188
+ } else {
189
+ for (const p of prepared)
190
+ await writeElement(model, p);
191
+ await applyRelationshipPlan(model, manager, null, plan, []);
192
+ }
193
+ return (prepared.length > 0 || plan.changes.length > 0 || plan.unstageKeys.length > 0) && await reloadModel(rootPath), {
194
+ ...active ? { changeset: active.name } : {},
195
+ elements: { counts: countByStatus(results), results },
196
+ ...relationships.length > 0 ? { relationships: { counts: countByStatus(plan.results), results: plan.results } } : {}
197
+ };
198
+ }
199
+ async function writeElement(model, prepared) {
200
+ let layerObj = await model.getLayer(prepared.layer);
201
+ layerObj || (layerObj = new Layer(prepared.layer), model.addLayer(layerObj));
202
+ const target = layerObj;
203
+ await new MutationHandler(model, prepared.id, prepared.layer).executeAdd(prepared.element, (elem) => {
204
+ target.addElement(elem);
205
+ });
206
+ }
207
+ function idFor(spec) {
208
+ if (!isValidLayer(spec.layer) || !isValidNodeType(spec.layer, spec.type))
209
+ return;
210
+ const id = generateElementId(spec.layer, normalizeNodeType(spec.layer, spec.type), spec.name);
211
+ return id.split(".")[2] ? id : void 0;
96
212
  }
97
213
  const modelAddTool = {
98
214
  name: "model_add",
99
- description: "Add a new element to a layer, validated against its spec schema.",
215
+ description: "Add elements, validated against their spec schemas. Send one (layer/type/name) or many (elements: [...], with optional relationships: [...] that may name the new elements); a list is staged in one write, so use it when adding several. A list returns a result per element and per relationship: staged (added, with no changeset active), restored or duplicate (relationships), or invalid with the reason. Invalid items are skipped; the rest are still staged.",
100
216
  inputSchema,
101
217
  handler: modelAddHandler
102
218
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/mcp/tools/model-add.ts"],
4
- "sourcesContent": ["/**\n * model_add \u2014 add an element to a layer. Mirrors `dr add`, operating through\n * the shared MutationHandler so staging and base-model persistence stay\n * consistent with the CLI.\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 { getAllLayerIds, isValidLayer } from \"../../generated/layer-registry.js\";\nimport { getNodeTypesForLayer, isValidNodeType, normalizeNodeType } from \"../../generated/node-types.js\";\nimport { generateElementId, generateUUID } from \"../../utils/id-generator.js\";\nimport { buildSourceReference, validateSourceReferenceOptions } from \"../../utils/source-reference.js\";\nimport { schemaLookupHint } from \"../../utils/error-hints.js\";\nimport { SchemaValidator } from \"../../validators/schema-validator.js\";\nimport { CLIError, ErrorCategory, findSimilar, formatValidOptions } from \"../../utils/errors.js\";\nimport { jsonResult, loadModel, reloadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\n\nconst inputSchema = {\n layer: z.string().describe(\"Canonical layer name (e.g. 'api', 'data-model').\"),\n type: z.string().describe(\"Element type within the layer (e.g. 'operation', 'entity').\"),\n name: z.string().describe(\"Human-readable element name; used to derive the element ID.\"),\n description: z.string().optional(),\n attributes: z.record(z.string(), z.unknown()).optional().describe(\"Type-specific attributes, validated against the element's spec schema.\"),\n sourceFile: z.string().optional().describe(\"Source file path relative to the repository root.\"),\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 rootPath: rootPathSchema,\n};\n\nexport type ModelAddArgs = z.infer<z.ZodObject<typeof inputSchema>>;\n\nexport async function modelAddHandler(args: ModelAddArgs): Promise<CallToolResult> {\n return runTool(async () => {\n if (!isValidLayer(args.layer)) {\n const validLayers = getAllLayerIds();\n const similar = findSimilar(args.layer, validLayers, 3);\n const suggestions: string[] = [`Use a valid layer name: ${formatValidOptions(validLayers)}`];\n if (similar.length > 0) suggestions.unshift(`Did you mean: ${similar.join(\" or \")}?`);\n throw new CLIError(`Unknown layer \"${args.layer}\"`, ErrorCategory.USER, suggestions);\n }\n\n if (!isValidNodeType(args.layer, args.type)) {\n const validNodeTypes = getNodeTypesForLayer(args.layer);\n const typeNames = validNodeTypes.map((t) => t.type).sort();\n const similar = findSimilar(args.type, typeNames, 3);\n const suggestions: string[] = [`Valid types for ${args.layer}: ${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 \"${args.layer}\"`, ErrorCategory.USER, suggestions);\n }\n\n const resolvedType = normalizeNodeType(args.layer, args.type);\n const elementPath = generateElementId(args.layer, resolvedType, args.name);\n const elementUUID = generateUUID();\n\n const slugPart = elementPath.split(\".\")[2];\n if (!slugPart) {\n throw new CLIError(`Cannot generate a valid element ID from name \"${args.name}\"`, ErrorCategory.USER, [\n \"Element names must contain at least one letter or digit\",\n ]);\n }\n\n validateSourceReferenceOptions(args, \"mcp\");\n\n const model = await loadModel(args.rootPath);\n\n // Inside a changeset getLayer() projects even a layer the model has no elements in, so ask the\n // model's layer list whether this layer is new to it.\n const layerKnown = model.getLayerNames().includes(args.layer);\n let layerObj = await model.getLayer(args.layer);\n let isNewLayer = false;\n if (!layerObj) {\n layerObj = new Layer(args.layer);\n isNewLayer = true;\n }\n\n const now = new Date().toISOString();\n const element = new Element({\n id: elementUUID,\n path: elementPath,\n spec_node_id: `${args.layer}.${resolvedType}`,\n layer_id: args.layer,\n type: resolvedType,\n name: args.name,\n description: args.description,\n attributes: args.attributes ?? {},\n metadata: {\n created_at: now,\n updated_at: now,\n version: 1,\n },\n layer: args.layer,\n });\n\n const sourceRef = buildSourceReference(args);\n if (sourceRef) {\n element.setSourceReference(sourceRef);\n }\n\n {\n const schemaValidator = new SchemaValidator();\n const tempLayer = new Layer(args.layer);\n tempLayer.addElement(element);\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(`Element ${elementPath} has invalid attributes:\\n${errorMessages}`, ErrorCategory.USER, [\n schemaLookupHint(\"mcp\", args.layer, resolvedType),\n ]);\n }\n }\n\n if (layerObj.getElement(elementPath)) {\n throw new CLIError(`Element ${elementPath} already exists in ${args.layer} layer`, ErrorCategory.USER, [\n `Use \"model_show\" to view the existing element`,\n `Use \"model_update\" to modify it`,\n `Use \"model_delete\" to remove it first if you want to recreate it`,\n ]);\n }\n\n if (isNewLayer) {\n model.addLayer(layerObj);\n }\n\n const mutationHandler = new MutationHandler(model, elementPath, args.layer);\n await mutationHandler.executeAdd(element, (elem) => {\n layerObj!.addElement(elem);\n });\n\n const stagingManager = mutationHandler.getStagingManager();\n const activeChangeset = await stagingManager.getActive();\n const staged = !!(activeChangeset && activeChangeset.status === \"staged\");\n if (staged && !layerKnown) {\n // The cached model did not list this layer: reread it so every later tool call\n // (relationship_add, model_show, model_update, ...) sees the layer and the element.\n await reloadModel(args.rootPath);\n }\n\n return jsonResult({\n status: staged ? \"staged\" : \"added\",\n changeset: staged ? activeChangeset!.name : undefined,\n id: elementPath,\n layer: args.layer,\n type: resolvedType,\n name: args.name,\n });\n });\n}\n\nexport const modelAddTool: McpToolDefinition<typeof inputSchema> = {\n name: \"model_add\",\n description: \"Add a new element to a layer, validated against its spec schema.\",\n inputSchema,\n handler: modelAddHandler,\n};\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * model_add \u2014 add an element to a layer. Mirrors `dr add`, operating through\n * the shared MutationHandler so staging and base-model persistence stay\n * consistent with the CLI.\n *\n * It also takes a list (`elements`, with optional `relationships` between them and the model):\n * each item is validated on its own, and the valid ones are staged in one write. Invalid items\n * are skipped and reported.\n */\n\nimport { z } from \"zod\";\nimport { strictItem } from \"../strict-schema.js\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { StagedChange } from \"../../core/changeset.js\";\nimport { ChangesetGraph } from \"../../core/changeset-graph.js\";\nimport { Element } from \"../../core/element.js\";\nimport { Layer } from \"../../core/layer.js\";\nimport type { Model } from \"../../core/model.js\";\nimport { MutationHandler } from \"../../core/mutation-handler.js\";\nimport { countByStatus, planRelationshipBatch, type PendingElement } from \"../../core/relationship-staging.js\";\nimport { StagingAreaManager } from \"../../core/staging-area.js\";\nimport { getAllLayerIds, isValidLayer } from \"../../generated/layer-registry.js\";\nimport { getNodeTypesForLayer, isValidNodeType, normalizeNodeType } from \"../../generated/node-types.js\";\nimport { generateElementId, generateUUID } from \"../../utils/id-generator.js\";\nimport { buildSourceReference, resolveSourceReferenceOptions } from \"../../utils/source-reference.js\";\nimport { schemaLookupHint } from \"../../utils/error-hints.js\";\nimport { SchemaValidator } from \"../../validators/schema-validator.js\";\nimport { CLIError, ErrorCategory, findSimilar, formatValidOptions, getErrorMessage } from \"../../utils/errors.js\";\nimport { jsonResult, loadModel, reloadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\nimport { activeStagedChangeset } from \"./changeset-shared.js\";\nimport { applyRelationshipPlan, previewChangeset, relationshipItemSchema } from \"./relationship-tools.js\";\n\nconst provenanceSchema = z\n .enum([\"extracted\", \"manual\", \"inferred\", \"generated\"])\n .optional()\n .describe(\n \"How the source reference was made. Defaults to 'manual' when sourceFile is sent alone. 'extracted' and \" +\n \"'manual' need a sourceFile (sent without one, it is ignored with a warning). For a planned element with no \" +\n \"code yet, send no source fields.\"\n );\n\nconst elementFields = {\n description: z.string().optional(),\n attributes: z.record(z.string(), z.unknown()).optional().describe(\"Type-specific attributes, validated against the element's spec schema.\"),\n sourceFile: z.string().optional().describe(\"Source file path relative to the repository root.\"),\n sourceSymbol: z.string().optional(),\n sourceProvenance: provenanceSchema,\n sourceRepoRemote: z.string().optional(),\n sourceRepoCommit: z.string().optional().describe(\"Full 40-character Git commit SHA.\"),\n};\n\nconst elementItemSchema = strictItem(\"An elements[] item\", {\n layer: z.string().describe(\"Canonical layer name.\"),\n type: z.string().describe(\"Element type within the layer.\"),\n name: z.string().describe(\"Human-readable element name; used to derive the element ID.\"),\n ...elementFields,\n});\n\nconst inputSchema = {\n layer: z.string().optional().describe(\"Canonical layer name (e.g. 'api', 'data-model'). Single form.\"),\n type: z.string().optional().describe(\"Element type within the layer (e.g. 'operation', 'entity'). Single form.\"),\n name: z.string().optional().describe(\"Human-readable element name; used to derive the element ID. Single form.\"),\n ...elementFields,\n elements: z\n .array(elementItemSchema)\n .optional()\n .describe(\n \"Batch form, instead of layer/type/name: a list of elements ({layer, type, name, description?, attributes?, \" +\n \"source fields?}), staged in one write. Each is validated on its own; an invalid one is skipped and reported.\"\n ),\n relationships: z\n .array(relationshipItemSchema)\n .optional()\n .describe(\n \"Batch form only: relationships to stage in the same write, as relationship_add takes them. They may name \" +\n \"elements this call adds (by the id layer.type.kebab-name).\"\n ),\n rootPath: rootPathSchema,\n};\n\nexport type ModelAddArgs = z.infer<z.ZodObject<typeof inputSchema>>;\n\n/** The single form's fields: refused beside `elements`, where they would otherwise be dropped. */\nconst SINGLE_FORM_FIELDS = [\n \"layer\",\n \"type\",\n \"name\",\n ...(Object.keys(elementFields) as Array<keyof typeof elementFields>),\n] as const;\ntype ElementSpec = z.infer<typeof elementItemSchema>;\n\ninterface PreparedElement {\n element: Element;\n id: string;\n layer: string;\n type: string;\n warnings: string[];\n}\n\n/** Validates one element and builds it; throws a CLIError saying what is wrong. */\nasync function prepareElement(spec: ElementSpec): Promise<PreparedElement> {\n if (!isValidLayer(spec.layer)) {\n const validLayers = getAllLayerIds();\n const similar = findSimilar(spec.layer, validLayers, 3);\n const suggestions: string[] = [`Use a valid layer name: ${formatValidOptions(validLayers)}`];\n if (similar.length > 0) suggestions.unshift(`Did you mean: ${similar.join(\" or \")}?`);\n throw new CLIError(`Unknown layer \"${spec.layer}\"`, ErrorCategory.USER, suggestions);\n }\n\n if (!isValidNodeType(spec.layer, spec.type)) {\n const validNodeTypes = getNodeTypesForLayer(spec.layer);\n const typeNames = validNodeTypes.map((t) => t.type).sort();\n const similar = findSimilar(spec.type, typeNames, 3);\n const suggestions: string[] = [`Valid types for ${spec.layer}: ${formatValidOptions(typeNames)}`];\n if (similar.length > 0) suggestions.unshift(`Did you mean: ${similar.join(\" or \")}?`);\n throw new CLIError(`Invalid element type \"${spec.type}\" for layer \"${spec.layer}\"`, ErrorCategory.USER, suggestions);\n }\n\n const resolvedType = normalizeNodeType(spec.layer, spec.type);\n const elementPath = generateElementId(spec.layer, resolvedType, spec.name);\n\n const slugPart = elementPath.split(\".\")[2];\n if (!slugPart) {\n throw new CLIError(`Cannot generate a valid element ID from name \"${spec.name}\"`, ErrorCategory.USER, [\n \"Element names must contain at least one letter or digit\",\n ]);\n }\n\n const { options: sourceOptions, warnings } = resolveSourceReferenceOptions(spec, \"mcp\");\n\n const now = new Date().toISOString();\n const element = new Element({\n id: generateUUID(),\n path: elementPath,\n spec_node_id: `${spec.layer}.${resolvedType}`,\n layer_id: spec.layer,\n type: resolvedType,\n name: spec.name,\n description: spec.description,\n attributes: spec.attributes ?? {},\n metadata: {\n created_at: now,\n updated_at: now,\n version: 1,\n },\n layer: spec.layer,\n });\n\n const sourceRef = buildSourceReference(sourceOptions);\n if (sourceRef) {\n element.setSourceReference(sourceRef);\n }\n\n const schemaValidator = new SchemaValidator();\n const tempLayer = new Layer(spec.layer);\n tempLayer.addElement(element);\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(`Element ${elementPath} has invalid attributes:\\n${errorMessages}`, ErrorCategory.USER, [\n schemaLookupHint(\"mcp\", spec.layer, resolvedType),\n ]);\n }\n\n return { element, id: elementPath, layer: spec.layer, type: resolvedType, warnings };\n}\n\nexport async function modelAddHandler(args: ModelAddArgs): Promise<CallToolResult> {\n return runTool(async () => {\n if (args.elements !== undefined) {\n const singleFields = SINGLE_FORM_FIELDS.filter((field) => args[field] !== undefined);\n if (singleFields.length > 0) {\n throw new CLIError(\n `Send either a single element or elements, not both (got ${singleFields.join(\", \")} beside elements)`,\n ErrorCategory.USER,\n [\"Put those fields on the items of the elements list\"]\n );\n }\n return jsonResult(await addElementBatch(args.elements, args.relationships ?? [], args.rootPath));\n }\n if (args.relationships !== undefined) {\n throw new CLIError(\"relationships goes with the elements list\", ErrorCategory.USER, [\n \"Send {elements: [...], relationships: [...]}, or use relationship_add for relationships alone\",\n ]);\n }\n if (!args.layer || !args.type || !args.name) {\n throw new CLIError(\"model_add needs layer, type and name, or an elements list\", ErrorCategory.USER, [\n 'Single: {layer: \"api\", type: \"operation\", name: \"Create Order\"}',\n \"Batch: {elements: [{layer, type, name}, ...], relationships: [{source, predicate, target}, ...]}\",\n ]);\n }\n return addSingleElement({ ...args, layer: args.layer, type: args.type, name: args.name }, args.rootPath);\n });\n}\n\nasync function addSingleElement(spec: ElementSpec, rootPath: string | undefined): Promise<CallToolResult> {\n const prepared = await prepareElement(spec);\n const { element, id: elementPath, layer } = prepared;\n\n const model = await loadModel(rootPath);\n\n // Inside a changeset getLayer() projects even a layer the model has no elements in, so ask the\n // model's layer list whether this layer is new to it.\n const layerKnown = model.getLayerNames().includes(layer);\n let layerObj = await model.getLayer(layer);\n let isNewLayer = false;\n if (!layerObj) {\n layerObj = new Layer(layer);\n isNewLayer = true;\n }\n\n if (layerObj.getElement(elementPath)) {\n throw new CLIError(`Element ${elementPath} already exists in ${layer} layer`, ErrorCategory.USER, [\n `Use \"model_show\" to view the existing element`,\n `Use \"model_update\" to modify it`,\n `Use \"model_delete\" to remove it first if you want to recreate it`,\n ]);\n }\n\n if (isNewLayer) {\n model.addLayer(layerObj);\n }\n\n const mutationHandler = new MutationHandler(model, elementPath, layer);\n await mutationHandler.executeAdd(element, (elem) => {\n layerObj!.addElement(elem);\n });\n\n const stagingManager = mutationHandler.getStagingManager();\n const activeChangeset = await stagingManager.getActive();\n const staged = !!(activeChangeset && activeChangeset.status === \"staged\");\n if (staged && !layerKnown) {\n // The cached model did not list this layer: reread it so every later tool call\n // (relationship_add, model_show, model_update, ...) sees the layer and the element.\n await reloadModel(rootPath);\n }\n\n return jsonResult({\n status: staged ? \"staged\" : \"added\",\n changeset: staged ? activeChangeset!.name : undefined,\n id: elementPath,\n layer,\n type: prepared.type,\n name: spec.name,\n ...(prepared.warnings.length > 0 ? { warnings: prepared.warnings } : {}),\n });\n}\n\ninterface ElementBatchResult {\n index: number;\n id?: string;\n name: string;\n status: \"staged\" | \"added\" | \"invalid\";\n reason?: string;\n suggestions?: string[];\n warnings?: string[];\n}\n\n/**\n * model_add's batch form: validate every element and relationship, then stage the valid ones in\n * one write (or, with no active changeset, write them to the model).\n */\nasync function addElementBatch(\n specs: ElementSpec[],\n relationships: z.infer<typeof relationshipItemSchema>[],\n rootPath: string | undefined\n) {\n const model = await loadModel(rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const active = await activeStagedChangeset(manager);\n const changeset = active ?? previewChangeset();\n const graph = await ChangesetGraph.build(model, changeset);\n\n const prepared: PreparedElement[] = [];\n const pending = new Map<string, PendingElement>();\n const rejected = new Map<string, string>();\n const results: ElementBatchResult[] = [];\n\n for (const [index, spec] of specs.entries()) {\n const asSent = { index, name: spec.name };\n let item: PreparedElement;\n try {\n item = await prepareElement(spec);\n } catch (error) {\n const id = idFor(spec);\n if (id) rejected.set(id, getErrorMessage(error));\n results.push({\n ...asSent,\n ...(id ? { id } : {}),\n status: \"invalid\",\n reason: getErrorMessage(error),\n ...(error instanceof CLIError && error.suggestions?.length ? { suggestions: error.suggestions } : {}),\n });\n continue;\n }\n const existing = graph.getElement(item.id);\n if (existing && existing.status !== \"deleted\") {\n results.push({\n ...asSent,\n id: item.id,\n status: \"invalid\",\n reason: `Element ${item.id} already exists`,\n suggestions: ['Use \"model_update\" to change it'],\n });\n continue;\n }\n if (pending.has(item.id)) {\n results.push({ ...asSent, id: item.id, status: \"invalid\", reason: \"Repeated earlier in this batch\" });\n continue;\n }\n pending.set(item.id, { id: item.id, layer: item.layer, type: item.type });\n prepared.push(item);\n results.push({\n ...asSent,\n id: item.id,\n status: active ? \"staged\" : \"added\",\n ...(item.warnings.length > 0 ? { warnings: item.warnings } : {}),\n });\n }\n\n const plan = await planRelationshipBatch(graph, changeset, relationships, {\n pending,\n rejected,\n addStatus: active ? \"staged\" : \"added\",\n });\n\n if (active) {\n const elementChanges: Array<Omit<StagedChange, \"sequenceNumber\">> = prepared.map((p) => ({\n type: \"add\",\n elementId: p.id,\n layerName: p.layer,\n after: p.element.toJSON() as unknown as Record<string, unknown>,\n }));\n await applyRelationshipPlan(model, manager, active, plan, elementChanges);\n } else {\n for (const p of prepared) {\n await writeElement(model, p);\n }\n await applyRelationshipPlan(model, manager, null, plan, []);\n }\n if (prepared.length > 0 || plan.changes.length > 0 || plan.unstageKeys.length > 0) {\n await reloadModel(rootPath);\n }\n\n return {\n ...(active ? { changeset: active.name } : {}),\n elements: { counts: countByStatus(results), results },\n ...(relationships.length > 0\n ? { relationships: { counts: countByStatus(plan.results), results: plan.results } }\n : {}),\n };\n}\n\n/** Writes one element to the base model (no changeset is active). */\nasync function writeElement(model: Model, prepared: PreparedElement): Promise<void> {\n let layerObj = await model.getLayer(prepared.layer);\n if (!layerObj) {\n layerObj = new Layer(prepared.layer);\n model.addLayer(layerObj);\n }\n const target = layerObj;\n const handler = new MutationHandler(model, prepared.id, prepared.layer);\n await handler.executeAdd(prepared.element, (elem) => {\n target.addElement(elem);\n });\n}\n\n/** The id an element spec would get, when its layer and type are valid. */\nfunction idFor(spec: ElementSpec): string | undefined {\n if (!isValidLayer(spec.layer) || !isValidNodeType(spec.layer, spec.type)) return undefined;\n const id = generateElementId(spec.layer, normalizeNodeType(spec.layer, spec.type), spec.name);\n return id.split(\".\")[2] ? id : undefined;\n}\n\nexport const modelAddTool: McpToolDefinition<typeof inputSchema> = {\n name: \"model_add\",\n description:\n \"Add elements, validated against their spec schemas. Send one (layer/type/name) or many (elements: [...], \" +\n \"with optional relationships: [...] that may name the new elements); a list is staged in one write, so use it \" +\n \"when adding several. A list returns a result per element and per relationship: staged (added, with no \" +\n \"changeset active), restored or duplicate (relationships), or invalid with the reason. Invalid items are \" +\n \"skipped; the rest are still staged.\",\n inputSchema,\n handler: modelAddHandler,\n};\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -5,7 +5,7 @@ import { MutationHandler } from "../../core/mutation-handler.js";
5
5
  import { setStagedSourceReference } from "../../core/staged-source-reference.js";
6
6
  import { getNodeTypesForLayer, isValidNodeType, normalizeNodeType } from "../../generated/node-types.js";
7
7
  import { findElementLayer } from "../../utils/element-utils.js";
8
- import { buildSourceReference, validateSourceReferenceOptions } from "../../utils/source-reference.js";
8
+ import { buildSourceReference, precheckUpdateSourceReferenceOptions, resolveSourceReferenceOptions } from "../../utils/source-reference.js";
9
9
  import { schemaLookupHint, undefinedTypeWarning } from "../../utils/error-hints.js";
10
10
  import { SchemaValidator, suggestNodeTypes } from "../../validators/schema-validator.js";
11
11
  import { CLIError, ErrorCategory, findSimilar, formatValidOptions } from "../../utils/errors.js";
@@ -19,8 +19,8 @@ const inputSchema = {
19
19
  attributes: z.record(z.string(), z.unknown()).optional().describe("Replaces the element's attributes, validated against its spec schema."),
20
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."),
21
21
  sourceFile: z.string().optional(),
22
- sourceSymbol: z.string().optional(),
23
- 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."),
24
24
  sourceRepoRemote: z.string().optional(),
25
25
  sourceRepoCommit: z.string().optional().describe("Full 40-character Git commit SHA."),
26
26
  clearSourceReference: z.boolean().optional().describe("Remove the element's source reference. Cannot be combined with other source options."),
@@ -32,7 +32,7 @@ function validateUpdateSourceReferenceOptions(args) {
32
32
  throw new CLIError("Cannot use clearSourceReference with other source reference options", ErrorCategory.USER, [
33
33
  "Specify either clearSourceReference or other source options, not both"
34
34
  ]);
35
- hasSourceOptions && validateSourceReferenceOptions(args, "mcp");
35
+ hasSourceOptions && precheckUpdateSourceReferenceOptions(args, "mcp");
36
36
  }
37
37
  async function modelUpdateHandler(args) {
38
38
  return runTool(async () => {
@@ -47,9 +47,15 @@ async function modelUpdateHandler(args) {
47
47
  throw new CLIError(`Element ${args.id} not found`, ErrorCategory.NOT_FOUND, [
48
48
  'Use "model_search" to find similar elements'
49
49
  ]);
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))
51
- return jsonResult({ status: "no-op", id: args.id, message: "No fields specified for update" });
52
- const mutationHandler = new MutationHandler(model, args.id, layerName), warnings = [];
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
+ });
58
+ const mutationHandler = new MutationHandler(model, args.id, layerName);
53
59
  await mutationHandler.executeUpdate(element, async (elem, after) => {
54
60
  let resolvedNewType;
55
61
  if (args.type) {
@@ -85,12 +91,10 @@ ${errorMessages}`, ErrorCategory.USER, [
85
91
  ]);
86
92
  }
87
93
  }
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);
89
- const hasSourceReferenceUpdate = args.sourceFile || args.sourceSymbol || args.sourceProvenance || args.sourceRepoRemote || args.sourceRepoCommit;
90
- if (args.clearSourceReference)
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)
91
95
  elem.setSourceReference(void 0), setStagedSourceReference(after, void 0);
92
- else if (hasSourceReferenceUpdate) {
93
- const newRef = buildSourceReference(args);
96
+ else if (source.options.sourceProvenance) {
97
+ const newRef = buildSourceReference(source.options, currentSource);
94
98
  newRef && (elem.setSourceReference(newRef), setStagedSourceReference(after, newRef));
95
99
  }
96
100
  layer.updateElement(elem);
@@ -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 { 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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",
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": "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",
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), stats = ValidationFormatter.calculateStats(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.",