@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,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { strictItem } from "../strict-schema.js";
2
3
  import { ChangesetGraph } from "../../core/changeset-graph.js";
3
4
  import { StagingAreaManager } from "../../core/staging-area.js";
4
5
  import { stagedRelationshipChangesAtEnds, stagedRelationshipTriple } from "../../core/relationship-staging.js";
@@ -54,76 +55,100 @@ const changesetUpdateTool = {
54
55
  description: "Rename a changeset or change its description (the change's intent). Staged changes are untouched. Defaults to the active changeset.",
55
56
  inputSchema: updateInputSchema,
56
57
  handler: changesetUpdateHandler
57
- }, unstageInputSchema = {
58
+ }, elementIdField = z.string().optional().describe("Drop every staged change to this element (its add, updates or delete). ID or path."), sourceField = z.string().optional().describe("Drop staged relationship changes (adds and deletes) from this element: to target when given, otherwise to any element. The element's own changes are not touched."), targetField = z.string().optional().describe("Drop staged relationship changes (adds and deletes) to this element: from source when given, otherwise from any element. The element's own changes are not touched."), predicateField = z.string().optional().describe("Only relationship changes with this predicate. Omit for any."), keepRelationshipsField = z.boolean().optional().describe("With elementId: keep the staged relationship changes that depend on the element's change. By default, unstaging an element's add also drops staged relationships to it (they would dangle), and unstaging its delete also drops the relationship removals its cascade staged (removals staged before the delete are kept)."), unstageItemSchema = strictItem("A changes[] item", {
59
+ elementId: elementIdField,
60
+ source: sourceField,
61
+ target: targetField,
62
+ predicate: predicateField,
63
+ keepRelationships: keepRelationshipsField
64
+ }), unstageInputSchema = {
58
65
  changesetId: changesetIdSchema,
59
- elementId: z.string().optional().describe("Drop every staged change to this element (its add, updates or delete). ID or path."),
60
- source: z.string().optional().describe("Drop staged relationship changes (adds and deletes) from this element: to target when given, otherwise to any element. The element's own changes are not touched."),
61
- target: z.string().optional().describe("Drop staged relationship changes (adds and deletes) to this element: from source when given, otherwise from any element. The element's own changes are not touched."),
62
- predicate: z.string().optional().describe("Only relationship changes with this predicate. Omit for any."),
63
- keepRelationships: z.boolean().optional().describe("With elementId: keep the staged relationship changes that depend on the element's change. By default, unstaging an element's add also drops staged relationships to it (they would dangle), and unstaging its delete also drops the relationship removals its cascade staged (removals staged before the delete are kept)."),
66
+ elementId: elementIdField,
67
+ source: sourceField,
68
+ target: targetField,
69
+ predicate: predicateField,
70
+ keepRelationships: keepRelationshipsField,
71
+ changes: z.array(unstageItemSchema).min(1).optional().describe("Several unstages at once, each {elementId} or {source, predicate?, target} or {source} or {target}, with the same meaning as the single form. All are removed in one write, with a result per item: unstaged, already-unstaged (an earlier item took its changes), not-staged, or invalid with the reason. Cannot be combined with the single-form fields."),
64
72
  rootPath: rootPathSchema
65
73
  };
66
74
  function describeEnds(args) {
67
75
  return args.source && args.target ? `from ${args.source} to ${args.target}` : args.source ? `from ${args.source}` : `to ${args.target}`;
68
76
  }
77
+ function checkUnstageSpec(spec) {
78
+ const byElement = spec.elementId !== void 0, byRelationship = spec.source !== void 0 || spec.target !== void 0;
79
+ if (byElement === byRelationship)
80
+ throw new CLIError("Name either an element or a relationship to unstage", ErrorCategory.USER, [
81
+ `Pass "elementId" to drop an element's staged changes`,
82
+ 'Or pass "source" and/or "target" (and optionally "predicate") to drop staged relationship changes'
83
+ ]);
84
+ if (byRelationship && !spec.source && !spec.target)
85
+ throw new CLIError('"source" or "target" must not be empty', ErrorCategory.USER);
86
+ }
87
+ function selectUnstage(spec, graph, changeset) {
88
+ const changes = changeset.changes, keys = /* @__PURE__ */ new Set(), notes = [];
89
+ if (spec.elementId !== void 0) {
90
+ const element = graph.getElement(spec.elementId), ids = /* @__PURE__ */ new Set([spec.elementId, ...element ? [element.id, element.uuid ?? element.id] : []]), own = changes.filter((c) => !c.type.startsWith("relationship-") && ids.has(c.elementId));
91
+ for (const change of own)
92
+ keys.add(change.elementId);
93
+ const dependentType = own.some((c) => c.type === "add") ? "relationship-add" : own.some((c) => c.type === "delete") ? "relationship-delete" : null, touching = dependentType ? changes.filter((c) => {
94
+ if (c.type !== dependentType)
95
+ return !1;
96
+ const triple = stagedRelationshipTriple(c);
97
+ return !!triple && (ids.has(triple.source) || ids.has(triple.target) || element && (graph.resolve(triple.source) === element.id || graph.resolve(triple.target) === element.id));
98
+ }) : [], seq2 = (c) => c.sequenceNumber ?? changes.indexOf(c), deleteSeqs = own.filter((c) => c.type === "delete").map(seq2), firstDelete = deleteSeqs.length > 0 ? Math.min(...deleteSeqs) : -1, dependent = dependentType === "relationship-delete" ? touching.filter((c) => seq2(c) > firstDelete) : touching, deliberate = touching.filter((c) => !dependent.includes(c));
99
+ if (deliberate.length > 0 && notes.push(`${deliberate.length} relationship removal(s) on ${spec.elementId} staged before its delete were kept (${deliberate.map((c) => c.elementId).join(", ")}); unstage them by source and target if they should go too`), dependent.length > 0)
100
+ if (spec.keepRelationships)
101
+ notes.push(`${dependent.length} staged relationship change(s) on ${spec.elementId} were kept (keepRelationships)` + (dependentType === "relationship-add" ? "; they will dangle unless the element is staged again" : ""));
102
+ else
103
+ for (const change of dependent)
104
+ keys.add(change.elementId);
105
+ return { keys, notes };
106
+ }
107
+ const matched = stagedRelationshipChangesAtEnds(graph, changeset, {
108
+ source: spec.source || void 0,
109
+ target: spec.target || void 0,
110
+ predicate: spec.predicate
111
+ }), seq = (c) => c.sequenceNumber ?? changes.indexOf(c), deleteSeq = /* @__PURE__ */ new Map();
112
+ for (const change of changes) {
113
+ if (change.type !== "delete")
114
+ continue;
115
+ const id = graph.resolve(change.elementId);
116
+ deleteSeq.set(id, Math.min(deleteSeq.get(id) ?? 1 / 0, seq(change)));
117
+ }
118
+ const deletedEndOf = (change) => {
119
+ if (change.type !== "relationship-delete")
120
+ return;
121
+ const triple = stagedRelationshipTriple(change);
122
+ if (triple)
123
+ return [graph.resolve(triple.source), graph.resolve(triple.target)].find((end) => deleteSeq.has(end) && deleteSeq.get(end) < seq(change));
124
+ }, cascaded = matched.filter((c) => deletedEndOf(c) !== void 0), deletedEnds = [...new Set(cascaded.map((c) => deletedEndOf(c)))].join(", "), cascadeList = cascaded.map((c) => c.elementId).join(", "), bothEnds = !!spec.source && !!spec.target;
125
+ for (const change of matched)
126
+ !bothEnds && cascaded.includes(change) || keys.add(change.elementId);
127
+ return cascaded.length > 0 && bothEnds ? notes.push(`Unstaged ${cascaded.length} relationship removal(s) cascaded from the staged delete of ${deletedEnds} (${cascadeList}): those relationships now dangle, so the changeset will not commit until they are removed again or the delete is unstaged ("changeset_unstage" with elementId)`) : cascaded.length > 0 && notes.push(`Kept ${cascaded.length} relationship removal(s) cascaded from the staged delete of ${deletedEnds} (${cascadeList}): dropping them would leave those relationships dangling. Unstage the delete ("changeset_unstage" with elementId) to restore them, or name both source and target to drop one anyway`), { keys, notes };
128
+ }
129
+ function notStagedMessage(spec, changesetName) {
130
+ return spec.elementId !== void 0 ? `No staged change to ${spec.elementId} in '${changesetName}'` : `No staged relationship change ${describeEnds(spec)}${spec.predicate ? ` (${spec.predicate})` : ""} in '${changesetName}'`;
131
+ }
132
+ const SINGLE_FORM_FIELDS = ["elementId", "source", "target", "predicate", "keepRelationships"];
69
133
  async function changesetUnstageHandler(args) {
70
134
  return runTool(async () => {
71
- const byElement = args.elementId !== void 0, byRelationship = args.source !== void 0 || args.target !== void 0;
72
- if (byElement === byRelationship)
73
- throw new CLIError("Name either an element or a relationship to unstage", ErrorCategory.USER, [
74
- `Pass "elementId" to drop an element's staged changes`,
75
- 'Or pass "source" and/or "target" (and optionally "predicate") to drop staged relationship changes'
76
- ]);
77
- if (byRelationship && !args.source && !args.target)
78
- throw new CLIError('"source" or "target" must not be empty', ErrorCategory.USER);
135
+ if (args.changes) {
136
+ const mixed = SINGLE_FORM_FIELDS.filter((field) => args[field] !== void 0);
137
+ if (mixed.length > 0)
138
+ throw new CLIError(`"changes" cannot be combined with ${mixed.map((f) => `"${f}"`).join(", ")}`, ErrorCategory.USER, [
139
+ 'Put those fields on the items of the "changes" list'
140
+ ]);
141
+ return unstageBatch(args.changes, args.changesetId, args.rootPath);
142
+ }
143
+ checkUnstageSpec(args);
79
144
  const model = await loadModel(args.rootPath), manager = new StagingAreaManager(model.rootPath, model), changeset = await resolveChangeset(manager, args.changesetId);
80
145
  requireStaged(changeset, "unstage changes");
81
- const graph = await ChangesetGraph.build(model, changeset), changes = changeset.changes, keys = /* @__PURE__ */ new Set(), notes = [];
82
- if (byElement) {
83
- const element = graph.getElement(args.elementId), ids = /* @__PURE__ */ new Set([args.elementId, ...element ? [element.id, element.uuid ?? element.id] : []]), own = changes.filter((c) => !c.type.startsWith("relationship-") && ids.has(c.elementId));
84
- for (const change of own)
85
- keys.add(change.elementId);
86
- const dependentType = own.some((c) => c.type === "add") ? "relationship-add" : own.some((c) => c.type === "delete") ? "relationship-delete" : null, touching = dependentType ? changes.filter((c) => {
87
- if (c.type !== dependentType)
88
- return !1;
89
- const triple = stagedRelationshipTriple(c);
90
- return !!triple && (ids.has(triple.source) || ids.has(triple.target) || element && (graph.resolve(triple.source) === element.id || graph.resolve(triple.target) === element.id));
91
- }) : [], seq = (c) => c.sequenceNumber ?? changes.indexOf(c), deleteSeqs = own.filter((c) => c.type === "delete").map(seq), firstDelete = deleteSeqs.length > 0 ? Math.min(...deleteSeqs) : -1, dependent = dependentType === "relationship-delete" ? touching.filter((c) => seq(c) > firstDelete) : touching, deliberate = touching.filter((c) => !dependent.includes(c));
92
- if (deliberate.length > 0 && notes.push(`${deliberate.length} relationship removal(s) on ${args.elementId} staged before its delete were kept (${deliberate.map((c) => c.elementId).join(", ")}); unstage them by source and target if they should go too`), dependent.length > 0)
93
- if (args.keepRelationships)
94
- notes.push(`${dependent.length} staged relationship change(s) on ${args.elementId} were kept (keepRelationships)` + (dependentType === "relationship-add" ? "; they will dangle unless the element is staged again" : ""));
95
- else
96
- for (const change of dependent)
97
- keys.add(change.elementId);
98
- } else {
99
- const matched = stagedRelationshipChangesAtEnds(graph, changeset, {
100
- source: args.source || void 0,
101
- target: args.target || void 0,
102
- predicate: args.predicate
103
- }), seq = (c) => c.sequenceNumber ?? changes.indexOf(c), deleteSeq = /* @__PURE__ */ new Map();
104
- for (const change of changes) {
105
- if (change.type !== "delete")
106
- continue;
107
- const id = graph.resolve(change.elementId);
108
- deleteSeq.set(id, Math.min(deleteSeq.get(id) ?? 1 / 0, seq(change)));
109
- }
110
- const deletedEndOf = (change) => {
111
- if (change.type !== "relationship-delete")
112
- return;
113
- const triple = stagedRelationshipTriple(change);
114
- if (triple)
115
- return [graph.resolve(triple.source), graph.resolve(triple.target)].find((end) => deleteSeq.has(end) && deleteSeq.get(end) < seq(change));
116
- }, cascaded = matched.filter((c) => deletedEndOf(c) !== void 0), deletedEnds = [...new Set(cascaded.map((c) => deletedEndOf(c)))].join(", "), cascadeList = cascaded.map((c) => c.elementId).join(", "), bothEnds = !!args.source && !!args.target;
117
- for (const change of matched)
118
- !bothEnds && cascaded.includes(change) || keys.add(change.elementId);
119
- cascaded.length > 0 && bothEnds ? notes.push(`Unstaged ${cascaded.length} relationship removal(s) cascaded from the staged delete of ${deletedEnds} (${cascadeList}): those relationships now dangle, so the changeset will not commit until they are removed again or the delete is unstaged ("changeset_unstage" with elementId)`) : cascaded.length > 0 && notes.push(`Kept ${cascaded.length} relationship removal(s) cascaded from the staged delete of ${deletedEnds} (${cascadeList}): dropping them would leave those relationships dangling. Unstage the delete ("changeset_unstage" with elementId) to restore them, or name both source and target to drop one anyway`);
120
- }
121
- const removed = changes.filter((c) => keys.has(c.elementId));
146
+ const graph = await ChangesetGraph.build(model, changeset), changes = changeset.changes, { keys, notes } = selectUnstage(args, graph, changeset), removed = changes.filter((c) => keys.has(c.elementId));
122
147
  if (removed.length === 0)
123
148
  return jsonResult({
124
149
  status: "not-staged",
125
150
  changeset: changeset.name,
126
- message: byElement ? `No staged change to ${args.elementId} in '${changeset.name}'` : `No staged relationship change ${describeEnds(args)}${args.predicate ? ` (${args.predicate})` : ""} in '${changeset.name}'`,
151
+ message: notStagedMessage(args, changeset.name),
127
152
  suggestions: ['Use "changeset_show" to list the staged changes'],
128
153
  ...notes.length > 0 ? { notes } : {}
129
154
  });
@@ -138,9 +163,60 @@ async function changesetUnstageHandler(args) {
138
163
  });
139
164
  });
