@getripple/mcp 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +84 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +33 -0
- package/dist/server.js +312 -0
- package/dist/server.js.map +1 -0
- package/dist/tools.d.ts +62 -0
- package/dist/tools.js +706 -0
- package/dist/tools.js.map +1 -0
- package/examples/local-dev.config.json +12 -0
- package/examples/published.config.json +13 -0
- package/package.json +54 -0
package/dist/tools.js
ADDED
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.createRippleMcpToolHost = exports.RippleMcpToolHost = exports.RIPPLE_MCP_TOOLS = void 0;
|
|
27
|
+
const path = __importStar(require("path"));
|
|
28
|
+
const core_1 = require("@getripple/core");
|
|
29
|
+
const MCP_CONTROL_MODES = ["brainstorm", "function", "file", "task", "pr"];
|
|
30
|
+
const MCP_AUDIT_MODES = ["staged", "changed"];
|
|
31
|
+
const MCP_APPROVAL_GATES = ["before-risky-edit", "before-merge"];
|
|
32
|
+
exports.RIPPLE_MCP_TOOLS = [
|
|
33
|
+
{
|
|
34
|
+
name: "ripple_doctor",
|
|
35
|
+
description: "Return Ripple readiness diagnostics for graph scanning, git, CI workflow, and latest change intent.",
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {},
|
|
39
|
+
additionalProperties: false,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "ripple_check_changed",
|
|
44
|
+
description: "Return Ripple's changed-file safety check for JS/TS changes against a base git ref.",
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: {
|
|
48
|
+
baseRef: {
|
|
49
|
+
type: "string",
|
|
50
|
+
description: "Git base ref to diff against, for example HEAD or origin/main.",
|
|
51
|
+
},
|
|
52
|
+
tokenBudget: {
|
|
53
|
+
type: "number",
|
|
54
|
+
description: "Maximum context token budget per changed file.",
|
|
55
|
+
},
|
|
56
|
+
intentPath: {
|
|
57
|
+
type: "string",
|
|
58
|
+
description: "Optional saved Ripple change intent path, id, or latest.",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
additionalProperties: false,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "ripple_check_staged",
|
|
66
|
+
description: "Return Ripple's staged-file safety check for currently staged JS/TS changes.",
|
|
67
|
+
inputSchema: {
|
|
68
|
+
type: "object",
|
|
69
|
+
properties: {
|
|
70
|
+
tokenBudget: {
|
|
71
|
+
type: "number",
|
|
72
|
+
description: "Maximum context token budget per staged file.",
|
|
73
|
+
},
|
|
74
|
+
intentPath: {
|
|
75
|
+
type: "string",
|
|
76
|
+
description: "Optional saved Ripple change intent path, id, or latest.",
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
additionalProperties: false,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "ripple_audit_change",
|
|
84
|
+
description: "Return one compact audit report: saved intent, current policy, drift check, repair plan, and final decision.",
|
|
85
|
+
inputSchema: {
|
|
86
|
+
type: "object",
|
|
87
|
+
properties: {
|
|
88
|
+
mode: {
|
|
89
|
+
type: "string",
|
|
90
|
+
enum: MCP_AUDIT_MODES,
|
|
91
|
+
description: "Audit staged files by default, or changed files against baseRef.",
|
|
92
|
+
},
|
|
93
|
+
baseRef: {
|
|
94
|
+
type: "string",
|
|
95
|
+
description: "Git base ref for mode=changed, for example HEAD or origin/main.",
|
|
96
|
+
},
|
|
97
|
+
tokenBudget: {
|
|
98
|
+
type: "number",
|
|
99
|
+
description: "Maximum context token budget per audited file.",
|
|
100
|
+
},
|
|
101
|
+
intentPath: {
|
|
102
|
+
type: "string",
|
|
103
|
+
description: "Saved Ripple change intent path, id, or latest. Defaults to latest.",
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
additionalProperties: false,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "ripple_gate",
|
|
111
|
+
description: "Return the compact continue/stop gate decision for a saved Ripple intent without the full audit report.",
|
|
112
|
+
inputSchema: {
|
|
113
|
+
type: "object",
|
|
114
|
+
properties: {
|
|
115
|
+
mode: {
|
|
116
|
+
type: "string",
|
|
117
|
+
enum: MCP_AUDIT_MODES,
|
|
118
|
+
description: "Gate staged files by default, or changed files against baseRef.",
|
|
119
|
+
},
|
|
120
|
+
baseRef: {
|
|
121
|
+
type: "string",
|
|
122
|
+
description: "Git base ref for mode=changed, for example HEAD or origin/main.",
|
|
123
|
+
},
|
|
124
|
+
tokenBudget: {
|
|
125
|
+
type: "number",
|
|
126
|
+
description: "Maximum context token budget per gated file.",
|
|
127
|
+
},
|
|
128
|
+
intentPath: {
|
|
129
|
+
type: "string",
|
|
130
|
+
description: "Saved Ripple change intent path, id, or latest. Defaults to latest.",
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
additionalProperties: false,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: "ripple_get_approval_status",
|
|
138
|
+
description: "Return whether the saved Ripple change intent has a valid, missing, stale, or not-required human approval record.",
|
|
139
|
+
inputSchema: {
|
|
140
|
+
type: "object",
|
|
141
|
+
properties: {
|
|
142
|
+
intentPath: {
|
|
143
|
+
type: "string",
|
|
144
|
+
description: "Saved Ripple change intent path, id, or latest. Defaults to latest.",
|
|
145
|
+
},
|
|
146
|
+
gate: {
|
|
147
|
+
type: "string",
|
|
148
|
+
enum: MCP_APPROVAL_GATES,
|
|
149
|
+
description: "Optional approval gate override.",
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
additionalProperties: false,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "ripple_repair_intent_drift",
|
|
157
|
+
description: "Return a concrete repair plan for staged changes that drifted from a saved Ripple change intent.",
|
|
158
|
+
inputSchema: {
|
|
159
|
+
type: "object",
|
|
160
|
+
properties: {
|
|
161
|
+
tokenBudget: {
|
|
162
|
+
type: "number",
|
|
163
|
+
description: "Maximum context token budget per staged file.",
|
|
164
|
+
},
|
|
165
|
+
intentPath: {
|
|
166
|
+
type: "string",
|
|
167
|
+
description: "Saved Ripple change intent path, id, or latest.",
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
additionalProperties: false,
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "ripple_get_agent_workflow",
|
|
175
|
+
description: "Return the Ripple agent workflow protocol and command/output contracts.",
|
|
176
|
+
inputSchema: {
|
|
177
|
+
type: "object",
|
|
178
|
+
properties: {},
|
|
179
|
+
additionalProperties: false,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "ripple_get_focus",
|
|
184
|
+
description: "Return focused architectural context for one target file.",
|
|
185
|
+
inputSchema: {
|
|
186
|
+
type: "object",
|
|
187
|
+
properties: {
|
|
188
|
+
filePath: {
|
|
189
|
+
type: "string",
|
|
190
|
+
description: "Project-relative or absolute path to a JS/TS file.",
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
required: ["filePath"],
|
|
194
|
+
additionalProperties: false,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: "ripple_get_blast_radius",
|
|
199
|
+
description: "Return direct downstream files affected by changing one file.",
|
|
200
|
+
inputSchema: {
|
|
201
|
+
type: "object",
|
|
202
|
+
properties: {
|
|
203
|
+
filePath: {
|
|
204
|
+
type: "string",
|
|
205
|
+
description: "Project-relative or absolute path to a JS/TS file.",
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
required: ["filePath"],
|
|
209
|
+
additionalProperties: false,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: "ripple_explain_policy",
|
|
214
|
+
description: "Return the repo trust-boundary policy that applies to one target file before an agent plans or edits.",
|
|
215
|
+
inputSchema: {
|
|
216
|
+
type: "object",
|
|
217
|
+
properties: {
|
|
218
|
+
filePath: {
|
|
219
|
+
type: "string",
|
|
220
|
+
description: "Project-relative or absolute target file path.",
|
|
221
|
+
},
|
|
222
|
+
targetFile: {
|
|
223
|
+
type: "string",
|
|
224
|
+
description: "Alias for filePath.",
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
additionalProperties: false,
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "ripple_plan_context",
|
|
232
|
+
description: "Return a token-budgeted read plan for a task and target file, optionally saving an agent control boundary.",
|
|
233
|
+
inputSchema: {
|
|
234
|
+
type: "object",
|
|
235
|
+
properties: {
|
|
236
|
+
task: {
|
|
237
|
+
type: "string",
|
|
238
|
+
description: "Short description of the coding task.",
|
|
239
|
+
},
|
|
240
|
+
filePath: {
|
|
241
|
+
type: "string",
|
|
242
|
+
description: "Project-relative or absolute target file path.",
|
|
243
|
+
},
|
|
244
|
+
targetFile: {
|
|
245
|
+
type: "string",
|
|
246
|
+
description: "Alias for filePath.",
|
|
247
|
+
},
|
|
248
|
+
tokenBudget: {
|
|
249
|
+
type: "number",
|
|
250
|
+
description: "Maximum context token budget.",
|
|
251
|
+
},
|
|
252
|
+
mode: {
|
|
253
|
+
type: "string",
|
|
254
|
+
enum: MCP_CONTROL_MODES,
|
|
255
|
+
description: "Agent control boundary for the saved intent. Defaults to file.",
|
|
256
|
+
},
|
|
257
|
+
controlMode: {
|
|
258
|
+
type: "string",
|
|
259
|
+
enum: MCP_CONTROL_MODES,
|
|
260
|
+
description: "Alias for mode.",
|
|
261
|
+
},
|
|
262
|
+
symbol: {
|
|
263
|
+
type: "string",
|
|
264
|
+
description: "Allowed symbol name for function mode.",
|
|
265
|
+
},
|
|
266
|
+
allowedSymbols: {
|
|
267
|
+
type: "array",
|
|
268
|
+
items: { type: "string" },
|
|
269
|
+
description: "Allowed symbol ids or names for function mode.",
|
|
270
|
+
},
|
|
271
|
+
saveIntent: {
|
|
272
|
+
type: "boolean",
|
|
273
|
+
description: "When true, save a Ripple change intent for later staged-check validation.",
|
|
274
|
+
},
|
|
275
|
+
intentPath: {
|
|
276
|
+
type: "string",
|
|
277
|
+
description: "Optional path or id for the saved change intent. Defaults to latest.",
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
required: ["task"],
|
|
281
|
+
additionalProperties: false,
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: "ripple_get_recent_changes",
|
|
286
|
+
description: "Return recent Ripple history groups for this workspace.",
|
|
287
|
+
inputSchema: {
|
|
288
|
+
type: "object",
|
|
289
|
+
properties: {
|
|
290
|
+
limit: {
|
|
291
|
+
type: "number",
|
|
292
|
+
description: "Maximum number of history groups to return.",
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
additionalProperties: false,
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
];
|
|
299
|
+
class RippleMcpToolHost {
|
|
300
|
+
constructor(options) {
|
|
301
|
+
this.scanned = false;
|
|
302
|
+
this.workspaceRoot = path.resolve(options.workspaceRoot);
|
|
303
|
+
this.engine = new core_1.GraphEngine(this.workspaceRoot);
|
|
304
|
+
}
|
|
305
|
+
listTools() {
|
|
306
|
+
return exports.RIPPLE_MCP_TOOLS;
|
|
307
|
+
}
|
|
308
|
+
async initialize() {
|
|
309
|
+
if (this.scanned) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
await runWithQuietConsoleLog(() => this.engine.initialScan());
|
|
313
|
+
this.scanned = true;
|
|
314
|
+
}
|
|
315
|
+
dispose() {
|
|
316
|
+
this.engine.dispose();
|
|
317
|
+
}
|
|
318
|
+
async callTool(name, args = {}) {
|
|
319
|
+
if (name === "ripple_get_agent_workflow") {
|
|
320
|
+
return {
|
|
321
|
+
tool: name,
|
|
322
|
+
data: (0, core_1.getAgentWorkflowSummary)(),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if (name === "ripple_explain_policy") {
|
|
326
|
+
return {
|
|
327
|
+
tool: name,
|
|
328
|
+
data: this.explainPolicy(args),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
if (name === "ripple_get_approval_status") {
|
|
332
|
+
return {
|
|
333
|
+
tool: name,
|
|
334
|
+
data: this.getApprovalStatus(args),
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
await this.initialize();
|
|
338
|
+
if (name === "ripple_get_focus") {
|
|
339
|
+
return {
|
|
340
|
+
tool: name,
|
|
341
|
+
data: this.getFocus(args),
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
if (name === "ripple_doctor") {
|
|
345
|
+
return {
|
|
346
|
+
tool: name,
|
|
347
|
+
data: this.getReadiness(),
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
if (name === "ripple_get_blast_radius") {
|
|
351
|
+
return {
|
|
352
|
+
tool: name,
|
|
353
|
+
data: this.getBlastRadius(args),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
if (name === "ripple_plan_context") {
|
|
357
|
+
return {
|
|
358
|
+
tool: name,
|
|
359
|
+
data: this.planContext(args),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
if (name === "ripple_check_staged") {
|
|
363
|
+
return {
|
|
364
|
+
tool: name,
|
|
365
|
+
data: this.checkStaged(args),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
if (name === "ripple_check_changed") {
|
|
369
|
+
return {
|
|
370
|
+
tool: name,
|
|
371
|
+
data: this.checkChanged(args),
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
if (name === "ripple_audit_change") {
|
|
375
|
+
return {
|
|
376
|
+
tool: name,
|
|
377
|
+
data: this.auditChange(args),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
if (name === "ripple_gate") {
|
|
381
|
+
return {
|
|
382
|
+
tool: name,
|
|
383
|
+
data: this.gateChange(args),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
if (name === "ripple_repair_intent_drift") {
|
|
387
|
+
return {
|
|
388
|
+
tool: name,
|
|
389
|
+
data: this.repairIntentDrift(args),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
if (name === "ripple_get_recent_changes") {
|
|
393
|
+
return {
|
|
394
|
+
tool: name,
|
|
395
|
+
data: this.getRecentChanges(args),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
const exhaustiveCheck = name;
|
|
399
|
+
throw new Error(`Unknown Ripple MCP tool: ${exhaustiveCheck}`);
|
|
400
|
+
}
|
|
401
|
+
getFocus(args) {
|
|
402
|
+
const filePath = requiredString(args, "filePath");
|
|
403
|
+
const focus = this.engine.getFileFocusSummary(filePath);
|
|
404
|
+
if (!focus) {
|
|
405
|
+
throw new Error(`File is not in the Ripple graph: ${filePath}`);
|
|
406
|
+
}
|
|
407
|
+
return focus;
|
|
408
|
+
}
|
|
409
|
+
getReadiness() {
|
|
410
|
+
return (0, core_1.buildRippleReadinessSummary)(this.workspaceRoot, this.engine);
|
|
411
|
+
}
|
|
412
|
+
getBlastRadius(args) {
|
|
413
|
+
const filePath = requiredString(args, "filePath");
|
|
414
|
+
const blastRadius = this.engine.getBlastRadiusSummary(filePath);
|
|
415
|
+
if (!blastRadius) {
|
|
416
|
+
throw new Error(`File is not in the Ripple graph: ${filePath}`);
|
|
417
|
+
}
|
|
418
|
+
return blastRadius;
|
|
419
|
+
}
|
|
420
|
+
explainPolicy(args) {
|
|
421
|
+
const filePath = optionalString(args, "filePath") ?? optionalString(args, "targetFile");
|
|
422
|
+
if (!filePath) {
|
|
423
|
+
throw new Error("ripple_explain_policy requires filePath or targetFile.");
|
|
424
|
+
}
|
|
425
|
+
return (0, core_1.explainRipplePolicyForTarget)((0, core_1.loadRipplePolicy)(this.workspaceRoot), normalizeProjectPath(filePath));
|
|
426
|
+
}
|
|
427
|
+
planContext(args) {
|
|
428
|
+
const parsed = parsePlanContextArgs(args);
|
|
429
|
+
const targetFile = parsed.filePath ?? parsed.targetFile;
|
|
430
|
+
if (!targetFile) {
|
|
431
|
+
throw new Error("ripple_plan_context requires filePath or targetFile.");
|
|
432
|
+
}
|
|
433
|
+
const plan = this.engine.planContext(parsed.task ?? "", targetFile, parsed.tokenBudget);
|
|
434
|
+
if (!plan) {
|
|
435
|
+
throw new Error(`File is not in the Ripple graph: ${targetFile}`);
|
|
436
|
+
}
|
|
437
|
+
const loadedPolicy = (0, core_1.loadRipplePolicy)(this.workspaceRoot);
|
|
438
|
+
const policyExplanation = (0, core_1.explainRipplePolicyForTarget)(loadedPolicy, plan.targetFile, {
|
|
439
|
+
controlMode: parsed.controlMode ?? parsed.mode,
|
|
440
|
+
});
|
|
441
|
+
if (!parsed.saveIntent) {
|
|
442
|
+
return {
|
|
443
|
+
...plan,
|
|
444
|
+
policyExplanation,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
const policy = (0, core_1.resolveRipplePolicyForTarget)(loadedPolicy, plan.targetFile);
|
|
448
|
+
const changeIntent = (0, core_1.buildChangeIntent)(plan, {
|
|
449
|
+
controlMode: parsed.controlMode ?? parsed.mode,
|
|
450
|
+
allowedSymbols: uniqueItems([
|
|
451
|
+
...(parsed.allowedSymbols ?? []),
|
|
452
|
+
...(parsed.symbol ? [parsed.symbol] : []),
|
|
453
|
+
]),
|
|
454
|
+
policy,
|
|
455
|
+
policyExplanation,
|
|
456
|
+
});
|
|
457
|
+
const savedPath = (0, core_1.saveChangeIntent)(this.workspaceRoot, changeIntent, parsed.intentPath);
|
|
458
|
+
const readiness = (0, core_1.buildRippleReadinessSummary)(this.workspaceRoot, this.engine);
|
|
459
|
+
changeIntent.readinessSnapshot = (0, core_1.buildChangeIntentReadinessSnapshot)(readiness);
|
|
460
|
+
(0, core_1.saveChangeIntent)(this.workspaceRoot, changeIntent, savedPath);
|
|
461
|
+
return {
|
|
462
|
+
...plan,
|
|
463
|
+
policyExplanation,
|
|
464
|
+
changeIntent,
|
|
465
|
+
changeIntentPath: formatWorkspacePath(this.workspaceRoot, savedPath),
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
getRecentChanges(args) {
|
|
469
|
+
return this.engine.getRecentHistorySummary(optionalPositiveInteger(args, "limit", 10));
|
|
470
|
+
}
|
|
471
|
+
getApprovalStatus(args) {
|
|
472
|
+
const intentPath = optionalString(args, "intentPath") ?? "latest";
|
|
473
|
+
const intent = (0, core_1.loadChangeIntent)(this.workspaceRoot, intentPath);
|
|
474
|
+
return approvalStatusWithIntent(intent, (0, core_1.resolveRippleApprovalStatus)(this.workspaceRoot, intent, optionalApprovalGate(args, "gate")));
|
|
475
|
+
}
|
|
476
|
+
checkStaged(args) {
|
|
477
|
+
const stagedSummary = (0, core_1.buildStagedCheckSummary)(this.engine, {
|
|
478
|
+
workspaceRoot: this.workspaceRoot,
|
|
479
|
+
stagedFiles: (0, core_1.listGitStagedFiles)(this.workspaceRoot),
|
|
480
|
+
tokenBudget: optionalPositiveInteger(args, "tokenBudget", 4000),
|
|
481
|
+
});
|
|
482
|
+
const intentPath = optionalString(args, "intentPath");
|
|
483
|
+
if (!intentPath) {
|
|
484
|
+
return stagedSummary;
|
|
485
|
+
}
|
|
486
|
+
const intent = (0, core_1.loadChangeIntent)(this.workspaceRoot, intentPath);
|
|
487
|
+
return (0, core_1.validateStagedCheckAgainstIntent)(stagedSummary, intent, {
|
|
488
|
+
currentPolicyExplanation: this.currentPolicyExplanationForIntent(intent),
|
|
489
|
+
currentReadinessSnapshot: this.currentReadinessSnapshot(),
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
checkChanged(args) {
|
|
493
|
+
const baseRef = optionalString(args, "baseRef") ?? "HEAD";
|
|
494
|
+
const changedSummary = (0, core_1.buildStagedCheckSummary)(this.engine, {
|
|
495
|
+
workspaceRoot: this.workspaceRoot,
|
|
496
|
+
stagedFiles: (0, core_1.listGitChangedFiles)(this.workspaceRoot, baseRef),
|
|
497
|
+
mode: "changed",
|
|
498
|
+
baseRef,
|
|
499
|
+
tokenBudget: optionalPositiveInteger(args, "tokenBudget", 4000),
|
|
500
|
+
});
|
|
501
|
+
const intentPath = optionalString(args, "intentPath");
|
|
502
|
+
if (!intentPath) {
|
|
503
|
+
return changedSummary;
|
|
504
|
+
}
|
|
505
|
+
const intent = (0, core_1.loadChangeIntent)(this.workspaceRoot, intentPath);
|
|
506
|
+
return (0, core_1.validateStagedCheckAgainstIntent)(changedSummary, intent, {
|
|
507
|
+
currentPolicyExplanation: this.currentPolicyExplanationForIntent(intent),
|
|
508
|
+
currentReadinessSnapshot: this.currentReadinessSnapshot(),
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
auditChange(args) {
|
|
512
|
+
const mode = optionalAuditMode(args, "mode") ?? "staged";
|
|
513
|
+
const baseRef = optionalString(args, "baseRef") ?? "HEAD";
|
|
514
|
+
const intentPath = optionalString(args, "intentPath") ?? "latest";
|
|
515
|
+
const intent = (0, core_1.loadChangeIntent)(this.workspaceRoot, intentPath);
|
|
516
|
+
const currentPolicyExplanation = this.currentPolicyExplanationForIntent(intent);
|
|
517
|
+
const stagedCheck = (0, core_1.buildStagedCheckSummary)(this.engine, {
|
|
518
|
+
workspaceRoot: this.workspaceRoot,
|
|
519
|
+
stagedFiles: mode === "changed"
|
|
520
|
+
? (0, core_1.listGitChangedFiles)(this.workspaceRoot, baseRef)
|
|
521
|
+
: (0, core_1.listGitStagedFiles)(this.workspaceRoot),
|
|
522
|
+
mode,
|
|
523
|
+
baseRef: mode === "changed" ? baseRef : undefined,
|
|
524
|
+
tokenBudget: optionalPositiveInteger(args, "tokenBudget", 4000),
|
|
525
|
+
});
|
|
526
|
+
const validatedCheck = (0, core_1.validateStagedCheckAgainstIntent)(stagedCheck, intent, {
|
|
527
|
+
currentPolicyExplanation,
|
|
528
|
+
currentReadinessSnapshot: this.currentReadinessSnapshot(),
|
|
529
|
+
});
|
|
530
|
+
const repairPlan = (0, core_1.buildIntentDriftRepairPlan)(validatedCheck);
|
|
531
|
+
return (0, core_1.buildRippleAuditSummary)({
|
|
532
|
+
workspaceRoot: this.workspaceRoot,
|
|
533
|
+
mode,
|
|
534
|
+
baseRef: mode === "changed" ? baseRef : undefined,
|
|
535
|
+
stagedCheck: validatedCheck,
|
|
536
|
+
repairPlan,
|
|
537
|
+
intent,
|
|
538
|
+
currentPolicyExplanation,
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
gateChange(args) {
|
|
542
|
+
return (0, core_1.buildRippleGateSummary)(this.auditChange(args));
|
|
543
|
+
}
|
|
544
|
+
repairIntentDrift(args) {
|
|
545
|
+
const intentPath = optionalString(args, "intentPath") ?? "latest";
|
|
546
|
+
const stagedSummary = this.checkStaged({
|
|
547
|
+
tokenBudget: optionalPositiveInteger(args, "tokenBudget", 4000),
|
|
548
|
+
intentPath,
|
|
549
|
+
});
|
|
550
|
+
return (0, core_1.buildIntentDriftRepairPlan)(stagedSummary);
|
|
551
|
+
}
|
|
552
|
+
currentPolicyExplanationForIntent(intent) {
|
|
553
|
+
return (0, core_1.explainRipplePolicyForIntent)((0, core_1.loadRipplePolicy)(this.workspaceRoot), intent);
|
|
554
|
+
}
|
|
555
|
+
currentReadinessSnapshot() {
|
|
556
|
+
return (0, core_1.buildChangeIntentReadinessSnapshot)((0, core_1.buildRippleReadinessSummary)(this.workspaceRoot, this.engine));
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
exports.RippleMcpToolHost = RippleMcpToolHost;
|
|
560
|
+
function createRippleMcpToolHost(options) {
|
|
561
|
+
return new RippleMcpToolHost(options);
|
|
562
|
+
}
|
|
563
|
+
exports.createRippleMcpToolHost = createRippleMcpToolHost;
|
|
564
|
+
function requiredString(args, key) {
|
|
565
|
+
const value = args[key];
|
|
566
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
567
|
+
throw new Error(`${key} must be a non-empty string.`);
|
|
568
|
+
}
|
|
569
|
+
return value;
|
|
570
|
+
}
|
|
571
|
+
function optionalString(args, key) {
|
|
572
|
+
const value = args[key];
|
|
573
|
+
if (value === undefined || value === null) {
|
|
574
|
+
return undefined;
|
|
575
|
+
}
|
|
576
|
+
if (typeof value !== "string") {
|
|
577
|
+
throw new Error(`${key} must be a string.`);
|
|
578
|
+
}
|
|
579
|
+
return value;
|
|
580
|
+
}
|
|
581
|
+
function optionalControlMode(args, key) {
|
|
582
|
+
const value = optionalString(args, key);
|
|
583
|
+
if (value === undefined) {
|
|
584
|
+
return undefined;
|
|
585
|
+
}
|
|
586
|
+
if (MCP_CONTROL_MODES.includes(value)) {
|
|
587
|
+
return value;
|
|
588
|
+
}
|
|
589
|
+
throw new Error(`${key} must be one of: ${MCP_CONTROL_MODES.join(", ")}.`);
|
|
590
|
+
}
|
|
591
|
+
function optionalAuditMode(args, key) {
|
|
592
|
+
const value = optionalString(args, key);
|
|
593
|
+
if (value === undefined) {
|
|
594
|
+
return undefined;
|
|
595
|
+
}
|
|
596
|
+
if (MCP_AUDIT_MODES.includes(value)) {
|
|
597
|
+
return value;
|
|
598
|
+
}
|
|
599
|
+
throw new Error(`${key} must be one of: ${MCP_AUDIT_MODES.join(", ")}.`);
|
|
600
|
+
}
|
|
601
|
+
function optionalApprovalGate(args, key) {
|
|
602
|
+
const value = optionalString(args, key);
|
|
603
|
+
if (value === undefined) {
|
|
604
|
+
return undefined;
|
|
605
|
+
}
|
|
606
|
+
if (MCP_APPROVAL_GATES.includes(value)) {
|
|
607
|
+
return value;
|
|
608
|
+
}
|
|
609
|
+
throw new Error(`${key} must be one of: ${MCP_APPROVAL_GATES.join(", ")}.`);
|
|
610
|
+
}
|
|
611
|
+
function optionalStringArray(args, key) {
|
|
612
|
+
const value = args[key];
|
|
613
|
+
if (value === undefined || value === null) {
|
|
614
|
+
return undefined;
|
|
615
|
+
}
|
|
616
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
617
|
+
throw new Error(`${key} must be an array of strings.`);
|
|
618
|
+
}
|
|
619
|
+
return value;
|
|
620
|
+
}
|
|
621
|
+
function optionalPositiveInteger(args, key, fallback) {
|
|
622
|
+
const value = args[key];
|
|
623
|
+
if (value === undefined || value === null) {
|
|
624
|
+
return fallback;
|
|
625
|
+
}
|
|
626
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
627
|
+
throw new Error(`${key} must be a positive integer.`);
|
|
628
|
+
}
|
|
629
|
+
return value;
|
|
630
|
+
}
|
|
631
|
+
function optionalBoolean(args, key, fallback) {
|
|
632
|
+
const value = args[key];
|
|
633
|
+
if (value === undefined || value === null) {
|
|
634
|
+
return fallback;
|
|
635
|
+
}
|
|
636
|
+
if (typeof value !== "boolean") {
|
|
637
|
+
throw new Error(`${key} must be a boolean.`);
|
|
638
|
+
}
|
|
639
|
+
return value;
|
|
640
|
+
}
|
|
641
|
+
function parsePlanContextArgs(args) {
|
|
642
|
+
return {
|
|
643
|
+
task: optionalString(args, "task"),
|
|
644
|
+
filePath: optionalString(args, "filePath"),
|
|
645
|
+
targetFile: optionalString(args, "targetFile"),
|
|
646
|
+
tokenBudget: optionalPositiveInteger(args, "tokenBudget", 4000),
|
|
647
|
+
mode: optionalControlMode(args, "mode"),
|
|
648
|
+
controlMode: optionalControlMode(args, "controlMode"),
|
|
649
|
+
symbol: optionalString(args, "symbol"),
|
|
650
|
+
allowedSymbols: optionalStringArray(args, "allowedSymbols"),
|
|
651
|
+
saveIntent: optionalBoolean(args, "saveIntent", false),
|
|
652
|
+
intentPath: optionalString(args, "intentPath"),
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
function uniqueItems(items) {
|
|
656
|
+
const seen = new Set();
|
|
657
|
+
return items.filter((item) => {
|
|
658
|
+
if (seen.has(item)) {
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
seen.add(item);
|
|
662
|
+
return true;
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
function approvalStatusWithIntent(intent, status) {
|
|
666
|
+
return {
|
|
667
|
+
...status,
|
|
668
|
+
intent: {
|
|
669
|
+
id: intent.id,
|
|
670
|
+
task: intent.task,
|
|
671
|
+
targetFile: intent.targetFile,
|
|
672
|
+
controlMode: intent.controlMode,
|
|
673
|
+
humanGate: intent.humanGate,
|
|
674
|
+
boundaryRisk: intent.boundaryRisk,
|
|
675
|
+
},
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
function formatWorkspacePath(workspaceRoot, filePath) {
|
|
679
|
+
const relative = path.relative(workspaceRoot, filePath);
|
|
680
|
+
if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) {
|
|
681
|
+
return relative.split(path.sep).join("/");
|
|
682
|
+
}
|
|
683
|
+
return filePath;
|
|
684
|
+
}
|
|
685
|
+
function normalizeProjectPath(filePath) {
|
|
686
|
+
return filePath.replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
687
|
+
}
|
|
688
|
+
async function runWithQuietConsoleLog(task) {
|
|
689
|
+
const originalLog = console.log;
|
|
690
|
+
const originalStdoutWrite = process.stdout.write;
|
|
691
|
+
const stderrWrite = process.stderr.write;
|
|
692
|
+
console.log = (...args) => {
|
|
693
|
+
console.error(...args);
|
|
694
|
+
};
|
|
695
|
+
process.stdout.write = ((...args) => {
|
|
696
|
+
return stderrWrite.apply(process.stderr, args);
|
|
697
|
+
});
|
|
698
|
+
try {
|
|
699
|
+
return await task();
|
|
700
|
+
}
|
|
701
|
+
finally {
|
|
702
|
+
console.log = originalLog;
|
|
703
|
+
process.stdout.write = originalStdoutWrite;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
//# sourceMappingURL=tools.js.map
|