@rigour-labs/mcp 2.9.4 → 2.11.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/README.md +72 -0
- package/dist/index.js +279 -95
- package/dist/smoke.test.js +4 -6
- package/package.json +3 -2
- package/src/index.ts +283 -77
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# 🛡️ Rigour MCP Server
|
|
2
|
+
|
|
3
|
+
**The Quality Gate for AI-Assisted Engineering.**
|
|
4
|
+
|
|
5
|
+
Rigour is a local-first Model Context Protocol (MCP) server that forces AI agents (Claude, Cursor, Windsurf, etc.) to meet strict engineering standards before marking tasks as complete.
|
|
6
|
+
|
|
7
|
+
[](https://github.com/mcp)
|
|
8
|
+
[](https://www.npmjs.com/package/@rigour-labs/mcp)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 🚀 Overview
|
|
13
|
+
|
|
14
|
+
Rigour moves code quality enforcement from the "Post-Commit" phase to the "In-Progress" phase. By running as an MCP server inside your editor, it provides the AI with a deterministic PASS/FAIL loop, preventing "Vibe Coding" and broken builds.
|
|
15
|
+
|
|
16
|
+
### Key Features:
|
|
17
|
+
- **Quality Gates**: Deterministic checks for file size, complexity, and hygiene.
|
|
18
|
+
- **Context Memory**: Persistent memory that tracks project rules and patterns across sessions.
|
|
19
|
+
- **Pattern Reinvention Blocking**: Warns or blocks the AI when it tries to rewrite existing utilities.
|
|
20
|
+
- **Security Audits**: Real-time CVE detection for dependencies the AI is suggesting.
|
|
21
|
+
- **Zero Cloud**: 100% local analysis. Your code never leaves your machine.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 🛠️ Available Tools
|
|
26
|
+
|
|
27
|
+
| Tool | Description |
|
|
28
|
+
|:---|:---|
|
|
29
|
+
| `rigour_check` | Runs all configured quality gates on the current workspace. |
|
|
30
|
+
| `rigour_explain` | Explains why a specific gate failed and provides actionable fix instructions. |
|
|
31
|
+
| `rigour_check_pattern` | Checks if a proposed code pattern already exists in the codebase. |
|
|
32
|
+
| `rigour_remember` | Stores project-specific context or rules in Rigour's persistent memory. |
|
|
33
|
+
| `rigour_recall` | Retrieves stored context to guide AI generation. |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 📦 Installation
|
|
38
|
+
|
|
39
|
+
### 1. Install via npm
|
|
40
|
+
```bash
|
|
41
|
+
npm install -g @rigour-labs/mcp
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. Configure your IDE
|
|
45
|
+
|
|
46
|
+
#### Cursor / Claude Desktop
|
|
47
|
+
Add the following to your MCP settings:
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"mcpServers": {
|
|
51
|
+
"rigour": {
|
|
52
|
+
"command": "npx",
|
|
53
|
+
"args": ["-y", "@rigour-labs/mcp"],
|
|
54
|
+
"env": {
|
|
55
|
+
"RIGOUR_CWD": "/path/to/your/project"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 📖 Documentation
|
|
65
|
+
|
|
66
|
+
For full configuration and advanced usage, visit **[docs.rigour.run](https://docs.rigour.run)**.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 📜 License
|
|
71
|
+
|
|
72
|
+
MIT © [Rigour Labs](https://github.com/rigour-labs)
|
package/dist/index.js
CHANGED
|
@@ -1,17 +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 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({
|
|
15
12
|
name: "rigour-mcp",
|
|
16
13
|
version: "1.0.0",
|
|
17
14
|
}, {
|
|
@@ -20,31 +17,48 @@ const server = new index_js_1.Server({
|
|
|
20
17
|
},
|
|
21
18
|
});
|
|
22
19
|
async function loadConfig(cwd) {
|
|
23
|
-
const configPath =
|
|
24
|
-
if (!(await
|
|
20
|
+
const configPath = path.join(cwd, "rigour.yml");
|
|
21
|
+
if (!(await fs.pathExists(configPath))) {
|
|
25
22
|
throw new Error("Rigour configuration (rigour.yml) not found. The agent must run `rigour init` first to establish engineering standards.");
|
|
26
23
|
}
|
|
27
|
-
const configContent = await
|
|
28
|
-
return
|
|
24
|
+
const configContent = await fs.readFile(configPath, "utf-8");
|
|
25
|
+
return ConfigSchema.parse(yaml.parse(configContent));
|
|
29
26
|
}
|
|
30
27
|
async function getMemoryPath(cwd) {
|
|
31
|
-
const rigourDir =
|
|
32
|
-
await
|
|
33
|
-
return
|
|
28
|
+
const rigourDir = path.join(cwd, ".rigour");
|
|
29
|
+
await fs.ensureDir(rigourDir);
|
|
30
|
+
return path.join(rigourDir, "memory.json");
|
|
34
31
|
}
|
|
35
32
|
async function loadMemory(cwd) {
|
|
36
33
|
const memPath = await getMemoryPath(cwd);
|
|
37
|
-
if (await
|
|
38
|
-
const content = await
|
|
34
|
+
if (await fs.pathExists(memPath)) {
|
|
35
|
+
const content = await fs.readFile(memPath, "utf-8");
|
|
39
36
|
return JSON.parse(content);
|
|
40
37
|
}
|
|
41
38
|
return { memories: {} };
|
|
42
39
|
}
|
|
43
40
|
async function saveMemory(cwd, store) {
|
|
44
41
|
const memPath = await getMemoryPath(cwd);
|
|
45
|
-
await
|
|
42
|
+
await fs.writeFile(memPath, JSON.stringify(store, null, 2));
|
|
46
43
|
}
|
|
47
|
-
|
|
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 () => {
|
|
48
62
|
return {
|
|
49
63
|
tools: [
|
|
50
64
|
{
|
|
@@ -189,19 +203,67 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
|
189
203
|
required: ["cwd", "key"],
|
|
190
204
|
},
|
|
191
205
|
},
|
|
206
|
+
{
|
|
207
|
+
name: "rigour_check_pattern",
|
|
208
|
+
description: "Checks if a proposed code pattern (function, component, etc.) already exists, is stale, or has security vulnerabilities (CVEs). CALL THIS BEFORE CREATING NEW CODE.",
|
|
209
|
+
inputSchema: {
|
|
210
|
+
type: "object",
|
|
211
|
+
properties: {
|
|
212
|
+
cwd: {
|
|
213
|
+
type: "string",
|
|
214
|
+
description: "Absolute path to the project root.",
|
|
215
|
+
},
|
|
216
|
+
name: {
|
|
217
|
+
type: "string",
|
|
218
|
+
description: "The name of the function, class, or component you want to create.",
|
|
219
|
+
},
|
|
220
|
+
type: {
|
|
221
|
+
type: "string",
|
|
222
|
+
description: "The type of pattern (e.g., 'function', 'component', 'hook', 'type').",
|
|
223
|
+
},
|
|
224
|
+
intent: {
|
|
225
|
+
type: "string",
|
|
226
|
+
description: "What the code is for (e.g., 'format dates', 'user authentication').",
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
required: ["cwd", "name"],
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: "rigour_security_audit",
|
|
234
|
+
description: "Runs a live security audit (CVE check) on the project dependencies.",
|
|
235
|
+
inputSchema: {
|
|
236
|
+
type: "object",
|
|
237
|
+
properties: {
|
|
238
|
+
cwd: {
|
|
239
|
+
type: "string",
|
|
240
|
+
description: "Absolute path to the project root.",
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
required: ["cwd"],
|
|
244
|
+
},
|
|
245
|
+
},
|
|
192
246
|
],
|
|
193
247
|
};
|
|
194
248
|
});
|
|
195
|
-
server.setRequestHandler(
|
|
249
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
196
250
|
const { name, arguments: args } = request.params;
|
|
197
251
|
const cwd = args?.cwd || process.cwd();
|
|
252
|
+
const requestId = randomUUID();
|
|
198
253
|
try {
|
|
254
|
+
await logStudioEvent(cwd, {
|
|
255
|
+
type: "tool_call",
|
|
256
|
+
requestId,
|
|
257
|
+
tool: name,
|
|
258
|
+
arguments: args
|
|
259
|
+
});
|
|
199
260
|
const config = await loadConfig(cwd);
|
|
200
|
-
const runner = new
|
|
261
|
+
const runner = new GateRunner(config);
|
|
262
|
+
let result;
|
|
201
263
|
switch (name) {
|
|
202
264
|
case "rigour_check": {
|
|
203
265
|
const report = await runner.run(cwd);
|
|
204
|
-
|
|
266
|
+
result = {
|
|
205
267
|
content: [
|
|
206
268
|
{
|
|
207
269
|
type: "text",
|
|
@@ -209,11 +271,14 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
209
271
|
},
|
|
210
272
|
],
|
|
211
273
|
};
|
|
274
|
+
// Add the report to the tool_response log for high-fidelity Studio visualization
|
|
275
|
+
result._rigour_report = report;
|
|
276
|
+
break;
|
|
212
277
|
}
|
|
213
278
|
case "rigour_explain": {
|
|
214
279
|
const report = await runner.run(cwd);
|
|
215
280
|
if (report.status === "PASS") {
|
|
216
|
-
|
|
281
|
+
result = {
|
|
217
282
|
content: [
|
|
218
283
|
{
|
|
219
284
|
type: "text",
|
|
@@ -222,21 +287,24 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
222
287
|
],
|
|
223
288
|
};
|
|
224
289
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
290
|
+
else {
|
|
291
|
+
const bullets = report.failures.map((f, i) => {
|
|
292
|
+
return `${i + 1}. [${f.id.toUpperCase()}] ${f.title}: ${f.details}${f.hint ? ` (Hint: ${f.hint})` : ''}`;
|
|
293
|
+
}).join("\n");
|
|
294
|
+
result = {
|
|
295
|
+
content: [
|
|
296
|
+
{
|
|
297
|
+
type: "text",
|
|
298
|
+
text: `RIGOUR EXPLAIN:\n\n${bullets}`,
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
break;
|
|
236
304
|
}
|
|
237
305
|
case "rigour_status": {
|
|
238
306
|
const report = await runner.run(cwd);
|
|
239
|
-
|
|
307
|
+
result = {
|
|
240
308
|
content: [
|
|
241
309
|
{
|
|
242
310
|
type: "text",
|
|
@@ -249,11 +317,12 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
249
317
|
},
|
|
250
318
|
],
|
|
251
319
|
};
|
|
320
|
+
break;
|
|
252
321
|
}
|
|
253
322
|
case "rigour_get_fix_packet": {
|
|
254
323
|
const report = await runner.run(cwd);
|
|
255
324
|
if (report.status === "PASS") {
|
|
256
|
-
|
|
325
|
+
result = {
|
|
257
326
|
content: [
|
|
258
327
|
{
|
|
259
328
|
type: "text",
|
|
@@ -262,28 +331,31 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
262
331
|
],
|
|
263
332
|
};
|
|
264
333
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
334
|
+
else {
|
|
335
|
+
const packet = report.failures.map((f, i) => {
|
|
336
|
+
let text = `FIX TASK ${i + 1}: [${f.id.toUpperCase()}] ${f.title}\n`;
|
|
337
|
+
text += ` - CONTEXT: ${f.details}\n`;
|
|
338
|
+
if (f.files && f.files.length > 0) {
|
|
339
|
+
text += ` - TARGET FILES: ${f.files.join(", ")}\n`;
|
|
340
|
+
}
|
|
341
|
+
if (f.hint) {
|
|
342
|
+
text += ` - REFACTORING GUIDANCE: ${f.hint}\n`;
|
|
343
|
+
}
|
|
344
|
+
return text;
|
|
345
|
+
}).join("\n---\n");
|
|
346
|
+
result = {
|
|
347
|
+
content: [
|
|
348
|
+
{
|
|
349
|
+
type: "text",
|
|
350
|
+
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}`,
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
break;
|
|
284
356
|
}
|
|
285
357
|
case "rigour_list_gates":
|
|
286
|
-
|
|
358
|
+
result = {
|
|
287
359
|
content: [
|
|
288
360
|
{
|
|
289
361
|
type: "text",
|
|
@@ -296,8 +368,9 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
296
368
|
},
|
|
297
369
|
],
|
|
298
370
|
};
|
|
371
|
+
break;
|
|
299
372
|
case "rigour_get_config":
|
|
300
|
-
|
|
373
|
+
result = {
|
|
301
374
|
content: [
|
|
302
375
|
{
|
|
303
376
|
type: "text",
|
|
@@ -305,6 +378,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
305
378
|
},
|
|
306
379
|
],
|
|
307
380
|
};
|
|
381
|
+
break;
|
|
308
382
|
case "rigour_remember": {
|
|
309
383
|
const { key, value } = args;
|
|
310
384
|
const store = await loadMemory(cwd);
|
|
@@ -313,7 +387,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
313
387
|
timestamp: new Date().toISOString(),
|
|
314
388
|
};
|
|
315
389
|
await saveMemory(cwd, store);
|
|
316
|
-
|
|
390
|
+
result = {
|
|
317
391
|
content: [
|
|
318
392
|
{
|
|
319
393
|
type: "text",
|
|
@@ -321,6 +395,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
321
395
|
},
|
|
322
396
|
],
|
|
323
397
|
};
|
|
398
|
+
break;
|
|
324
399
|
}
|
|
325
400
|
case "rigour_recall": {
|
|
326
401
|
const { key } = args;
|
|
@@ -328,7 +403,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
328
403
|
if (key) {
|
|
329
404
|
const memory = store.memories[key];
|
|
330
405
|
if (!memory) {
|
|
331
|
-
|
|
406
|
+
result = {
|
|
332
407
|
content: [
|
|
333
408
|
{
|
|
334
409
|
type: "text",
|
|
@@ -337,69 +412,169 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
337
412
|
],
|
|
338
413
|
};
|
|
339
414
|
}
|
|
340
|
-
|
|
415
|
+
else {
|
|
416
|
+
result = {
|
|
417
|
+
content: [
|
|
418
|
+
{
|
|
419
|
+
type: "text",
|
|
420
|
+
text: `RECALLED MEMORY [${key}]:\n${memory.value}\n\n(Stored: ${memory.timestamp})`,
|
|
421
|
+
},
|
|
422
|
+
],
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
const keys = Object.keys(store.memories);
|
|
428
|
+
if (keys.length === 0) {
|
|
429
|
+
result = {
|
|
430
|
+
content: [
|
|
431
|
+
{
|
|
432
|
+
type: "text",
|
|
433
|
+
text: "NO MEMORIES STORED. Use rigour_remember to persist important instructions.",
|
|
434
|
+
},
|
|
435
|
+
],
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
const allMemories = keys.map(k => {
|
|
440
|
+
const mem = store.memories[k];
|
|
441
|
+
return `## ${k}\n${mem.value}\n(Stored: ${mem.timestamp})`;
|
|
442
|
+
}).join("\n\n---\n\n");
|
|
443
|
+
result = {
|
|
444
|
+
content: [
|
|
445
|
+
{
|
|
446
|
+
type: "text",
|
|
447
|
+
text: `RECALLED ALL MEMORIES (${keys.length} items):\n\n${allMemories}\n\n---\nIMPORTANT: Follow these stored instructions throughout this session.`,
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
case "rigour_forget": {
|
|
456
|
+
const { key } = args;
|
|
457
|
+
const store = await loadMemory(cwd);
|
|
458
|
+
if (!store.memories[key]) {
|
|
459
|
+
result = {
|
|
341
460
|
content: [
|
|
342
461
|
{
|
|
343
462
|
type: "text",
|
|
344
|
-
text: `
|
|
463
|
+
text: `NO MEMORY FOUND for key "${key}". Nothing to forget.`,
|
|
345
464
|
},
|
|
346
465
|
],
|
|
347
466
|
};
|
|
348
467
|
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
468
|
+
else {
|
|
469
|
+
delete store.memories[key];
|
|
470
|
+
await saveMemory(cwd, store);
|
|
471
|
+
result = {
|
|
352
472
|
content: [
|
|
353
473
|
{
|
|
354
474
|
type: "text",
|
|
355
|
-
text:
|
|
475
|
+
text: `MEMORY DELETED: "${key}" has been removed.`,
|
|
356
476
|
},
|
|
357
477
|
],
|
|
358
478
|
};
|
|
359
479
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
case "rigour_check_pattern": {
|
|
483
|
+
const { name: patternName, type, intent } = args;
|
|
484
|
+
const indexPath = getDefaultIndexPath(cwd);
|
|
485
|
+
const index = await loadPatternIndex(indexPath);
|
|
486
|
+
let resultText = "";
|
|
487
|
+
// 1. Check for Reinvention
|
|
488
|
+
if (index) {
|
|
489
|
+
const matcher = new PatternMatcher(index);
|
|
490
|
+
const matchResult = await matcher.match({ name: patternName, type, intent });
|
|
491
|
+
if (matchResult.status === "FOUND_SIMILAR") {
|
|
492
|
+
resultText += `🚨 PATTERN REINVENTION DETECTED\n`;
|
|
493
|
+
resultText += `Similar pattern already exists: "${matchResult.matches[0].pattern.name}" in ${matchResult.matches[0].pattern.file}\n`;
|
|
494
|
+
resultText += `SUGGESTION: ${matchResult.suggestion}\n\n`;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
resultText += `⚠️ Pattern index not found. Run 'rigour index' to enable reinvention detection.\n\n`;
|
|
499
|
+
}
|
|
500
|
+
// 2. Check for Staleness/Best Practices
|
|
501
|
+
const detector = new StalenessDetector(cwd);
|
|
502
|
+
const staleness = await detector.checkStaleness(`${type || 'function'} ${patternName} {}`);
|
|
503
|
+
if (staleness.status !== "FRESH") {
|
|
504
|
+
resultText += `⚠️ STALENESS/ANTI-PATTERN WARNING\n`;
|
|
505
|
+
for (const issue of staleness.issues) {
|
|
506
|
+
resultText += `- ${issue.reason}\n REPLACEMENT: ${issue.replacement}\n`;
|
|
507
|
+
}
|
|
508
|
+
resultText += `\n`;
|
|
509
|
+
}
|
|
510
|
+
// 3. Check Security for this library (if it's an import)
|
|
511
|
+
if (intent && intent.includes('import')) {
|
|
512
|
+
const security = new SecurityDetector(cwd);
|
|
513
|
+
const audit = await security.runAudit();
|
|
514
|
+
const relatedVulns = audit.vulnerabilities.filter(v => patternName.toLowerCase().includes(v.packageName.toLowerCase()) ||
|
|
515
|
+
intent.toLowerCase().includes(v.packageName.toLowerCase()));
|
|
516
|
+
if (relatedVulns.length > 0) {
|
|
517
|
+
resultText += `🛡️ SECURITY/CVE WARNING\n`;
|
|
518
|
+
for (const v of relatedVulns) {
|
|
519
|
+
resultText += `- [${v.severity.toUpperCase()}] ${v.packageName}: ${v.title} (${v.url})\n`;
|
|
520
|
+
}
|
|
521
|
+
resultText += `\n`;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (!resultText) {
|
|
525
|
+
resultText = `✅ Pattern "${patternName}" is fresh, secure, and unique to the codebase.\n\nRECOMMENDED ACTION: Proceed with implementation.`;
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
let recommendation = "Proceed with caution, addressing the warnings above.";
|
|
529
|
+
if (resultText.includes("🚨 PATTERN REINVENTION")) {
|
|
530
|
+
recommendation = "STOP and REUSE the existing pattern mentioned above. Do not create a duplicate.";
|
|
531
|
+
}
|
|
532
|
+
else if (resultText.includes("🛡️ SECURITY/CVE WARNING")) {
|
|
533
|
+
recommendation = "STOP and update your dependencies or find an alternative library. Do not proceed with vulnerable code.";
|
|
534
|
+
}
|
|
535
|
+
else if (resultText.includes("⚠️ STALENESS")) {
|
|
536
|
+
recommendation = "Follow the replacement suggestion to ensure best practices.";
|
|
537
|
+
}
|
|
538
|
+
resultText += `\nRECOMMENDED ACTION: ${recommendation}`;
|
|
539
|
+
}
|
|
540
|
+
result = {
|
|
365
541
|
content: [
|
|
366
542
|
{
|
|
367
543
|
type: "text",
|
|
368
|
-
text:
|
|
544
|
+
text: resultText,
|
|
369
545
|
},
|
|
370
546
|
],
|
|
371
547
|
};
|
|
548
|
+
break;
|
|
372
549
|
}
|
|
373
|
-
case "
|
|
374
|
-
const
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
return {
|
|
378
|
-
content: [
|
|
379
|
-
{
|
|
380
|
-
type: "text",
|
|
381
|
-
text: `NO MEMORY FOUND for key "${key}". Nothing to forget.`,
|
|
382
|
-
},
|
|
383
|
-
],
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
delete store.memories[key];
|
|
387
|
-
await saveMemory(cwd, store);
|
|
388
|
-
return {
|
|
550
|
+
case "rigour_security_audit": {
|
|
551
|
+
const security = new SecurityDetector(cwd);
|
|
552
|
+
const summary = await security.getSecuritySummary();
|
|
553
|
+
result = {
|
|
389
554
|
content: [
|
|
390
555
|
{
|
|
391
556
|
type: "text",
|
|
392
|
-
text:
|
|
557
|
+
text: summary,
|
|
393
558
|
},
|
|
394
559
|
],
|
|
395
560
|
};
|
|
561
|
+
break;
|
|
396
562
|
}
|
|
397
563
|
default:
|
|
398
564
|
throw new Error(`Unknown tool: ${name}`);
|
|
399
565
|
}
|
|
566
|
+
await logStudioEvent(cwd, {
|
|
567
|
+
type: "tool_response",
|
|
568
|
+
requestId,
|
|
569
|
+
tool: name,
|
|
570
|
+
status: "success",
|
|
571
|
+
content: result.content,
|
|
572
|
+
_rigour_report: result._rigour_report
|
|
573
|
+
});
|
|
574
|
+
return result;
|
|
400
575
|
}
|
|
401
576
|
catch (error) {
|
|
402
|
-
|
|
577
|
+
const errorResponse = {
|
|
403
578
|
content: [
|
|
404
579
|
{
|
|
405
580
|
type: "text",
|
|
@@ -408,10 +583,19 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
|
408
583
|
],
|
|
409
584
|
isError: true,
|
|
410
585
|
};
|
|
586
|
+
await logStudioEvent(cwd, {
|
|
587
|
+
type: "tool_response",
|
|
588
|
+
requestId,
|
|
589
|
+
tool: name,
|
|
590
|
+
status: "error",
|
|
591
|
+
error: error.message,
|
|
592
|
+
content: errorResponse.content
|
|
593
|
+
});
|
|
594
|
+
return errorResponse;
|
|
411
595
|
}
|
|
412
596
|
});
|
|
413
597
|
async function main() {
|
|
414
|
-
const transport = new
|
|
598
|
+
const transport = new StdioServerTransport();
|
|
415
599
|
await server.connect(transport);
|
|
416
600
|
console.error("Rigour MCP server v1.0.0 running on stdio");
|
|
417
601
|
}
|
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.11.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": {
|
|
@@ -18,7 +19,7 @@
|
|
|
18
19
|
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
19
20
|
"fs-extra": "^11.2.0",
|
|
20
21
|
"yaml": "^2.8.2",
|
|
21
|
-
"@rigour-labs/core": "2.
|
|
22
|
+
"@rigour-labs/core": "2.11.0"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
25
|
"@types/node": "^25.0.3"
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,19 @@ import {
|
|
|
8
8
|
import fs from "fs-extra";
|
|
9
9
|
import path from "path";
|
|
10
10
|
import yaml from "yaml";
|
|
11
|
-
import {
|
|
11
|
+
import { randomUUID } from "crypto";
|
|
12
|
+
import {
|
|
13
|
+
GateRunner,
|
|
14
|
+
ConfigSchema,
|
|
15
|
+
Report,
|
|
16
|
+
} from "@rigour-labs/core";
|
|
17
|
+
import {
|
|
18
|
+
PatternMatcher,
|
|
19
|
+
loadPatternIndex,
|
|
20
|
+
getDefaultIndexPath,
|
|
21
|
+
StalenessDetector,
|
|
22
|
+
SecurityDetector
|
|
23
|
+
} from "@rigour-labs/core/pattern-index";
|
|
12
24
|
|
|
13
25
|
const server = new Server(
|
|
14
26
|
{
|
|
@@ -56,6 +68,23 @@ async function saveMemory(cwd: string, store: MemoryStore): Promise<void> {
|
|
|
56
68
|
await fs.writeFile(memPath, JSON.stringify(store, null, 2));
|
|
57
69
|
}
|
|
58
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
|
+
|
|
59
88
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
60
89
|
return {
|
|
61
90
|
tools: [
|
|
@@ -201,6 +230,46 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
201
230
|
required: ["cwd", "key"],
|
|
202
231
|
},
|
|
203
232
|
},
|
|
233
|
+
{
|
|
234
|
+
name: "rigour_check_pattern",
|
|
235
|
+
description: "Checks if a proposed code pattern (function, component, etc.) already exists, is stale, or has security vulnerabilities (CVEs). CALL THIS BEFORE CREATING NEW CODE.",
|
|
236
|
+
inputSchema: {
|
|
237
|
+
type: "object",
|
|
238
|
+
properties: {
|
|
239
|
+
cwd: {
|
|
240
|
+
type: "string",
|
|
241
|
+
description: "Absolute path to the project root.",
|
|
242
|
+
},
|
|
243
|
+
name: {
|
|
244
|
+
type: "string",
|
|
245
|
+
description: "The name of the function, class, or component you want to create.",
|
|
246
|
+
},
|
|
247
|
+
type: {
|
|
248
|
+
type: "string",
|
|
249
|
+
description: "The type of pattern (e.g., 'function', 'component', 'hook', 'type').",
|
|
250
|
+
},
|
|
251
|
+
intent: {
|
|
252
|
+
type: "string",
|
|
253
|
+
description: "What the code is for (e.g., 'format dates', 'user authentication').",
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
required: ["cwd", "name"],
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: "rigour_security_audit",
|
|
261
|
+
description: "Runs a live security audit (CVE check) on the project dependencies.",
|
|
262
|
+
inputSchema: {
|
|
263
|
+
type: "object",
|
|
264
|
+
properties: {
|
|
265
|
+
cwd: {
|
|
266
|
+
type: "string",
|
|
267
|
+
description: "Absolute path to the project root.",
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
required: ["cwd"],
|
|
271
|
+
},
|
|
272
|
+
},
|
|
204
273
|
],
|
|
205
274
|
};
|
|
206
275
|
});
|
|
@@ -208,15 +277,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
208
277
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
209
278
|
const { name, arguments: args } = request.params;
|
|
210
279
|
const cwd = (args as any)?.cwd || process.cwd();
|
|
280
|
+
const requestId = randomUUID();
|
|
211
281
|
|
|
212
282
|
try {
|
|
283
|
+
await logStudioEvent(cwd, {
|
|
284
|
+
type: "tool_call",
|
|
285
|
+
requestId,
|
|
286
|
+
tool: name,
|
|
287
|
+
arguments: args
|
|
288
|
+
});
|
|
289
|
+
|
|
213
290
|
const config = await loadConfig(cwd);
|
|
214
291
|
const runner = new GateRunner(config);
|
|
215
292
|
|
|
293
|
+
let result: any;
|
|
294
|
+
|
|
216
295
|
switch (name) {
|
|
217
296
|
case "rigour_check": {
|
|
218
297
|
const report = await runner.run(cwd);
|
|
219
|
-
|
|
298
|
+
result = {
|
|
220
299
|
content: [
|
|
221
300
|
{
|
|
222
301
|
type: "text",
|
|
@@ -224,12 +303,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
224
303
|
},
|
|
225
304
|
],
|
|
226
305
|
};
|
|
306
|
+
|
|
307
|
+
// Add the report to the tool_response log for high-fidelity Studio visualization
|
|
308
|
+
(result as any)._rigour_report = report;
|
|
309
|
+
break;
|
|
227
310
|
}
|
|
228
311
|
|
|
229
312
|
case "rigour_explain": {
|
|
230
313
|
const report = await runner.run(cwd);
|
|
231
314
|
if (report.status === "PASS") {
|
|
232
|
-
|
|
315
|
+
result = {
|
|
233
316
|
content: [
|
|
234
317
|
{
|
|
235
318
|
type: "text",
|
|
@@ -237,25 +320,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
237
320
|
},
|
|
238
321
|
],
|
|
239
322
|
};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}).join("\n");
|
|
323
|
+
} else {
|
|
324
|
+
const bullets = report.failures.map((f, i) => {
|
|
325
|
+
return `${i + 1}. [${f.id.toUpperCase()}] ${f.title}: ${f.details}${f.hint ? ` (Hint: ${f.hint})` : ''}`;
|
|
326
|
+
}).join("\n");
|
|
245
327
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
328
|
+
result = {
|
|
329
|
+
content: [
|
|
330
|
+
{
|
|
331
|
+
type: "text",
|
|
332
|
+
text: `RIGOUR EXPLAIN:\n\n${bullets}`,
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
break;
|
|
254
338
|
}
|
|
255
339
|
|
|
256
340
|
case "rigour_status": {
|
|
257
341
|
const report = await runner.run(cwd);
|
|
258
|
-
|
|
342
|
+
result = {
|
|
259
343
|
content: [
|
|
260
344
|
{
|
|
261
345
|
type: "text",
|
|
@@ -268,12 +352,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
268
352
|
},
|
|
269
353
|
],
|
|
270
354
|
};
|
|
355
|
+
break;
|
|
271
356
|
}
|
|
272
357
|
|
|
273
358
|
case "rigour_get_fix_packet": {
|
|
274
359
|
const report = await runner.run(cwd);
|
|
275
360
|
if (report.status === "PASS") {
|
|
276
|
-
|
|
361
|
+
result = {
|
|
277
362
|
content: [
|
|
278
363
|
{
|
|
279
364
|
type: "text",
|
|
@@ -281,32 +366,33 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
281
366
|
},
|
|
282
367
|
],
|
|
283
368
|
};
|
|
369
|
+
} else {
|
|
370
|
+
const packet = report.failures.map((f, i) => {
|
|
371
|
+
let text = `FIX TASK ${i + 1}: [${f.id.toUpperCase()}] ${f.title}\n`;
|
|
372
|
+
text += ` - CONTEXT: ${f.details}\n`;
|
|
373
|
+
if (f.files && f.files.length > 0) {
|
|
374
|
+
text += ` - TARGET FILES: ${f.files.join(", ")}\n`;
|
|
375
|
+
}
|
|
376
|
+
if (f.hint) {
|
|
377
|
+
text += ` - REFACTORING GUIDANCE: ${f.hint}\n`;
|
|
378
|
+
}
|
|
379
|
+
return text;
|
|
380
|
+
}).join("\n---\n");
|
|
381
|
+
|
|
382
|
+
result = {
|
|
383
|
+
content: [
|
|
384
|
+
{
|
|
385
|
+
type: "text",
|
|
386
|
+
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}`,
|
|
387
|
+
},
|
|
388
|
+
],
|
|
389
|
+
};
|
|
284
390
|
}
|
|
285
|
-
|
|
286
|
-
const packet = report.failures.map((f, i) => {
|
|
287
|
-
let text = `FIX TASK ${i + 1}: [${f.id.toUpperCase()}] ${f.title}\n`;
|
|
288
|
-
text += ` - CONTEXT: ${f.details}\n`;
|
|
289
|
-
if (f.files && f.files.length > 0) {
|
|
290
|
-
text += ` - TARGET FILES: ${f.files.join(", ")}\n`;
|
|
291
|
-
}
|
|
292
|
-
if (f.hint) {
|
|
293
|
-
text += ` - REFACTORING GUIDANCE: ${f.hint}\n`;
|
|
294
|
-
}
|
|
295
|
-
return text;
|
|
296
|
-
}).join("\n---\n");
|
|
297
|
-
|
|
298
|
-
return {
|
|
299
|
-
content: [
|
|
300
|
-
{
|
|
301
|
-
type: "text",
|
|
302
|
-
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}`,
|
|
303
|
-
},
|
|
304
|
-
],
|
|
305
|
-
};
|
|
391
|
+
break;
|
|
306
392
|
}
|
|
307
393
|
|
|
308
394
|
case "rigour_list_gates":
|
|
309
|
-
|
|
395
|
+
result = {
|
|
310
396
|
content: [
|
|
311
397
|
{
|
|
312
398
|
type: "text",
|
|
@@ -319,9 +405,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
319
405
|
},
|
|
320
406
|
],
|
|
321
407
|
};
|
|
408
|
+
break;
|
|
322
409
|
|
|
323
410
|
case "rigour_get_config":
|
|
324
|
-
|
|
411
|
+
result = {
|
|
325
412
|
content: [
|
|
326
413
|
{
|
|
327
414
|
type: "text",
|
|
@@ -329,6 +416,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
329
416
|
},
|
|
330
417
|
],
|
|
331
418
|
};
|
|
419
|
+
break;
|
|
332
420
|
|
|
333
421
|
case "rigour_remember": {
|
|
334
422
|
const { key, value } = args as any;
|
|
@@ -338,7 +426,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
338
426
|
timestamp: new Date().toISOString(),
|
|
339
427
|
};
|
|
340
428
|
await saveMemory(cwd, store);
|
|
341
|
-
|
|
429
|
+
result = {
|
|
342
430
|
content: [
|
|
343
431
|
{
|
|
344
432
|
type: "text",
|
|
@@ -346,6 +434,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
346
434
|
},
|
|
347
435
|
],
|
|
348
436
|
};
|
|
437
|
+
break;
|
|
349
438
|
}
|
|
350
439
|
|
|
351
440
|
case "rigour_recall": {
|
|
@@ -355,7 +444,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
355
444
|
if (key) {
|
|
356
445
|
const memory = store.memories[key];
|
|
357
446
|
if (!memory) {
|
|
358
|
-
|
|
447
|
+
result = {
|
|
359
448
|
content: [
|
|
360
449
|
{
|
|
361
450
|
type: "text",
|
|
@@ -363,77 +452,183 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
363
452
|
},
|
|
364
453
|
],
|
|
365
454
|
};
|
|
455
|
+
} else {
|
|
456
|
+
result = {
|
|
457
|
+
content: [
|
|
458
|
+
{
|
|
459
|
+
type: "text",
|
|
460
|
+
text: `RECALLED MEMORY [${key}]:\n${memory.value}\n\n(Stored: ${memory.timestamp})`,
|
|
461
|
+
},
|
|
462
|
+
],
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
} else {
|
|
466
|
+
const keys = Object.keys(store.memories);
|
|
467
|
+
if (keys.length === 0) {
|
|
468
|
+
result = {
|
|
469
|
+
content: [
|
|
470
|
+
{
|
|
471
|
+
type: "text",
|
|
472
|
+
text: "NO MEMORIES STORED. Use rigour_remember to persist important instructions.",
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
};
|
|
476
|
+
} else {
|
|
477
|
+
const allMemories = keys.map(k => {
|
|
478
|
+
const mem = store.memories[k];
|
|
479
|
+
return `## ${k}\n${mem.value}\n(Stored: ${mem.timestamp})`;
|
|
480
|
+
}).join("\n\n---\n\n");
|
|
481
|
+
|
|
482
|
+
result = {
|
|
483
|
+
content: [
|
|
484
|
+
{
|
|
485
|
+
type: "text",
|
|
486
|
+
text: `RECALLED ALL MEMORIES (${keys.length} items):\n\n${allMemories}\n\n---\nIMPORTANT: Follow these stored instructions throughout this session.`,
|
|
487
|
+
},
|
|
488
|
+
],
|
|
489
|
+
};
|
|
366
490
|
}
|
|
367
|
-
|
|
491
|
+
}
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
case "rigour_forget": {
|
|
496
|
+
const { key } = args as any;
|
|
497
|
+
const store = await loadMemory(cwd);
|
|
498
|
+
|
|
499
|
+
if (!store.memories[key]) {
|
|
500
|
+
result = {
|
|
368
501
|
content: [
|
|
369
502
|
{
|
|
370
503
|
type: "text",
|
|
371
|
-
text: `
|
|
504
|
+
text: `NO MEMORY FOUND for key "${key}". Nothing to forget.`,
|
|
372
505
|
},
|
|
373
506
|
],
|
|
374
507
|
};
|
|
375
|
-
}
|
|
508
|
+
} else {
|
|
509
|
+
delete store.memories[key];
|
|
510
|
+
await saveMemory(cwd, store);
|
|
376
511
|
|
|
377
|
-
|
|
378
|
-
if (keys.length === 0) {
|
|
379
|
-
return {
|
|
512
|
+
result = {
|
|
380
513
|
content: [
|
|
381
514
|
{
|
|
382
515
|
type: "text",
|
|
383
|
-
text:
|
|
516
|
+
text: `MEMORY DELETED: "${key}" has been removed.`,
|
|
384
517
|
},
|
|
385
518
|
],
|
|
386
519
|
};
|
|
387
520
|
}
|
|
521
|
+
break;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
case "rigour_check_pattern": {
|
|
525
|
+
const { name: patternName, type, intent } = args as any;
|
|
526
|
+
const indexPath = getDefaultIndexPath(cwd);
|
|
527
|
+
const index = await loadPatternIndex(indexPath);
|
|
528
|
+
|
|
529
|
+
let resultText = "";
|
|
530
|
+
|
|
531
|
+
// 1. Check for Reinvention
|
|
532
|
+
if (index) {
|
|
533
|
+
const matcher = new PatternMatcher(index);
|
|
534
|
+
const matchResult = await matcher.match({ name: patternName, type, intent });
|
|
535
|
+
|
|
536
|
+
if (matchResult.status === "FOUND_SIMILAR") {
|
|
537
|
+
resultText += `🚨 PATTERN REINVENTION DETECTED\n`;
|
|
538
|
+
resultText += `Similar pattern already exists: "${matchResult.matches[0].pattern.name}" in ${matchResult.matches[0].pattern.file}\n`;
|
|
539
|
+
resultText += `SUGGESTION: ${matchResult.suggestion}\n\n`;
|
|
540
|
+
}
|
|
541
|
+
} else {
|
|
542
|
+
resultText += `⚠️ Pattern index not found. Run 'rigour index' to enable reinvention detection.\n\n`;
|
|
543
|
+
}
|
|
388
544
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
545
|
+
// 2. Check for Staleness/Best Practices
|
|
546
|
+
const detector = new StalenessDetector(cwd);
|
|
547
|
+
const staleness = await detector.checkStaleness(`${type || 'function'} ${patternName} {}`);
|
|
548
|
+
|
|
549
|
+
if (staleness.status !== "FRESH") {
|
|
550
|
+
resultText += `⚠️ STALENESS/ANTI-PATTERN WARNING\n`;
|
|
551
|
+
for (const issue of staleness.issues) {
|
|
552
|
+
resultText += `- ${issue.reason}\n REPLACEMENT: ${issue.replacement}\n`;
|
|
553
|
+
}
|
|
554
|
+
resultText += `\n`;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// 3. Check Security for this library (if it's an import)
|
|
558
|
+
if (intent && intent.includes('import')) {
|
|
559
|
+
const security = new SecurityDetector(cwd);
|
|
560
|
+
const audit = await security.runAudit();
|
|
561
|
+
const relatedVulns = audit.vulnerabilities.filter(v =>
|
|
562
|
+
patternName.toLowerCase().includes(v.packageName.toLowerCase()) ||
|
|
563
|
+
intent.toLowerCase().includes(v.packageName.toLowerCase())
|
|
564
|
+
);
|
|
565
|
+
|
|
566
|
+
if (relatedVulns.length > 0) {
|
|
567
|
+
resultText += `🛡️ SECURITY/CVE WARNING\n`;
|
|
568
|
+
for (const v of relatedVulns) {
|
|
569
|
+
resultText += `- [${v.severity.toUpperCase()}] ${v.packageName}: ${v.title} (${v.url})\n`;
|
|
570
|
+
}
|
|
571
|
+
resultText += `\n`;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
393
574
|
|
|
394
|
-
|
|
575
|
+
if (!resultText) {
|
|
576
|
+
resultText = `✅ Pattern "${patternName}" is fresh, secure, and unique to the codebase.\n\nRECOMMENDED ACTION: Proceed with implementation.`;
|
|
577
|
+
} else {
|
|
578
|
+
let recommendation = "Proceed with caution, addressing the warnings above.";
|
|
579
|
+
if (resultText.includes("🚨 PATTERN REINVENTION")) {
|
|
580
|
+
recommendation = "STOP and REUSE the existing pattern mentioned above. Do not create a duplicate.";
|
|
581
|
+
} else if (resultText.includes("🛡️ SECURITY/CVE WARNING")) {
|
|
582
|
+
recommendation = "STOP and update your dependencies or find an alternative library. Do not proceed with vulnerable code.";
|
|
583
|
+
} else if (resultText.includes("⚠️ STALENESS")) {
|
|
584
|
+
recommendation = "Follow the replacement suggestion to ensure best practices.";
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
resultText += `\nRECOMMENDED ACTION: ${recommendation}`;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
result = {
|
|
395
591
|
content: [
|
|
396
592
|
{
|
|
397
593
|
type: "text",
|
|
398
|
-
text:
|
|
594
|
+
text: resultText,
|
|
399
595
|
},
|
|
400
596
|
],
|
|
401
597
|
};
|
|
598
|
+
break;
|
|
402
599
|
}
|
|
403
600
|
|
|
404
|
-
case "
|
|
405
|
-
const
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
if (!store.memories[key]) {
|
|
409
|
-
return {
|
|
410
|
-
content: [
|
|
411
|
-
{
|
|
412
|
-
type: "text",
|
|
413
|
-
text: `NO MEMORY FOUND for key "${key}". Nothing to forget.`,
|
|
414
|
-
},
|
|
415
|
-
],
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
delete store.memories[key];
|
|
420
|
-
await saveMemory(cwd, store);
|
|
421
|
-
|
|
422
|
-
return {
|
|
601
|
+
case "rigour_security_audit": {
|
|
602
|
+
const security = new SecurityDetector(cwd);
|
|
603
|
+
const summary = await security.getSecuritySummary();
|
|
604
|
+
result = {
|
|
423
605
|
content: [
|
|
424
606
|
{
|
|
425
607
|
type: "text",
|
|
426
|
-
text:
|
|
608
|
+
text: summary,
|
|
427
609
|
},
|
|
428
610
|
],
|
|
429
611
|
};
|
|
612
|
+
break;
|
|
430
613
|
}
|
|
431
614
|
|
|
432
615
|
default:
|
|
433
616
|
throw new Error(`Unknown tool: ${name}`);
|
|
434
617
|
}
|
|
618
|
+
|
|
619
|
+
await logStudioEvent(cwd, {
|
|
620
|
+
type: "tool_response",
|
|
621
|
+
requestId,
|
|
622
|
+
tool: name,
|
|
623
|
+
status: "success",
|
|
624
|
+
content: result.content,
|
|
625
|
+
_rigour_report: (result as any)._rigour_report
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
return result;
|
|
629
|
+
|
|
435
630
|
} catch (error: any) {
|
|
436
|
-
|
|
631
|
+
const errorResponse = {
|
|
437
632
|
content: [
|
|
438
633
|
{
|
|
439
634
|
type: "text",
|
|
@@ -442,6 +637,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
442
637
|
],
|
|
443
638
|
isError: true,
|
|
444
639
|
};
|
|
640
|
+
|
|
641
|
+
await logStudioEvent(cwd, {
|
|
642
|
+
type: "tool_response",
|
|
643
|
+
requestId,
|
|
644
|
+
tool: name,
|
|
645
|
+
status: "error",
|
|
646
|
+
error: error.message,
|
|
647
|
+
content: errorResponse.content
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
return errorResponse;
|
|
445
651
|
}
|
|
446
652
|
});
|
|
447
653
|
|