140
165
  }
166
+ async function unstageBatch(specs, changesetId, rootPath) {
167
+ const model = await loadModel(rootPath), manager = new StagingAreaManager(model.rootPath, model), changeset = await resolveChangeset(manager, changesetId);
168
+ requireStaged(changeset, "unstage changes");
169
+ const graph = await ChangesetGraph.build(model, changeset), changes = changeset.changes, claimed = /* @__PURE__ */ new Set(), results = [];
170
+ for (const [index, spec] of specs.entries()) {
171
+ const asSent = {
172
+ index,
173
+ status: "unstaged",
174
+ ...spec.elementId !== void 0 ? { elementId: spec.elementId } : {},
175
+ ...spec.source !== void 0 ? { source: spec.source } : {},
176
+ ...spec.target !== void 0 ? { target: spec.target } : {},
177
+ ...spec.predicate !== void 0 ? { predicate: spec.predicate } : {}
178
+ };
179
+ let selection;
180
+ try {
181
+ checkUnstageSpec(spec), selection = selectUnstage(spec, graph, changeset);
182
+ } catch (error) {
183
+ results.push({
184
+ ...asSent,
185
+ status: "invalid",
186
+ reason: getErrorMessage(error),
187
+ ...error instanceof CLIError && error.suggestions?.length ? { suggestions: error.suggestions } : {}
188
+ });
189
+ continue;
190
+ }
191
+ const notes = [...selection.notes], fresh = [...selection.keys].filter((key) => !claimed.has(key)), earlier = selection.keys.size - fresh.length;
192
+ for (const key of fresh)
193
+ claimed.add(key);
194
+ const removed = changes.filter((c) => fresh.includes(c.elementId)), status = removed.length > 0 ? "unstaged" : earlier > 0 ? "already-unstaged" : "not-staged";
195
+ earlier > 0 && notes.push(`${earlier} of its change(s) were already unstaged by an earlier item`), results.push({
196
+ ...asSent,
197
+ status,
198
+ ...removed.length > 0 ? { unstaged: removed.map(summarize) } : {},
199
+ ...status === "not-staged" ? { message: notStagedMessage(spec, changeset.name) } : {},
200
+ ...notes.length > 0 ? { notes } : {}
201
+ });
202
+ }
203
+ claimed.size > 0 && (await manager.unstageMany(changeset.id, [...claimed]), await reloadModel(rootPath));
204
+ const counts = {};
205
+ for (const result of results)
206
+ counts[result.status] = (counts[result.status] ?? 0) + 1;
207
+ const removedCount = changes.filter((c) => claimed.has(c.elementId)).length;
208
+ return jsonResult({
209
+ changeset: changeset.name,
210
+ counts,
211
+ results,
212
+ unstagedChanges: removedCount,
213
+ remainingChanges: changes.length - removedCount,
214
+ ...results.some((r) => r.status === "not-staged") ? { suggestions: ['Use "changeset_show" to list the staged changes'] } : {}
215
+ });
216
+ }
141
217
  const changesetUnstageTool = {
142
218
  name: "changeset_unstage",
143
- description: "Undo part of a staged changeset: drop the staged changes to one element (elementId), or the staged relationship adds/deletes between source and target, or every one from source alone or to target alone (optionally one predicate). The base model is not touched. Defaults to the active changeset. Use changeset_show to see what is staged.",
219
+ description: "Undo part of a staged changeset: drop the staged changes to one element (elementId), or the staged relationship adds/deletes between source and target, or every one from source alone or to target alone (optionally one predicate). To drop several, send changes: [...] (one write, a result per item). The base model is not touched. Defaults to the active changeset. Use changeset_show to see what is staged.",
144
220
  inputSchema: unstageInputSchema,
145
221
  handler: changesetUnstageHandler
146
222
  }, discardInputSchema = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/mcp/tools/changeset-editing.ts"],
