@rigour-labs/mcp 6.5.4 → 6.6.1

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 CHANGED
@@ -132,6 +132,8 @@ No global install: npx fetches the server when your agent starts it. Keep a vers
132
132
 
133
133
  Only Rigour's core tools are listed by default; add `"RIGOUR_MCP_TOOLS": "governance,telemetry"` (or `"full"`) to `env` for the rest. Node.js 22 or later is required.
134
134
 
135
+ To keep a server apart from your other work (one per employer or client), add `"RIGOUR_USER_MEMORY": "off"`: it then never reads or writes the memories you keep for all your repositories.
136
+
135
137
  ---
136
138
 
137
139
  ## 📖 Documentation
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema
15
15
  import { randomUUID } from "crypto";
16
16
  import { GateRunner, countUsage, flushDailyUsage } from "@rigour-labs/core";
17
17
  // Utils
18
- import { loadConfig, loadMcpSettings, logStudioEvent } from './utils/config.js';
18
+ import { loadConfig, loadMcpSettings, logStudioEvent, resolveCwd } from './utils/config.js';
19
19
  import { bindServer } from './utils/notifications.js';
20
20
  import { getMcpVersion } from './utils/package-version.js';
21
21
  import { buildMcpResultMeta, buildStudioImpact } from './utils/impact-receipt.js';
@@ -85,7 +85,7 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
85
85
  // ─── Tool Dispatch ────────────────────────────────────────────────
86
86
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
87
87
  const { name, arguments: args } = request.params;
88
- const cwd = args?.cwd || process.cwd();
88
+ const cwd = resolveCwd(args);
89
89
  const requestId = randomUUID();
90
90
  try {
91
91
  await logStudioEvent(cwd, { type: "tool_call", requestId, tool: name, arguments: args });
@@ -369,7 +369,7 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
369
369
  }));
