@rigour-labs/mcp 2.19.2 → 2.20.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 +106 -0
- package/package.json +2 -2
- package/src/index.ts +111 -0
package/dist/index.js
CHANGED
|
@@ -67,6 +67,35 @@ async function logStudioEvent(cwd, event) {
|
|
|
67
67
|
// Silent fail - Studio logging is non-blocking and zero-telemetry
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
// Helper to parse diff and get modified lines per file
|
|
71
|
+
function parseDiff(diff) {
|
|
72
|
+
const lines = diff.split('\n');
|
|
73
|
+
const mapping = {};
|
|
74
|
+
let currentFile = "";
|
|
75
|
+
let currentLine = 0;
|
|
76
|
+
for (const line of lines) {
|
|
77
|
+
if (line.startsWith('+++ b/')) {
|
|
78
|
+
currentFile = line.slice(6);
|
|
79
|
+
mapping[currentFile] = new Set();
|
|
80
|
+
}
|
|
81
|
+
else if (line.startsWith('@@')) {
|
|
82
|
+
const match = line.match(/\+(\d+)/);
|
|
83
|
+
if (match) {
|
|
84
|
+
currentLine = parseInt(match[1], 10);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
88
|
+
if (currentFile) {
|
|
89
|
+
mapping[currentFile].add(currentLine);
|
|
90
|
+
}
|
|
91
|
+
currentLine++;
|
|
92
|
+
}
|
|
93
|
+
else if (!line.startsWith('-')) {
|
|
94
|
+
currentLine++;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return mapping;
|
|
98
|
+
}
|
|
70
99
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
71
100
|
return {
|
|
72
101
|
tools: [
|
|
@@ -430,6 +459,37 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
430
459
|
},
|
|
431
460
|
required: ["cwd", "handoffId", "agentId"],
|
|
432
461
|
},
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
name: "rigour_review",
|
|
465
|
+
description: "Perform a high-fidelity code review on a pull request diff. Analyzes changed files using all active quality gates.",
|
|
466
|
+
inputSchema: {
|
|
467
|
+
type: "object",
|
|
468
|
+
properties: {
|
|
469
|
+
cwd: {
|
|
470
|
+
type: "string",
|
|
471
|
+
description: "Absolute path to the project root.",
|
|
472
|
+
},
|
|
473
|
+
repository: {
|
|
474
|
+
type: "string",
|
|
475
|
+
description: "Full repository name (e.g., 'owner/repo').",
|
|
476
|
+
},
|
|
477
|
+
branch: {
|
|
478
|
+
type: "string",
|
|
479
|
+
description: "The branch containing the changes.",
|
|
480
|
+
},
|
|
481
|
+
diff: {
|
|
482
|
+
type: "string",
|
|
483
|
+
description: "The git diff content to analyze.",
|
|
484
|
+
},
|
|
485
|
+
files: {
|
|
486
|
+
type: "array",
|
|
487
|
+
items: { type: "string" },
|
|
488
|
+
description: "List of filenames that were changed.",
|
|
489
|
+
},
|
|
490
|
+
},
|
|
491
|
+
required: ["cwd", "diff"],
|
|
492
|
+
},
|
|
433
493
|
}
|
|
434
494
|
],
|
|
435
495
|
};
|
|
@@ -1154,6 +1214,52 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1154
1214
|
};
|
|
1155
1215
|
break;
|
|
1156
1216
|
}
|
|
1217
|
+
case "rigour_review": {
|
|
1218
|
+
const { diff, files: changedFiles } = args;
|
|
1219
|
+
// 1. Map diff to line numbers for filtering
|
|
1220
|
+
const diffMapping = parseDiff(diff);
|
|
1221
|
+
const targetFiles = changedFiles || Object.keys(diffMapping);
|
|
1222
|
+
// 2. Run high-fidelity analysis on changed files
|
|
1223
|
+
const report = await runner.run(cwd, targetFiles);
|
|
1224
|
+
// 3. Filter failures to only those on changed lines (or global gate failures)
|
|
1225
|
+
const filteredFailures = report.failures.filter(failure => {
|
|
1226
|
+
// Global failures (no file associated) are always reported
|
|
1227
|
+
if (!failure.files || failure.files.length === 0)
|
|
1228
|
+
return true;
|
|
1229
|
+
return failure.files.some(file => {
|
|
1230
|
+
const fileModifiedLines = diffMapping[file];
|
|
1231
|
+
// If we can't find the file in the diff, assume it's a side-effect or skip
|
|
1232
|
+
if (!fileModifiedLines)
|
|
1233
|
+
return false;
|
|
1234
|
+
// If failure has line info, check if it's in modifiedLines
|
|
1235
|
+
if (failure.line !== undefined) {
|
|
1236
|
+
return fileModifiedLines.has(failure.line);
|
|
1237
|
+
}
|
|
1238
|
+
// Fallback: if file is changed and no specific line is given, report it
|
|
1239
|
+
return true;
|
|
1240
|
+
});
|
|
1241
|
+
});
|
|
1242
|
+
result = {
|
|
1243
|
+
content: [
|
|
1244
|
+
{
|
|
1245
|
+
type: "text",
|
|
1246
|
+
text: JSON.stringify({
|
|
1247
|
+
status: filteredFailures.length > 0 ? "FAIL" : "PASS",
|
|
1248
|
+
failures: filteredFailures.map(f => ({
|
|
1249
|
+
id: f.id,
|
|
1250
|
+
gate: f.title,
|
|
1251
|
+
severity: "FAIL", // Rigour failures are FAIL by default
|
|
1252
|
+
message: f.details,
|
|
1253
|
+
file: f.files?.[0] || "",
|
|
1254
|
+
line: f.line || 1,
|
|
1255
|
+
suggestion: f.hint
|
|
1256
|
+
}))
|
|
1257
|
+
}),
|
|
1258
|
+
},
|
|
1259
|
+
],
|
|
1260
|
+
};
|
|
1261
|
+
break;
|
|
1262
|
+
}
|
|
1157
1263
|
default:
|
|
1158
1264
|
throw new Error(`Unknown tool: ${name}`);
|
|
1159
1265
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.20.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"mcpName": "io.github.rigour-labs/rigour",
|
|
6
6
|
"description": "Quality gates for AI-generated code. Forces AI agents to meet strict engineering standards with PASS/FAIL enforcement.",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"execa": "^8.0.1",
|
|
21
21
|
"fs-extra": "^11.2.0",
|
|
22
22
|
"yaml": "^2.8.2",
|
|
23
|
-
"@rigour-labs/core": "2.
|
|
23
|
+
"@rigour-labs/core": "2.20.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@types/node": "^25.0.3",
|
package/src/index.ts
CHANGED
|
@@ -93,6 +93,34 @@ async function logStudioEvent(cwd: string, event: any) {
|
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
// Helper to parse diff and get modified lines per file
|
|
97
|
+
function parseDiff(diff: string): Record<string, Set<number>> {
|
|
98
|
+
const lines = diff.split('\n');
|
|
99
|
+
const mapping: Record<string, Set<number>> = {};
|
|
100
|
+
let currentFile = "";
|
|
101
|
+
let currentLine = 0;
|
|
102
|
+
|
|
103
|
+
for (const line of lines) {
|
|
104
|
+
if (line.startsWith('+++ b/')) {
|
|
105
|
+
currentFile = line.slice(6);
|
|
106
|
+
mapping[currentFile] = new Set();
|
|
107
|
+
} else if (line.startsWith('@@')) {
|
|
108
|
+
const match = line.match(/\+(\d+)/);
|
|
109
|
+
if (match) {
|
|
110
|
+
currentLine = parseInt(match[1], 10);
|
|
111
|
+
}
|
|
112
|
+
} else if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
113
|
+
if (currentFile) {
|
|
114
|
+
mapping[currentFile].add(currentLine);
|
|
115
|
+
}
|
|
116
|
+
currentLine++;
|
|
117
|
+
} else if (!line.startsWith('-')) {
|
|
118
|
+
currentLine++;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return mapping;
|
|
122
|
+
}
|
|
123
|
+
|
|
96
124
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
97
125
|
return {
|
|
98
126
|
tools: [
|
|
@@ -456,6 +484,37 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
456
484
|
},
|
|
457
485
|
required: ["cwd", "handoffId", "agentId"],
|
|
458
486
|
},
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
name: "rigour_review",
|
|
490
|
+
description: "Perform a high-fidelity code review on a pull request diff. Analyzes changed files using all active quality gates.",
|
|
491
|
+
inputSchema: {
|
|
492
|
+
type: "object",
|
|
493
|
+
properties: {
|
|
494
|
+
cwd: {
|
|
495
|
+
type: "string",
|
|
496
|
+
description: "Absolute path to the project root.",
|
|
497
|
+
},
|
|
498
|
+
repository: {
|
|
499
|
+
type: "string",
|
|
500
|
+
description: "Full repository name (e.g., 'owner/repo').",
|
|
501
|
+
},
|
|
502
|
+
branch: {
|
|
503
|
+
type: "string",
|
|
504
|
+
description: "The branch containing the changes.",
|
|
505
|
+
},
|
|
506
|
+
diff: {
|
|
507
|
+
type: "string",
|
|
508
|
+
description: "The git diff content to analyze.",
|
|
509
|
+
},
|
|
510
|
+
files: {
|
|
511
|
+
type: "array",
|
|
512
|
+
items: { type: "string" },
|
|
513
|
+
description: "List of filenames that were changed.",
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
required: ["cwd", "diff"],
|
|
517
|
+
},
|
|
459
518
|
}
|
|
460
519
|
],
|
|
461
520
|
};
|
|
@@ -1270,6 +1329,58 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1270
1329
|
break;
|
|
1271
1330
|
}
|
|
1272
1331
|
|
|
1332
|
+
case "rigour_review": {
|
|
1333
|
+
const { diff, files: changedFiles } = args as any;
|
|
1334
|
+
|
|
1335
|
+
// 1. Map diff to line numbers for filtering
|
|
1336
|
+
const diffMapping = parseDiff(diff);
|
|
1337
|
+
const targetFiles = changedFiles || Object.keys(diffMapping);
|
|
1338
|
+
|
|
1339
|
+
// 2. Run high-fidelity analysis on changed files
|
|
1340
|
+
const report = await runner.run(cwd, targetFiles);
|
|
1341
|
+
|
|
1342
|
+
// 3. Filter failures to only those on changed lines (or global gate failures)
|
|
1343
|
+
const filteredFailures = report.failures.filter(failure => {
|
|
1344
|
+
// Global failures (no file associated) are always reported
|
|
1345
|
+
if (!failure.files || failure.files.length === 0) return true;
|
|
1346
|
+
|
|
1347
|
+
return failure.files.some(file => {
|
|
1348
|
+
const fileModifiedLines = diffMapping[file];
|
|
1349
|
+
// If we can't find the file in the diff, assume it's a side-effect or skip
|
|
1350
|
+
if (!fileModifiedLines) return false;
|
|
1351
|
+
|
|
1352
|
+
// If failure has line info, check if it's in modifiedLines
|
|
1353
|
+
if (failure.line !== undefined) {
|
|
1354
|
+
return fileModifiedLines.has(failure.line);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// Fallback: if file is changed and no specific line is given, report it
|
|
1358
|
+
return true;
|
|
1359
|
+
});
|
|
1360
|
+
});
|
|
1361
|
+
|
|
1362
|
+
result = {
|
|
1363
|
+
content: [
|
|
1364
|
+
{
|
|
1365
|
+
type: "text",
|
|
1366
|
+
text: JSON.stringify({
|
|
1367
|
+
status: filteredFailures.length > 0 ? "FAIL" : "PASS",
|
|
1368
|
+
failures: filteredFailures.map(f => ({
|
|
1369
|
+
id: f.id,
|
|
1370
|
+
gate: f.title,
|
|
1371
|
+
severity: "FAIL", // Rigour failures are FAIL by default
|
|
1372
|
+
message: f.details,
|
|
1373
|
+
file: f.files?.[0] || "",
|
|
1374
|
+
line: f.line || 1,
|
|
1375
|
+
suggestion: f.hint
|
|
1376
|
+
}))
|
|
1377
|
+
}),
|
|
1378
|
+
},
|
|
1379
|
+
],
|
|
1380
|
+
};
|
|
1381
|
+
break;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1273
1384
|
default:
|
|
1274
1385
|
throw new Error(`Unknown tool: ${name}`);
|
|
1275
1386
|
}
|