4
- "sourcesContent": ["/**\n * changeset_update, changeset_unstage, changeset_discard \u2014 revise a staged changeset while\n * planning a change. Mirror `dr changeset unstage|discard` and add the rename / re-describe step\n * the CLI has no command for, all through StagingAreaManager.\n *\n * Every mutation reloads the cached model afterwards, like changeset-lifecycle.ts: the model\n * projects the active changeset's elements at load time, and the projection cache is keyed by\n * what is staged.\n *\n * Commit and apply stay off the MCP surface (landing a change is the human's step). Discard is\n * exposed because an agent must be able to abandon its own plan, but it clears the staged changes\n * for good, so it needs `confirm: true`. See docs/CHANGE_MODELING.md.\n */\n\nimport { z } from \"zod\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { ChangesetGraph } from \"../../core/changeset-graph.js\";\nimport type { Changeset, StagedChange } from \"../../core/changeset.js\";\nimport { StagingAreaManager } from \"../../core/staging-area.js\";\nimport { stagedRelationshipChangesAtEnds, stagedRelationshipTriple } from \"../../core/relationship-staging.js\";\nimport { CLIError, ErrorCategory, getErrorMessage } from \"../../utils/errors.js\";\nimport { jsonResult, loadModel, reloadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\nimport { resolveChangeset } from \"./changeset-shared.js\";\n\nconst changesetIdSchema = z\n .string()\n .optional()\n .describe(\"Changeset ID or name. Defaults to the active changeset.\");\n\nfunction requireStaged(changeset: Changeset, action: string): void {\n if (changeset.status !== \"staged\") {\n throw new CLIError(`Changeset '${changeset.name}' is ${changeset.status}; cannot ${action}`, ErrorCategory.USER, [\n 'Use \"changeset_list\" to find a staged changeset, or \"changeset_create\" to start a new one',\n ]);\n }\n}\n\n/** Wraps a StagingAreaManager error (plain Error) as a user error the agent can act on. */\nasync function asUserError<T>(fn: () => Promise<T>, suggestions: string[]): Promise<T> {\n try {\n return await fn();\n } catch (error) {\n if (error instanceof CLIError) throw error;\n throw new CLIError(getErrorMessage(error), ErrorCategory.USER, suggestions);\n }\n}\n\nfunction summarize(change: StagedChange): Record<string, unknown> {\n const triple = change.type.startsWith(\"relationship-\") ? stagedRelationshipTriple(change) : null;\n return {\n type: change.type,\n elementId: change.elementId,\n layer: change.layerName,\n ...(triple ? { relationship: triple } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// changeset_update\n// ---------------------------------------------------------------------------\n\nconst updateInputSchema = {\n changesetId: changesetIdSchema,\n name: z.string().min(1).optional().describe(\"New name. Must not be another changeset's name.\"),\n description: z\n .string()\n .optional()\n .describe(\"New description: the change's intent and what done looks like. An empty string clears it.\"),\n rootPath: rootPathSchema,\n};\n\nexport type ChangesetUpdateArgs = z.infer<z.ZodObject<typeof updateInputSchema>>;\n\nexport async function changesetUpdateHandler(args: ChangesetUpdateArgs): Promise<CallToolResult> {\n return runTool(async () => {\n if (args.name === undefined && args.description === undefined) {\n throw new CLIError(\"Nothing to update\", ErrorCategory.USER, ['Pass \"name\" and/or \"description\"']);\n }\n const model = await loadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const changeset = await resolveChangeset(manager, args.changesetId);\n\n const updated = await asUserError(\n () => manager.update(changeset.id!, { name: args.name, description: args.description }),\n ['Use \"changeset_list\" to see the names already taken']\n );\n await reloadModel(args.rootPath);\n\n return jsonResult({\n id: updated.id,\n name: updated.name,\n ...(changeset.name !== updated.name ? { previousName: changeset.name } : {}),\n description: updated.description ?? null,\n status: updated.status,\n changes: updated.changes.length,\n });\n });\n}\n\nexport const changesetUpdateTool: McpToolDefinition<typeof updateInputSchema> = {\n name: \"changeset_update\",\n description:\n \"Rename a changeset or change its description (the change's intent). Staged changes are untouched. \" +\n \"Defaults to the active changeset.\",\n inputSchema: updateInputSchema,\n handler: changesetUpdateHandler,\n};\n\n// ---------------------------------------------------------------------------\n// changeset_unstage\n// ---------------------------------------------------------------------------\n\nconst unstageInputSchema = {\n changesetId: changesetIdSchema,\n elementId: z\n .string()\n .optional()\n .describe(\"Drop every staged change to this element (its add, updates or delete). ID or path.\"),\n source: z\n .string()\n .optional()\n .describe(\n \"Drop staged relationship changes (adds and deletes) from this element: to target when given, otherwise to \" +\n \"any element. The element's own changes are not touched.\"\n ),\n target: z\n .string()\n .optional()\n .describe(\n \"Drop staged relationship changes (adds and deletes) to this element: from source when given, otherwise from \" +\n \"any element. The element's own changes are not touched.\"\n ),\n predicate: z.string().optional().describe(\"Only relationship changes with this predicate. Omit for any.\"),\n keepRelationships: z\n .boolean()\n .optional()\n .describe(\n \"With elementId: keep the staged relationship changes that depend on the element's change. By default, \" +\n \"unstaging an element's add also drops staged relationships to it (they would dangle), and unstaging its \" +\n \"delete also drops the relationship removals its cascade staged (removals staged before the delete are kept).\"\n ),\n rootPath: rootPathSchema,\n};\n\nexport type ChangesetUnstageArgs = z.infer<z.ZodObject<typeof unstageInputSchema>>;\n\nfunction describeEnds(args: { source?: string; target?: string }): string {\n if (args.source && args.target) return `from ${args.source} to ${args.target}`;\n return args.source ? `from ${args.source}` : `to ${args.target}`;\n}\n\nexport async function changesetUnstageHandler(args: ChangesetUnstageArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const byElement = args.elementId !== undefined;\n const byRelationship = args.source !== undefined || args.target !== undefined;\n if (byElement === byRelationship) {\n throw new CLIError(\"Name either an element or a relationship to unstage\", ErrorCategory.USER, [\n 'Pass \"elementId\" to drop an element\\'s staged changes',\n 'Or pass \"source\" and/or \"target\" (and optionally \"predicate\") to drop staged relationship changes',\n ]);\n }\n if (byRelationship && !args.source && !args.target) {\n throw new CLIError('\"source\" or \"target\" must not be empty', ErrorCategory.USER);\n }\n\n const model = await loadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const changeset = await resolveChangeset(manager, args.changesetId);\n requireStaged(changeset, \"unstage changes\");\n const graph = await ChangesetGraph.build(model, changeset);\n const changes = changeset.changes as StagedChange[];\n\n const keys = new Set<string>();\n const notes: string[] = [];\n\n if (byElement) {\n const element = graph.getElement(args.elementId!);\n const ids = new Set([args.elementId!, ...(element ? [element.id, element.uuid ?? element.id] : [])]);\n const own = changes.filter((c) => !c.type.startsWith(\"relationship-\") && ids.has(c.elementId));\n for (const change of own) keys.add(change.elementId);\n\n // Relationship changes that only make sense with the element's change.\n const dependentType = own.some((c) => c.type === \"add\")\n ? \"relationship-add\"\n : own.some((c) => c.type === \"delete\")\n ? \"relationship-delete\"\n : null;\n const touching = dependentType\n ? changes.filter((c) => {\n if (c.type !== dependentType) return false;\n const triple = stagedRelationshipTriple(c);\n return (\n !!triple &&\n (ids.has(triple.source) ||\n ids.has(triple.target) ||\n (element && (graph.resolve(triple.source) === element.id || graph.resolve(triple.target) === element.id)))\n );\n })\n : [];\n // A delete cascades into relationship removals staged right after it. Removals staged before the\n // delete were the agent's own decisions, not the cascade's, so they stay.\n const seq = (c: StagedChange): number => c.sequenceNumber ?? changes.indexOf(c);\n const deleteSeqs = own.filter((c) => c.type === \"delete\").map(seq);\n const firstDelete = deleteSeqs.length > 0 ? Math.min(...deleteSeqs) : -1;\n const dependent =\n dependentType === \"relationship-delete\" ? touching.filter((c) => seq(c) > firstDelete) : touching;\n const deliberate = touching.filter((c) => !dependent.includes(c));\n if (deliberate.length > 0) {\n notes.push(\n `${deliberate.length} relationship removal(s) on ${args.elementId} staged before its delete were kept ` +\n `(${deliberate.map((c) => c.elementId).join(\", \")}); unstage them by source and target if they should go too`\n );\n }\n if (dependent.length > 0) {\n if (args.keepRelationships) {\n notes.push(\n `${dependent.length} staged relationship change(s) on ${args.elementId} were kept (keepRelationships)` +\n (dependentType === \"relationship-add\" ? \"; they will dangle unless the element is staged again\" : \"\")\n );\n } else {\n for (const change of dependent) keys.add(change.elementId);\n }\n }\n } else {\n const matched = stagedRelationshipChangesAtEnds(graph, changeset, {\n source: args.source || undefined,\n target: args.target || undefined,\n predicate: args.predicate,\n });\n // A relationship removal staged after the delete of one of its ends is that delete's cascade:\n // without it the deleted element's relationship dangles and the changeset no longer commits.\n const seq = (c: StagedChange): number => c.sequenceNumber ?? changes.indexOf(c);\n const deleteSeq = new Map<string, number>();\n for (const change of changes) {\n if (change.type !== \"delete\") continue;\n const id = graph.resolve(change.elementId);\n deleteSeq.set(id, Math.min(deleteSeq.get(id) ?? Infinity, seq(change)));\n }\n const deletedEndOf = (change: StagedChange): string | undefined => {\n if (change.type !== \"relationship-delete\") return undefined;\n const triple = stagedRelationshipTriple(change);\n if (!triple) return undefined;\n return [graph.resolve(triple.source), graph.resolve(triple.target)].find(\n (end) => deleteSeq.has(end) && deleteSeq.get(end)! < seq(change)\n );\n };\n const cascaded = matched.filter((c) => deletedEndOf(c) !== undefined);\n const deletedEnds = [...new Set(cascaded.map((c) => deletedEndOf(c)!))].join(\", \");\n const cascadeList = cascaded.map((c) => c.elementId).join(\", \");\n // One end sweeps every relationship there, so it leaves a delete's cascade alone. Naming both\n // ends asks for those relationships specifically, so they go, with a warning.\n const bothEnds = !!args.source && !!args.target;\n for (const change of matched) {\n if (!bothEnds && cascaded.includes(change)) continue;\n keys.add(change.elementId);\n }\n if (cascaded.length > 0 && bothEnds) {\n notes.push(\n `Unstaged ${cascaded.length} relationship removal(s) cascaded from the staged delete of ${deletedEnds} ` +\n `(${cascadeList}): those relationships now dangle, so the changeset will not commit until they are ` +\n `removed again or the delete is unstaged (\"changeset_unstage\" with elementId)`\n );\n } else if (cascaded.length > 0) {\n notes.push(\n `Kept ${cascaded.length} relationship removal(s) cascaded from the staged delete of ${deletedEnds} ` +\n `(${cascadeList}): dropping them would leave those relationships dangling. Unstage the delete ` +\n `(\"changeset_unstage\" with elementId) to restore them, or name both source and target to drop one anyway`\n );\n }\n }\n\n const removed = changes.filter((c) => keys.has(c.elementId));\n if (removed.length === 0) {\n return jsonResult({\n status: \"not-staged\",\n changeset: changeset.name,\n message: byElement\n ? `No staged change to ${args.elementId} in '${changeset.name}'`\n : `No staged relationship change ${describeEnds(args)}` +\n `${args.predicate ? ` (${args.predicate})` : \"\"} in '${changeset.name}'`,\n suggestions: ['Use \"changeset_show\" to list the staged changes'],\n ...(notes.length > 0 ? { notes } : {}),\n });\n }\n\n for (const key of keys) {\n await manager.unstage(changeset.id!, key);\n }\n await reloadModel(args.rootPath);\n\n return jsonResult({\n status: \"unstaged\",\n changeset: changeset.name,\n unstaged: removed.map(summarize),\n remainingChanges: changes.length - removed.length,\n ...(notes.length > 0 ? { notes } : {}),\n });\n });\n}\n\nexport const changesetUnstageTool: McpToolDefinition<typeof unstageInputSchema> = {\n name: \"changeset_unstage\",\n description:\n \"Undo part of a staged changeset: drop the staged changes to one element (elementId), or the staged \" +\n \"relationship adds/deletes between source and target, or every one from source alone or to target alone \" +\n \"(optionally one predicate). The base model is not touched. \" +\n \"Defaults to the active changeset. Use changeset_show to see what is staged.\",\n inputSchema: unstageInputSchema,\n handler: changesetUnstageHandler,\n};\n\n// ---------------------------------------------------------------------------\n// changeset_discard\n// ---------------------------------------------------------------------------\n\nconst discardInputSchema = {\n changesetId: z.string().describe(\"Changeset ID or name to discard. Required: discard is never implied.\"),\n confirm: z\n .boolean()\n .describe(\n \"Must be true. Discarding deletes the changeset's staged changes permanently (only its name, description and \" +\n \"a 'discarded' status remain), so confirm only when the plan is to be abandoned.\"\n ),\n rootPath: rootPathSchema,\n};\n\nexport type ChangesetDiscardArgs = z.infer<z.ZodObject<typeof discardInputSchema>>;\n\nexport async function changesetDiscardHandler(args: ChangesetDiscardArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const changeset = await resolveChangeset(manager, args.changesetId);\n requireStaged(changeset, \"discard it\");\n\n if (args.confirm !== true) {\n throw new CLIError(\n `Discarding '${changeset.name}' deletes its ${changeset.changes.length} staged change(s) permanently`,\n ErrorCategory.USER,\n [\n 'Pass \"confirm\": true to discard it',\n 'Use \"changeset_unstage\" to drop individual changes instead',\n 'Use \"changeset_deactivate\" to stop staging into it and keep its changes',\n ]\n );\n }\n\n const wasActive = (await manager.getActiveId()) === changeset.id;\n const discardedChanges = changeset.changes.length;\n await manager.discard(changeset.id!);\n if (wasActive) {\n await manager.clearActive();\n }\n await reloadModel(args.rootPath);\n\n return jsonResult({\n id: changeset.id,\n name: changeset.name,\n status: \"discarded\",\n discardedChanges,\n deactivated: wasActive,\n });\n });\n}\n\nexport const changesetDiscardTool: McpToolDefinition<typeof discardInputSchema> = {\n name: \"changeset_discard\",\n description:\n \"Abandon a staged changeset: PERMANENTLY deletes its staged changes and marks it discarded (deactivating it if \" +\n \"active). Not recoverable, so it requires confirm: true. The base model is not touched. To undo part of a \" +\n \"plan use changeset_unstage; to pause it use changeset_deactivate.\",\n inputSchema: discardInputSchema,\n handler: changesetDiscardHandler,\n};\n"],
5
- "mappings": "AAcA,SAAS,SAAS;AAElB,SAAS,sBAAsB;AAE/B,SAAS,0BAA0B;AACnC,SAAS,iCAAiC,gCAAgC;AAC1E,SAAS,UAAU,eAAe,uBAAuB;AACzD,SAAS,YAAY,WAAW,aAAa,gBAAgB,eAAuC;AACpG,SAAS,wBAAwB;AAEjC,MAAM,oBAAoB,EACvB,OAAM,EACN,SAAQ,EACR,SAAS,yDAAyD;AAErE,SAAS,cAAc,WAAsB,QAAc;AACzD,MAAI,UAAU,WAAW;AACvB,UAAM,IAAI,SAAS,cAAc,UAAU,IAAI,QAAQ,UAAU,MAAM,YAAY,MAAM,IAAI,cAAc,MAAM;MAC/G;KACD;AAEL;AAGA,eAAe,YAAe,IAAsB,aAAqB;AACvE,MAAI;AACF,WAAO,MAAM,GAAE;EACjB,SAAS,OAAO;AACd,UAAI,iBAAiB,WAAgB,QAC/B,IAAI,SAAS,gBAAgB,KAAK,GAAG,cAAc,MAAM,WAAW;EAC5E;AACF;AAEA,SAAS,UAAU,QAAoB;AACrC,QAAM,SAAS,OAAO,KAAK,WAAW,eAAe,IAAI,yBAAyB,MAAM,IAAI;AAC5F,SAAO;IACL,MAAM,OAAO;IACb,WAAW,OAAO;IAClB,OAAO,OAAO;IACd,GAAI,SAAS,EAAE,cAAc,OAAM,IAAK,CAAA;;AAE5C;AAMA,MAAM,oBAAoB;EACxB,aAAa;EACb,MAAM,EAAE,OAAM,EAAG,IAAI,CAAC,EAAE,SAAQ,EAAG,SAAS,iDAAiD;EAC7F,aAAa,EACV,OAAM,EACN,SAAQ,EACR,SAAS,2FAA2F;EACvG,UAAU;;AAKZ,eAAsB,uBAAuB,MAAyB;AACpE,SAAO,QAAQ,YAAW;AACxB,QAAI,KAAK,SAAS,UAAa,KAAK,gBAAgB;AAClD,YAAM,IAAI,SAAS,qBAAqB,cAAc,MAAM,CAAC,kCAAkC,CAAC;AAElG,UAAM,QAAQ,MAAM,UAAU,KAAK,QAAQ,GACrC,UAAU,IAAI,mBAAmB,MAAM,UAAU,KAAK,GACtD,YAAY,MAAM,iBAAiB,SAAS,KAAK,WAAW,GAE5D,UAAU,MAAM,YACpB,MAAM,QAAQ,OAAO,UAAU,IAAK,EAAE,MAAM,KAAK,MAAM,aAAa,KAAK,YAAW,CAAE,GACtF,CAAC,qDAAqD,CAAC;AAEzD,iBAAM,YAAY,KAAK,QAAQ,GAExB,WAAW;MAChB,IAAI,QAAQ;MACZ,MAAM,QAAQ;MACd,GAAI,UAAU,SAAS,QAAQ,OAAO,EAAE,cAAc,UAAU,KAAI,IAAK,CAAA;MACzE,aAAa,QAAQ,eAAe;MACpC,QAAQ,QAAQ;MAChB,SAAS,QAAQ,QAAQ;KAC1B;EACH,CAAC;AACH;AAEO,MAAM,sBAAmE;EAC9E,MAAM;EACN,aACE;EAEF,aAAa;EACb,SAAS;GAOL,qBAAqB;EACzB,aAAa;EACb,WAAW,EACR,OAAM,EACN,SAAQ,EACR,SAAS,oFAAoF;EAChG,QAAQ,EACL,OAAM,EACN,SAAQ,EACR,SACC,mKAC2D;EAE/D,QAAQ,EACL,OAAM,EACN,SAAQ,EACR,SACC,qKAC2D;EAE/D,WAAW,EAAE,OAAM,EAAG,SAAQ,EAAG,SAAS,8DAA8D;EACxG,mBAAmB,EAChB,QAAO,EACP,SAAQ,EACR,SACC,4TAEgH;EAEpH,UAAU;;AAKZ,SAAS,aAAa,MAA0C;AAC9D,SAAI,KAAK,UAAU,KAAK,SAAe,QAAQ,KAAK,MAAM,OAAO,KAAK,MAAM,KACrE,KAAK,SAAS,QAAQ,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM;AAChE;AAEA,eAAsB,wBAAwB,MAA0B;AACtE,SAAO,QAAQ,YAAW;AACxB,UAAM,YAAY,KAAK,cAAc,QAC/B,iBAAiB,KAAK,WAAW,UAAa,KAAK,WAAW;AACpE,QAAI,cAAc;AAChB,YAAM,IAAI,SAAS,uDAAuD,cAAc,MAAM;QAC5F;QACA;OACD;AAEH,QAAI,kBAAkB,CAAC,KAAK,UAAU,CAAC,KAAK;AAC1C,YAAM,IAAI,SAAS,0CAA0C,cAAc,IAAI;AAGjF,UAAM,QAAQ,MAAM,UAAU,KAAK,QAAQ,GACrC,UAAU,IAAI,mBAAmB,MAAM,UAAU,KAAK,GACtD,YAAY,MAAM,iBAAiB,SAAS,KAAK,WAAW;AAClE,kBAAc,WAAW,iBAAiB;AAC1C,UAAM,QAAQ,MAAM,eAAe,MAAM,OAAO,SAAS,GACnD,UAAU,UAAU,SAEpB,OAAO,oBAAI,IAAG,GACd,QAAkB,CAAA;AAExB,QAAI,WAAW;AACb,YAAM,UAAU,MAAM,WAAW,KAAK,SAAU,GAC1C,MAAM,oBAAI,IAAI,CAAC,KAAK,WAAY,GAAI,UAAU,CAAC,QAAQ,IAAI,QAAQ,QAAQ,QAAQ,EAAE,IAAI,CAAA,CAAG,CAAC,GAC7F,MAAM,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,WAAW,eAAe,KAAK,IAAI,IAAI,EAAE,SAAS,CAAC;AAC7F,iBAAW,UAAU;AAAK,aAAK,IAAI,OAAO,SAAS;AAGnD,YAAM,gBAAgB,IAAI,KAAK,CAAC,MAAM,EAAE,SAAS,KAAK,IAClD,qBACA,IAAI,KAAK,CAAC,MAAM,EAAE,SAAS,QAAQ,IACjC,wBACA,MACA,WAAW,gBACb,QAAQ,OAAO,CAAC,MAAK;AACnB,YAAI,EAAE,SAAS;AAAe,iBAAO;AACrC,cAAM,SAAS,yBAAyB,CAAC;AACzC,eACE,CAAC,CAAC,WACD,IAAI,IAAI,OAAO,MAAM,KACpB,IAAI,IAAI,OAAO,MAAM,KACpB,YAAY,MAAM,QAAQ,OAAO,MAAM,MAAM,QAAQ,MAAM,MAAM,QAAQ,OAAO,MAAM,MAAM,QAAQ;MAE3G,CAAC,IACD,CAAA,GAGE,MAAM,CAAC,MAA4B,EAAE,kBAAkB,QAAQ,QAAQ,CAAC,GACxE,aAAa,IAAI,OAAO,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,IAAI,GAAG,GAC3D,cAAc,WAAW,SAAS,IAAI,KAAK,IAAI,GAAG,UAAU,IAAI,IAChE,YACJ,kBAAkB,wBAAwB,SAAS,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,WAAW,IAAI,UACrF,aAAa,SAAS,OAAO,CAAC,MAAM,CAAC,UAAU,SAAS,CAAC,CAAC;AAOhE,UANI,WAAW,SAAS,KACtB,MAAM,KACJ,GAAG,WAAW,MAAM,+BAA+B,KAAK,SAAS,wCAC3D,WAAW,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,IAAI,CAAC,4DAA4D,GAG/G,UAAU,SAAS;AACrB,YAAI,KAAK;AACP,gBAAM,KACJ,GAAG,UAAU,MAAM,qCAAqC,KAAK,SAAS,oCACnE,kBAAkB,qBAAqB,0DAA0D,GAAG;;AAGzG,qBAAW,UAAU;AAAW,iBAAK,IAAI,OAAO,SAAS;IAG/D,OAAO;AACL,YAAM,UAAU,gCAAgC,OAAO,WAAW;QAChE,QAAQ,KAAK,UAAU;QACvB,QAAQ,KAAK,UAAU;QACvB,WAAW,KAAK;OACjB,GAGK,MAAM,CAAC,MAA4B,EAAE,kBAAkB,QAAQ,QAAQ,CAAC,GACxE,YAAY,oBAAI,IAAG;AACzB,iBAAW,UAAU,SAAS;AAC5B,YAAI,OAAO,SAAS;AAAU;AAC9B,cAAM,KAAK,MAAM,QAAQ,OAAO,SAAS;AACzC,kBAAU,IAAI,IAAI,KAAK,IAAI,UAAU,IAAI,EAAE,KAAK,OAAU,IAAI,MAAM,CAAC,CAAC;MACxE;AACA,YAAM,eAAe,CAAC,WAA4C;AAChE,YAAI,OAAO,SAAS;AAAuB;AAC3C,cAAM,SAAS,yBAAyB,MAAM;AAC9C,YAAK;AACL,iBAAO,CAAC,MAAM,QAAQ,OAAO,MAAM,GAAG,MAAM,QAAQ,OAAO,MAAM,CAAC,EAAE,KAClE,CAAC,QAAQ,UAAU,IAAI,GAAG,KAAK,UAAU,IAAI,GAAG,IAAK,IAAI,MAAM,CAAC;MAEpE,GACM,WAAW,QAAQ,OAAO,CAAC,MAAM,aAAa,CAAC,MAAM,MAAS,GAC9D,cAAc,CAAC,GAAG,IAAI,IAAI,SAAS,IAAI,CAAC,MAAM,aAAa,CAAC,CAAE,CAAC,CAAC,EAAE,KAAK,IAAI,GAC3E,cAAc,SAAS,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,IAAI,GAGxD,WAAW,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,KAAK;AACzC,iBAAW,UAAU;AACnB,QAAI,CAAC,YAAY,SAAS,SAAS,MAAM,KACzC,KAAK,IAAI,OAAO,SAAS;AAE3B,MAAI,SAAS,SAAS,KAAK,WACzB,MAAM,KACJ,YAAY,SAAS,MAAM,+DAA+D,WAAW,KAC/F,WAAW,iKAC+D,IAEzE,SAAS,SAAS,KAC3B,MAAM,KACJ,QAAQ,SAAS,MAAM,+DAA+D,WAAW,KAC3F,WAAW,uLAC0F;IAGjH;AAEA,UAAM,UAAU,QAAQ,OAAO,CAAC,MAAM,KAAK,IAAI,EAAE,SAAS,CAAC;AAC3D,QAAI,QAAQ,WAAW;AACrB,aAAO,WAAW;QAChB,QAAQ;QACR,WAAW,UAAU;QACrB,SAAS,YACL,uBAAuB,KAAK,SAAS,QAAQ,UAAU,IAAI,MAC3D,iCAAiC,aAAa,IAAI,CAAC,GAChD,KAAK,YAAY,KAAK,KAAK,SAAS,MAAM,EAAE,QAAQ,UAAU,IAAI;QACzE,aAAa,CAAC,iDAAiD;QAC/D,GAAI,MAAM,SAAS,IAAI,EAAE,MAAK,IAAK,CAAA;OACpC;AAGH,eAAW,OAAO;AAChB,YAAM,QAAQ,QAAQ,UAAU,IAAK,GAAG;AAE1C,iBAAM,YAAY,KAAK,QAAQ,GAExB,WAAW;MAChB,QAAQ;MACR,WAAW,UAAU;MACrB,UAAU,QAAQ,IAAI,SAAS;MAC/B,kBAAkB,QAAQ,SAAS,QAAQ;MAC3C,GAAI,MAAM,SAAS,IAAI,EAAE,MAAK,IAAK,CAAA;KACpC;EACH,CAAC;AACH;AAEO,MAAM,uBAAqE;EAChF,MAAM;EACN,aACE;EAIF,aAAa;EACb,SAAS;GAOL,qBAAqB;EACzB,aAAa,EAAE,OAAM,EAAG,SAAS,sEAAsE;EACvG,SAAS,EACN,QAAO,EACP,SACC,6LACmF;EAEvF,UAAU;;AAKZ,eAAsB,wBAAwB,MAA0B;AACtE,SAAO,QAAQ,YAAW;AACxB,UAAM,QAAQ,MAAM,UAAU,KAAK,QAAQ,GACrC,UAAU,IAAI,mBAAmB,MAAM,UAAU,KAAK,GACtD,YAAY,MAAM,iBAAiB,SAAS,KAAK,WAAW;AAGlE,QAFA,cAAc,WAAW,YAAY,GAEjC,KAAK,YAAY;AACnB,YAAM,IAAI,SACR,eAAe,UAAU,IAAI,iBAAiB,UAAU,QAAQ,MAAM,iCACtE,cAAc,MACd;QACE;QACA;QACA;OACD;AAIL,UAAM,YAAa,MAAM,QAAQ,YAAW,MAAQ,UAAU,IACxD,mBAAmB,UAAU,QAAQ;AAC3C,iBAAM,QAAQ,QAAQ,UAAU,EAAG,GAC/B,aACF,MAAM,QAAQ,YAAW,GAE3B,MAAM,YAAY,KAAK,QAAQ,GAExB,WAAW;MAChB,IAAI,UAAU;MACd,MAAM,UAAU;MAChB,QAAQ;MACR;MACA,aAAa;KACd;EACH,CAAC;AACH;AAEO,MAAM,uBAAqE;EAChF,MAAM;EACN,aACE;EAGF,aAAa;EACb,SAAS;;",
6
- "names": []
4
+ "sourcesContent": ["/**\n * changeset_update, changeset_unstage, changeset_discard \u2014 revise a staged changeset while\n * planning a change. Mirror `dr changeset unstage|discard` and add the rename / re-describe step\n * the CLI has no command for, all through StagingAreaManager.\n *\n * Every mutation reloads the cached model afterwards, like changeset-lifecycle.ts: the model\n * projects the active changeset's elements at load time, and the projection cache is keyed by\n * what is staged.\n *\n * Landing a change is the human's step: changeset_commit (changeset-commit.ts) asks a person to\n * confirm it. Discard is exposed because an agent must be able to abandon its own plan, but it\n * clears the staged changes for good, so it needs `confirm: true`. See docs/CHANGE_MODELING.md.\n */\n\nimport { z } from \"zod\";\nimport { strictItem } from \"../strict-schema.js\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { ChangesetGraph } from \"../../core/changeset-graph.js\";\nimport type { Changeset, StagedChange } from \"../../core/changeset.js\";\nimport { StagingAreaManager } from \"../../core/staging-area.js\";\nimport { stagedRelationshipChangesAtEnds, stagedRelationshipTriple } from \"../../core/relationship-staging.js\";\nimport { CLIError, ErrorCategory, getErrorMessage } from \"../../utils/errors.js\";\nimport { jsonResult, loadModel, reloadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\nimport { resolveChangeset } from \"./changeset-shared.js\";\n\nconst changesetIdSchema = z\n .string()\n .optional()\n .describe(\"Changeset ID or name. Defaults to the active changeset.\");\n\nfunction requireStaged(changeset: Changeset, action: string): void {\n if (changeset.status !== \"staged\") {\n throw new CLIError(`Changeset '${changeset.name}' is ${changeset.status}; cannot ${action}`, ErrorCategory.USER, [\n 'Use \"changeset_list\" to find a staged changeset, or \"changeset_create\" to start a new one',\n ]);\n }\n}\n\n/** Wraps a StagingAreaManager error (plain Error) as a user error the agent can act on. */\nasync function asUserError<T>(fn: () => Promise<T>, suggestions: string[]): Promise<T> {\n try {\n return await fn();\n } catch (error) {\n if (error instanceof CLIError) throw error;\n throw new CLIError(getErrorMessage(error), ErrorCategory.USER, suggestions);\n }\n}\n\nfunction summarize(change: StagedChange): Record<string, unknown> {\n const triple = change.type.startsWith(\"relationship-\") ? stagedRelationshipTriple(change) : null;\n return {\n type: change.type,\n elementId: change.elementId,\n layer: change.layerName,\n ...(triple ? { relationship: triple } : {}),\n };\n}\n\n// ---------------------------------------------------------------------------\n// changeset_update\n// ---------------------------------------------------------------------------\n\nconst updateInputSchema = {\n changesetId: changesetIdSchema,\n name: z.string().min(1).optional().describe(\"New name. Must not be another changeset's name.\"),\n description: z\n .string()\n .optional()\n .describe(\"New description: the change's intent and what done looks like. An empty string clears it.\"),\n rootPath: rootPathSchema,\n};\n\nexport type ChangesetUpdateArgs = z.infer<z.ZodObject<typeof updateInputSchema>>;\n\nexport async function changesetUpdateHandler(args: ChangesetUpdateArgs): Promise<CallToolResult> {\n return runTool(async () => {\n if (args.name === undefined && args.description === undefined) {\n throw new CLIError(\"Nothing to update\", ErrorCategory.USER, ['Pass \"name\" and/or \"description\"']);\n }\n const model = await loadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const changeset = await resolveChangeset(manager, args.changesetId);\n\n const updated = await asUserError(\n () => manager.update(changeset.id!, { name: args.name, description: args.description }),\n ['Use \"changeset_list\" to see the names already taken']\n );\n await reloadModel(args.rootPath);\n\n return jsonResult({\n id: updated.id,\n name: updated.name,\n ...(changeset.name !== updated.name ? { previousName: changeset.name } : {}),\n description: updated.description ?? null,\n status: updated.status,\n changes: updated.changes.length,\n });\n });\n}\n\nexport const changesetUpdateTool: McpToolDefinition<typeof updateInputSchema> = {\n name: \"changeset_update\",\n description:\n \"Rename a changeset or change its description (the change's intent). Staged changes are untouched. \" +\n \"Defaults to the active changeset.\",\n inputSchema: updateInputSchema,\n handler: changesetUpdateHandler,\n};\n\n// ---------------------------------------------------------------------------\n// changeset_unstage\n// ---------------------------------------------------------------------------\n\nconst elementIdField = z\n .string()\n .optional()\n .describe(\"Drop every staged change to this element (its add, updates or delete). ID or path.\");\nconst sourceField = z\n .string()\n .optional()\n .describe(\n \"Drop staged relationship changes (adds and deletes) from this element: to target when given, otherwise to \" +\n \"any element. The element's own changes are not touched.\"\n );\nconst targetField = z\n .string()\n .optional()\n .describe(\n \"Drop staged relationship changes (adds and deletes) to this element: from source when given, otherwise from \" +\n \"any element. The element's own changes are not touched.\"\n );\nconst predicateField = z.string().optional().describe(\"Only relationship changes with this predicate. Omit for any.\");\nconst keepRelationshipsField = z\n .boolean()\n .optional()\n .describe(\n \"With elementId: keep the staged relationship changes that depend on the element's change. By default, \" +\n \"unstaging an element's add also drops staged relationships to it (they would dangle), and unstaging its \" +\n \"delete also drops the relationship removals its cascade staged (removals staged before the delete are kept).\"\n );\n\nconst unstageItemSchema = strictItem(\"A changes[] item\", {\n elementId: elementIdField,\n source: sourceField,\n target: targetField,\n predicate: predicateField,\n keepRelationships: keepRelationshipsField,\n});\n\ntype UnstageSpec = z.infer<typeof unstageItemSchema>;\n\nconst unstageInputSchema = {\n changesetId: changesetIdSchema,\n elementId: elementIdField,\n source: sourceField,\n target: targetField,\n predicate: predicateField,\n keepRelationships: keepRelationshipsField,\n changes: z\n .array(unstageItemSchema)\n .min(1)\n .optional()\n .describe(\n \"Several unstages at once, each {elementId} or {source, predicate?, target} or {source} or {target}, with the \" +\n \"same meaning as the single form. All are removed in one write, with a result per item: unstaged, \" +\n \"already-unstaged (an earlier item took its changes), not-staged, or invalid with the reason. Cannot be \" +\n \"combined with the single-form fields.\"\n ),\n rootPath: rootPathSchema,\n};\n\nexport type ChangesetUnstageArgs = z.infer<z.ZodObject<typeof unstageInputSchema>>;\n\nfunction describeEnds(args: { source?: string; target?: string }): string {\n if (args.source && args.target) return `from ${args.source} to ${args.target}`;\n return args.source ? `from ${args.source}` : `to ${args.target}`;\n}\n\n/** Refuses a spec that names neither or both of an element and a relationship. */\nfunction checkUnstageSpec(spec: UnstageSpec): void {\n const byElement = spec.elementId !== undefined;\n const byRelationship = spec.source !== undefined || spec.target !== undefined;\n if (byElement === byRelationship) {\n throw new CLIError(\"Name either an element or a relationship to unstage\", ErrorCategory.USER, [\n 'Pass \"elementId\" to drop an element\\'s staged changes',\n 'Or pass \"source\" and/or \"target\" (and optionally \"predicate\") to drop staged relationship changes',\n ]);\n }\n if (byRelationship && !spec.source && !spec.target) {\n throw new CLIError('\"source\" or \"target\" must not be empty', ErrorCategory.USER);\n }\n}\n\n/** The change keys one unstage spec selects, and what it kept or should say. */\nfunction selectUnstage(\n spec: UnstageSpec,\n graph: ChangesetGraph,\n changeset: Changeset\n): { keys: Set<string>; notes: string[] } {\n const changes = changeset.changes as StagedChange[];\n const keys = new Set<string>();\n const notes: string[] = [];\n\n if (spec.elementId !== undefined) {\n const element = graph.getElement(spec.elementId);\n const ids = new Set([spec.elementId, ...(element ? [element.id, element.uuid ?? element.id] : [])]);\n const own = changes.filter((c) => !c.type.startsWith(\"relationship-\") && ids.has(c.elementId));\n for (const change of own) keys.add(change.elementId);\n\n // Relationship changes that only make sense with the element's change.\n const dependentType = own.some((c) => c.type === \"add\")\n ? \"relationship-add\"\n : own.some((c) => c.type === \"delete\")\n ? \"relationship-delete\"\n : null;\n const touching = dependentType\n ? changes.filter((c) => {\n if (c.type !== dependentType) return false;\n const triple = stagedRelationshipTriple(c);\n return (\n !!triple &&\n (ids.has(triple.source) ||\n ids.has(triple.target) ||\n (element && (graph.resolve(triple.source) === element.id || graph.resolve(triple.target) === element.id)))\n );\n })\n : [];\n // A delete cascades into relationship removals staged right after it. Removals staged before the\n // delete were the agent's own decisions, not the cascade's, so they stay.\n const seq = (c: StagedChange): number => c.sequenceNumber ?? changes.indexOf(c);\n const deleteSeqs = own.filter((c) => c.type === \"delete\").map(seq);\n const firstDelete = deleteSeqs.length > 0 ? Math.min(...deleteSeqs) : -1;\n const dependent =\n dependentType === \"relationship-delete\" ? touching.filter((c) => seq(c) > firstDelete) : touching;\n const deliberate = touching.filter((c) => !dependent.includes(c));\n if (deliberate.length > 0) {\n notes.push(\n `${deliberate.length} relationship removal(s) on ${spec.elementId} staged before its delete were kept ` +\n `(${deliberate.map((c) => c.elementId).join(\", \")}); unstage them by source and target if they should go too`\n );\n }\n if (dependent.length > 0) {\n if (spec.keepRelationships) {\n notes.push(\n `${dependent.length} staged relationship change(s) on ${spec.elementId} were kept (keepRelationships)` +\n (dependentType === \"relationship-add\" ? \"; they will dangle unless the element is staged again\" : \"\")\n );\n } else {\n for (const change of dependent) keys.add(change.elementId);\n }\n }\n return { keys, notes };\n }\n\n const matched = stagedRelationshipChangesAtEnds(graph, changeset, {\n source: spec.source || undefined,\n target: spec.target || undefined,\n predicate: spec.predicate,\n });\n // A relationship removal staged after the delete of one of its ends is that delete's cascade:\n // without it the deleted element's relationship dangles and the changeset no longer commits.\n const seq = (c: StagedChange): number => c.sequenceNumber ?? changes.indexOf(c);\n const deleteSeq = new Map<string, number>();\n for (const change of changes) {\n if (change.type !== \"delete\") continue;\n const id = graph.resolve(change.elementId);\n deleteSeq.set(id, Math.min(deleteSeq.get(id) ?? Infinity, seq(change)));\n }\n const deletedEndOf = (change: StagedChange): string | undefined => {\n if (change.type !== \"relationship-delete\") return undefined;\n const triple = stagedRelationshipTriple(change);\n if (!triple) return undefined;\n return [graph.resolve(triple.source), graph.resolve(triple.target)].find(\n (end) => deleteSeq.has(end) && deleteSeq.get(end)! < seq(change)\n );\n };\n const cascaded = matched.filter((c) => deletedEndOf(c) !== undefined);\n const deletedEnds = [...new Set(cascaded.map((c) => deletedEndOf(c)!))].join(\", \");\n const cascadeList = cascaded.map((c) => c.elementId).join(\", \");\n // One end sweeps every relationship there, so it leaves a delete's cascade alone. Naming both\n // ends asks for those relationships specifically, so they go, with a warning.\n const bothEnds = !!spec.source && !!spec.target;\n for (const change of matched) {\n if (!bothEnds && cascaded.includes(change)) continue;\n keys.add(change.elementId);\n }\n if (cascaded.length > 0 && bothEnds) {\n notes.push(\n `Unstaged ${cascaded.length} relationship removal(s) cascaded from the staged delete of ${deletedEnds} ` +\n `(${cascadeList}): those relationships now dangle, so the changeset will not commit until they are ` +\n `removed again or the delete is unstaged (\"changeset_unstage\" with elementId)`\n );\n } else if (cascaded.length > 0) {\n notes.push(\n `Kept ${cascaded.length} relationship removal(s) cascaded from the staged delete of ${deletedEnds} ` +\n `(${cascadeList}): dropping them would leave those relationships dangling. Unstage the delete ` +\n `(\"changeset_unstage\" with elementId) to restore them, or name both source and target to drop one anyway`\n );\n }\n return { keys, notes };\n}\n\nfunction notStagedMessage(spec: UnstageSpec, changesetName: string): string {\n return spec.elementId !== undefined\n ? `No staged change to ${spec.elementId} in '${changesetName}'`\n : `No staged relationship change ${describeEnds(spec)}` +\n `${spec.predicate ? ` (${spec.predicate})` : \"\"} in '${changesetName}'`;\n}\n\nconst SINGLE_FORM_FIELDS = [\"elementId\", \"source\", \"target\", \"predicate\", \"keepRelationships\"] as const;\n\nexport async function changesetUnstageHandler(args: ChangesetUnstageArgs): Promise<CallToolResult> {\n return runTool(async () => {\n if (args.changes) {\n const mixed = SINGLE_FORM_FIELDS.filter((field) => args[field] !== undefined);\n if (mixed.length > 0) {\n throw new CLIError(`\"changes\" cannot be combined with ${mixed.map((f) => `\"${f}\"`).join(\", \")}`, ErrorCategory.USER, [\n 'Put those fields on the items of the \"changes\" list',\n ]);\n }\n return unstageBatch(args.changes, args.changesetId, args.rootPath);\n }\n\n checkUnstageSpec(args);\n\n const model = await loadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const changeset = await resolveChangeset(manager, args.changesetId);\n requireStaged(changeset, \"unstage changes\");\n const graph = await ChangesetGraph.build(model, changeset);\n const changes = changeset.changes as StagedChange[];\n const { keys, notes } = selectUnstage(args, graph, changeset);\n\n const removed = changes.filter((c) => keys.has(c.elementId));\n if (removed.length === 0) {\n return jsonResult({\n status: \"not-staged\",\n changeset: changeset.name,\n message: notStagedMessage(args, changeset.name),\n suggestions: ['Use \"changeset_show\" to list the staged changes'],\n ...(notes.length > 0 ? { notes } : {}),\n });\n }\n\n // Each key is a revision of its own, as before the list form: briefs count unstages by revision.\n for (const key of keys) {\n await manager.unstage(changeset.id!, key);\n }\n await reloadModel(args.rootPath);\n\n return jsonResult({\n status: \"unstaged\",\n changeset: changeset.name,\n unstaged: removed.map(summarize),\n remainingChanges: changes.length - removed.length,\n ...(notes.length > 0 ? { notes } : {}),\n });\n });\n}\n\ninterface UnstageBatchResult {\n index: number;\n status: \"unstaged\" | \"already-unstaged\" | \"not-staged\" | \"invalid\";\n elementId?: string;\n source?: string;\n target?: string;\n predicate?: string;\n unstaged?: Record<string, unknown>[];\n message?: string;\n reason?: string;\n suggestions?: string[];\n notes?: string[];\n}\n\n/**\n * changeset_unstage's list form: select each item's changes against the changeset as it stands,\n * then remove them all in one write. A change two items select is reported under the first.\n */\nasync function unstageBatch(specs: UnstageSpec[], changesetId: string | undefined, rootPath: string | undefined) {\n const model = await loadModel(rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const changeset = await resolveChangeset(manager, changesetId);\n requireStaged(changeset, \"unstage changes\");\n const graph = await ChangesetGraph.build(model, changeset);\n const changes = changeset.changes as StagedChange[];\n\n const claimed = new Set<string>();\n const results: UnstageBatchResult[] = [];\n for (const [index, spec] of specs.entries()) {\n const asSent: UnstageBatchResult = {\n index,\n status: \"unstaged\",\n ...(spec.elementId !== undefined ? { elementId: spec.elementId } : {}),\n ...(spec.source !== undefined ? { source: spec.source } : {}),\n ...(spec.target !== undefined ? { target: spec.target } : {}),\n ...(spec.predicate !== undefined ? { predicate: spec.predicate } : {}),\n };\n let selection: { keys: Set<string>; notes: string[] };\n try {\n checkUnstageSpec(spec);\n selection = selectUnstage(spec, graph, changeset);\n } catch (error) {\n results.push({\n ...asSent,\n status: \"invalid\",\n reason: getErrorMessage(error),\n ...(error instanceof CLIError && error.suggestions?.length ? { suggestions: error.suggestions } : {}),\n });\n continue;\n }\n const notes = [...selection.notes];\n const fresh = [...selection.keys].filter((key) => !claimed.has(key));\n const earlier = selection.keys.size - fresh.length;\n for (const key of fresh) claimed.add(key);\n const removed = changes.filter((c) => fresh.includes(c.elementId));\n const status: UnstageBatchResult[\"status\"] =\n removed.length > 0 ? \"unstaged\" : earlier > 0 ? \"already-unstaged\" : \"not-staged\";\n if (earlier > 0) notes.push(`${earlier} of its change(s) were already unstaged by an earlier item`);\n results.push({\n ...asSent,\n status,\n ...(removed.length > 0 ? { unstaged: removed.map(summarize) } : {}),\n ...(status === \"not-staged\" ? { message: notStagedMessage(spec, changeset.name) } : {}),\n ...(notes.length > 0 ? { notes } : {}),\n });\n }\n\n if (claimed.size > 0) {\n await manager.unstageMany(changeset.id!, [...claimed]);\n await reloadModel(rootPath);\n }\n\n const counts: Record<string, number> = {};\n for (const result of results) counts[result.status] = (counts[result.status] ?? 0) + 1;\n const removedCount = changes.filter((c) => claimed.has(c.elementId)).length;\n return jsonResult({\n changeset: changeset.name,\n counts,\n results,\n unstagedChanges: removedCount,\n remainingChanges: changes.length - removedCount,\n ...(results.some((r) => r.status === \"not-staged\")\n ? { suggestions: ['Use \"changeset_show\" to list the staged changes'] }\n : {}),\n });\n}\n\nexport const changesetUnstageTool: McpToolDefinition<typeof unstageInputSchema> = {\n name: \"changeset_unstage\",\n description:\n \"Undo part of a staged changeset: drop the staged changes to one element (elementId), or the staged \" +\n \"relationship adds/deletes between source and target, or every one from source alone or to target alone \" +\n \"(optionally one predicate). To drop several, send changes: [...] (one write, a result per item). The base \" +\n \"model is not touched. Defaults to the active changeset. Use changeset_show to see what is staged.\",\n inputSchema: unstageInputSchema,\n handler: changesetUnstageHandler,\n};\n\n// ---------------------------------------------------------------------------\n// changeset_discard\n// ---------------------------------------------------------------------------\n\nconst discardInputSchema = {\n changesetId: z.string().describe(\"Changeset ID or name to discard. Required: discard is never implied.\"),\n confirm: z\n .boolean()\n .describe(\n \"Must be true. Discarding deletes the changeset's staged changes permanently (only its name, description and \" +\n \"a 'discarded' status remain), so confirm only when the plan is to be abandoned.\"\n ),\n rootPath: rootPathSchema,\n};\n\nexport type ChangesetDiscardArgs = z.infer<z.ZodObject<typeof discardInputSchema>>;\n\nexport async function changesetDiscardHandler(args: ChangesetDiscardArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const changeset = await resolveChangeset(manager, args.changesetId);\n requireStaged(changeset, \"discard it\");\n\n if (args.confirm !== true) {\n throw new CLIError(\n `Discarding '${changeset.name}' deletes its ${changeset.changes.length} staged change(s) permanently`,\n ErrorCategory.USER,\n [\n 'Pass \"confirm\": true to discard it',\n 'Use \"changeset_unstage\" to drop individual changes instead',\n 'Use \"changeset_deactivate\" to stop staging into it and keep its changes',\n ]\n );\n }\n\n const wasActive = (await manager.getActiveId()) === changeset.id;\n const discardedChanges = changeset.changes.length;\n await manager.discard(changeset.id!);\n if (wasActive) {\n await manager.clearActive();\n }\n await reloadModel(args.rootPath);\n\n return jsonResult({\n id: changeset.id,\n name: changeset.name,\n status: \"discarded\",\n discardedChanges,\n deactivated: wasActive,\n });\n });\n}\n\nexport const changesetDiscardTool: McpToolDefinition<typeof discardInputSchema> = {\n name: \"changeset_discard\",\n description:\n \"Abandon a staged changeset: PERMANENTLY deletes its staged changes and marks it discarded (deactivating it if \" +\n \"active). Not recoverable, so it requires confirm: true. The base model is not touched. To undo part of a \" +\n \"plan use changeset_unstage; to pause it use changeset_deactivate.\",\n inputSchema: discardInputSchema,\n handler: changesetDiscardHandler,\n};\n"],
5
+ "mappings": "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",
6
+ "names": ["seq"]
7
7
  }