370
370
  server.setRequestHandler(GetPromptRequestSchema, async (request) => {
371
371
  const { name, arguments: promptArgs } = request.params;
372
- const cwd = promptArgs?.cwd || process.env.RIGOUR_CWD || process.cwd();
372
+ const cwd = resolveCwd(promptArgs);
373
373
  const mcpSettings = await loadMcpSettings(cwd);
374
374
  const deepMode = mcpSettings.deep_default_mode;
375
375
  const prompt = PROMPT_DEFINITIONS.find((p) => p.name === name);
@@ -11,7 +11,7 @@
11
11
  import fs from 'fs-extra';
12
12
  import path from 'path';
13
13
  import { createHash } from 'crypto';
14
- import { getTaskContextStats, getTaskCostStats, getCacheStats, explainContext, getSemanticQueryCache, setSemanticQueryCache, findRelatedSemanticQueryCache, filterExistingEditScope, estimateTokenCount, ensureAutomaticIndex, getApplicableLessons, searchTeamKnowledge, recordScopeOffer, } from '@rigour-labs/core';
14
+ import { getTaskContextStats, getTaskCostStats, getCacheStats, explainContext, getSemanticQueryCache, setSemanticQueryCache, findRelatedSemanticQueryCache, filterExistingEditScope, estimateTokenCount, ensureAutomaticIndex, getApplicableLessons, recordLessonsServed, searchTeamKnowledge, recordScopeOffer, } from '@rigour-labs/core';
15
15
  import { loadPatternIndex, getDefaultIndexPath, generateEmbedding, semanticSearch, } from '@rigour-labs/core/pattern-index';
16
16
  import { buildTelemetryMeta, getWorkspaceCommitSha } from '../utils/context-telemetry.js';
17
17
  import { appendContextFooter } from '../utils/context-footer.js';
@@ -262,6 +262,7 @@ export async function handleContextScope(cwd, query, limit = 10, taskId, agentId
262
262
  getApplicableLessons(cwd, agentId, process.env.RIGOUR_TEAM_ID),
263
263
  searchTeamKnowledge(query, Math.min(limit, 8)),
264
264
  ]);
265
+ recordLessonsServed(cwd, 'context', applicableLearning.lessons.map(lesson => lesson.subject));
265
266
  let fullFileTokens = 0;
266
267
  const offered = [];
267
268
  for (const file of editScope) {
@@ -3,50 +3,11 @@
3
3
  *
4
4
  * Wraps PatternIndexer for agent-accessible index build/update.
5
5
  */
6
- import path from 'path';
7
6
  import { PatternIndexer, savePatternIndex, loadPatternIndex, getDefaultIndexPath, } from '@rigour-labs/core/pattern-index';
8
- import { setStaticCache, setComponentCache, recordIndexChoice, } from '@rigour-labs/core';
7
+ import { recordIndexChoice } from '@rigour-labs/core';
9
8
  import { notifyProgress } from '../utils/notifications.js';
10
- import { buildTelemetryMeta, getWorkspaceCommitSha } from '../utils/context-telemetry.js';
9
+ import { buildTelemetryMeta } from '../utils/context-telemetry.js';
11
10
  import { appendContextFooter } from '../utils/context-footer.js';
12
- import fs from 'fs-extra';
13
- async function syncIndexToCache(cwd, index) {
14
- const commitSha = await getWorkspaceCommitSha(cwd);
15
- const repo = path.basename(cwd);
16
- const byFile = new Map();
17
- for (const pattern of index.patterns) {
18
- const list = byFile.get(pattern.file) ?? [];
19
- list.push(pattern);
20
- byFile.set(pattern.file, list);
21
- }
22
- for (const [filePath, patterns] of byFile) {
23
- const absPath = path.join(cwd, filePath);
24
- let content = '';
25
- try {
26
- if (await fs.pathExists(absPath)) {
27
- content = await fs.readFile(absPath, 'utf-8');
28
- }
29
- }
30
- catch {
31
- content = patterns.map(p => p.signature ?? p.name).join('\n');
32
- }
33
- await setStaticCache(repo, 'main', filePath, content || filePath, {
34
- exports: patterns.map(p => p.name),
35
- rigourPatterns: patterns.map(p => `${p.type}:${p.name}`),
36
- ownership: path.dirname(filePath),
37
- }, cwd);
38
- const componentName = path.dirname(filePath) || filePath;
39
- const dossier = {
40
- component: componentName,
41
- responsibility: `Indexed patterns in ${filePath}`,
42
- canonicalFiles: [filePath],
43
- contracts: patterns.filter(p => p.type === 'interface' || p.type === 'type').map(p => p.name),
44
- directConsumers: [],
45
- validationCommands: [],
46
- };
47
- await setComponentCache(componentName, commitSha, dossier, `index-${index.lastUpdated}`, '3', cwd);
48
- }
49
- }
50
11
  export async function handleIndex(cwd, options = {}) {
51
12
  const indexPath = options.output || getDefaultIndexPath(cwd);
52
13
  const candidateEstimate = 'Full codebase AST scan for pattern extraction';
@@ -63,7 +24,6 @@ export async function handleIndex(cwd, options = {}) {
63
24
  index = await indexer.buildIndex();
64
25
  }
65
26
  await savePatternIndex(index, indexPath);
66
- await syncIndexToCache(cwd, index);
67
27
  await recordIndexChoice(cwd, index, semantic);
68
28
  notifyProgress('info', 'Pattern index complete');
69
29
  const byType = Object.entries(index.stats.byType)
@@ -76,7 +36,7 @@ export async function handleIndex(cwd, options = {}) {
76
36
  text += `- Duration: ${index.stats.indexDurationMs}ms\n`;
77
37
  text += `- Search by meaning: ${semantic ? (index.patterns.some(p => p.embedding?.length) ? 'on' : 'unavailable (local model did not load; name matching still works)') : 'off'}\n`;
78
38
  text += `- Types: ${byType}\n\n`;
79
- text += `Index synced to context cache layers. Use rigour_context_scope before reading files.`;
39
+ text += `Use rigour_context_scope before reading files.`;
80
40
  const telemetry = buildTelemetryMeta({
81
41
  candidateText: candidateEstimate,
82
42
  returnedText: text,
@@ -14,7 +14,7 @@
14
14
  * @since v2.17.0 — extracted from monolithic index.ts
15
15
  * @since v4.2.0 — DLP gate on memory persistence
16
16
  */
17
- import { loadMemory, saveMemory } from '../utils/config.js';
17
+ import { loadMemory, saveMemory, USER_MEMORY_OFF, userMemoryEnabled } from '../utils/config.js';
18
18
  import { scanInputForCredentials, formatDLPAlert, createDLPAuditEntry, loadTeamConfiguration, shareMemoryLesson, } from '@rigour-labs/core';
19
19
  import { loadPatternIndex, getDefaultIndexPath, } from '@rigour-labs/core/pattern-index';
20
20
  import { buildTelemetryMeta } from '../utils/context-telemetry.js';
@@ -95,6 +95,8 @@ export async function handleRemember(cwd, key, value, scope = 'repo') {
95
95
  }],
96
96
  };
97
97
  }
98
+ if (scope === 'user' && !userMemoryEnabled())
99
+ return { content: [{ type: "text", text: `MEMORY NOT STORED: ${USER_MEMORY_OFF}` }] };
98
100
  // ── DLP Gate: deep-scan key + value (including JSON interiors) ──
99
101
  const textToScan = deepScanValue(key, value);
100
102
  const dlpResult = scanInputForCredentials(textToScan);
@@ -145,9 +147,11 @@ async function shareWithTeam(cwd, key, value) {
145
147
  if (!(await loadTeamConfiguration())) {
146
148
  return '\nKept as a team candidate on this machine; it is shared once team mode is configured (rigour team configure).';
147
149
  }
148
- return '\nShared with your team as a candidate. Teammates\' agents receive it once someone promotes it in Studio (Knowledge › Lessons).';
150
+ return '\nShared with your team as a candidate. Teammates\' agents receive it once someone shares it from Studio (How it learns).';
149
151
  }
150
152
  export async function handleForget(cwd, key, scope = 'repo') {
153
+ if (scope === 'user' && !userMemoryEnabled())
154
+ return { content: [{ type: "text", text: `NOTHING FORGOTTEN: ${USER_MEMORY_OFF}` }] };
151
155
  const store = await loadMemory(cwd, scope);
152
156
  if (!store.memories[key]) {
153
157
  return { content: [{ type: "text", text: `NO MEMORY FOUND for key "${key}" in ${scope} memory. Nothing to forget.` }] };
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * Every returned memory passes the credential scan first; nothing is cached.
12
12
  */
13
- import { formatDLPAlert, createDLPAuditEntry, listKnowledgeLessons, rankByMeaning, rankMemories, scanInputForCredentials, searchTeamKnowledge } from '@rigour-labs/core';
13
+ import { formatDLPAlert, createDLPAuditEntry, listKnowledgeLessons, rankByMeaning, rankMemories, recordLessonsServed, scanInputForCredentials, searchTeamKnowledge } from '@rigour-labs/core';
14
14
  import { loadMemory } from '../utils/config.js';
15
15
  import { appendDLPAudit, getIndexHealthBlock, wrapRecallResult } from './memory-handlers.js';
16
16
  export async function handleRecall(cwd, args = {}) {
@@ -51,6 +51,7 @@ async function recallQuery(cwd, query, entries, candidateText) {
51
51
  searchTeamKnowledge(query, 3),
52
52
  ]);
53
53
  const teamOnly = team.status === 'ready' ? team.candidates.filter(c => !lessons.some(l => l.id === c.lessonId)) : [];
54
+ recordLessonsServed(cwd, 'recall', [...lessons.map(l => l.subject), ...teamOnly.map(c => c.subject)]);
54
55
  const sections = [];
55
56
  if (ranked.length) {
56
57
  sections.push(`MEMORIES MATCHING "${query}":\n\n` + ranked.map(m => `## ${m.key} (${m.scope}, ${m.match} ${m.score.toFixed(2)})\n${m.value}`).join('\n\n'));
@@ -9,7 +9,7 @@ import path from "path";
9
9
  import yaml from "yaml";
10
10
  import fs from "fs-extra";
11
11
  import { assessPattern, loadPatternIndex, getDefaultIndexPath, SecurityDetector, } from "@rigour-labs/core/pattern-index";
12
- import { ConfigSchema, estimateTokenCount } from "@rigour-labs/core";
12
+ import { ConfigSchema, estimateTokenCount, recordReuseSuggested } from "@rigour-labs/core";
13
13
  import { notifyProgress } from '../utils/notifications.js';
14
14
  import { buildTelemetryMeta } from '../utils/context-telemetry.js';
15
15
  import { appendContextFooter } from '../utils/context-footer.js';
@@ -90,6 +90,8 @@ export async function handleCheckPattern(cwd, patternName, type, intent, file, s
90
90
  resultText += action === 'BLOCK' ? `🚨 PATTERN REINVENTION DETECTED\n` : `💡 SIMILAR PATTERN EXISTS\n`;
91
91
  resultText += `"${best.pattern.name}" in ${best.pattern.file}:${best.pattern.line} (${best.matchType}, ${best.confidence}%)\n`;
92
92
  resultText += `SUGGESTION: ${assessment.suggestion}\n\n`;
93
+ if (action !== 'ALLOW')
94
+ recordReuseSuggested(cwd, patternName, `${best.pattern.name} in ${best.pattern.file}:${best.pattern.line}`, action);
93
95
  }
94
96
  if (assessment.deprecations.length) {
95
97
  resultText += `⚠️ THE EXISTING "${matchedPattern?.name}" USES SOMETHING DEPRECATED\n`;
@@ -5,15 +5,26 @@
5
5
  * With no diff, the change is taken from git: uncommitted work (what the
6
6
  * agent just wrote, new files included), or the branch against `base`.
7
7
  */
8
- import { acknowledgeReview, buildReviewTask, diffFromGit, recordFixLessons, recordReviewOutcome, reviewChange, toReviewFinding } from "@rigour-labs/core";
8
+ import { acknowledgeReview, buildQualityReceipt, buildReviewTask, receiptReport, diffFromGit, recordFixLessons, recordLessonsServed, recordReviewOutcome, reviewChange, toReviewFinding } from "@rigour-labs/core";
9
9
  import { notifyProgress } from '../utils/notifications.js';
10
+ /** What is known about each changed function before a model looks; never fails the review. */
11
+ function qualityReceipt(config, cwd, diff) {
12
+ try {
13
+ return { quality_receipt: receiptReport(buildQualityReceipt(cwd, diff, { policy: config.gates.deep?.router, lessonMode: config.gates.deep?.review_lessons })) };
14
+ }
15
+ catch {
16
+ return {};
17
+ }
18
+ }
10
19
  export async function handleReview(config, cwd, args) {
11
20
  notifyProgress("info", args.diff ? "Reviewing the provided diff..." : `Reviewing ${args.base ? `this branch against ${args.base}` : "uncommitted changes"}...`);
12
21
  try {
13
22
  const diff = args.diff ?? diffFromGit(cwd, args.base ? { mode: 'base', base: args.base } : { mode: 'working' });
14
23
  const result = await reviewChange({ cwd, config, diff, files: args.files });
15
24
  const task = args.mode === 'agent' ? buildReviewTask(cwd, diff, config.gates.deep?.router, config.gates.deep?.review_lessons, config.gates.deep?.repo_rules) : undefined;
16
- const capture = recordReviewOutcome(cwd, result.findings, Object.keys(result.changedLines));
25
+ const capture = recordReviewOutcome(cwd, result.findings, Object.keys(result.changedLines), 'review');
26
+ if (task)
27
+ recordLessonsServed(cwd, 'review', task.lessons.map(l => l.text));
17
28
  await recordFixLessons(cwd, capture.fixes).catch(() => undefined); // learning never fails a review
18
29
  const stats = result.report?.stats;
19
30
  return text({
@@ -30,6 +41,7 @@ export async function handleReview(config, cwd, args) {
30
41
  excluded_outside_changed_lines: result.excludedOutsideChangedLines,
31
42
  unlocated_failures: result.unlocated,
32
43
  gate_errors: result.gateErrors,
44
+ ...qualityReceipt(config, cwd, diff),
33
45
  ...(task ? { review_task: { items: task.items, already_reviewed: task.alreadyReviewed, team_lessons: task.lessons, repo_rules: task.rules, instructions: task.instructions } } : {}),
34
46
  next_step: nextStep(result.findings.length, task?.items.length ?? 0),
35
47
  });
@@ -295,6 +295,15 @@ export interface MemoryStore {
295
295
  }
296
296
  /** The home whose .rigour/ holds user-scope memory: RIGOUR_HOME when set (tests, sandboxes), else the OS home. */
297
297
  export declare function userHome(): string;
298
+ /**
299
+ * False when RIGOUR_USER_MEMORY=off. A server kept apart from the user's other work (a private
300
+ * HOME per employer, a sandbox) then never reads or writes ~/.rigour/memory.json, even if it is
301
+ * started with the real home by mistake.
302
+ */
303
+ export declare function userMemoryEnabled(): boolean;
304
+ export declare const USER_MEMORY_OFF = "User memory is off on this server (RIGOUR_USER_MEMORY=off); use scope \"repo\".";
305
+ /** The repository a call works in: the call's own cwd, else RIGOUR_CWD, else where the server was started. */
306
+ export declare function resolveCwd(args: unknown): string;
298
307
  /** Where memories live: `repo` in this checkout's .rigour/, `user` in ~/.rigour/ for every repository. */
299
308
  export type LocalMemoryScope = 'repo' | 'user';
300
309
  export declare function getMemoryPath(cwd: string, scope?: LocalMemoryScope): Promise<string>;
@@ -38,12 +38,28 @@ function noteMissingConfig(cwd) {
38
38
  export function userHome() {
39
39
  return process.env.RIGOUR_HOME || os.homedir();
40
40
  }
41
+ /**
42
+ * False when RIGOUR_USER_MEMORY=off. A server kept apart from the user's other work (a private
43
+ * HOME per employer, a sandbox) then never reads or writes ~/.rigour/memory.json, even if it is
44
+ * started with the real home by mistake.
45
+ */
46
+ export function userMemoryEnabled() {
47
+ return process.env.RIGOUR_USER_MEMORY?.trim().toLowerCase() !== 'off';
48
+ }
49
+ export const USER_MEMORY_OFF = 'User memory is off on this server (RIGOUR_USER_MEMORY=off); use scope "repo".';
50
+ /** The repository a call works in: the call's own cwd, else RIGOUR_CWD, else where the server was started. */
51
+ export function resolveCwd(args) {
52
+ const given = args?.cwd;
53
+ return typeof given === 'string' && given ? given : process.env.RIGOUR_CWD || process.cwd();
54
+ }
41
55
  export async function getMemoryPath(cwd, scope = 'repo') {
42
56
  const rigourDir = scope === 'user' ? path.join(userHome(), ".rigour") : path.join(cwd, ".rigour");
43
57
  await fs.ensureDir(rigourDir);
44
58
  return path.join(rigourDir, "memory.json");
45
59
  }
46
60
  export async function loadMemory(cwd, scope = 'repo') {
61
+ if (scope === 'user' && !userMemoryEnabled())
62
+ return { memories: {} };
47
63
  const memPath = await getMemoryPath(cwd, scope);
48
64
  if (await fs.pathExists(memPath)) {
49
65
  const content = await fs.readFile(memPath, "utf-8");
@@ -60,6 +76,8 @@ export async function loadMemory(cwd, scope = 'repo') {
60
76
  return { memories: {} };
61
77
  }
62
78
  export async function saveMemory(cwd, store, scope = 'repo') {
79
+ if (scope === 'user' && !userMemoryEnabled())
80
+ throw new Error(USER_MEMORY_OFF);
63
81
  const memPath = await getMemoryPath(cwd, scope);
64
82
  await fs.writeFile(memPath, JSON.stringify(store, null, 2));
65
83
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigour-labs/mcp",
3
- "version": "6.5.4",
3
+ "version": "6.6.1",
4
4
  "description": "MCP server + live dashboard for AI code governance — OWASP LLM Top 10 (10/10), real-time MCP App UI, 25+ security patterns, memory and lessons recalled by meaning, hallucinated import detection, multi-agent governance. Works with Claude, Cursor, VS Code, ChatGPT, Goose, Windsurf. Industry presets for HIPAA, SOC2, FedRAMP.",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -65,7 +65,7 @@
65
65
  "execa": "^8.0.1",
66
66
  "fs-extra": "^11.4.0",
67
67
  "yaml": "^2.8.2",
68
- "@rigour-labs/core": "6.5.4"
68
+ "@rigour-labs/core": "6.6.1"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/node": "^25.0.3",