@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 CHANGED
@@ -1,18 +1,14 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
5
- };
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
8
- const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
9
- const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
10
- const fs_extra_1 = __importDefault(require("fs-extra"));
11
- const path_1 = __importDefault(require("path"));
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 = path_1.default.join(cwd, "rigour.yml");
25
- if (!(await fs_extra_1.default.pathExists(configPath))) {
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 fs_extra_1.default.readFile(configPath, "utf-8");
29
- return core_1.ConfigSchema.parse(yaml_1.default.parse(configContent));
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 = path_1.default.join(cwd, ".rigour");
33
- await fs_extra_1.default.ensureDir(rigourDir);
34
- return path_1.default.join(rigourDir, "memory.json");
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 fs_extra_1.default.pathExists(memPath)) {
39
- const content = await fs_extra_1.default.readFile(memPath, "utf-8");
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 fs_extra_1.default.writeFile(memPath, JSON.stringify(store, null, 2));
42
+ await fs.writeFile(memPath, JSON.stringify(store, null, 2));
47
43
  }
48
- server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
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(types_js_1.CallToolRequestSchema, async (request) => {
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 core_1.GateRunner(config);
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
- return {
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
- return {
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
- const bullets = report.failures.map((f, i) => {
267
- return `${i + 1}. [${f.id.toUpperCase()}] ${f.title}: ${f.details}${f.hint ? ` (Hint: ${f.hint})` : ''}`;
268
- }).join("\n");
269
- return {
270
- content: [
271
- {
272
- type: "text",
273
- text: `RIGOUR EXPLAIN:\n\n${bullets}`,
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
- return {
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
- return {
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
- const packet = report.failures.map((f, i) => {
307
- let text = `FIX TASK ${i + 1}: [${f.id.toUpperCase()}] ${f.title}\n`;
308
- text += ` - CONTEXT: ${f.details}\n`;
309
- if (f.files && f.files.length > 0) {
310
- text += ` - TARGET FILES: ${f.files.join(", ")}\n`;
311
- }
312
- if (f.hint) {
313
- text += ` - REFACTORING GUIDANCE: ${f.hint}\n`;
314
- }
315
- return text;
316
- }).join("\n---\n");
317
- return {
318
- content: [
319
- {
320
- type: "text",
321
- 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}`,
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
- return {
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
- return {
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
- return {
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
- return {
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
- return {
382
- content: [
383
- {
384
- type: "text",
385
- text: `RECALLED MEMORY [${key}]:\n${memory.value}\n\n(Stored: ${memory.timestamp})`,
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
- const keys = Object.keys(store.memories);
391
- if (keys.length === 0) {
392
- return {
393
- content: [
394
- {
395
- type: "text",
396
- text: "NO MEMORIES STORED. Use rigour_remember to persist important instructions.",
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
- const allMemories = keys.map(k => {
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
- return {
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
- delete store.memories[key];
428
- await saveMemory(cwd, store);
429
- return {
430
- content: [
431
- {
432
- type: "text",
433
- text: `MEMORY DELETED: "${key}" has been removed.`,
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 = (0, pattern_index_1.getDefaultIndexPath)(cwd);
441
- const index = await (0, pattern_index_1.loadPatternIndex)(indexPath);
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 pattern_index_1.PatternMatcher(index);
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 pattern_index_1.StalenessDetector(cwd);
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 pattern_index_1.SecurityDetector(cwd);
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
- return {
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 pattern_index_1.SecurityDetector(cwd);
573
+ const security = new SecurityDetector(cwd);
507
574
  const summary = await security.getSecuritySummary();
508
- return {
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
- return {
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 stdio_js_1.StdioServerTransport();
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
  }
@@ -1,8 +1,6 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const vitest_1 = require("vitest");
4
- (0, vitest_1.describe)('MCP Smoke Test', () => {
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.10.0",
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.10.0"
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
- return {
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
- return {
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
- const bullets = report.failures.map((f, i) => {
294
- return `${i + 1}. [${f.id.toUpperCase()}] ${f.title}: ${f.details}${f.hint ? ` (Hint: ${f.hint})` : ''}`;
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
- return {
298
- content: [
299
- {
300
- type: "text",
301
- text: `RIGOUR EXPLAIN:\n\n${bullets}`,
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
- return {
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
- return {
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
- const packet = report.failures.map((f, i) => {
338
- let text = `FIX TASK ${i + 1}: [${f.id.toUpperCase()}] ${f.title}\n`;
339
- text += ` - CONTEXT: ${f.details}\n`;
340
- if (f.files && f.files.length > 0) {
341
- text += ` - TARGET FILES: ${f.files.join(", ")}\n`;
342
- }
343
- if (f.hint) {
344
- text += ` - REFACTORING GUIDANCE: ${f.hint}\n`;
345
- }
346
- return text;
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
- return {
350
- content: [
351
- {
352
- type: "text",
353
- 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}`,
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
- return {
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
- return {
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
- return {
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
- return {
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
- return {
419
- content: [
420
- {
421
- type: "text",
422
- text: `RECALLED MEMORY [${key}]:\n${memory.value}\n\n(Stored: ${memory.timestamp})`,
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
- const keys = Object.keys(store.memories);
429
- if (keys.length === 0) {
430
- return {
431
- content: [
432
- {
433
- type: "text",
434
- text: "NO MEMORIES STORED. Use rigour_remember to persist important instructions.",
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
- return {
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
- delete store.memories[key];
471
- await saveMemory(cwd, store);
530
+ } else {
531
+ delete store.memories[key];
532
+ await saveMemory(cwd, store);
472
533
 
473
- return {
474
- content: [
475
- {
476
- type: "text",
477
- text: `MEMORY DELETED: "${key}" has been removed.`,
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
- return {
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
- return {
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
- return {
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