@@ -30,7 +30,7 @@ async function changesetCreateHandler(args) {
30
30
  }
31
31
  const changesetCreateTool = {
32
32
  name: "changeset_create",
33
- description: "Start planning a change: create a changeset carrying the change's intent and (by default) activate it, so model_add / model_update / model_delete / relationship_add / relationship_delete stage into it instead of writing the model. The human commits it later; it is never committed over MCP.",
33
+ description: "Start planning a change: create a changeset carrying the change's intent and (by default) activate it, so model_add / model_update / model_delete / relationship_add / relationship_delete stage into it instead of writing the model. Before calling it, propose the changeset's name and intent (description) to the user and confirm both with them. Landing it is the user's step: changeset_commit commits only after they confirm it, or they commit it in the viewer.",
34
34
  inputSchema: createInputSchema,
35
35
  handler: changesetCreateHandler
36
36
  }, activateInputSchema = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/mcp/tools/changeset-lifecycle.ts"],
4
- "sourcesContent": ["/**\n * changeset_create, changeset_activate, changeset_deactivate \u2014 open and switch the changeset\n * that model and relationship mutations stage into. Mirrors `dr changeset create|activate|deactivate`.\n *\n * Every one of these reloads the cached model afterwards: the model decides at load time whether\n * its layers are projected through an active changeset.\n *\n * Commit, apply and delete are deliberately not exposed over MCP: landing a change is the\n * human's step (viewer or CLI). Rename, unstage and (confirmed) discard are in\n * changeset-editing.ts. See docs/CHANGE_MODELING.md.\n */\n\nimport { z } from \"zod\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { StagingAreaManager } from \"../../core/staging-area.js\";\nimport { CLIError, ErrorCategory } from \"../../utils/errors.js\";\nimport { jsonResult, loadModel, reloadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\nimport { resolveChangeset } from \"./changeset-shared.js\";\n\n// ---------------------------------------------------------------------------\n// changeset_create\n// ---------------------------------------------------------------------------\n\nconst createInputSchema = {\n name: z.string().min(1).describe(\"Short name for the change, e.g. 'add-order-export'.\"),\n description: z\n .string()\n .optional()\n .describe(\"The change's intent: what it is for and what done looks like. Shown to the human reviewing it.\"),\n activate: z\n .boolean()\n .optional()\n .describe(\"Make it the active changeset so mutations stage into it (default true).\"),\n rootPath: rootPathSchema,\n};\n\nexport type ChangesetCreateArgs = z.infer<z.ZodObject<typeof createInputSchema>>;\n\nexport async function changesetCreateHandler(args: ChangesetCreateArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n\n if (await manager.load(args.name)) {\n throw new CLIError(`Changeset '${args.name}' already exists`, ErrorCategory.USER, [\n `Use \"changeset_activate\" with \"${args.name}\" to continue it`,\n \"Or choose another name\",\n ]);\n }\n\n const changeset = await manager.create(args.name, args.description);\n const activate = args.activate ?? true;\n const previouslyActive = activate ? await manager.getActiveId() : null;\n if (activate) {\n await manager.setActive(changeset.id);\n await reloadModel(args.rootPath);\n }\n\n return jsonResult({\n id: changeset.id,\n name: changeset.name,\n ...(changeset.description ? { description: changeset.description } : {}),\n status: changeset.status,\n active: activate,\n ...(previouslyActive && previouslyActive !== changeset.id ? { deactivated: previouslyActive } : {}),\n });\n });\n}\n\nexport const changesetCreateTool: McpToolDefinition<typeof createInputSchema> = {\n name: \"changeset_create\",\n description:\n \"Start planning a change: create a changeset carrying the change's intent and (by default) activate it, \" +\n \"so model_add / model_update / model_delete / relationship_add / relationship_delete stage into it instead \" +\n \"of writing the model. The human commits it later; it is never committed over MCP.\",\n inputSchema: createInputSchema,\n handler: changesetCreateHandler,\n};\n\n// ---------------------------------------------------------------------------\n// changeset_activate\n// ---------------------------------------------------------------------------\n\nconst activateInputSchema = {\n changesetId: z.string().describe(\"Changeset ID or name.\"),\n rootPath: rootPathSchema,\n};\n\nexport type ChangesetActivateArgs = z.infer<z.ZodObject<typeof activateInputSchema>>;\n\nexport async function changesetActivateHandler(args: ChangesetActivateArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const changeset = await resolveChangeset(manager, args.changesetId);\n\n if (changeset.status !== \"staged\") {\n throw new CLIError(\n `Changeset '${changeset.name}' is ${changeset.status} and cannot accept changes`,\n ErrorCategory.USER,\n ['Use \"changeset_create\" to start a new one']\n );\n }\n\n const previouslyActive = await manager.getActiveId();\n await manager.setActive(changeset.id);\n await reloadModel(args.rootPath);\n\n return jsonResult({\n id: changeset.id,\n name: changeset.name,\n active: true,\n changes: changeset.changes.length,\n ...(previouslyActive && previouslyActive !== changeset.id ? { deactivated: previouslyActive } : {}),\n });\n });\n}\n\nexport const changesetActivateTool: McpToolDefinition<typeof activateInputSchema> = {\n name: \"changeset_activate\",\n description: \"Make an existing staged changeset the active one, so mutations stage into it.\",\n inputSchema: activateInputSchema,\n handler: changesetActivateHandler,\n};\n\n// ---------------------------------------------------------------------------\n// changeset_deactivate\n// ---------------------------------------------------------------------------\n\nconst deactivateInputSchema = {\n rootPath: rootPathSchema,\n};\n\nexport type ChangesetDeactivateArgs = z.infer<z.ZodObject<typeof deactivateInputSchema>>;\n\nexport async function changesetDeactivateHandler(args: ChangesetDeactivateArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const activeId = await manager.getActiveId();\n if (activeId) {\n await manager.clearActive();\n await reloadModel(args.rootPath);\n }\n return jsonResult({ deactivated: activeId ?? null });\n });\n}\n\nexport const changesetDeactivateTool: McpToolDefinition<typeof deactivateInputSchema> = {\n name: \"changeset_deactivate\",\n description:\n \"Deactivate the active changeset. Its changes stay staged; afterwards mutations write the base model directly.\",\n inputSchema: deactivateInputSchema,\n handler: changesetDeactivateHandler,\n};\n"],
5
- "mappings": "AAYA,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,UAAU,qBAAqB;AACxC,SAAS,YAAY,WAAW,aAAa,gBAAgB,eAAuC;AACpG,SAAS,wBAAwB;AAMjC,MAAM,oBAAoB;EACxB,MAAM,EAAE,OAAM,EAAG,IAAI,CAAC,EAAE,SAAS,qDAAqD;EACtF,aAAa,EACV,OAAM,EACN,SAAQ,EACR,SAAS,gGAAgG;EAC5G,UAAU,EACP,QAAO,EACP,SAAQ,EACR,SAAS,yEAAyE;EACrF,UAAU;;AAKZ,eAAsB,uBAAuB,MAAyB;AACpE,SAAO,QAAQ,YAAW;AACxB,UAAM,QAAQ,MAAM,UAAU,KAAK,QAAQ,GACrC,UAAU,IAAI,mBAAmB,MAAM,UAAU,KAAK;AAE5D,QAAI,MAAM,QAAQ,KAAK,KAAK,IAAI;AAC9B,YAAM,IAAI,SAAS,cAAc,KAAK,IAAI,oBAAoB,cAAc,MAAM;QAChF,kCAAkC,KAAK,IAAI;QAC3C;OACD;AAGH,UAAM,YAAY,MAAM,QAAQ,OAAO,KAAK,MAAM,KAAK,WAAW,GAC5D,WAAW,KAAK,YAAY,IAC5B,mBAAmB,WAAW,MAAM,QAAQ,YAAW,IAAK;AAClE,WAAI,aACF,MAAM,QAAQ,UAAU,UAAU,EAAE,GACpC,MAAM,YAAY,KAAK,QAAQ,IAG1B,WAAW;MAChB,IAAI,UAAU;MACd,MAAM,UAAU;MAChB,GAAI,UAAU,cAAc,EAAE,aAAa,UAAU,YAAW,IAAK,CAAA;MACrE,QAAQ,UAAU;MAClB,QAAQ;MACR,GAAI,oBAAoB,qBAAqB,UAAU,KAAK,EAAE,aAAa,iBAAgB,IAAK,CAAA;KACjG;EACH,CAAC;AACH;AAEO,MAAM,sBAAmE;EAC9E,MAAM;EACN,aACE;EAGF,aAAa;EACb,SAAS;GAOL,sBAAsB;EAC1B,aAAa,EAAE,OAAM,EAAG,SAAS,uBAAuB;EACxD,UAAU;;AAKZ,eAAsB,yBAAyB,MAA2B;AACxE,SAAO,QAAQ,YAAW;AACxB,UAAM,QAAQ,MAAM,UAAU,KAAK,QAAQ,GACrC,UAAU,IAAI,mBAAmB,MAAM,UAAU,KAAK,GACtD,YAAY,MAAM,iBAAiB,SAAS,KAAK,WAAW;AAElE,QAAI,UAAU,WAAW;AACvB,YAAM,IAAI,SACR,cAAc,UAAU,IAAI,QAAQ,UAAU,MAAM,8BACpD,cAAc,MACd,CAAC,2CAA2C,CAAC;AAIjD,UAAM,mBAAmB,MAAM,QAAQ,YAAW;AAClD,iBAAM,QAAQ,UAAU,UAAU,EAAE,GACpC,MAAM,YAAY,KAAK,QAAQ,GAExB,WAAW;MAChB,IAAI,UAAU;MACd,MAAM,UAAU;MAChB,QAAQ;MACR,SAAS,UAAU,QAAQ;MAC3B,GAAI,oBAAoB,qBAAqB,UAAU,KAAK,EAAE,aAAa,iBAAgB,IAAK,CAAA;KACjG;EACH,CAAC;AACH;AAEO,MAAM,wBAAuE;EAClF,MAAM;EACN,aAAa;EACb,aAAa;EACb,SAAS;GAOL,wBAAwB;EAC5B,UAAU;;AAKZ,eAAsB,2BAA2B,MAA6B;AAC5E,SAAO,QAAQ,YAAW;AACxB,UAAM,QAAQ,MAAM,UAAU,KAAK,QAAQ,GACrC,UAAU,IAAI,mBAAmB,MAAM,UAAU,KAAK,GACtD,WAAW,MAAM,QAAQ,YAAW;AAC1C,WAAI,aACF,MAAM,QAAQ,YAAW,GACzB,MAAM,YAAY,KAAK,QAAQ,IAE1B,WAAW,EAAE,aAAa,YAAY,KAAI,CAAE;EACrD,CAAC;AACH;AAEO,MAAM,0BAA2E;EACtF,MAAM;EACN,aACE;EACF,aAAa;EACb,SAAS;;",
4
+ "sourcesContent": ["/**\n * changeset_create, changeset_activate, changeset_deactivate \u2014 open and switch the changeset\n * that model and relationship mutations stage into. Mirrors `dr changeset create|activate|deactivate`.\n *\n * Every one of these reloads the cached model afterwards: the model decides at load time whether\n * its layers are projected through an active changeset.\n *\n * Commit, apply and delete are deliberately not exposed over MCP: landing a change is the\n * human's step (viewer or CLI). Rename, unstage and (confirmed) discard are in\n * changeset-editing.ts. See docs/CHANGE_MODELING.md.\n */\n\nimport { z } from \"zod\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { StagingAreaManager } from \"../../core/staging-area.js\";\nimport { CLIError, ErrorCategory } from \"../../utils/errors.js\";\nimport { jsonResult, loadModel, reloadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\nimport { resolveChangeset } from \"./changeset-shared.js\";\n\n// ---------------------------------------------------------------------------\n// changeset_create\n// ---------------------------------------------------------------------------\n\nconst createInputSchema = {\n name: z.string().min(1).describe(\"Short name for the change, e.g. 'add-order-export'.\"),\n description: z\n .string()\n .optional()\n .describe(\"The change's intent: what it is for and what done looks like. Shown to the human reviewing it.\"),\n activate: z\n .boolean()\n .optional()\n .describe(\"Make it the active changeset so mutations stage into it (default true).\"),\n rootPath: rootPathSchema,\n};\n\nexport type ChangesetCreateArgs = z.infer<z.ZodObject<typeof createInputSchema>>;\n\nexport async function changesetCreateHandler(args: ChangesetCreateArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n\n if (await manager.load(args.name)) {\n throw new CLIError(`Changeset '${args.name}' already exists`, ErrorCategory.USER, [\n `Use \"changeset_activate\" with \"${args.name}\" to continue it`,\n \"Or choose another name\",\n ]);\n }\n\n const changeset = await manager.create(args.name, args.description);\n const activate = args.activate ?? true;\n const previouslyActive = activate ? await manager.getActiveId() : null;\n if (activate) {\n await manager.setActive(changeset.id);\n await reloadModel(args.rootPath);\n }\n\n return jsonResult({\n id: changeset.id,\n name: changeset.name,\n ...(changeset.description ? { description: changeset.description } : {}),\n status: changeset.status,\n active: activate,\n ...(previouslyActive && previouslyActive !== changeset.id ? { deactivated: previouslyActive } : {}),\n });\n });\n}\n\nexport const changesetCreateTool: McpToolDefinition<typeof createInputSchema> = {\n name: \"changeset_create\",\n description:\n \"Start planning a change: create a changeset carrying the change's intent and (by default) activate it, \" +\n \"so model_add / model_update / model_delete / relationship_add / relationship_delete stage into it instead \" +\n \"of writing the model. Before calling it, propose the changeset's name and intent (description) to the user \" +\n \"and confirm both with them. Landing it is the user's step: changeset_commit commits only after they confirm \" +\n \"it, or they commit it in the viewer.\",\n inputSchema: createInputSchema,\n handler: changesetCreateHandler,\n};\n\n// ---------------------------------------------------------------------------\n// changeset_activate\n// ---------------------------------------------------------------------------\n\nconst activateInputSchema = {\n changesetId: z.string().describe(\"Changeset ID or name.\"),\n rootPath: rootPathSchema,\n};\n\nexport type ChangesetActivateArgs = z.infer<z.ZodObject<typeof activateInputSchema>>;\n\nexport async function changesetActivateHandler(args: ChangesetActivateArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const changeset = await resolveChangeset(manager, args.changesetId);\n\n if (changeset.status !== \"staged\") {\n throw new CLIError(\n `Changeset '${changeset.name}' is ${changeset.status} and cannot accept changes`,\n ErrorCategory.USER,\n ['Use \"changeset_create\" to start a new one']\n );\n }\n\n const previouslyActive = await manager.getActiveId();\n await manager.setActive(changeset.id);\n await reloadModel(args.rootPath);\n\n return jsonResult({\n id: changeset.id,\n name: changeset.name,\n active: true,\n changes: changeset.changes.length,\n ...(previouslyActive && previouslyActive !== changeset.id ? { deactivated: previouslyActive } : {}),\n });\n });\n}\n\nexport const changesetActivateTool: McpToolDefinition<typeof activateInputSchema> = {\n name: \"changeset_activate\",\n description: \"Make an existing staged changeset the active one, so mutations stage into it.\",\n inputSchema: activateInputSchema,\n handler: changesetActivateHandler,\n};\n\n// ---------------------------------------------------------------------------\n// changeset_deactivate\n// ---------------------------------------------------------------------------\n\nconst deactivateInputSchema = {\n rootPath: rootPathSchema,\n};\n\nexport type ChangesetDeactivateArgs = z.infer<z.ZodObject<typeof deactivateInputSchema>>;\n\nexport async function changesetDeactivateHandler(args: ChangesetDeactivateArgs): Promise<CallToolResult> {\n return runTool(async () => {\n const model = await loadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const activeId = await manager.getActiveId();\n if (activeId) {\n await manager.clearActive();\n await reloadModel(args.rootPath);\n }\n return jsonResult({ deactivated: activeId ?? null });\n });\n}\n\nexport const changesetDeactivateTool: McpToolDefinition<typeof deactivateInputSchema> = {\n name: \"changeset_deactivate\",\n description:\n \"Deactivate the active changeset. Its changes stay staged; afterwards mutations write the base model directly.\",\n inputSchema: deactivateInputSchema,\n handler: changesetDeactivateHandler,\n};\n"],
5
+ "mappings": "AAYA,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,UAAU,qBAAqB;AACxC,SAAS,YAAY,WAAW,aAAa,gBAAgB,eAAuC;AACpG,SAAS,wBAAwB;AAMjC,MAAM,oBAAoB;EACxB,MAAM,EAAE,OAAM,EAAG,IAAI,CAAC,EAAE,SAAS,qDAAqD;EACtF,aAAa,EACV,OAAM,EACN,SAAQ,EACR,SAAS,gGAAgG;EAC5G,UAAU,EACP,QAAO,EACP,SAAQ,EACR,SAAS,yEAAyE;EACrF,UAAU;;AAKZ,eAAsB,uBAAuB,MAAyB;AACpE,SAAO,QAAQ,YAAW;AACxB,UAAM,QAAQ,MAAM,UAAU,KAAK,QAAQ,GACrC,UAAU,IAAI,mBAAmB,MAAM,UAAU,KAAK;AAE5D,QAAI,MAAM,QAAQ,KAAK,KAAK,IAAI;AAC9B,YAAM,IAAI,SAAS,cAAc,KAAK,IAAI,oBAAoB,cAAc,MAAM;QAChF,kCAAkC,KAAK,IAAI;QAC3C;OACD;AAGH,UAAM,YAAY,MAAM,QAAQ,OAAO,KAAK,MAAM,KAAK,WAAW,GAC5D,WAAW,KAAK,YAAY,IAC5B,mBAAmB,WAAW,MAAM,QAAQ,YAAW,IAAK;AAClE,WAAI,aACF,MAAM,QAAQ,UAAU,UAAU,EAAE,GACpC,MAAM,YAAY,KAAK,QAAQ,IAG1B,WAAW;MAChB,IAAI,UAAU;MACd,MAAM,UAAU;MAChB,GAAI,UAAU,cAAc,EAAE,aAAa,UAAU,YAAW,IAAK,CAAA;MACrE,QAAQ,UAAU;MAClB,QAAQ;MACR,GAAI,oBAAoB,qBAAqB,UAAU,KAAK,EAAE,aAAa,iBAAgB,IAAK,CAAA;KACjG;EACH,CAAC;AACH;AAEO,MAAM,sBAAmE;EAC9E,MAAM;EACN,aACE;EAKF,aAAa;EACb,SAAS;GAOL,sBAAsB;EAC1B,aAAa,EAAE,OAAM,EAAG,SAAS,uBAAuB;EACxD,UAAU;;AAKZ,eAAsB,yBAAyB,MAA2B;AACxE,SAAO,QAAQ,YAAW;AACxB,UAAM,QAAQ,MAAM,UAAU,KAAK,QAAQ,GACrC,UAAU,IAAI,mBAAmB,MAAM,UAAU,KAAK,GACtD,YAAY,MAAM,iBAAiB,SAAS,KAAK,WAAW;AAElE,QAAI,UAAU,WAAW;AACvB,YAAM,IAAI,SACR,cAAc,UAAU,IAAI,QAAQ,UAAU,MAAM,8BACpD,cAAc,MACd,CAAC,2CAA2C,CAAC;AAIjD,UAAM,mBAAmB,MAAM,QAAQ,YAAW;AAClD,iBAAM,QAAQ,UAAU,UAAU,EAAE,GACpC,MAAM,YAAY,KAAK,QAAQ,GAExB,WAAW;MAChB,IAAI,UAAU;MACd,MAAM,UAAU;MAChB,QAAQ;MACR,SAAS,UAAU,QAAQ;MAC3B,GAAI,oBAAoB,qBAAqB,UAAU,KAAK,EAAE,aAAa,iBAAgB,IAAK,CAAA;KACjG;EACH,CAAC;AACH;AAEO,MAAM,wBAAuE;EAClF,MAAM;EACN,aAAa;EACb,aAAa;EACb,SAAS;GAOL,wBAAwB;EAC5B,UAAU;;AAKZ,eAAsB,2BAA2B,MAA6B;AAC5E,SAAO,QAAQ,YAAW;AACxB,UAAM,QAAQ,MAAM,UAAU,KAAK,QAAQ,GACrC,UAAU,IAAI,mBAAmB,MAAM,UAAU,KAAK,GACtD,WAAW,MAAM,QAAQ,YAAW;AAC1C,WAAI,aACF,MAAM,QAAQ,YAAW,GACzB,MAAM,YAAY,KAAK,QAAQ,IAE1B,WAAW,EAAE,aAAa,YAAY,KAAI,CAAE;EACrD,CAAC;AACH;AAEO,MAAM,0BAA2E;EACtF,MAAM;EACN,aACE;EACF,aAAa;EACb,SAAS;;",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { strictItem } from "../strict-schema.js";
2
3
  import { verifyChangesetInModel } from "../../core/changeset-verify.js";
