@rigour-labs/mcp 4.0.4 → 4.1.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 +1 -1
- package/dist/index.js +28 -8
- package/dist/tools/agent-handlers.js +26 -4
- package/dist/tools/deep-handlers.js +16 -6
- package/dist/tools/deep-handlers.test.d.ts +1 -0
- package/dist/tools/deep-handlers.test.js +48 -0
- package/dist/tools/definitions.d.ts +77 -0
- package/dist/tools/definitions.js +50 -2
- package/dist/tools/execution-handlers.js +7 -1
- package/dist/tools/hooks-handler.js +5 -2
- package/dist/tools/mcp-settings-handler.d.ts +12 -0
- package/dist/tools/mcp-settings-handler.js +20 -0
- package/dist/tools/mcp-settings-handler.test.d.ts +1 -0
- package/dist/tools/mcp-settings-handler.test.js +31 -0
- package/dist/tools/prompts.js +1 -1
- package/dist/tools/quality-handlers.d.ts +11 -1
- package/dist/tools/quality-handlers.js +33 -3
- package/dist/tools/quality-handlers.test.d.ts +1 -0
- package/dist/tools/quality-handlers.test.js +108 -0
- package/dist/utils/config.d.ts +6 -0
- package/dist/utils/config.js +38 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ Rigour moves code quality enforcement from the "Post-Commit" phase to the "In-Pr
|
|
|
24
24
|
- **Security Audits**: Real-time CVE detection for dependencies the AI is suggesting.
|
|
25
25
|
- **Multi-Agent Governance**: Agent registration, scope isolation, checkpoint supervision, and verified handoffs for multi-agent workflows.
|
|
26
26
|
- **Industry Presets**: SOC2, HIPAA, FedRAMP-ready gate configurations.
|
|
27
|
-
- **
|
|
27
|
+
- **Local-First**: Deterministic gates run locally. If deep analysis is configured with a cloud provider, code context may be sent to that provider.
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema
|
|
|
13
13
|
import { randomUUID } from "crypto";
|
|
14
14
|
import { GateRunner } from "@rigour-labs/core";
|
|
15
15
|
// Utils
|
|
16
|
-
import { loadConfig, logStudioEvent } from './utils/config.js';
|
|
16
|
+
import { loadConfig, loadMcpSettings, logStudioEvent } from './utils/config.js';
|
|
17
17
|
// Tool definitions
|
|
18
18
|
import { TOOL_DEFINITIONS } from './tools/definitions.js';
|
|
19
19
|
// Tool handlers
|
|
@@ -25,6 +25,7 @@ import { handleAgentRegister, handleCheckpoint, handleHandoff, handleAgentDeregi
|
|
|
25
25
|
import { handleReview } from './tools/review-handler.js';
|
|
26
26
|
import { handleHooksCheck, handleHooksInit } from './tools/hooks-handler.js';
|
|
27
27
|
import { handleCheckDeep, handleDeepStats } from './tools/deep-handlers.js';
|
|
28
|
+
import { handleMcpGetSettings, handleMcpSetSettings } from './tools/mcp-settings-handler.js';
|
|
28
29
|
// ─── Server Setup ─────────────────────────────────────────────────
|
|
29
30
|
const server = new Server({ name: "rigour-mcp", version: "3.0.1" }, { capabilities: { tools: {}, prompts: {} } });
|
|
30
31
|
// ─── Tool Listing ─────────────────────────────────────────────────
|
|
@@ -43,9 +44,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
43
44
|
let result;
|
|
44
45
|
switch (name) {
|
|
45
46
|
// Quality gates
|
|
46
|
-
case "rigour_check":
|
|
47
|
-
|
|
47
|
+
case "rigour_check": {
|
|
48
|
+
const { files, deep, pro, apiKey, provider, apiBaseUrl, modelName } = args;
|
|
49
|
+
const mcpSettings = await loadMcpSettings(cwd);
|
|
50
|
+
result = await handleCheck(runner, cwd, {
|
|
51
|
+
files,
|
|
52
|
+
deep: deep ?? mcpSettings.deep_default_mode,
|
|
53
|
+
pro,
|
|
54
|
+
apiKey,
|
|
55
|
+
provider,
|
|
56
|
+
apiBaseUrl,
|
|
57
|
+
modelName,
|
|
58
|
+
});
|
|
48
59
|
break;
|
|
60
|
+
}
|
|
49
61
|
case "rigour_explain":
|
|
50
62
|
result = await handleExplain(runner, cwd);
|
|
51
63
|
break;
|
|
@@ -61,6 +73,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
61
73
|
case "rigour_get_config":
|
|
62
74
|
result = handleGetConfig(config);
|
|
63
75
|
break;
|
|
76
|
+
case "rigour_mcp_get_settings":
|
|
77
|
+
result = await handleMcpGetSettings(cwd);
|
|
78
|
+
break;
|
|
79
|
+
case "rigour_mcp_set_settings":
|
|
80
|
+
result = await handleMcpSetSettings(cwd, args);
|
|
81
|
+
break;
|
|
64
82
|
// Memory
|
|
65
83
|
case "rigour_remember":
|
|
66
84
|
result = await handleRemember(cwd, args.key, args.value);
|
|
@@ -156,6 +174,8 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
|
156
174
|
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
157
175
|
const { name, arguments: promptArgs } = request.params;
|
|
158
176
|
const cwd = promptArgs?.cwd || process.env.RIGOUR_CWD || process.cwd();
|
|
177
|
+
const mcpSettings = await loadMcpSettings(cwd);
|
|
178
|
+
const deepMode = mcpSettings.deep_default_mode;
|
|
159
179
|
const prompt = PROMPT_DEFINITIONS.find((p) => p.name === name);
|
|
160
180
|
if (!prompt) {
|
|
161
181
|
throw new Error(`Unknown prompt: ${name}`);
|
|
@@ -170,7 +190,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
170
190
|
role: "user",
|
|
171
191
|
content: {
|
|
172
192
|
type: "text",
|
|
173
|
-
text: `Initialize Rigour quality gates for the project at ${cwd}. Run \`rigour_check\` to see current quality score, then use \`rigour_hooks_init\` to install real-time hooks for the detected AI coding tool. Report the score breakdown (overall, AI health, structural) and any critical violations.`,
|
|
193
|
+
text: `Initialize Rigour quality gates for the project at ${cwd}. Run \`rigour_check\` to see current quality score, then use \`rigour_hooks_init\` to install real-time hooks for the detected AI coding tool. MCP default deep mode for \`rigour_check\` is "${deepMode}". Report the score breakdown (overall, AI health, structural) and any critical violations.`,
|
|
174
194
|
},
|
|
175
195
|
},
|
|
176
196
|
],
|
|
@@ -184,7 +204,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
184
204
|
role: "user",
|
|
185
205
|
content: {
|
|
186
206
|
type: "text",
|
|
187
|
-
text: `Run \`rigour_check\` on ${cwd}. If the project FAILS, retrieve the fix packet with \`rigour_get_fix_packet\` and fix every violation in priority order (critical → high → medium → low). After each fix, re-run \`rigour_check\` to verify. Repeat until PASS. Do NOT skip any violation. Report progress after each iteration.`,
|
|
207
|
+
text: `Run \`rigour_check\` on ${cwd}. MCP default deep mode for \`rigour_check\` is "${deepMode}". If the project FAILS, retrieve the fix packet with \`rigour_get_fix_packet\` and fix every violation in priority order (critical → high → medium → low). After each fix, re-run \`rigour_check\` to verify. Repeat until PASS. Do NOT skip any violation. Report progress after each iteration.`,
|
|
188
208
|
},
|
|
189
209
|
},
|
|
190
210
|
],
|
|
@@ -198,7 +218,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
198
218
|
role: "user",
|
|
199
219
|
content: {
|
|
200
220
|
type: "text",
|
|
201
|
-
text: `Perform a full security review on ${cwd}. First run \`rigour_security_audit\` for CVE checks on dependencies. Then run \`rigour_check\` and filter for security-provenance violations (hardcoded secrets, SQL injection, XSS, command injection, path traversal). Report all findings with severity, file locations, and remediation instructions.`,
|
|
221
|
+
text: `Perform a full security review on ${cwd}. First run \`rigour_security_audit\` for CVE checks on dependencies. Then run \`rigour_check\` (default deep mode "${deepMode}") and filter for security-provenance violations (hardcoded secrets, SQL injection, XSS, command injection, path traversal). Report all findings with severity, file locations, and remediation instructions.`,
|
|
202
222
|
},
|
|
203
223
|
},
|
|
204
224
|
],
|
|
@@ -212,7 +232,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
212
232
|
role: "user",
|
|
213
233
|
content: {
|
|
214
234
|
type: "text",
|
|
215
|
-
text: `Run a pre-commit quality check on ${cwd}. Execute \`rigour_check\` and \`rigour_hooks_check\` on all staged files. If any critical or high severity violations exist, list them and block the commit. For medium/low violations, warn but allow. Provide a one-line summary: PASS (safe to commit) or FAIL (must fix first).`,
|
|
235
|
+
text: `Run a pre-commit quality check on ${cwd}. Execute \`rigour_check\` (default deep mode "${deepMode}") and \`rigour_hooks_check\` on all staged files. If any critical or high severity violations exist, list them and block the commit. For medium/low violations, warn but allow. Provide a one-line summary: PASS (safe to commit) or FAIL (must fix first).`,
|
|
216
236
|
},
|
|
217
237
|
},
|
|
218
238
|
],
|
|
@@ -226,7 +246,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
226
246
|
role: "user",
|
|
227
247
|
content: {
|
|
228
248
|
type: "text",
|
|
229
|
-
text: `Generate an AI code health report for ${cwd}. Run \`rigour_check\` and focus on AI-drift provenance violations: hallucinated imports, duplication drift, context window artifacts, inconsistent error handling, and promise safety. Compare AI health score vs structural score. Provide a summary table of AI-specific issues and concrete next steps to improve the AI health score.`,
|
|
249
|
+
text: `Generate an AI code health report for ${cwd}. Run \`rigour_check\` (default deep mode "${deepMode}") and focus on AI-drift provenance violations: hallucinated imports, duplication drift, context window artifacts, inconsistent error handling, and promise safety. Compare AI health score vs structural score. Provide a summary table of AI-specific issues and concrete next steps to improve the AI health score.`,
|
|
230
250
|
},
|
|
231
251
|
},
|
|
232
252
|
],
|
|
@@ -9,12 +9,23 @@
|
|
|
9
9
|
import fs from "fs-extra";
|
|
10
10
|
import path from "path";
|
|
11
11
|
import { logStudioEvent } from '../utils/config.js';
|
|
12
|
+
function parseJsonOrNull(raw) {
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(raw);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
12
20
|
// ─── Agent Register ───────────────────────────────────────────────
|
|
13
21
|
export async function handleAgentRegister(cwd, agentId, taskScope, requestId) {
|
|
14
22
|
const sessionPath = path.join(cwd, '.rigour', 'agent-session.json');
|
|
15
23
|
let session = { agents: [], startedAt: new Date().toISOString() };
|
|
16
24
|
if (await fs.pathExists(sessionPath)) {
|
|
17
|
-
|
|
25
|
+
const parsed = parseJsonOrNull(await fs.readFile(sessionPath, 'utf-8'));
|
|
26
|
+
if (parsed) {
|
|
27
|
+
session = parsed;
|
|
28
|
+
}
|
|
18
29
|
}
|
|
19
30
|
const existingIdx = session.agents.findIndex((a) => a.agentId === agentId);
|
|
20
31
|
if (existingIdx >= 0) {
|
|
@@ -63,7 +74,10 @@ export async function handleCheckpoint(cwd, progressPct, filesChanged, summary,
|
|
|
63
74
|
status: 'active',
|
|
64
75
|
};
|
|
65
76
|
if (await fs.pathExists(checkpointPath)) {
|
|
66
|
-
|
|
77
|
+
const parsed = parseJsonOrNull(await fs.readFile(checkpointPath, 'utf-8'));
|
|
78
|
+
if (parsed) {
|
|
79
|
+
session = parsed;
|
|
80
|
+
}
|
|
67
81
|
}
|
|
68
82
|
const checkpointId = `cp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
69
83
|
const warnings = [];
|
|
@@ -128,7 +142,10 @@ export async function handleAgentDeregister(cwd, agentId, requestId) {
|
|
|
128
142
|
if (!await fs.pathExists(sessionPath)) {
|
|
129
143
|
return { content: [{ type: "text", text: `❌ No active agent session found.` }] };
|
|
130
144
|
}
|
|
131
|
-
const session =
|
|
145
|
+
const session = parseJsonOrNull(await fs.readFile(sessionPath, 'utf-8'));
|
|
146
|
+
if (!session || !Array.isArray(session.agents)) {
|
|
147
|
+
return { content: [{ type: "text", text: `❌ Agent session file is malformed.` }], isError: true };
|
|
148
|
+
}
|
|
132
149
|
const initialCount = session.agents.length;
|
|
133
150
|
session.agents = session.agents.filter((a) => a.agentId !== agentId);
|
|
134
151
|
if (session.agents.length === initialCount) {
|
|
@@ -150,7 +167,12 @@ export async function handleHandoffAccept(cwd, handoffId, agentId, requestId) {
|
|
|
150
167
|
return { content: [{ type: "text", text: `❌ No handoffs found.` }] };
|
|
151
168
|
}
|
|
152
169
|
const content = await fs.readFile(handoffPath, 'utf-8');
|
|
153
|
-
const handoffs = content
|
|
170
|
+
const handoffs = content
|
|
171
|
+
.trim()
|
|
172
|
+
.split('\n')
|
|
173
|
+
.filter(l => l)
|
|
174
|
+
.map(line => parseJsonOrNull(line))
|
|
175
|
+
.filter((entry) => !!entry);
|
|
154
176
|
const handoff = handoffs.find((h) => h.handoffId === handoffId);
|
|
155
177
|
if (!handoff) {
|
|
156
178
|
return { content: [{ type: "text", text: `❌ Handoff "${handoffId}" not found.` }] };
|
|
@@ -6,15 +6,25 @@
|
|
|
6
6
|
* @since v4.0.0
|
|
7
7
|
*/
|
|
8
8
|
import path from "path";
|
|
9
|
+
function resolveDeepExecution(args) {
|
|
10
|
+
const requestedProvider = (args.provider || '').toLowerCase();
|
|
11
|
+
const isForcedLocal = requestedProvider === 'local';
|
|
12
|
+
const isLocal = !args.apiKey || isForcedLocal;
|
|
13
|
+
return {
|
|
14
|
+
isLocal,
|
|
15
|
+
provider: isLocal ? 'local' : (args.provider || 'claude'),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
9
18
|
/**
|
|
10
19
|
* Run quality gates with deep LLM-powered analysis.
|
|
11
20
|
*/
|
|
12
21
|
export async function handleCheckDeep(runner, cwd, config, args) {
|
|
22
|
+
const execution = resolveDeepExecution(args);
|
|
13
23
|
const deepOpts = {
|
|
14
24
|
enabled: true,
|
|
15
25
|
pro: !!args.pro,
|
|
16
26
|
apiKey: args.apiKey,
|
|
17
|
-
provider:
|
|
27
|
+
provider: execution.provider,
|
|
18
28
|
apiBaseUrl: args.apiBaseUrl,
|
|
19
29
|
modelName: args.modelName,
|
|
20
30
|
};
|
|
@@ -26,7 +36,7 @@ export async function handleCheckDeep(runner, cwd, config, args) {
|
|
|
26
36
|
if (db) {
|
|
27
37
|
const repoName = path.basename(cwd);
|
|
28
38
|
const scanId = insertScan(db, repoName, report, {
|
|
29
|
-
deepTier: args.pro ? 'pro' : (
|
|
39
|
+
deepTier: args.pro ? 'pro' : (execution.isLocal ? 'deep' : 'cloud'),
|
|
30
40
|
deepModel: report.stats.deep?.model,
|
|
31
41
|
});
|
|
32
42
|
insertFindings(db, scanId, report.failures);
|
|
@@ -41,19 +51,19 @@ export async function handleCheckDeep(runner, cwd, config, args) {
|
|
|
41
51
|
const aiHealth = stats.ai_health_score ?? 100;
|
|
42
52
|
const codeQuality = stats.code_quality_score ?? stats.structural_score ?? 100;
|
|
43
53
|
const overall = stats.score ?? 100;
|
|
44
|
-
const isLocal =
|
|
54
|
+
const isLocal = execution.isLocal;
|
|
45
55
|
let text = `RIGOUR DEEP ANALYSIS: ${report.status}\n\n`;
|
|
46
56
|
text += `AI Health: ${aiHealth}/100\n`;
|
|
47
57
|
text += `Code Quality: ${codeQuality}/100\n`;
|
|
48
58
|
text += `Overall: ${overall}/100\n\n`;
|
|
49
59
|
if (isLocal) {
|
|
50
|
-
text += `🔒
|
|
60
|
+
text += `🔒 Local sidecar/model execution. Code remains on this machine.\n`;
|
|
51
61
|
}
|
|
52
62
|
else {
|
|
53
|
-
text += `☁️ Code
|
|
63
|
+
text += `☁️ Cloud provider execution. Code context may be sent to ${execution.provider} API.\n`;
|
|
54
64
|
}
|
|
55
65
|
if (stats.deep) {
|
|
56
|
-
const tier = stats.deep.tier === 'cloud' ?
|
|
66
|
+
const tier = stats.deep.tier === 'cloud' ? execution.provider : stats.deep.tier;
|
|
57
67
|
const model = stats.deep.model || 'unknown';
|
|
58
68
|
const inferenceSec = stats.deep.total_ms ? (stats.deep.total_ms / 1000).toFixed(1) + 's' : '';
|
|
59
69
|
text += `Model: ${model} (${tier}) ${inferenceSec}\n`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { handleCheckDeep } from './deep-handlers.js';
|
|
3
|
+
describe('handleCheckDeep privacy routing', () => {
|
|
4
|
+
const baseReport = {
|
|
5
|
+
status: 'PASS',
|
|
6
|
+
summary: { 'ast-analysis': 'PASS' },
|
|
7
|
+
failures: [],
|
|
8
|
+
stats: {
|
|
9
|
+
duration_ms: 10,
|
|
10
|
+
score: 100,
|
|
11
|
+
ai_health_score: 100,
|
|
12
|
+
structural_score: 100,
|
|
13
|
+
deep: { enabled: true, tier: 'deep', model: 'Qwen2.5-Coder-0.5B', total_ms: 1000 },
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
it('reports local execution by default', async () => {
|
|
17
|
+
const run = vi.fn().mockResolvedValue(baseReport);
|
|
18
|
+
const runner = { run };
|
|
19
|
+
const result = await handleCheckDeep(runner, '/repo', {}, {});
|
|
20
|
+
expect(run).toHaveBeenCalledWith('/repo', undefined, {
|
|
21
|
+
enabled: true,
|
|
22
|
+
pro: false,
|
|
23
|
+
apiKey: undefined,
|
|
24
|
+
provider: 'local',
|
|
25
|
+
apiBaseUrl: undefined,
|
|
26
|
+
modelName: undefined,
|
|
27
|
+
});
|
|
28
|
+
expect(result.content[0].text).toContain('Local sidecar/model execution');
|
|
29
|
+
});
|
|
30
|
+
it('respects provider=local even when apiKey exists', async () => {
|
|
31
|
+
const run = vi.fn().mockResolvedValue(baseReport);
|
|
32
|
+
const runner = { run };
|
|
33
|
+
const result = await handleCheckDeep(runner, '/repo', {}, {
|
|
34
|
+
apiKey: 'sk-test',
|
|
35
|
+
provider: 'local',
|
|
36
|
+
});
|
|
37
|
+
expect(run).toHaveBeenCalledWith('/repo', undefined, {
|
|
38
|
+
enabled: true,
|
|
39
|
+
pro: false,
|
|
40
|
+
apiKey: 'sk-test',
|
|
41
|
+
provider: 'local',
|
|
42
|
+
apiBaseUrl: undefined,
|
|
43
|
+
modelName: undefined,
|
|
44
|
+
});
|
|
45
|
+
expect(result.content[0].text).toContain('Local sidecar/model execution');
|
|
46
|
+
expect(result.content[0].text).not.toContain('Code context may be sent');
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -21,6 +21,83 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
21
21
|
inputSchema: {
|
|
22
22
|
type: string;
|
|
23
23
|
properties: {
|
|
24
|
+
files: {
|
|
25
|
+
type: string;
|
|
26
|
+
items: {
|
|
27
|
+
type: string;
|
|
28
|
+
};
|
|
29
|
+
description: string;
|
|
30
|
+
};
|
|
31
|
+
deep: {
|
|
32
|
+
type: string;
|
|
33
|
+
enum: string[];
|
|
34
|
+
description: string;
|
|
35
|
+
};
|
|
36
|
+
pro: {
|
|
37
|
+
type: string;
|
|
38
|
+
description: string;
|
|
39
|
+
};
|
|
40
|
+
apiKey: {
|
|
41
|
+
type: string;
|
|
42
|
+
description: string;
|
|
43
|
+
};
|
|
44
|
+
provider: {
|
|
45
|
+
type: string;
|
|
46
|
+
description: string;
|
|
47
|
+
};
|
|
48
|
+
apiBaseUrl: {
|
|
49
|
+
type: string;
|
|
50
|
+
description: string;
|
|
51
|
+
};
|
|
52
|
+
modelName: {
|
|
53
|
+
type: string;
|
|
54
|
+
description: string;
|
|
55
|
+
};
|
|
56
|
+
cwd: {
|
|
57
|
+
type: "string";
|
|
58
|
+
description: string;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
required: string[];
|
|
62
|
+
};
|
|
63
|
+
annotations: {
|
|
64
|
+
title: string;
|
|
65
|
+
readOnlyHint: boolean;
|
|
66
|
+
destructiveHint: boolean;
|
|
67
|
+
idempotentHint: boolean;
|
|
68
|
+
openWorldHint: boolean;
|
|
69
|
+
};
|
|
70
|
+
} | {
|
|
71
|
+
name: string;
|
|
72
|
+
description: string;
|
|
73
|
+
inputSchema: {
|
|
74
|
+
type: string;
|
|
75
|
+
properties: {
|
|
76
|
+
cwd: {
|
|
77
|
+
type: "string";
|
|
78
|
+
description: string;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
required: string[];
|
|
82
|
+
};
|
|
83
|
+
annotations: {
|
|
84
|
+
title: string;
|
|
85
|
+
readOnlyHint: boolean;
|
|
86
|
+
destructiveHint: boolean;
|
|
87
|
+
idempotentHint: boolean;
|
|
88
|
+
openWorldHint: boolean;
|
|
89
|
+
};
|
|
90
|
+
} | {
|
|
91
|
+
name: string;
|
|
92
|
+
description: string;
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: string;
|
|
95
|
+
properties: {
|
|
96
|
+
deep_default_mode: {
|
|
97
|
+
type: string;
|
|
98
|
+
enum: string[];
|
|
99
|
+
description: string;
|
|
100
|
+
};
|
|
24
101
|
cwd: {
|
|
25
102
|
type: "string";
|
|
26
103
|
description: string;
|
|
@@ -27,10 +27,19 @@ export const TOOL_DEFINITIONS = [
|
|
|
27
27
|
// ─── Core Quality Gates ───────────────────────────────
|
|
28
28
|
{
|
|
29
29
|
name: "rigour_check",
|
|
30
|
-
description: "Run quality gate checks on the project.
|
|
30
|
+
description: "Run quality gate checks on the project. Deep modes: off (fast deterministic gates only), quick (deep enabled with standard local tier unless cloud provider is configured), full (deep enabled, optional pro model).",
|
|
31
31
|
inputSchema: {
|
|
32
32
|
type: "object",
|
|
33
|
-
properties:
|
|
33
|
+
properties: {
|
|
34
|
+
...cwdParam(),
|
|
35
|
+
files: { type: "array", items: { type: "string" }, description: "Optional file paths (relative to cwd) to limit scan scope for both deterministic and deep checks." },
|
|
36
|
+
deep: { type: "string", enum: ["off", "quick", "full"], description: "Deep mode: 'off' (default), 'quick' (deep enabled with standard model), 'full' (deep enabled, combine with pro=true for larger local model)." },
|
|
37
|
+
pro: { type: "boolean", description: "Use larger local deep model tier when deep is enabled." },
|
|
38
|
+
apiKey: { type: "string", description: "Optional cloud API key for deep analysis." },
|
|
39
|
+
provider: { type: "string", description: "Cloud provider for deep analysis (claude, openai, gemini, groq, mistral, together, deepseek, ollama, etc.)." },
|
|
40
|
+
apiBaseUrl: { type: "string", description: "Custom API base URL for self-hosted/proxy deep endpoints." },
|
|
41
|
+
modelName: { type: "string", description: "Override cloud model name for deep analysis." },
|
|
42
|
+
},
|
|
34
43
|
required: ["cwd"],
|
|
35
44
|
},
|
|
36
45
|
annotations: {
|
|
@@ -121,6 +130,45 @@ export const TOOL_DEFINITIONS = [
|
|
|
121
130
|
openWorldHint: false,
|
|
122
131
|
},
|
|
123
132
|
},
|
|
133
|
+
{
|
|
134
|
+
name: "rigour_mcp_get_settings",
|
|
135
|
+
description: "Get Rigour MCP runtime settings for this repository (.rigour/mcp-settings.json).",
|
|
136
|
+
inputSchema: {
|
|
137
|
+
type: "object",
|
|
138
|
+
properties: cwdParam(),
|
|
139
|
+
required: ["cwd"],
|
|
140
|
+
},
|
|
141
|
+
annotations: {
|
|
142
|
+
title: "Get MCP Settings",
|
|
143
|
+
readOnlyHint: true,
|
|
144
|
+
destructiveHint: false,
|
|
145
|
+
idempotentHint: true,
|
|
146
|
+
openWorldHint: false,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: "rigour_mcp_set_settings",
|
|
151
|
+
description: "Set Rigour MCP runtime settings for this repository. Currently supports deep_default_mode: off | quick | full.",
|
|
152
|
+
inputSchema: {
|
|
153
|
+
type: "object",
|
|
154
|
+
properties: {
|
|
155
|
+
...cwdParam(),
|
|
156
|
+
deep_default_mode: {
|
|
157
|
+
type: "string",
|
|
158
|
+
enum: ["off", "quick", "full"],
|
|
159
|
+
description: "Default deep mode applied to rigour_check when deep is not passed.",
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
required: ["cwd", "deep_default_mode"],
|
|
163
|
+
},
|
|
164
|
+
annotations: {
|
|
165
|
+
title: "Set MCP Settings",
|
|
166
|
+
readOnlyHint: false,
|
|
167
|
+
destructiveHint: false,
|
|
168
|
+
idempotentHint: true,
|
|
169
|
+
openWorldHint: false,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
124
172
|
// ─── Memory Persistence ───────────────────────────────
|
|
125
173
|
{
|
|
126
174
|
name: "rigour_remember",
|
|
@@ -122,7 +122,13 @@ async function pollArbitration(cwd, rid, timeout) {
|
|
|
122
122
|
const content = await fs.readFile(eventsPath, 'utf-8');
|
|
123
123
|
const lines = content.split('\n').filter(l => l.trim());
|
|
124
124
|
for (const line of lines.reverse()) {
|
|
125
|
-
|
|
125
|
+
let event;
|
|
126
|
+
try {
|
|
127
|
+
event = JSON.parse(line);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
126
132
|
if (event.tool === 'human_arbitration' && event.requestId === rid) {
|
|
127
133
|
return event.decision;
|
|
128
134
|
}
|
|
@@ -40,8 +40,8 @@ export async function handleHooksCheck(cwd, files, timeout) {
|
|
|
40
40
|
export async function handleHooksInit(cwd, tool, force = false, dryRun = false) {
|
|
41
41
|
try {
|
|
42
42
|
const hookTool = tool;
|
|
43
|
-
const
|
|
44
|
-
const files = generateHookFiles(hookTool,
|
|
43
|
+
const checkerCommand = 'rigour hooks check';
|
|
44
|
+
const files = generateHookFiles(hookTool, checkerCommand);
|
|
45
45
|
if (dryRun) {
|
|
46
46
|
const preview = files.map(f => `${f.path}:\n${f.content}`).join('\n\n');
|
|
47
47
|
return {
|
|
@@ -61,6 +61,9 @@ export async function handleHooksInit(cwd, tool, force = false, dryRun = false)
|
|
|
61
61
|
}
|
|
62
62
|
await fs.ensureDir(path.dirname(fullPath));
|
|
63
63
|
await fs.writeFile(fullPath, file.content);
|
|
64
|
+
if (file.executable) {
|
|
65
|
+
await fs.chmod(fullPath, 0o755);
|
|
66
|
+
}
|
|
64
67
|
written.push(file.path);
|
|
65
68
|
}
|
|
66
69
|
const parts = [];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type ToolResult = {
|
|
2
|
+
content: {
|
|
3
|
+
type: string;
|
|
4
|
+
text: string;
|
|
5
|
+
}[];
|
|
6
|
+
isError?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function handleMcpGetSettings(cwd: string): Promise<ToolResult>;
|
|
9
|
+
export declare function handleMcpSetSettings(cwd: string, settings: {
|
|
10
|
+
deep_default_mode?: string;
|
|
11
|
+
}): Promise<ToolResult>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { loadMcpSettings, saveMcpSettings } from "../utils/config.js";
|
|
2
|
+
export async function handleMcpGetSettings(cwd) {
|
|
3
|
+
const settings = await loadMcpSettings(cwd);
|
|
4
|
+
return {
|
|
5
|
+
content: [{ type: "text", text: JSON.stringify(settings, null, 2) }],
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export async function handleMcpSetSettings(cwd, settings) {
|
|
9
|
+
const value = settings.deep_default_mode;
|
|
10
|
+
if (value !== "off" && value !== "quick" && value !== "full") {
|
|
11
|
+
return {
|
|
12
|
+
content: [{ type: "text", text: "Invalid deep_default_mode. Use one of: off, quick, full." }],
|
|
13
|
+
isError: true,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
await saveMcpSettings(cwd, { deep_default_mode: value });
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: "text", text: `Saved MCP settings: deep_default_mode=${value}` }],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { handleMcpGetSettings, handleMcpSetSettings } from './mcp-settings-handler.js';
|
|
6
|
+
describe('mcp settings handlers', () => {
|
|
7
|
+
let testDir;
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'rigour-mcp-settings-'));
|
|
10
|
+
await fs.ensureDir(path.join(testDir, '.rigour'));
|
|
11
|
+
});
|
|
12
|
+
afterEach(async () => {
|
|
13
|
+
await fs.remove(testDir);
|
|
14
|
+
});
|
|
15
|
+
it('returns default settings when file is missing', async () => {
|
|
16
|
+
const result = await handleMcpGetSettings(testDir);
|
|
17
|
+
const parsed = JSON.parse(result.content[0].text);
|
|
18
|
+
expect(parsed.deep_default_mode).toBe('off');
|
|
19
|
+
});
|
|
20
|
+
it('persists deep_default_mode through set/get', async () => {
|
|
21
|
+
const setResult = await handleMcpSetSettings(testDir, { deep_default_mode: 'quick' });
|
|
22
|
+
expect(setResult.isError).toBeUndefined();
|
|
23
|
+
const getResult = await handleMcpGetSettings(testDir);
|
|
24
|
+
const parsed = JSON.parse(getResult.content[0].text);
|
|
25
|
+
expect(parsed.deep_default_mode).toBe('quick');
|
|
26
|
+
});
|
|
27
|
+
it('rejects invalid deep_default_mode', async () => {
|
|
28
|
+
const result = await handleMcpSetSettings(testDir, { deep_default_mode: 'invalid' });
|
|
29
|
+
expect(result.isError).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
});
|
package/dist/tools/prompts.js
CHANGED
|
@@ -64,7 +64,7 @@ export const PROMPT_DEFINITIONS = [
|
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
name: "rigour-deep-analysis",
|
|
67
|
-
description: "Run deep LLM-powered code quality analysis. AST extracts facts, LLM interprets patterns (SOLID violations, code smells, architecture issues), AST verifies findings.
|
|
67
|
+
description: "Run deep LLM-powered code quality analysis. AST extracts facts, LLM interprets patterns (SOLID violations, code smells, architecture issues), AST verifies findings. Local sidecar by default; cloud provider mode when configured.",
|
|
68
68
|
arguments: [
|
|
69
69
|
{
|
|
70
70
|
name: "cwd",
|
|
@@ -16,7 +16,17 @@ type ToolResult = {
|
|
|
16
16
|
isError?: boolean;
|
|
17
17
|
_rigour_report?: Report;
|
|
18
18
|
};
|
|
19
|
-
|
|
19
|
+
type DeepMode = 'off' | 'quick' | 'full';
|
|
20
|
+
export interface CheckArgs {
|
|
21
|
+
files?: string[];
|
|
22
|
+
deep?: DeepMode;
|
|
23
|
+
pro?: boolean;
|
|
24
|
+
apiKey?: string;
|
|
25
|
+
provider?: string;
|
|
26
|
+
apiBaseUrl?: string;
|
|
27
|
+
modelName?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function handleCheck(runner: GateRunner, cwd: string, args?: CheckArgs): Promise<ToolResult>;
|
|
20
30
|
export declare function handleExplain(runner: GateRunner, cwd: string): Promise<ToolResult>;
|
|
21
31
|
export declare function handleStatus(runner: GateRunner, cwd: string): Promise<ToolResult>;
|
|
22
32
|
export declare function handleGetFixPacket(runner: GateRunner, cwd: string, config: Config): Promise<ToolResult>;
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
function resolveDeepExecution(args) {
|
|
2
|
+
const requestedProvider = (args.provider || '').toLowerCase();
|
|
3
|
+
const isForcedLocal = requestedProvider === 'local';
|
|
4
|
+
const isLocal = !args.apiKey || isForcedLocal;
|
|
5
|
+
return {
|
|
6
|
+
isLocal,
|
|
7
|
+
provider: isLocal ? 'local' : (args.provider || 'claude'),
|
|
8
|
+
};
|
|
9
|
+
}
|
|
1
10
|
// ─── Score / Severity Formatters ──────────────────────────────────
|
|
2
11
|
function formatScoreText(stats) {
|
|
3
12
|
let text = '';
|
|
@@ -17,14 +26,35 @@ function formatSeverityText(stats) {
|
|
|
17
26
|
return parts.length > 0 ? `\nSeverity: ${parts.join(', ')}` : '';
|
|
18
27
|
}
|
|
19
28
|
// ─── Handlers ─────────────────────────────────────────────────────
|
|
20
|
-
export async function handleCheck(runner, cwd) {
|
|
21
|
-
const
|
|
29
|
+
export async function handleCheck(runner, cwd, args = {}) {
|
|
30
|
+
const deepMode = args.deep || 'off';
|
|
31
|
+
const fileTargets = args.files && args.files.length > 0 ? args.files : undefined;
|
|
32
|
+
const execution = resolveDeepExecution(args);
|
|
33
|
+
let deepOpts;
|
|
34
|
+
if (deepMode !== 'off') {
|
|
35
|
+
deepOpts = {
|
|
36
|
+
enabled: true,
|
|
37
|
+
// full mode always means pro-depth analysis in MCP.
|
|
38
|
+
pro: deepMode === 'full' ? true : !!args.pro,
|
|
39
|
+
apiKey: args.apiKey,
|
|
40
|
+
provider: execution.provider,
|
|
41
|
+
apiBaseUrl: args.apiBaseUrl,
|
|
42
|
+
modelName: args.modelName,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const report = await runner.run(cwd, fileTargets, deepOpts);
|
|
22
46
|
const scoreText = formatScoreText(report.stats);
|
|
23
47
|
const sevText = formatSeverityText(report.stats);
|
|
48
|
+
const deepText = deepMode === 'off'
|
|
49
|
+
? ''
|
|
50
|
+
: `\nDeep: ${deepMode} | Execution: ${execution.isLocal ? 'local' : 'cloud'}${report.stats.deep?.model ? ` | Model: ${report.stats.deep.model}` : ''}` +
|
|
51
|
+
`${execution.isLocal
|
|
52
|
+
? '\nPrivacy: Local sidecar/model execution. Code remains on this machine.'
|
|
53
|
+
: `\nPrivacy: Cloud provider execution. Code context may be sent to ${execution.provider} API.`}`;
|
|
24
54
|
const result = {
|
|
25
55
|
content: [{
|
|
26
56
|
type: "text",
|
|
27
|
-
text: `RIGOUR AUDIT RESULT: ${report.status}${scoreText}${sevText}\n\nSummary:\n${Object.entries(report.summary).map(([k, v]) => `- ${k}: ${v}`).join("\n")}`,
|
|
57
|
+
text: `RIGOUR AUDIT RESULT: ${report.status}${scoreText}${sevText}${deepText}\n\nSummary:\n${Object.entries(report.summary).map(([k, v]) => `- ${k}: ${v}`).join("\n")}`,
|
|
28
58
|
}],
|
|
29
59
|
};
|
|
30
60
|
result._rigour_report = report;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { handleCheck } from './quality-handlers.js';
|
|
3
|
+
describe('handleCheck deep routing', () => {
|
|
4
|
+
const baseReport = {
|
|
5
|
+
status: 'PASS',
|
|
6
|
+
summary: { 'ast-analysis': 'PASS' },
|
|
7
|
+
failures: [],
|
|
8
|
+
stats: {
|
|
9
|
+
duration_ms: 10,
|
|
10
|
+
score: 100,
|
|
11
|
+
ai_health_score: 100,
|
|
12
|
+
structural_score: 100,
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
it('runs standard check by default', async () => {
|
|
16
|
+
const run = vi.fn().mockResolvedValue(baseReport);
|
|
17
|
+
const runner = { run };
|
|
18
|
+
await handleCheck(runner, '/repo');
|
|
19
|
+
expect(run).toHaveBeenCalledWith('/repo', undefined, undefined);
|
|
20
|
+
});
|
|
21
|
+
it('maps quick deep mode and file scope', async () => {
|
|
22
|
+
const run = vi.fn().mockResolvedValue({
|
|
23
|
+
...baseReport,
|
|
24
|
+
stats: {
|
|
25
|
+
...baseReport.stats,
|
|
26
|
+
deep: { enabled: true, tier: 'deep', model: 'Qwen2.5-Coder-0.5B' },
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
const runner = { run };
|
|
30
|
+
const result = await handleCheck(runner, '/repo', {
|
|
31
|
+
deep: 'quick',
|
|
32
|
+
files: ['src/a.ts', 'src/b.ts'],
|
|
33
|
+
});
|
|
34
|
+
expect(run).toHaveBeenCalledWith('/repo', ['src/a.ts', 'src/b.ts'], {
|
|
35
|
+
enabled: true,
|
|
36
|
+
pro: false,
|
|
37
|
+
apiKey: undefined,
|
|
38
|
+
provider: 'local',
|
|
39
|
+
apiBaseUrl: undefined,
|
|
40
|
+
modelName: undefined,
|
|
41
|
+
});
|
|
42
|
+
expect(result.content[0].text).toContain('Deep: quick');
|
|
43
|
+
expect(result.content[0].text).toContain('Execution: local');
|
|
44
|
+
expect(result.content[0].text).toContain('Code remains on this machine');
|
|
45
|
+
});
|
|
46
|
+
it('maps full deep mode with cloud provider', async () => {
|
|
47
|
+
const run = vi.fn().mockResolvedValue({
|
|
48
|
+
...baseReport,
|
|
49
|
+
stats: {
|
|
50
|
+
...baseReport.stats,
|
|
51
|
+
deep: { enabled: true, tier: 'cloud', model: 'claude-sonnet' },
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
const runner = { run };
|
|
55
|
+
await handleCheck(runner, '/repo', {
|
|
56
|
+
deep: 'full',
|
|
57
|
+
pro: true,
|
|
58
|
+
apiKey: 'sk-test',
|
|
59
|
+
provider: 'openai',
|
|
60
|
+
modelName: 'gpt-4o-mini',
|
|
61
|
+
apiBaseUrl: 'https://example.com/v1',
|
|
62
|
+
});
|
|
63
|
+
expect(run).toHaveBeenCalledWith('/repo', undefined, {
|
|
64
|
+
enabled: true,
|
|
65
|
+
pro: true,
|
|
66
|
+
apiKey: 'sk-test',
|
|
67
|
+
provider: 'openai',
|
|
68
|
+
apiBaseUrl: 'https://example.com/v1',
|
|
69
|
+
modelName: 'gpt-4o-mini',
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
it('treats full deep mode as pro even when pro flag is omitted', async () => {
|
|
73
|
+
const run = vi.fn().mockResolvedValue(baseReport);
|
|
74
|
+
const runner = { run };
|
|
75
|
+
await handleCheck(runner, '/repo', {
|
|
76
|
+
deep: 'full',
|
|
77
|
+
apiKey: 'sk-test',
|
|
78
|
+
provider: 'openai',
|
|
79
|
+
});
|
|
80
|
+
expect(run).toHaveBeenCalledWith('/repo', undefined, {
|
|
81
|
+
enabled: true,
|
|
82
|
+
pro: true,
|
|
83
|
+
apiKey: 'sk-test',
|
|
84
|
+
provider: 'openai',
|
|
85
|
+
apiBaseUrl: undefined,
|
|
86
|
+
modelName: undefined,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
it('forces local execution when provider=local even if apiKey is present', async () => {
|
|
90
|
+
const run = vi.fn().mockResolvedValue(baseReport);
|
|
91
|
+
const runner = { run };
|
|
92
|
+
const result = await handleCheck(runner, '/repo', {
|
|
93
|
+
deep: 'full',
|
|
94
|
+
apiKey: 'sk-test',
|
|
95
|
+
provider: 'local',
|
|
96
|
+
});
|
|
97
|
+
expect(run).toHaveBeenCalledWith('/repo', undefined, {
|
|
98
|
+
enabled: true,
|
|
99
|
+
pro: true,
|
|
100
|
+
apiKey: 'sk-test',
|
|
101
|
+
provider: 'local',
|
|
102
|
+
apiBaseUrl: undefined,
|
|
103
|
+
modelName: undefined,
|
|
104
|
+
});
|
|
105
|
+
expect(result.content[0].text).toContain('Execution: local');
|
|
106
|
+
expect(result.content[0].text).toContain('Code remains on this machine');
|
|
107
|
+
});
|
|
108
|
+
});
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -202,5 +202,11 @@ export interface MemoryStore {
|
|
|
202
202
|
export declare function getMemoryPath(cwd: string): Promise<string>;
|
|
203
203
|
export declare function loadMemory(cwd: string): Promise<MemoryStore>;
|
|
204
204
|
export declare function saveMemory(cwd: string, store: MemoryStore): Promise<void>;
|
|
205
|
+
export interface McpSettings {
|
|
206
|
+
deep_default_mode: 'off' | 'quick' | 'full';
|
|
207
|
+
}
|
|
208
|
+
export declare function getMcpSettingsPath(cwd: string): Promise<string>;
|
|
209
|
+
export declare function loadMcpSettings(cwd: string): Promise<McpSettings>;
|
|
210
|
+
export declare function saveMcpSettings(cwd: string, settings: McpSettings): Promise<void>;
|
|
205
211
|
export declare function logStudioEvent(cwd: string, event: any): Promise<void>;
|
|
206
212
|
export declare function parseDiff(diff: string): Record<string, Set<number>>;
|
package/dist/utils/config.js
CHANGED
|
@@ -37,7 +37,15 @@ export async function loadMemory(cwd) {
|
|
|
37
37
|
const memPath = await getMemoryPath(cwd);
|
|
38
38
|
if (await fs.pathExists(memPath)) {
|
|
39
39
|
const content = await fs.readFile(memPath, "utf-8");
|
|
40
|
-
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse(content);
|
|
42
|
+
if (parsed && typeof parsed === 'object' && parsed.memories && typeof parsed.memories === 'object') {
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// fall through to default
|
|
48
|
+
}
|
|
41
49
|
}
|
|
42
50
|
return { memories: {} };
|
|
43
51
|
}
|
|
@@ -45,6 +53,35 @@ export async function saveMemory(cwd, store) {
|
|
|
45
53
|
const memPath = await getMemoryPath(cwd);
|
|
46
54
|
await fs.writeFile(memPath, JSON.stringify(store, null, 2));
|
|
47
55
|
}
|
|
56
|
+
const DEFAULT_MCP_SETTINGS = {
|
|
57
|
+
deep_default_mode: 'off',
|
|
58
|
+
};
|
|
59
|
+
export async function getMcpSettingsPath(cwd) {
|
|
60
|
+
const rigourDir = path.join(cwd, ".rigour");
|
|
61
|
+
await fs.ensureDir(rigourDir);
|
|
62
|
+
return path.join(rigourDir, "mcp-settings.json");
|
|
63
|
+
}
|
|
64
|
+
export async function loadMcpSettings(cwd) {
|
|
65
|
+
const settingsPath = await getMcpSettingsPath(cwd);
|
|
66
|
+
if (!(await fs.pathExists(settingsPath))) {
|
|
67
|
+
return DEFAULT_MCP_SETTINGS;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const raw = await fs.readJson(settingsPath);
|
|
71
|
+
const deepMode = raw?.deep_default_mode;
|
|
72
|
+
if (deepMode === 'quick' || deepMode === 'full' || deepMode === 'off') {
|
|
73
|
+
return { deep_default_mode: deepMode };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// Fall through to defaults.
|
|
78
|
+
}
|
|
79
|
+
return DEFAULT_MCP_SETTINGS;
|
|
80
|
+
}
|
|
81
|
+
export async function saveMcpSettings(cwd, settings) {
|
|
82
|
+
const settingsPath = await getMcpSettingsPath(cwd);
|
|
83
|
+
await fs.writeJson(settingsPath, settings, { spaces: 2 });
|
|
84
|
+
}
|
|
48
85
|
// ─── Studio Event Logging ─────────────────────────────────────────
|
|
49
86
|
export async function logStudioEvent(cwd, event) {
|
|
50
87
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rigour-labs/mcp",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "MCP server for AI code governance — OWASP LLM Top 10 (10/10), real-time hooks, 25+ security patterns, hallucinated import detection, multi-agent governance. Works with Claude, Cursor, Cline, Windsurf, Gemini. Industry presets for HIPAA, SOC2, FedRAMP.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://rigour.run",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"execa": "^8.0.1",
|
|
49
49
|
"fs-extra": "^11.2.0",
|
|
50
50
|
"yaml": "^2.8.2",
|
|
51
|
-
"@rigour-labs/core": "4.0
|
|
51
|
+
"@rigour-labs/core": "4.1.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/node": "^25.0.3",
|