@rigour-labs/mcp 2.10.0 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +272 -116
- package/dist/smoke.test.js +4 -6
- package/package.json +4 -2
- package/src/index.ts +253 -91
package/dist/index.js
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const yaml_1 = __importDefault(require("yaml"));
|
|
13
|
-
const core_1 = require("@rigour-labs/core");
|
|
14
|
-
const pattern_index_1 = require("@rigour-labs/core/pattern-index");
|
|
15
|
-
const server = new index_js_1.Server({
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import fs from "fs-extra";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import yaml from "yaml";
|
|
8
|
+
import { randomUUID } from "crypto";
|
|
9
|
+
import { GateRunner, ConfigSchema, } from "@rigour-labs/core";
|
|
10
|
+
import { PatternMatcher, loadPatternIndex, getDefaultIndexPath, StalenessDetector, SecurityDetector } from "@rigour-labs/core/pattern-index";
|
|
11
|
+
const server = new Server({
|
|
16
12
|
name: "rigour-mcp",
|
|
17
13
|
version: "1.0.0",
|
|
18
14
|
}, {
|
|
@@ -21,31 +17,48 @@ const server = new index_js_1.Server({
|
|
|
21
17
|
},
|
|
22
18
|
});
|
|
23
19
|
async function loadConfig(cwd) {
|
|
24
|
-
const configPath =
|
|
25
|
-
if (!(await
|
|
20
|
+
const configPath = path.join(cwd, "rigour.yml");
|
|
21
|
+
if (!(await fs.pathExists(configPath))) {
|
|
26
22
|
throw new Error("Rigour configuration (rigour.yml) not found. The agent must run `rigour init` first to establish engineering standards.");
|
|
27
23
|
}
|
|
28
|
-
const configContent = await
|
|
29
|
-
return
|
|
24
|
+
const configContent = await fs.readFile(configPath, "utf-8");
|
|
25
|
+
return ConfigSchema.parse(yaml.parse(configContent));
|
|
30
26
|
}
|
|
31
27
|
async function getMemoryPath(cwd) {
|
|
32
|
-
const rigourDir =
|
|
33
|
-
await
|
|
34
|
-
return
|
|
28
|
+
const rigourDir = path.join(cwd, ".rigour");
|
|
29
|
+
await fs.ensureDir(rigourDir);
|
|
30
|
+
return path.join(rigourDir, "memory.json");
|
|
35
31
|
}
|
|
36
32
|
async function loadMemory(cwd) {
|
|
37
33
|
const memPath = await getMemoryPath(cwd);
|
|
38
|
-
if (await
|
|
39
|
-
const content = await
|
|
34
|
+
if (await fs.pathExists(memPath)) {
|
|
35
|
+
const content = await fs.readFile(memPath, "utf-8");
|
|
40
36
|
return JSON.parse(content);
|
|
41
37
|
}
|
|
42
38
|
return { memories: {} };
|
|
43
39
|
}
|
|
44
40
|
async function saveMemory(cwd, store) {
|
|
45
41
|
const memPath = await getMemoryPath(cwd);
|
|
46
|
-
await
|
|
42
|
+
await fs.writeFile(memPath, JSON.stringify(store, null, 2));
|
|
47
43
|
}
|
|
48
|
-
|
|
44
|
+
// Helper to log events for Rigour Studio
|
|
45
|
+
async function logStudioEvent(cwd, event) {
|
|
46
|
+
try {
|
|
47
|
+
const rigourDir = path.join(cwd, ".rigour");
|
|
48
|
+
await fs.ensureDir(rigourDir);
|
|
49
|
+
const eventsPath = path.join(rigourDir, "events.jsonl");
|
|
50
|
+
const logEntry = JSON.stringify({
|
|
51
|
+
id: randomUUID(),
|
|
52
|
+
timestamp: new Date().toISOString(),
|
|
53
|
+
...event
|
|
54
|
+
}) + "\n";
|
|
55
|
+
await fs.appendFile(eventsPath, logEntry);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Silent fail - Studio logging is non-blocking and zero-telemetry
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
49
62
|
return {
|
|
50
63
|
tools: [
|
|
51
64
|
{
|
|
@@ -230,19 +243,49 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
|
230
243
|
required: ["cwd"],
|
|
231
244
|
},
|
|
232
245
|
},
|
|
246
|
+
{
|
|
247
|
+
name: "rigour_run",
|
|
248
|
+
description: "Execute a command under Rigour supervision. This tool can be INTERCEPTED and ARBITRATED by the Governance Studio.",
|
|
249
|
+
inputSchema: {
|
|
250
|
+
type: "object",
|
|
251
|
+
properties: {
|
|
252
|
+
cwd: {
|
|
253
|
+
type: "string",
|
|
254
|
+
description: "Absolute path to the project root.",
|
|
255
|
+
},
|
|
256
|
+
command: {
|
|
257
|
+
type: "string",
|
|
258
|
+
description: "The command to run (e.g., 'npm test', 'pytest').",
|
|
259
|
+
},
|
|
260
|
+
silent: {
|
|
261
|
+
type: "boolean",
|
|
262
|
+
description: "If true, hides the command output from the agent.",
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
required: ["cwd", "command"],
|
|
266
|
+
},
|
|
267
|
+
}
|
|
233
268
|
],
|
|
234
269
|
};
|
|
235
270
|
});
|
|
236
|
-
server.setRequestHandler(
|
|
271
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
237
272
|
const { name, arguments: args } = request.params;
|
|
238
273
|
const cwd = args?.cwd || process.cwd();
|
|
274
|
+
const requestId = randomUUID();
|
|
239
275
|
try {
|
|
276
|
+
await logStudioEvent(cwd, {
|
|
277
|
+
type: "tool_call",
|
|
278
|
+
requestId,
|
|
279
|
+
tool: name,
|
|
280
|
+
arguments: args
|
|
281
|
+
});
|
|
240
282
|
const config = await loadConfig(cwd);
|
|
241
|
-
const runner = new
|
|
283
|
+
const runner = new GateRunner(config);
|
|
284
|
+
let result;
|
|
242
285
|
switch (name) {
|
|
243
286
|
case "rigour_check": {
|
|
244
287
|
const report = await runner.run(cwd);
|
|
245
|
-
|
|
288
|
+
result = {
|
|
246
289
|
content: [
|
|
247
290
|
{
|
|
248
291
|
type: "text",
|
|
@@ -250,11 +293,14 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
250
293
|
},
|
|
251
294
|
],
|
|
252
295
|
};
|
|
296
|
+
// Add the report to the tool_response log for high-fidelity Studio visualization
|
|
297
|
+
result._rigour_report = report;
|
|
298
|
+
break;
|
|
253
299
|
}
|
|
254
300
|
case "rigour_explain": {
|
|
255
301
|
const report = await runner.run(cwd);
|
|
256
302
|
if (report.status === "PASS") {
|
|
257
|
-
|
|
303
|
+
result = {
|
|
258
304
|
content: [
|
|
259
305
|
{
|
|
260
306
|
type: "text",
|
|
@@ -263,21 +309,24 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
263
309
|
],
|
|
264
310
|
};
|
|
265
311
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
312
|
+
else {
|
|
313
|
+
const bullets = report.failures.map((f, i) => {
|
|
314
|
+
return `${i + 1}. [${f.id.toUpperCase()}] ${f.title}: ${f.details}${f.hint ? ` (Hint: ${f.hint})` : ''}`;
|
|
315
|
+
}).join("\n");
|
|
316
|
+
result = {
|
|
317
|
+
content: [
|
|
318
|
+
{
|
|
319
|
+
type: "text",
|
|
320
|
+
text: `RIGOUR EXPLAIN:\n\n${bullets}`,
|
|
321
|
+
},
|
|
322
|
+
],
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
break;
|
|
277
326
|
}
|
|
278
327
|
case "rigour_status": {
|
|
279
328
|
const report = await runner.run(cwd);
|
|
280
|
-
|
|
329
|
+
result = {
|
|
281
330
|
content: [
|
|
282
331
|
{
|
|
283
332
|
type: "text",
|
|
@@ -290,11 +339,12 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
290
339
|
},
|
|
291
340
|
],
|
|
292
341
|
};
|
|
342
|
+
break;
|
|
293
343
|
}
|
|
294
344
|
case "rigour_get_fix_packet": {
|
|
295
345
|
const report = await runner.run(cwd);
|
|
296
346
|
if (report.status === "PASS") {
|
|
297
|
-
|
|
347
|
+
result = {
|
|
298
348
|
content: [
|
|
299
349
|
{
|
|
300
350
|
type: "text",
|
|
@@ -303,28 +353,31 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
303
353
|
],
|
|
304
354
|
};
|
|
305
355
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
356
|
+
else {
|
|
357
|
+
const packet = report.failures.map((f, i) => {
|
|
358
|
+
let text = `FIX TASK ${i + 1}: [${f.id.toUpperCase()}] ${f.title}\n`;
|
|
359
|
+
text += ` - CONTEXT: ${f.details}\n`;
|
|
360
|
+
if (f.files && f.files.length > 0) {
|
|
361
|
+
text += ` - TARGET FILES: ${f.files.join(", ")}\n`;
|
|
362
|
+
}
|
|
363
|
+
if (f.hint) {
|
|
364
|
+
text += ` - REFACTORING GUIDANCE: ${f.hint}\n`;
|
|
365
|
+
}
|
|
366
|
+
return text;
|
|
367
|
+
}).join("\n---\n");
|
|
368
|
+
result = {
|
|
369
|
+
content: [
|
|
370
|
+
{
|
|
371
|
+
type: "text",
|
|
372
|
+
text: `ENGINEERING REFINEMENT REQUIRED:\n\nThe project state violated ${report.failures.length} quality gates. You MUST address these failures before declaring the task complete:\n\n${packet}`,
|
|
373
|
+
},
|
|
374
|
+
],
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
break;
|
|
325
378
|
}
|
|
326
379
|
case "rigour_list_gates":
|
|
327
|
-
|
|
380
|
+
result = {
|
|
328
381
|
content: [
|
|
329
382
|
{
|
|
330
383
|
type: "text",
|
|
@@ -337,8 +390,9 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
337
390
|
},
|
|
338
391
|
],
|
|
339
392
|
};
|
|
393
|
+
break;
|
|
340
394
|
case "rigour_get_config":
|
|
341
|
-
|
|
395
|
+
result = {
|
|
342
396
|
content: [
|
|
343
397
|
{
|
|
344
398
|
type: "text",
|
|
@@ -346,6 +400,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
346
400
|
},
|
|
347
401
|
],
|
|
348
402
|
};
|
|
403
|
+
break;
|
|
349
404
|
case "rigour_remember": {
|
|
350
405
|
const { key, value } = args;
|
|
351
406
|
const store = await loadMemory(cwd);
|
|
@@ -354,7 +409,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
354
409
|
timestamp: new Date().toISOString(),
|
|
355
410
|
};
|
|
356
411
|
await saveMemory(cwd, store);
|
|
357
|
-
|
|
412
|
+
result = {
|
|
358
413
|
content: [
|
|
359
414
|
{
|
|
360
415
|
type: "text",
|
|
@@ -362,6 +417,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
362
417
|
},
|
|
363
418
|
],
|
|
364
419
|
};
|
|
420
|
+
break;
|
|
365
421
|
}
|
|
366
422
|
case "rigour_recall": {
|
|
367
423
|
const { key } = args;
|
|
@@ -369,7 +425,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
369
425
|
if (key) {
|
|
370
426
|
const memory = store.memories[key];
|
|
371
427
|
if (!memory) {
|
|
372
|
-
|
|
428
|
+
result = {
|
|
373
429
|
content: [
|
|
374
430
|
{
|
|
375
431
|
type: "text",
|
|
@@ -378,44 +434,51 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
378
434
|
],
|
|
379
435
|
};
|
|
380
436
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
437
|
+
else {
|
|
438
|
+
result = {
|
|
439
|
+
content: [
|
|
440
|
+
{
|
|
441
|
+
type: "text",
|
|
442
|
+
text: `RECALLED MEMORY [${key}]:\n${memory.value}\n\n(Stored: ${memory.timestamp})`,
|
|
443
|
+
},
|
|
444
|
+
],
|
|
445
|
+
};
|
|
446
|
+
}
|
|
389
447
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
448
|
+
else {
|
|
449
|
+
const keys = Object.keys(store.memories);
|
|
450
|
+
if (keys.length === 0) {
|
|
451
|
+
result = {
|
|
452
|
+
content: [
|
|
453
|
+
{
|
|
454
|
+
type: "text",
|
|
455
|
+
text: "NO MEMORIES STORED. Use rigour_remember to persist important instructions.",
|
|
456
|
+
},
|
|
457
|
+
],
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
const allMemories = keys.map(k => {
|
|
462
|
+
const mem = store.memories[k];
|
|
463
|
+
return `## ${k}\n${mem.value}\n(Stored: ${mem.timestamp})`;
|
|
464
|
+
}).join("\n\n---\n\n");
|
|
465
|
+
result = {
|
|
466
|
+
content: [
|
|
467
|
+
{
|
|
468
|
+
type: "text",
|
|
469
|
+
text: `RECALLED ALL MEMORIES (${keys.length} items):\n\n${allMemories}\n\n---\nIMPORTANT: Follow these stored instructions throughout this session.`,
|
|
470
|
+
},
|
|
471
|
+
],
|
|
472
|
+
};
|
|
473
|
+
}
|
|
400
474
|
}
|
|
401
|
-
|
|
402
|
-
const mem = store.memories[k];
|
|
403
|
-
return `## ${k}\n${mem.value}\n(Stored: ${mem.timestamp})`;
|
|
404
|
-
}).join("\n\n---\n\n");
|
|
405
|
-
return {
|
|
406
|
-
content: [
|
|
407
|
-
{
|
|
408
|
-
type: "text",
|
|
409
|
-
text: `RECALLED ALL MEMORIES (${keys.length} items):\n\n${allMemories}\n\n---\nIMPORTANT: Follow these stored instructions throughout this session.`,
|
|
410
|
-
},
|
|
411
|
-
],
|
|
412
|
-
};
|
|
475
|
+
break;
|
|
413
476
|
}
|
|
414
477
|
case "rigour_forget": {
|
|
415
478
|
const { key } = args;
|
|
416
479
|
const store = await loadMemory(cwd);
|
|
417
480
|
if (!store.memories[key]) {
|
|
418
|
-
|
|
481
|
+
result = {
|
|
419
482
|
content: [
|
|
420
483
|
{
|
|
421
484
|
type: "text",
|
|
@@ -424,25 +487,28 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
424
487
|
],
|
|
425
488
|
};
|
|
426
489
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
490
|
+
else {
|
|
491
|
+
delete store.memories[key];
|
|
492
|
+
await saveMemory(cwd, store);
|
|
493
|
+
result = {
|
|
494
|
+
content: [
|
|
495
|
+
{
|
|
496
|
+
type: "text",
|
|
497
|
+
text: `MEMORY DELETED: "${key}" has been removed.`,
|
|
498
|
+
},
|
|
499
|
+
],
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
break;
|
|
437
503
|
}
|
|
438
504
|
case "rigour_check_pattern": {
|
|
439
505
|
const { name: patternName, type, intent } = args;
|
|
440
|
-
const indexPath =
|
|
441
|
-
const index = await
|
|
506
|
+
const indexPath = getDefaultIndexPath(cwd);
|
|
507
|
+
const index = await loadPatternIndex(indexPath);
|
|
442
508
|
let resultText = "";
|
|
443
509
|
// 1. Check for Reinvention
|
|
444
510
|
if (index) {
|
|
445
|
-
const matcher = new
|
|
511
|
+
const matcher = new PatternMatcher(index);
|
|
446
512
|
const matchResult = await matcher.match({ name: patternName, type, intent });
|
|
447
513
|
if (matchResult.status === "FOUND_SIMILAR") {
|
|
448
514
|
resultText += `🚨 PATTERN REINVENTION DETECTED\n`;
|
|
@@ -454,7 +520,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
454
520
|
resultText += `⚠️ Pattern index not found. Run 'rigour index' to enable reinvention detection.\n\n`;
|
|
455
521
|
}
|
|
456
522
|
// 2. Check for Staleness/Best Practices
|
|
457
|
-
const detector = new
|
|
523
|
+
const detector = new StalenessDetector(cwd);
|
|
458
524
|
const staleness = await detector.checkStaleness(`${type || 'function'} ${patternName} {}`);
|
|
459
525
|
if (staleness.status !== "FRESH") {
|
|
460
526
|
resultText += `⚠️ STALENESS/ANTI-PATTERN WARNING\n`;
|
|
@@ -465,7 +531,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
465
531
|
}
|
|
466
532
|
// 3. Check Security for this library (if it's an import)
|
|
467
533
|
if (intent && intent.includes('import')) {
|
|
468
|
-
const security = new
|
|
534
|
+
const security = new SecurityDetector(cwd);
|
|
469
535
|
const audit = await security.runAudit();
|
|
470
536
|
const relatedVulns = audit.vulnerabilities.filter(v => patternName.toLowerCase().includes(v.packageName.toLowerCase()) ||
|
|
471
537
|
intent.toLowerCase().includes(v.packageName.toLowerCase()));
|
|
@@ -493,7 +559,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
493
559
|
}
|
|
494
560
|
resultText += `\nRECOMMENDED ACTION: ${recommendation}`;
|
|
495
561
|
}
|
|
496
|
-
|
|
562
|
+
result = {
|
|
497
563
|
content: [
|
|
498
564
|
{
|
|
499
565
|
type: "text",
|
|
@@ -501,11 +567,12 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
501
567
|
},
|
|
502
568
|
],
|
|
503
569
|
};
|
|
570
|
+
break;
|
|
504
571
|
}
|
|
505
572
|
case "rigour_security_audit": {
|
|
506
|
-
const security = new
|
|
573
|
+
const security = new SecurityDetector(cwd);
|
|
507
574
|
const summary = await security.getSecuritySummary();
|
|
508
|
-
|
|
575
|
+
result = {
|
|
509
576
|
content: [
|
|
510
577
|
{
|
|
511
578
|
type: "text",
|
|
@@ -513,13 +580,93 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
513
580
|
},
|
|
514
581
|
],
|
|
515
582
|
};
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
case "rigour_run": {
|
|
586
|
+
const { command } = args;
|
|
587
|
+
// 1. Log Interceptable Event
|
|
588
|
+
await logStudioEvent(cwd, {
|
|
589
|
+
type: "interception_requested",
|
|
590
|
+
requestId: requestId,
|
|
591
|
+
tool: "rigour_run",
|
|
592
|
+
command
|
|
593
|
+
});
|
|
594
|
+
// 2. Poll for Human Arbitration (Max 60s wait for this demo/test)
|
|
595
|
+
// In production, this would be a blocking call wait
|
|
596
|
+
console.error(`[RIGOUR] Waiting for human arbitration for command: ${command}`);
|
|
597
|
+
const pollArbitration = async (rid, timeout) => {
|
|
598
|
+
const start = Date.now();
|
|
599
|
+
const eventsPath = path.join(cwd, '.rigour/events.jsonl');
|
|
600
|
+
while (Date.now() - start < timeout) {
|
|
601
|
+
if (await fs.pathExists(eventsPath)) {
|
|
602
|
+
const content = await fs.readFile(eventsPath, 'utf-8');
|
|
603
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
604
|
+
for (const line of lines.reverse()) {
|
|
605
|
+
const event = JSON.parse(line);
|
|
606
|
+
if (event.tool === 'human_arbitration' && event.requestId === rid) {
|
|
607
|
+
return event.decision;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
612
|
+
}
|
|
613
|
+
return "approve"; // Default to auto-approve if no human response (for non-blocking feel)
|
|
614
|
+
};
|
|
615
|
+
const decision = await pollArbitration(requestId, 60000);
|
|
616
|
+
if (decision === 'reject') {
|
|
617
|
+
result = {
|
|
618
|
+
content: [
|
|
619
|
+
{
|
|
620
|
+
type: "text",
|
|
621
|
+
text: `❌ COMMAND REJECTED BY GOVERNOR: The execution of "${command}" was blocked by a human operator in the Governance Studio.`,
|
|
622
|
+
},
|
|
623
|
+
],
|
|
624
|
+
isError: true
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
// Execute
|
|
629
|
+
const { execa } = await import("execa");
|
|
630
|
+
try {
|
|
631
|
+
const { stdout, stderr } = await execa(command, { shell: true, cwd });
|
|
632
|
+
result = {
|
|
633
|
+
content: [
|
|
634
|
+
{
|
|
635
|
+
type: "text",
|
|
636
|
+
text: `✅ COMMAND EXECUTED (Approved by Governor):\n\nSTDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`,
|
|
637
|
+
},
|
|
638
|
+
],
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
catch (e) {
|
|
642
|
+
result = {
|
|
643
|
+
content: [
|
|
644
|
+
{
|
|
645
|
+
type: "text",
|
|
646
|
+
text: `❌ COMMAND FAILED:\n\n${e.message}`,
|
|
647
|
+
},
|
|
648
|
+
],
|
|
649
|
+
isError: true
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
break;
|
|
516
654
|
}
|
|
517
655
|
default:
|
|
518
656
|
throw new Error(`Unknown tool: ${name}`);
|
|
519
657
|
}
|
|
658
|
+
await logStudioEvent(cwd, {
|
|
659
|
+
type: "tool_response",
|
|
660
|
+
requestId,
|
|
661
|
+
tool: name,
|
|
662
|
+
status: "success",
|
|
663
|
+
content: result.content,
|
|
664
|
+
_rigour_report: result._rigour_report
|
|
665
|
+
});
|
|
666
|
+
return result;
|
|
520
667
|
}
|
|
521
668
|
catch (error) {
|
|
522
|
-
|
|
669
|
+
const errorResponse = {
|
|
523
670
|
content: [
|
|
524
671
|
{
|
|
525
672
|
type: "text",
|
|
@@ -528,10 +675,19 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
528
675
|
],
|
|
529
676
|
isError: true,
|
|
530
677
|
};
|
|
678
|
+
await logStudioEvent(cwd, {
|
|
679
|
+
type: "tool_response",
|
|
680
|
+
requestId,
|
|
681
|
+
tool: name,
|
|
682
|
+
status: "error",
|
|
683
|
+
error: error.message,
|
|
684
|
+
content: errorResponse.content
|
|
685
|
+
});
|
|
686
|
+
return errorResponse;
|
|
531
687
|
}
|
|
532
688
|
});
|
|
533
689
|
async function main() {
|
|
534
|
-
const transport = new
|
|
690
|
+
const transport = new StdioServerTransport();
|
|
535
691
|
await server.connect(transport);
|
|
536
692
|
console.error("Rigour MCP server v1.0.0 running on stdio");
|
|
537
693
|
}
|
package/dist/smoke.test.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
(
|
|
5
|
-
(0, vitest_1.it)('should pass', async () => {
|
|
6
|
-
(0, vitest_1.expect)(true).toBe(true);
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
describe('MCP Smoke Test', () => {
|
|
3
|
+
it('should pass', async () => {
|
|
4
|
+
expect(true).toBe(true);
|
|
7
5
|
});
|
|
8
6
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.0",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"mcpName": "io.github.rigour-labs/rigour",
|
|
5
6
|
"description": "Quality gates for AI-generated code. Forces AI agents to meet strict engineering standards with PASS/FAIL enforcement.",
|
|
6
7
|
"bin": {
|
|
@@ -16,9 +17,10 @@
|
|
|
16
17
|
},
|
|
17
18
|
"dependencies": {
|
|
18
19
|
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
20
|
+
"execa": "^8.0.1",
|
|
19
21
|
"fs-extra": "^11.2.0",
|
|
20
22
|
"yaml": "^2.8.2",
|
|
21
|
-
"@rigour-labs/core": "2.
|
|
23
|
+
"@rigour-labs/core": "2.12.0"
|
|
22
24
|
},
|
|
23
25
|
"devDependencies": {
|
|
24
26
|
"@types/node": "^25.0.3"
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
import fs from "fs-extra";
|
|
9
9
|
import path from "path";
|
|
10
10
|
import yaml from "yaml";
|
|
11
|
+
import { randomUUID } from "crypto";
|
|
11
12
|
import {
|
|
12
13
|
GateRunner,
|
|
13
14
|
ConfigSchema,
|
|
@@ -67,6 +68,23 @@ async function saveMemory(cwd: string, store: MemoryStore): Promise<void> {
|
|
|
67
68
|
await fs.writeFile(memPath, JSON.stringify(store, null, 2));
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
// Helper to log events for Rigour Studio
|
|
72
|
+
async function logStudioEvent(cwd: string, event: any) {
|
|
73
|
+
try {
|
|
74
|
+
const rigourDir = path.join(cwd, ".rigour");
|
|
75
|
+
await fs.ensureDir(rigourDir);
|
|
76
|
+
const eventsPath = path.join(rigourDir, "events.jsonl");
|
|
77
|
+
const logEntry = JSON.stringify({
|
|
78
|
+
id: randomUUID(),
|
|
79
|
+
timestamp: new Date().toISOString(),
|
|
80
|
+
...event
|
|
81
|
+
}) + "\n";
|
|
82
|
+
await fs.appendFile(eventsPath, logEntry);
|
|
83
|
+
} catch {
|
|
84
|
+
// Silent fail - Studio logging is non-blocking and zero-telemetry
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
70
88
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
71
89
|
return {
|
|
72
90
|
tools: [
|
|
@@ -252,6 +270,28 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
252
270
|
required: ["cwd"],
|
|
253
271
|
},
|
|
254
272
|
},
|
|
273
|
+
{
|
|
274
|
+
name: "rigour_run",
|
|
275
|
+
description: "Execute a command under Rigour supervision. This tool can be INTERCEPTED and ARBITRATED by the Governance Studio.",
|
|
276
|
+
inputSchema: {
|
|
277
|
+
type: "object",
|
|
278
|
+
properties: {
|
|
279
|
+
cwd: {
|
|
280
|
+
type: "string",
|
|
281
|
+
description: "Absolute path to the project root.",
|
|
282
|
+
},
|
|
283
|
+
command: {
|
|
284
|
+
type: "string",
|
|
285
|
+
description: "The command to run (e.g., 'npm test', 'pytest').",
|
|
286
|
+
},
|
|
287
|
+
silent: {
|
|
288
|
+
type: "boolean",
|
|
289
|
+
description: "If true, hides the command output from the agent.",
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
required: ["cwd", "command"],
|
|
293
|
+
},
|
|
294
|
+
}
|
|
255
295
|
],
|
|
256
296
|
};
|
|
257
297
|
});
|
|
@@ -259,15 +299,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
259
299
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
260
300
|
const { name, arguments: args } = request.params;
|
|
261
301
|
const cwd = (args as any)?.cwd || process.cwd();
|
|
302
|
+
const requestId = randomUUID();
|
|
262
303
|
|
|
263
304
|
try {
|
|
305
|
+
await logStudioEvent(cwd, {
|
|
306
|
+
type: "tool_call",
|
|
307
|
+
requestId,
|
|
308
|
+
tool: name,
|
|
309
|
+
arguments: args
|
|
310
|
+
});
|
|
311
|
+
|
|
264
312
|
const config = await loadConfig(cwd);
|
|
265
313
|
const runner = new GateRunner(config);
|
|
266
314
|
|
|
315
|
+
let result: any;
|
|
316
|
+
|
|
267
317
|
switch (name) {
|
|
268
318
|
case "rigour_check": {
|
|
269
319
|
const report = await runner.run(cwd);
|
|
270
|
-
|
|
320
|
+
result = {
|
|
271
321
|
content: [
|
|
272
322
|
{
|
|
273
323
|
type: "text",
|
|
@@ -275,12 +325,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
275
325
|
},
|
|
276
326
|
],
|
|
277
327
|
};
|
|
328
|
+
|
|
329
|
+
// Add the report to the tool_response log for high-fidelity Studio visualization
|
|
330
|
+
(result as any)._rigour_report = report;
|
|
331
|
+
break;
|
|
278
332
|
}
|
|
279
333
|
|
|
280
334
|
case "rigour_explain": {
|
|
281
335
|
const report = await runner.run(cwd);
|
|
282
336
|
if (report.status === "PASS") {
|
|
283
|
-
|
|
337
|
+
result = {
|
|
284
338
|
content: [
|
|
285
339
|
{
|
|
286
340
|
type: "text",
|
|
@@ -288,25 +342,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
288
342
|
},
|
|
289
343
|
],
|
|
290
344
|
};
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}).join("\n");
|
|
345
|
+
} else {
|
|
346
|
+
const bullets = report.failures.map((f, i) => {
|
|
347
|
+
return `${i + 1}. [${f.id.toUpperCase()}] ${f.title}: ${f.details}${f.hint ? ` (Hint: ${f.hint})` : ''}`;
|
|
348
|
+
}).join("\n");
|
|
296
349
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
350
|
+
result = {
|
|
351
|
+
content: [
|
|
352
|
+
{
|
|
353
|
+
type: "text",
|
|
354
|
+
text: `RIGOUR EXPLAIN:\n\n${bullets}`,
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
305
360
|
}
|
|
306
361
|
|
|
307
362
|
case "rigour_status": {
|
|
308
363
|
const report = await runner.run(cwd);
|
|
309
|
-
|
|
364
|
+
result = {
|
|
310
365
|
content: [
|
|
311
366
|
{
|
|
312
367
|
type: "text",
|
|
@@ -319,12 +374,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
319
374
|
},
|
|
320
375
|
],
|
|
321
376
|
};
|
|
377
|
+
break;
|
|
322
378
|
}
|
|
323
379
|
|
|
324
380
|
case "rigour_get_fix_packet": {
|
|
325
381
|
const report = await runner.run(cwd);
|
|
326
382
|
if (report.status === "PASS") {
|
|
327
|
-
|
|
383
|
+
result = {
|
|
328
384
|
content: [
|
|
329
385
|
{
|
|
330
386
|
type: "text",
|
|
@@ -332,32 +388,33 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
332
388
|
},
|
|
333
389
|
],
|
|
334
390
|
};
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}).join("\n---\n");
|
|
391
|
+
} else {
|
|
392
|
+
const packet = report.failures.map((f, i) => {
|
|
393
|
+
let text = `FIX TASK ${i + 1}: [${f.id.toUpperCase()}] ${f.title}\n`;
|
|
394
|
+
text += ` - CONTEXT: ${f.details}\n`;
|
|
395
|
+
if (f.files && f.files.length > 0) {
|
|
396
|
+
text += ` - TARGET FILES: ${f.files.join(", ")}\n`;
|
|
397
|
+
}
|
|
398
|
+
if (f.hint) {
|
|
399
|
+
text += ` - REFACTORING GUIDANCE: ${f.hint}\n`;
|
|
400
|
+
}
|
|
401
|
+
return text;
|
|
402
|
+
}).join("\n---\n");
|
|
348
403
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
404
|
+
result = {
|
|
405
|
+
content: [
|
|
406
|
+
{
|
|
407
|
+
type: "text",
|
|
408
|
+
text: `ENGINEERING REFINEMENT REQUIRED:\n\nThe project state violated ${report.failures.length} quality gates. You MUST address these failures before declaring the task complete:\n\n${packet}`,
|
|
409
|
+
},
|
|
410
|
+
],
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
break;
|
|
357
414
|
}
|
|
358
415
|
|
|
359
416
|
case "rigour_list_gates":
|
|
360
|
-
|
|
417
|
+
result = {
|
|
361
418
|
content: [
|
|
362
419
|
{
|
|
363
420
|
type: "text",
|
|
@@ -370,9 +427,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
370
427
|
},
|
|
371
428
|
],
|
|
372
429
|
};
|
|
430
|
+
break;
|
|
373
431
|
|
|
374
432
|
case "rigour_get_config":
|
|
375
|
-
|
|
433
|
+
result = {
|
|
376
434
|
content: [
|
|
377
435
|
{
|
|
378
436
|
type: "text",
|
|
@@ -380,6 +438,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
380
438
|
},
|
|
381
439
|
],
|
|
382
440
|
};
|
|
441
|
+
break;
|
|
383
442
|
|
|
384
443
|
case "rigour_remember": {
|
|
385
444
|
const { key, value } = args as any;
|
|
@@ -389,7 +448,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
389
448
|
timestamp: new Date().toISOString(),
|
|
390
449
|
};
|
|
391
450
|
await saveMemory(cwd, store);
|
|
392
|
-
|
|
451
|
+
result = {
|
|
393
452
|
content: [
|
|
394
453
|
{
|
|
395
454
|
type: "text",
|
|
@@ -397,6 +456,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
397
456
|
},
|
|
398
457
|
],
|
|
399
458
|
};
|
|
459
|
+
break;
|
|
400
460
|
}
|
|
401
461
|
|
|
402
462
|
case "rigour_recall": {
|
|
@@ -406,7 +466,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
406
466
|
if (key) {
|
|
407
467
|
const memory = store.memories[key];
|
|
408
468
|
if (!memory) {
|
|
409
|
-
|
|
469
|
+
result = {
|
|
410
470
|
content: [
|
|
411
471
|
{
|
|
412
472
|
type: "text",
|
|
@@ -414,42 +474,44 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
414
474
|
},
|
|
415
475
|
],
|
|
416
476
|
};
|
|
477
|
+
} else {
|
|
478
|
+
result = {
|
|
479
|
+
content: [
|
|
480
|
+
{
|
|
481
|
+
type: "text",
|
|
482
|
+
text: `RECALLED MEMORY [${key}]:\n${memory.value}\n\n(Stored: ${memory.timestamp})`,
|
|
483
|
+
},
|
|
484
|
+
],
|
|
485
|
+
};
|
|
417
486
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
487
|
+
} else {
|
|
488
|
+
const keys = Object.keys(store.memories);
|
|
489
|
+
if (keys.length === 0) {
|
|
490
|
+
result = {
|
|
491
|
+
content: [
|
|
492
|
+
{
|
|
493
|
+
type: "text",
|
|
494
|
+
text: "NO MEMORIES STORED. Use rigour_remember to persist important instructions.",
|
|
495
|
+
},
|
|
496
|
+
],
|
|
497
|
+
};
|
|
498
|
+
} else {
|
|
499
|
+
const allMemories = keys.map(k => {
|
|
500
|
+
const mem = store.memories[k];
|
|
501
|
+
return `## ${k}\n${mem.value}\n(Stored: ${mem.timestamp})`;
|
|
502
|
+
}).join("\n\n---\n\n");
|
|
427
503
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
};
|
|
504
|
+
result = {
|
|
505
|
+
content: [
|
|
506
|
+
{
|
|
507
|
+
type: "text",
|
|
508
|
+
text: `RECALLED ALL MEMORIES (${keys.length} items):\n\n${allMemories}\n\n---\nIMPORTANT: Follow these stored instructions throughout this session.`,
|
|
509
|
+
},
|
|
510
|
+
],
|
|
511
|
+
};
|
|
512
|
+
}
|
|
438
513
|
}
|
|
439
|
-
|
|
440
|
-
const allMemories = keys.map(k => {
|
|
441
|
-
const mem = store.memories[k];
|
|
442
|
-
return `## ${k}\n${mem.value}\n(Stored: ${mem.timestamp})`;
|
|
443
|
-
}).join("\n\n---\n\n");
|
|
444
|
-
|
|
445
|
-
return {
|
|
446
|
-
content: [
|
|
447
|
-
{
|
|
448
|
-
type: "text",
|
|
449
|
-
text: `RECALLED ALL MEMORIES (${keys.length} items):\n\n${allMemories}\n\n---\nIMPORTANT: Follow these stored instructions throughout this session.`,
|
|
450
|
-
},
|
|
451
|
-
],
|
|
452
|
-
};
|
|
514
|
+
break;
|
|
453
515
|
}
|
|
454
516
|
|
|
455
517
|
case "rigour_forget": {
|
|
@@ -457,7 +519,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
457
519
|
const store = await loadMemory(cwd);
|
|
458
520
|
|
|
459
521
|
if (!store.memories[key]) {
|
|
460
|
-
|
|
522
|
+
result = {
|
|
461
523
|
content: [
|
|
462
524
|
{
|
|
463
525
|
type: "text",
|
|
@@ -465,19 +527,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
465
527
|
},
|
|
466
528
|
],
|
|
467
529
|
};
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
await saveMemory(cwd, store);
|
|
530
|
+
} else {
|
|
531
|
+
delete store.memories[key];
|
|
532
|
+
await saveMemory(cwd, store);
|
|
472
533
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
534
|
+
result = {
|
|
535
|
+
content: [
|
|
536
|
+
{
|
|
537
|
+
type: "text",
|
|
538
|
+
text: `MEMORY DELETED: "${key}" has been removed.`,
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
break;
|
|
481
544
|
}
|
|
482
545
|
|
|
483
546
|
case "rigour_check_pattern": {
|
|
@@ -546,7 +609,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
546
609
|
resultText += `\nRECOMMENDED ACTION: ${recommendation}`;
|
|
547
610
|
}
|
|
548
611
|
|
|
549
|
-
|
|
612
|
+
result = {
|
|
550
613
|
content: [
|
|
551
614
|
{
|
|
552
615
|
type: "text",
|
|
@@ -554,12 +617,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
554
617
|
},
|
|
555
618
|
],
|
|
556
619
|
};
|
|
620
|
+
break;
|
|
557
621
|
}
|
|
558
622
|
|
|
559
623
|
case "rigour_security_audit": {
|
|
560
624
|
const security = new SecurityDetector(cwd);
|
|
561
625
|
const summary = await security.getSecuritySummary();
|
|
562
|
-
|
|
626
|
+
result = {
|
|
563
627
|
content: [
|
|
564
628
|
{
|
|
565
629
|
type: "text",
|
|
@@ -567,13 +631,100 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
567
631
|
},
|
|
568
632
|
],
|
|
569
633
|
};
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
case "rigour_run": {
|
|
638
|
+
const { command } = args as any;
|
|
639
|
+
|
|
640
|
+
// 1. Log Interceptable Event
|
|
641
|
+
await logStudioEvent(cwd, {
|
|
642
|
+
type: "interception_requested",
|
|
643
|
+
requestId: requestId,
|
|
644
|
+
tool: "rigour_run",
|
|
645
|
+
command
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
// 2. Poll for Human Arbitration (Max 60s wait for this demo/test)
|
|
649
|
+
// In production, this would be a blocking call wait
|
|
650
|
+
console.error(`[RIGOUR] Waiting for human arbitration for command: ${command}`);
|
|
651
|
+
|
|
652
|
+
const pollArbitration = async (rid: string, timeout: number): Promise<string | null> => {
|
|
653
|
+
const start = Date.now();
|
|
654
|
+
const eventsPath = path.join(cwd, '.rigour/events.jsonl');
|
|
655
|
+
while (Date.now() - start < timeout) {
|
|
656
|
+
if (await fs.pathExists(eventsPath)) {
|
|
657
|
+
const content = await fs.readFile(eventsPath, 'utf-8');
|
|
658
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
659
|
+
for (const line of lines.reverse()) {
|
|
660
|
+
const event = JSON.parse(line);
|
|
661
|
+
if (event.tool === 'human_arbitration' && event.requestId === rid) {
|
|
662
|
+
return event.decision;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
667
|
+
}
|
|
668
|
+
return "approve"; // Default to auto-approve if no human response (for non-blocking feel)
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
const decision = await pollArbitration(requestId, 60000);
|
|
672
|
+
|
|
673
|
+
if (decision === 'reject') {
|
|
674
|
+
result = {
|
|
675
|
+
content: [
|
|
676
|
+
{
|
|
677
|
+
type: "text",
|
|
678
|
+
text: `❌ COMMAND REJECTED BY GOVERNOR: The execution of "${command}" was blocked by a human operator in the Governance Studio.`,
|
|
679
|
+
},
|
|
680
|
+
],
|
|
681
|
+
isError: true
|
|
682
|
+
};
|
|
683
|
+
} else {
|
|
684
|
+
// Execute
|
|
685
|
+
const { execa } = await import("execa");
|
|
686
|
+
try {
|
|
687
|
+
const { stdout, stderr } = await execa(command, { shell: true, cwd });
|
|
688
|
+
result = {
|
|
689
|
+
content: [
|
|
690
|
+
{
|
|
691
|
+
type: "text",
|
|
692
|
+
text: `✅ COMMAND EXECUTED (Approved by Governor):\n\nSTDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`,
|
|
693
|
+
},
|
|
694
|
+
],
|
|
695
|
+
};
|
|
696
|
+
} catch (e: any) {
|
|
697
|
+
result = {
|
|
698
|
+
content: [
|
|
699
|
+
{
|
|
700
|
+
type: "text",
|
|
701
|
+
text: `❌ COMMAND FAILED:\n\n${e.message}`,
|
|
702
|
+
},
|
|
703
|
+
],
|
|
704
|
+
isError: true
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
break;
|
|
570
709
|
}
|
|
571
710
|
|
|
572
711
|
default:
|
|
573
712
|
throw new Error(`Unknown tool: ${name}`);
|
|
574
713
|
}
|
|
714
|
+
|
|
715
|
+
await logStudioEvent(cwd, {
|
|
716
|
+
type: "tool_response",
|
|
717
|
+
requestId,
|
|
718
|
+
tool: name,
|
|
719
|
+
status: "success",
|
|
720
|
+
content: result.content,
|
|
721
|
+
_rigour_report: (result as any)._rigour_report
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
return result;
|
|
725
|
+
|
|
575
726
|
} catch (error: any) {
|
|
576
|
-
|
|
727
|
+
const errorResponse = {
|
|
577
728
|
content: [
|
|
578
729
|
{
|
|
579
730
|
type: "text",
|
|
@@ -582,6 +733,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
582
733
|
],
|
|
583
734
|
isError: true,
|
|
584
735
|
};
|
|
736
|
+
|
|
737
|
+
await logStudioEvent(cwd, {
|
|
738
|
+
type: "tool_response",
|
|
739
|
+
requestId,
|
|
740
|
+
tool: name,
|
|
741
|
+
status: "error",
|
|
742
|
+
error: error.message,
|
|
743
|
+
content: errorResponse.content
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
return errorResponse;
|
|
585
747
|
}
|
|
586
748
|
});
|
|
587
749
|
|