3
4
  import { StagingAreaManager } from "../../core/staging-area.js";
4
5
  import { CLIError, ErrorCategory } from "../../utils/errors.js";
@@ -6,10 +7,11 @@ import { jsonResult, reloadModel, rootPathSchema, runTool } from "./shared.js";
6
7
  import { resolveChangeset } from "./changeset-shared.js";
7
8
  const inputSchema = {
8
9
  planned: z.string().optional().describe("The planned changeset, by ID or name. Defaults to the active changeset."),
9
- against: z.object({
10
- changesetId: z.string().optional().describe("An as-built changeset (by ID or name), e.g. one written by extraction or by hand."),
11
- base: z.boolean().optional().describe("Compare with the base model, e.g. after the as-built changeset was committed on the branch.")
12
- }).describe('What was built: {"changesetId": "..."} or {"base": true}.'),
10
+ against: strictItem("against", {
11
+ changesetId: z.string().optional().describe("The as-built changeset (by ID or name). It should come from codebase_extract with since, not be written by the agent: a copy of the plan matches by definition."),
12
+ base: z.boolean().optional().describe("Compare with the base model, e.g. after the as-built changeset was committed on the branch."),
13
+ extracted: z.boolean().optional().describe("With base: the base holds a landed extraction of the build, so what extraction can't see is unverifiable rather than missing, as against an extracted as-built changeset.")
14
+ }).describe('What was built: {"changesetId": "..."} or {"base": true} (with "extracted": true after an extraction landed).'),
13
15
  matchBySource: z.boolean().optional().describe("Also match elements of the same layer and type that share a source file (default true)."),
14
16
  rootPath: rootPathSchema
15
17
  };
@@ -17,6 +19,10 @@ async function changesetVerifyHandler(args) {
17
19
  return runTool(async () => {
18
20
  if (args.against?.changesetId && args.against.base)
19
21
  throw new CLIError('Pass either "against.changesetId" or "against.base", not both', ErrorCategory.USER);
22
+ if (args.against?.extracted && !args.against.base)
23
+ throw new CLIError('"against.extracted" goes with "against.base"', ErrorCategory.USER, [
24
+ "An as-built changeset from codebase_extract is recognised on its own"
25
+ ]);
20
26
  if (!args.against?.changesetId && !args.against?.base)
21
27
  throw new CLIError("Nothing to verify against", ErrorCategory.USER, [
22
28
  'Pass "against": {"changesetId": "<as-built changeset>"} to compare with another changeset',
@@ -25,14 +31,14 @@ async function changesetVerifyHandler(args) {
25
31
  const model = await reloadModel(args.rootPath), manager = new StagingAreaManager(model.rootPath, model), planned = await resolveChangeset(manager, args.planned, "planned"), asBuilt = args.against?.changesetId ? await resolveChangeset(manager, args.against.changesetId) : void 0;
26
32
  return jsonResult(await verifyChangesetInModel(model, {
27
33
  planned,
28
- against: asBuilt ? { changeset: asBuilt } : { base: !0 },
34
+ against: asBuilt ? { changeset: asBuilt } : { base: !0, extracted: args.against.extracted === !0 },
29
35
  matchBySource: args.matchBySource
30
36
  }));
31
37
  });
32
38
  }
33
39
  const changesetVerifyTool = {
34
40
  name: "changeset_verify",
35
- description: "Compare a planned changeset with what was built: another (as-built) changeset, or the base model. Elements match by id, then by layer + type + name ignoring case and separators, then by source file; relationships match through those element matches. Reports matched, missing (planned, not built), unplanned (built, not planned), divergent (matched but type, description or attributes differ, with the fields), and planned deletions that still exist, with a verdict. Fuzzy matches are marked probable with their reason; confirm them. Reads the model fresh from disk, so a commit made outside MCP is seen. Read-only.",
41
+ description: "Compare a planned changeset with what was built: the as-built changeset codebase_extract wrote (with since, the branch's base), or the base model. Elements match by id, then by layer + type + name ignoring case and separators, then by source file; relationships match through those element matches. Reports matched, missing (planned, not built), unverifiable (planned, but extraction can't see it: intent, re-exports, tests in languages without test cards, relationships no code edge maps to; not part of the verdict), unplanned (built, not planned), divergent (matched but type, name or attributes differ, with both values), and planned deletions that still exist, with a verdict. Description differences are notes on a match, not divergence: don't edit the plan to match extracted text. extraction.asBuilt counts the as-built elements extraction really staged, from its own record (not the sourceProvenance they declare); the rest are agentAdded, with why, and a warning says when they are more than half. Fuzzy matches are marked probable with their reason; confirm them. Reads the model fresh from disk, so a commit made outside MCP is seen. Read-only.",
36
42
  inputSchema,
37
43
  handler: changesetVerifyHandler
38
44
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/mcp/tools/changeset-verify.ts"],
4
- "sourcesContent": ["/**\n * changeset_verify \u2014 compare a planned changeset with what was built.\n * See `cli/src/core/changeset-verify.ts` and docs/CHANGE_MODELING.md.\n */\n\nimport { z } from \"zod\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { verifyChangesetInModel } from \"../../core/changeset-verify.js\";\nimport { StagingAreaManager } from \"../../core/staging-area.js\";\nimport { CLIError, ErrorCategory } from \"../../utils/errors.js\";\nimport { jsonResult, reloadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\nimport { resolveChangeset } from \"./changeset-shared.js\";\n\nconst inputSchema = {\n planned: z\n .string()\n .optional()\n .describe(\"The planned changeset, by ID or name. Defaults to the active changeset.\"),\n against: z\n .object({\n changesetId: z\n .string()\n .optional()\n .describe(\"An as-built changeset (by ID or name), e.g. one written by extraction or by hand.\"),\n base: z\n .boolean()\n .optional()\n .describe(\"Compare with the base model, e.g. after the as-built changeset was committed on the branch.\"),\n })\n .describe('What was built: {\"changesetId\": \"...\"} or {\"base\": true}.'),\n matchBySource: z\n .boolean()\n .optional()\n .describe(\"Also match elements of the same layer and type that share a source file (default true).\"),\n rootPath: rootPathSchema,\n};\n\nexport type ChangesetVerifyArgs = z.infer<z.ZodObject<typeof inputSchema>>;\n\nexport async function changesetVerifyHandler(args: ChangesetVerifyArgs): Promise<CallToolResult> {\n return runTool(async () => {\n if (args.against?.changesetId && args.against.base) {\n throw new CLIError('Pass either \"against.changesetId\" or \"against.base\", not both', ErrorCategory.USER);\n }\n if (!args.against?.changesetId && !args.against?.base) {\n throw new CLIError(\"Nothing to verify against\", ErrorCategory.USER, [\n 'Pass \"against\": {\"changesetId\": \"<as-built changeset>\"} to compare with another changeset',\n 'Pass \"against\": {\"base\": true} to compare with the base model',\n ]);\n }\n // Always read the model fresh: the build is often committed outside MCP (`dr changeset commit` is\n // not exposed here), and a cached model would compare against the base as it was when the server\n // started. Verify is read-only and infrequent, so the reload is cheap insurance.\n const model = await reloadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const planned = await resolveChangeset(manager, args.planned, \"planned\");\n const asBuilt = args.against?.changesetId ? await resolveChangeset(manager, args.against.changesetId) : undefined;\n return jsonResult(\n await verifyChangesetInModel(model, {\n planned,\n against: asBuilt ? { changeset: asBuilt } : { base: true },\n matchBySource: args.matchBySource,\n })\n );\n });\n}\n\nexport const changesetVerifyTool: McpToolDefinition<typeof inputSchema> = {\n name: \"changeset_verify\",\n description:\n \"Compare a planned changeset with what was built: another (as-built) changeset, or the base model. Elements \" +\n \"match by id, then by layer + type + name ignoring case and separators, then by source file; relationships \" +\n \"match through those element matches. Reports matched, missing (planned, not built), unplanned (built, not \" +\n \"planned), divergent (matched but type, description or attributes differ, with the fields), and planned \" +\n \"deletions that still exist, with a verdict. Fuzzy matches are marked probable with their reason; confirm \" +\n \"them. Reads the model fresh from disk, so a commit made outside MCP is seen. Read-only.\",\n inputSchema,\n handler: changesetVerifyHandler,\n};\n"],
5
- "mappings": "AAKA,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,UAAU,qBAAqB;AACxC,SAAS,YAAY,aAAa,gBAAgB,eAAuC;AACzF,SAAS,wBAAwB;AAEjC,MAAM,cAAc;EAClB,SAAS,EACN,OAAM,EACN,SAAQ,EACR,SAAS,yEAAyE;EACrF,SAAS,EACN,OAAO;IACN,aAAa,EACV,OAAM,EACN,SAAQ,EACR,SAAS,mFAAmF;IAC/F,MAAM,EACH,QAAO,EACP,SAAQ,EACR,SAAS,6FAA6F;GAC1G,EACA,SAAS,2DAA2D;EACvE,eAAe,EACZ,QAAO,EACP,SAAQ,EACR,SAAS,yFAAyF;EACrG,UAAU;;AAKZ,eAAsB,uBAAuB,MAAyB;AACpE,SAAO,QAAQ,YAAW;AACxB,QAAI,KAAK,SAAS,eAAe,KAAK,QAAQ;AAC5C,YAAM,IAAI,SAAS,iEAAiE,cAAc,IAAI;AAExG,QAAI,CAAC,KAAK,SAAS,eAAe,CAAC,KAAK,SAAS;AAC/C,YAAM,IAAI,SAAS,6BAA6B,cAAc,MAAM;QAClE;QACA;OACD;AAKH,UAAM,QAAQ,MAAM,YAAY,KAAK,QAAQ,GACvC,UAAU,IAAI,mBAAmB,MAAM,UAAU,KAAK,GACtD,UAAU,MAAM,iBAAiB,SAAS,KAAK,SAAS,SAAS,GACjE,UAAU,KAAK,SAAS,cAAc,MAAM,iBAAiB,SAAS,KAAK,QAAQ,WAAW,IAAI;AACxG,WAAO,WACL,MAAM,uBAAuB,OAAO;MAClC;MACA,SAAS,UAAU,EAAE,WAAW,QAAO,IAAK,EAAE,MAAM,GAAI;MACxD,eAAe,KAAK;KACrB,CAAC;EAEN,CAAC;AACH;AAEO,MAAM,sBAA6D;EACxE,MAAM;EACN,aACE;EAMF;EACA,SAAS;;",
4
+ "sourcesContent": ["/**\n * changeset_verify \u2014 compare a planned changeset with what was built.\n * See `cli/src/core/changeset-verify.ts` and docs/CHANGE_MODELING.md.\n */\n\nimport { z } from \"zod\";\nimport { strictItem } from \"../strict-schema.js\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { verifyChangesetInModel } from \"../../core/changeset-verify.js\";\nimport { StagingAreaManager } from \"../../core/staging-area.js\";\nimport { CLIError, ErrorCategory } from \"../../utils/errors.js\";\nimport { jsonResult, reloadModel, rootPathSchema, runTool, type McpToolDefinition } from \"./shared.js\";\nimport { resolveChangeset } from \"./changeset-shared.js\";\n\nconst inputSchema = {\n planned: z\n .string()\n .optional()\n .describe(\"The planned changeset, by ID or name. Defaults to the active changeset.\"),\n against: strictItem(\"against\", {\n changesetId: z\n .string()\n .optional()\n .describe(\n \"The as-built changeset (by ID or name). It should come from codebase_extract with since, not be written \" +\n \"by the agent: a copy of the plan matches by definition.\"\n ),\n base: z\n .boolean()\n .optional()\n .describe(\"Compare with the base model, e.g. after the as-built changeset was committed on the branch.\"),\n extracted: z\n .boolean()\n .optional()\n .describe(\n \"With base: the base holds a landed extraction of the build, so what extraction can't see is \" +\n \"unverifiable rather than missing, as against an extracted as-built changeset.\"\n ),\n }).describe('What was built: {\"changesetId\": \"...\"} or {\"base\": true} (with \"extracted\": true after an extraction landed).'),\n matchBySource: z\n .boolean()\n .optional()\n .describe(\"Also match elements of the same layer and type that share a source file (default true).\"),\n rootPath: rootPathSchema,\n};\n\nexport type ChangesetVerifyArgs = z.infer<z.ZodObject<typeof inputSchema>>;\n\nexport async function changesetVerifyHandler(args: ChangesetVerifyArgs): Promise<CallToolResult> {\n return runTool(async () => {\n if (args.against?.changesetId && args.against.base) {\n throw new CLIError('Pass either \"against.changesetId\" or \"against.base\", not both', ErrorCategory.USER);\n }\n if (args.against?.extracted && !args.against.base) {\n throw new CLIError('\"against.extracted\" goes with \"against.base\"', ErrorCategory.USER, [\n \"An as-built changeset from codebase_extract is recognised on its own\",\n ]);\n }\n if (!args.against?.changesetId && !args.against?.base) {\n throw new CLIError(\"Nothing to verify against\", ErrorCategory.USER, [\n 'Pass \"against\": {\"changesetId\": \"<as-built changeset>\"} to compare with another changeset',\n 'Pass \"against\": {\"base\": true} to compare with the base model',\n ]);\n }\n // Always read the model fresh: the build is often committed outside MCP (`dr changeset commit` is\n // not exposed here), and a cached model would compare against the base as it was when the server\n // started. Verify is read-only and infrequent, so the reload is cheap insurance.\n const model = await reloadModel(args.rootPath);\n const manager = new StagingAreaManager(model.rootPath, model);\n const planned = await resolveChangeset(manager, args.planned, \"planned\");\n const asBuilt = args.against?.changesetId ? await resolveChangeset(manager, args.against.changesetId) : undefined;\n return jsonResult(\n await verifyChangesetInModel(model, {\n planned,\n against: asBuilt ? { changeset: asBuilt } : { base: true, extracted: args.against.extracted === true },\n matchBySource: args.matchBySource,\n })\n );\n });\n}\n\nexport const changesetVerifyTool: McpToolDefinition<typeof inputSchema> = {\n name: \"changeset_verify\",\n description:\n \"Compare a planned changeset with what was built: the as-built changeset codebase_extract wrote (with since, \" +\n \"the branch's base), or the base model. Elements match by id, then by layer + type + name ignoring case and \" +\n \"separators, then by source file; relationships match through those element matches. Reports matched, \" +\n \"missing (planned, not built), unverifiable (planned, but extraction can't see it: intent, re-exports, tests \" +\n \"in languages without test cards, relationships no code edge maps to; not part of the verdict), unplanned \" +\n \"(built, not planned), divergent (matched but type, name or attributes differ, with both values), and planned \" +\n \"deletions that still exist, with a verdict. Description differences are notes on a match, not divergence: \" +\n \"don't edit the plan to match extracted text. extraction.asBuilt counts the as-built elements extraction \" +\n \"really staged, from its own record (not the sourceProvenance they declare); the rest are agentAdded, with \" +\n \"why, and a warning says when they are more than half. Fuzzy matches are marked probable with their reason; \" +\n \"confirm them. Reads the model fresh from disk, so a commit made outside MCP is seen. Read-only.\",\n inputSchema,\n handler: changesetVerifyHandler,\n};\n"],
5
+ "mappings": "AAKA,SAAS,SAAS;AAClB,SAAS,kBAAkB;AAE3B,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,UAAU,qBAAqB;AACxC,SAAS,YAAY,aAAa,gBAAgB,eAAuC;AACzF,SAAS,wBAAwB;AAEjC,MAAM,cAAc;EAClB,SAAS,EACN,OAAM,EACN,SAAQ,EACR,SAAS,yEAAyE;EACrF,SAAS,WAAW,WAAW;IAC7B,aAAa,EACV,OAAM,EACN,SAAQ,EACR,SACC,iKAC2D;IAE/D,MAAM,EACH,QAAO,EACP,SAAQ,EACR,SAAS,6FAA6F;IACzG,WAAW,EACR,QAAO,EACP,SAAQ,EACR,SACC,2KACiF;GAEtF,EAAE,SAAS,+GAA+G;EAC3H,eAAe,EACZ,QAAO,EACP,SAAQ,EACR,SAAS,yFAAyF;EACrG,UAAU;;AAKZ,eAAsB,uBAAuB,MAAyB;AACpE,SAAO,QAAQ,YAAW;AACxB,QAAI,KAAK,SAAS,eAAe,KAAK,QAAQ;AAC5C,YAAM,IAAI,SAAS,iEAAiE,cAAc,IAAI;AAExG,QAAI,KAAK,SAAS,aAAa,CAAC,KAAK,QAAQ;AAC3C,YAAM,IAAI,SAAS,gDAAgD,cAAc,MAAM;QACrF;OACD;AAEH,QAAI,CAAC,KAAK,SAAS,eAAe,CAAC,KAAK,SAAS;AAC/C,YAAM,IAAI,SAAS,6BAA6B,cAAc,MAAM;QAClE;QACA;OACD;AAKH,UAAM,QAAQ,MAAM,YAAY,KAAK,QAAQ,GACvC,UAAU,IAAI,mBAAmB,MAAM,UAAU,KAAK,GACtD,UAAU,MAAM,iBAAiB,SAAS,KAAK,SAAS,SAAS,GACjE,UAAU,KAAK,SAAS,cAAc,MAAM,iBAAiB,SAAS,KAAK,QAAQ,WAAW,IAAI;AACxG,WAAO,WACL,MAAM,uBAAuB,OAAO;MAClC;MACA,SAAS,UAAU,EAAE,WAAW,QAAO,IAAK,EAAE,MAAM,IAAM,WAAW,KAAK,QAAQ,cAAc,GAAI;MACpG,eAAe,KAAK;KACrB,CAAC;EAEN,CAAC;AACH;AAEO,MAAM,sBAA6D;EACxE,MAAM;EACN,aACE;EAWF;EACA,SAAS;;",
6
6
  "names": []
7
7
  }