@rigour-labs/mcp 5.4.0 → 5.5.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
@@ -2,7 +2,7 @@
2
2
 
3
3
  **AI Agent Governance via Model Context Protocol — quality gates, DLP, drift detection, and deep analysis.**
4
4
 
5
- Rigour is a local-first MCP server that governs AI agents (Claude, Cursor, Cline, Windsurf) with deterministic quality gates, credential interception, and memory governance.
5
+ Rigour is a local-first MCP server that governs AI agents (Claude, Cursor, Cline, Windsurf) with deterministic quality gates, credential warnings, and memory governance.
6
6
 
7
7
  [![Registry](https://img.shields.io/badge/MCP-Registry-brightgreen)](https://github.com/mcp)
8
8
  [![npm version](https://img.shields.io/npm/v/@rigour-labs/mcp?color=cyan)](https://www.npmjs.com/package/@rigour-labs/mcp)
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ import { handleContextStats, handleTaskCost, handleCacheStats, handleContextExpl
34
34
  import { handleIndex } from './tools/index-handlers.js';
35
35
  import { recordContextEvent, estimateTokenCount } from '@rigour-labs/core';
36
36
  // ─── Server Setup ─────────────────────────────────────────────────
37
- const server = new Server({ name: "rigour-mcp", version: getMcpVersion('5.3.2') }, { capabilities: { tools: {}, prompts: {}, logging: {}, resources: {} } });
37
+ const server = new Server({ name: "rigour-mcp", version: getMcpVersion('5.5.0') }, { capabilities: { tools: {}, prompts: {}, logging: {}, resources: {} } });
38
38
  // Bind server for logging notifications from handlers
39
39
  bindServer(server);
40
40
  // ─── Tool Listing ─────────────────────────────────────────────────
@@ -0,0 +1 @@
1
+ export declare function getPinnedCheckerCommand(): string;
@@ -0,0 +1,30 @@
1
+ import { readJsonSync } from 'fs-extra';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ function readVersion(packagePath) {
5
+ try {
6
+ const pkg = readJsonSync(packagePath);
7
+ const version = pkg.version?.trim();
8
+ return version && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)
9
+ ? version
10
+ : null;
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
16
+ export function getPinnedCheckerCommand() {
17
+ const thisDir = path.dirname(fileURLToPath(import.meta.url));
18
+ const packageCandidates = [
19
+ path.resolve(thisDir, '../../../cli/package.json'),
20
+ path.resolve(thisDir, '../../../rigour-cli/package.json'),
21
+ path.resolve(thisDir, '../../package.json'),
22
+ ];
23
+ const version = packageCandidates
24
+ .map(readVersion)
25
+ .find((candidate) => candidate !== null);
26
+ if (!version) {
27
+ throw new Error('Unable to resolve a compatible Rigour CLI version');
28
+ }
29
+ return `npx --yes @rigour-labs/cli@${version} hooks check`;
30
+ }
@@ -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, estimateTokenCount, } from '@rigour-labs/core';
14
+ import { getTaskContextStats, getTaskCostStats, getCacheStats, explainContext, getSemanticQueryCache, setSemanticQueryCache, findRelatedSemanticQueryCache, filterExistingEditScope, estimateTokenCount, } 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';
@@ -122,26 +122,88 @@ function textMatchPatterns(query, patterns, limit) {
122
122
  .slice(0, limit)
123
123
  .map(s => s.pattern);
124
124
  }
125
+ async function serveCachedScope(cached, opts) {
126
+ const originalScope = cached.editScope || [];
127
+ const { valid, missing } = await filterExistingEditScope(originalScope, opts.cwd);
128
+ // Quality guard: require majority of scoped files still present, else recompute.
129
+ if (originalScope.length > 0) {
130
+ const retention = valid.length / originalScope.length;
131
+ if (valid.length === 0 || retention < 0.5) {
132
+ return null;
133
+ }
134
+ }
135
+ const validSet = new Set(valid);
136
+ const validationScope = (cached.validationScope || []).filter((item) => {
137
+ // Keep entries that don't look like file paths, or whose path still exists.
138
+ const maybePath = item.replace(/^review\s+/i, '').trim();
139
+ if (!maybePath.includes('/') && !maybePath.endsWith('.ts') && !maybePath.endsWith('.tsx')) {
140
+ return true;
141
+ }
142
+ return validSet.has(maybePath) || valid.some((f) => item.includes(f));
143
+ });
144
+ const evidence = (cached.evidence || []).filter((line) => {
145
+ if (missing.length === 0)
146
+ return true;
147
+ return !missing.some((m) => line.includes(m));
148
+ });
149
+ const payload = {
150
+ ...cached,
151
+ editScope: valid,
152
+ validationScope,
153
+ evidence,
154
+ staleFilesDropped: missing,
155
+ cacheNote: opts.note,
156
+ };
157
+ const cachedText = JSON.stringify(payload, null, 2);
158
+ const telemetry = scopeTelemetryMeta({
159
+ candidateText: opts.fullIndexCandidate,
160
+ returnedText: cachedText,
161
+ cacheStatus: opts.cacheStatus,
162
+ query: opts.query,
163
+ taskId: opts.taskId,
164
+ agentId: opts.agentId,
165
+ });
166
+ const label = opts.cacheStatus === 'partial-hit'
167
+ ? 'CONTEXT SCOPE (partial cache)'
168
+ : 'CONTEXT SCOPE (cached)';
169
+ const text = `${label}\n\n${cachedText}`;
170
+ return {
171
+ content: [{ type: 'text', text: appendContextFooter(text, telemetry, 'rigour_check_pattern before creating code') }],
172
+ _telemetry: telemetry,
173
+ };
174
+ }
125
175
  export async function handleContextScope(cwd, query, limit = 10, taskId, agentId) {
126
176
  const commitSha = await getWorkspaceCommitSha(cwd);
127
177
  const indexPath = getDefaultIndexPath(cwd);
128
178
  const fullIndexCandidate = `Full repository scan for: ${query}`;
129
179
  const cached = await getSemanticQueryCache(query, commitSha, cwd);
130
180
  if (cached) {
131
- const cachedText = JSON.stringify(cached, null, 2);
132
- const telemetry = scopeTelemetryMeta({
133
- candidateText: fullIndexCandidate,
134
- returnedText: cachedText,
135
- cacheStatus: 'semantic-hit',
181
+ const served = await serveCachedScope(cached, {
182
+ cwd,
136
183
  query,
184
+ fullIndexCandidate,
185
+ // Exact query+commit key hit — content-addressed semantic layer.
186
+ cacheStatus: 'exact-hit',
137
187
  taskId,
138
188
  agentId,
139
189
  });
140
- const text = `CONTEXT SCOPE (cached)\n\n${cachedText}`;
141
- return {
142
- content: [{ type: 'text', text: appendContextFooter(text, telemetry, 'rigour_check_pattern before creating code') }],
143
- _telemetry: telemetry,
144
- };
190
+ if (served)
191
+ return served;
192
+ }
193
+ // Quality-safe power-up: reuse highly overlapping queries at the same commit only.
194
+ const related = await findRelatedSemanticQueryCache(query, commitSha, cwd);
195
+ if (related) {
196
+ const served = await serveCachedScope(related.entry, {
197
+ cwd,
198
+ query,
199
+ fullIndexCandidate,
200
+ cacheStatus: 'partial-hit',
201
+ taskId,
202
+ agentId,
203
+ note: `Reused related query at same commit (overlap ${(related.overlap * 100).toFixed(0)}%): "${related.sourceQuery}"`,
204
+ });
205
+ if (served)
206
+ return served;
145
207
  }
146
208
  const index = await loadPatternIndex(indexPath);
147
209
  if (!index) {
@@ -165,12 +227,16 @@ export async function handleContextScope(cwd, query, limit = 10, taskId, agentId
165
227
  try {
166
228
  const queryVector = await generateEmbedding(query);
167
229
  const similarities = semanticSearch(queryVector, index.patterns);
230
+ // Slightly stricter threshold — quality over vanity hit-rate; partial cache covers near-queries.
168
231
  matched = index.patterns
169
232
  .map((p, i) => ({ pattern: p, similarity: similarities[i] ?? 0 }))
170
- .filter(r => r.similarity > 0.3)
233
+ .filter(r => r.similarity > 0.4)
171
234
  .sort((a, b) => b.similarity - a.similarity)
172
235
  .slice(0, limit)
173
236
  .map(r => r.pattern);
237
+ if (matched.length === 0) {
238
+ matched = textMatchPatterns(query, index.patterns, limit);
239
+ }
174
240
  }
175
241
  catch {
176
242
  matched = textMatchPatterns(query, index.patterns, limit);
@@ -441,7 +441,7 @@ export const TOOL_DEFINITIONS = [
441
441
  },
442
442
  {
443
443
  name: "rigour_hooks_init",
444
- description: "Generate hook configs for AI coding tools (Claude, Cursor, Cline, Windsurf). Installs real-time quality checks AND DLP (credential interception) hooks by default. DLP hooks intercept credentials in user input BEFORE they reach the AI agent. Pass dlp=false to disable DLP hooks only.",
444
+ description: "Generate hook configs for AI coding tools (Claude, Cursor, Cline, Windsurf). Installs real-time quality checks and non-blocking DLP credential warnings by default. Pass dlp=false to disable DLP hooks only.",
445
445
  inputSchema: {
446
446
  type: "object",
447
447
  properties: {
@@ -449,7 +449,7 @@ export const TOOL_DEFINITIONS = [
449
449
  tool: { type: "string", description: "Target tool: 'claude', 'cursor', 'cline', or 'windsurf'." },
450
450
  force: { type: "boolean", description: "Overwrite existing hook files (default: false)." },
451
451
  dryRun: { type: "boolean", description: "Preview changes without writing files (default: false)." },
452
- dlp: { type: "boolean", description: "Generate DLP (Data Loss Prevention) hooks for pre-input credential interception (default: true). Set false to skip DLP hooks." },
452
+ dlp: { type: "boolean", description: "Generate DLP hooks for pre-input credential warnings (default: true). Set false to skip DLP hooks." },
453
453
  },
454
454
  required: ["cwd", "tool"],
455
455
  },
@@ -4,7 +4,7 @@
4
4
  * Handlers for: rigour_input_scan, rigour_dlp_init
5
5
  *
6
6
  * AI Agent Data Loss Prevention — scans user input for credentials
7
- * before they reach AI agents.
7
+ * before agent actions.
8
8
  *
9
9
  * @since v4.2.0 — AI Agent DLP layer
10
10
  */
@@ -19,18 +19,18 @@ type ToolResult = {
19
19
  * rigour_input_scan — Scan text for credentials before agent processing.
20
20
  *
21
21
  * This is the core DLP tool. Agents can call this to validate user input
22
- * contains no secrets before processing it.
22
+ * may contain secrets before processing it.
23
23
  */
24
24
  export declare function handleInputScan(cwd: string, text: string, agent?: string, block?: boolean): Promise<ToolResult>;
25
25
  /**
26
26
  * rigour_dlp_init — Generate DLP hook configs for AI coding tools.
27
27
  *
28
- * Creates pre-input hooks that intercept credentials before they
29
- * reach the AI agent.
28
+ * Creates pre-input hooks that warn about possible credentials before
29
+ * agent actions.
30
30
  */
31
31
  export declare function handleDLPInit(cwd: string, tool: string, force?: boolean, dryRun?: boolean): Promise<ToolResult>;
32
32
  /**
33
- * rigour_dlp_audit — Query the DLP audit log for recent credential interceptions.
33
+ * rigour_dlp_audit — Query the DLP audit log for recent credential detections.
34
34
  */
35
35
  export declare function handleDLPAudit(cwd: string, limit?: number): Promise<ToolResult>;
36
36
  export {};
@@ -4,18 +4,20 @@
4
4
  * Handlers for: rigour_input_scan, rigour_dlp_init
5
5
  *
6
6
  * AI Agent Data Loss Prevention — scans user input for credentials
7
- * before they reach AI agents.
7
+ * before agent actions.
8
8
  *
9
9
  * @since v4.2.0 — AI Agent DLP layer
10
10
  */
11
11
  import { scanInputForCredentials, formatDLPAlert, createDLPAuditEntry, generateDLPHookFiles } from '@rigour-labs/core';
12
12
  import fs from 'fs-extra';
13
13
  import path from 'path';
14
+ import { getPinnedCheckerCommand } from './cli-command.js';
15
+ import { formatHookWriteResult, writeHookFiles } from './hook-file-writer.js';
14
16
  /**
15
17
  * rigour_input_scan — Scan text for credentials before agent processing.
16
18
  *
17
19
  * This is the core DLP tool. Agents can call this to validate user input
18
- * contains no secrets before processing it.
20
+ * may contain secrets before processing it.
19
21
  */
20
22
  export async function handleInputScan(cwd, text, agent, block) {
21
23
  if (!text || text.trim().length === 0) {
@@ -28,7 +30,7 @@ export async function handleInputScan(cwd, text, agent, block) {
28
30
  }
29
31
  const result = scanInputForCredentials(text, {
30
32
  enabled: true,
31
- block_on_detection: block ?? true,
33
+ block_on_detection: block ?? false,
32
34
  audit_log: true,
33
35
  });
34
36
  // Log to audit trail
@@ -66,13 +68,13 @@ export async function handleInputScan(cwd, text, agent, block) {
66
68
  /**
67
69
  * rigour_dlp_init — Generate DLP hook configs for AI coding tools.
68
70
  *
69
- * Creates pre-input hooks that intercept credentials before they
70
- * reach the AI agent.
71
+ * Creates pre-input hooks that warn about possible credentials before
72
+ * agent actions.
71
73
  */
72
74
  export async function handleDLPInit(cwd, tool, force = false, dryRun = false) {
73
75
  try {
74
76
  const hookTool = tool;
75
- const checkerCommand = 'rigour hooks check';
77
+ const checkerCommand = getPinnedCheckerCommand();
76
78
  const files = generateDLPHookFiles(hookTool, checkerCommand);
77
79
  if (dryRun) {
78
80
  const preview = files.map(f => `${f.path}:\n${f.content.slice(0, 200)}...`).join('\n\n');
@@ -83,30 +85,12 @@ export async function handleDLPInit(cwd, tool, force = false, dryRun = false) {
83
85
  }],
84
86
  };
85
87
  }
86
- const written = [];
87
- const skipped = [];
88
- for (const file of files) {
89
- const fullPath = path.join(cwd, file.path);
90
- if (!force && await fs.pathExists(fullPath)) {
91
- skipped.push(file.path);
92
- continue;
93
- }
94
- await fs.ensureDir(path.dirname(fullPath));
95
- await fs.writeFile(fullPath, file.content);
96
- if (file.executable) {
97
- await fs.chmod(fullPath, 0o755);
98
- }
99
- written.push(file.path);
100
- }
101
- const parts = [];
102
- if (written.length > 0)
103
- parts.push(`✓ Created: ${written.join(', ')}`);
104
- if (skipped.length > 0)
105
- parts.push(`⊘ Skipped (exists): ${skipped.join(', ')}. Use force=true to overwrite.`);
88
+ const writeResult = await writeHookFiles(cwd, files, force);
89
+ const parts = formatHookWriteResult(writeResult);
106
90
  parts.push(`Tool: ${tool}`);
107
- parts.push('DLP Protection: AWS keys, API tokens, database URLs, private keys, JWTs, passwords');
91
+ parts.push('DLP warnings: AWS keys, API tokens, database URLs, private keys, JWTs, passwords');
108
92
  parts.push('');
109
- parts.push('🛑 Credentials will now be intercepted BEFORE reaching the AI agent.');
93
+ parts.push('⚠ Possible credentials will be reported before agent actions.');
110
94
  return {
111
95
  content: [{ type: 'text', text: parts.join('\n') }],
112
96
  };
@@ -122,7 +106,7 @@ export async function handleDLPInit(cwd, tool, force = false, dryRun = false) {
122
106
  }
123
107
  }
124
108
  /**
125
- * rigour_dlp_audit — Query the DLP audit log for recent credential interceptions.
109
+ * rigour_dlp_audit — Query the DLP audit log for recent credential detections.
126
110
  */
127
111
  export async function handleDLPAudit(cwd, limit = 20) {
128
112
  try {
@@ -0,0 +1,14 @@
1
+ interface HookFile {
2
+ path: string;
3
+ content: string;
4
+ executable?: boolean;
5
+ }
6
+ export declare function mergeHookFiles(files: HookFile[]): HookFile[];
7
+ export interface HookWriteResult {
8
+ written: string[];
9
+ skipped: string[];
10
+ incompatible: string[];
11
+ }
12
+ export declare function writeHookFiles(cwd: string, files: HookFile[], force: boolean): Promise<HookWriteResult>;
13
+ export declare function formatHookWriteResult(result: HookWriteResult): string[];
14
+ export {};
@@ -0,0 +1,71 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ function parseHookConfig(content, filePath) {
4
+ try {
5
+ return JSON.parse(content);
6
+ }
7
+ catch (error) {
8
+ const reason = error instanceof Error ? error.message : String(error);
9
+ throw new Error(`Cannot merge generated hook config ${filePath}: ${reason}`);
10
+ }
11
+ }
12
+ export function mergeHookFiles(files) {
13
+ const merged = new Map();
14
+ for (const file of files) {
15
+ const existing = merged.get(file.path);
16
+ if (!existing) {
17
+ merged.set(file.path, file);
18
+ continue;
19
+ }
20
+ const existingConfig = parseHookConfig(existing.content, file.path);
21
+ const nextConfig = parseHookConfig(file.content, file.path);
22
+ merged.set(file.path, {
23
+ ...existing,
24
+ content: JSON.stringify({
25
+ ...existingConfig,
26
+ ...nextConfig,
27
+ hooks: { ...existingConfig.hooks, ...nextConfig.hooks },
28
+ }, null, 2),
29
+ executable: existing.executable || file.executable,
30
+ });
31
+ }
32
+ return [...merged.values()];
33
+ }
34
+ export async function writeHookFiles(cwd, files, force) {
35
+ const result = { written: [], skipped: [], incompatible: [] };
36
+ for (const file of files) {
37
+ const fullPath = path.join(cwd, file.path);
38
+ if (!force && await fs.pathExists(fullPath)) {
39
+ result.skipped.push(file.path);
40
+ continue;
41
+ }
42
+ try {
43
+ await fs.ensureDir(path.dirname(fullPath));
44
+ await fs.writeFile(fullPath, file.content);
45
+ if (file.executable)
46
+ await fs.chmod(fullPath, 0o755);
47
+ result.written.push(file.path);
48
+ }
49
+ catch (error) {
50
+ const code = error instanceof Error && 'code' in error
51
+ ? String(error.code)
52
+ : '';
53
+ if (code !== 'ENOTDIR' && code !== 'EEXIST')
54
+ throw error;
55
+ result.incompatible.push(file.path);
56
+ }
57
+ }
58
+ return result;
59
+ }
60
+ export function formatHookWriteResult(result) {
61
+ const parts = [];
62
+ if (result.written.length > 0)
63
+ parts.push(`✓ Created: ${result.written.join(', ')}`);
64
+ if (result.skipped.length > 0) {
65
+ parts.push(`⊘ Skipped (exists): ${result.skipped.join(', ')}. Use force=true to overwrite.`);
66
+ }
67
+ if (result.incompatible.length > 0) {
68
+ parts.push(`⚠ Not configured (parent path is a file): ${result.incompatible.join(', ')}`);
69
+ }
70
+ return parts;
71
+ }
@@ -18,7 +18,7 @@ export declare function handleHooksCheck(cwd: string, files: string[], timeout?:
18
18
  * rigour_hooks_init — Generate hook configs for AI coding tools.
19
19
  *
20
20
  * NEW in v4.2.0: When `dlp` param is true, also generates pre-input
21
- * DLP hooks that intercept credentials before agent processing.
21
+ * DLP hooks that warn about possible credentials before agent processing.
22
22
  */
23
23
  export declare function handleHooksInit(cwd: string, tool: string, force?: boolean, dryRun?: boolean, dlp?: boolean): Promise<ToolResult>;
24
24
  export {};
@@ -5,12 +5,14 @@
5
5
  * Now includes DLP (Data Loss Prevention) capabilities.
6
6
  *
7
7
  * @since v3.0.0 — real-time hooks for AI coding tools
8
- * @since v4.2.0 — AI Agent DLP layer (credential interception)
8
+ * @since v4.2.0 — AI Agent DLP warning layer
9
9
  */
10
10
  import { runHookChecker, generateHookFiles } from "@rigour-labs/core";
11
11
  import { scanInputForCredentials, formatDLPAlert, createDLPAuditEntry, generateDLPHookFiles } from "@rigour-labs/core";
12
12
  import fs from "fs-extra";
13
13
  import path from "path";
14
+ import { getPinnedCheckerCommand } from "./cli-command.js";
15
+ import { formatHookWriteResult, mergeHookFiles, writeHookFiles } from "./hook-file-writer.js";
14
16
  /**
15
17
  * rigour_hooks_check — Run the fast hook checker on specific files.
16
18
  * This is the same check that runs inside IDE hooks (Claude, Cursor, Cline, Windsurf).
@@ -24,7 +26,7 @@ export async function handleHooksCheck(cwd, files, timeout, text, agent) {
24
26
  if (text) {
25
27
  const result = scanInputForCredentials(text, {
26
28
  enabled: true,
27
- block_on_detection: true,
29
+ block_on_detection: false,
28
30
  audit_log: true,
29
31
  });
30
32
  // Log to audit trail (with rotation to prevent unbounded growth)
@@ -92,17 +94,17 @@ export async function handleHooksCheck(cwd, files, timeout, text, agent) {
92
94
  * rigour_hooks_init — Generate hook configs for AI coding tools.
93
95
  *
94
96
  * NEW in v4.2.0: When `dlp` param is true, also generates pre-input
95
- * DLP hooks that intercept credentials before agent processing.
97
+ * DLP hooks that warn about possible credentials before agent processing.
96
98
  */
97
99
  export async function handleHooksInit(cwd, tool, force = false, dryRun = false, dlp = true) {
98
100
  try {
99
101
  const hookTool = tool;
100
- const checkerCommand = 'rigour hooks check';
102
+ const checkerCommand = getPinnedCheckerCommand();
101
103
  // Generate post-output hooks (existing)
102
104
  const files = generateHookFiles(hookTool, checkerCommand);
103
105
  // Generate DLP pre-input hooks (new)
104
106
  const dlpFiles = dlp ? generateDLPHookFiles(hookTool, checkerCommand) : [];
105
- const allFiles = [...files, ...dlpFiles];
107
+ const allFiles = mergeHookFiles([...files, ...dlpFiles]);
106
108
  if (dryRun) {
107
109
  const preview = allFiles.map(f => `${f.path}:\n${f.content.slice(0, 300)}...`).join('\n\n');
108
110
  return {
@@ -112,32 +114,14 @@ export async function handleHooksInit(cwd, tool, force = false, dryRun = false,
112
114
  }],
113
115
  };
114
116
  }
115
- const written = [];
116
- const skipped = [];
117
- for (const file of allFiles) {
118
- const fullPath = path.join(cwd, file.path);
119
- if (!force && await fs.pathExists(fullPath)) {
120
- skipped.push(file.path);
121
- continue;
122
- }
123
- await fs.ensureDir(path.dirname(fullPath));
124
- await fs.writeFile(fullPath, file.content);
125
- if (file.executable) {
126
- await fs.chmod(fullPath, 0o755);
127
- }
128
- written.push(file.path);
129
- }
130
- const parts = [];
131
- if (written.length > 0)
132
- parts.push(`✓ Created: ${written.join(', ')}`);
133
- if (skipped.length > 0)
134
- parts.push(`⊘ Skipped (exists): ${skipped.join(', ')}. Use force=true to overwrite.`);
117
+ const writeResult = await writeHookFiles(cwd, allFiles, force);
118
+ const parts = formatHookWriteResult(writeResult);
135
119
  parts.push(`Tool: ${tool}`);
136
120
  parts.push('Checks: file-size, security-patterns, hallucinated-imports, command-injection');
137
121
  if (dlp) {
138
122
  parts.push('');
139
- parts.push('🛑 DLP Protection: AWS keys, API tokens, database URLs, private keys, JWTs, passwords');
140
- parts.push('Credentials will be intercepted BEFORE reaching the AI agent.');
123
+ parts.push('⚠ DLP warnings: AWS keys, API tokens, database URLs, private keys, JWTs, passwords');
124
+ parts.push('Possible credentials will be reported before agent actions.');
141
125
  }
142
126
  return {
143
127
  content: [{ type: "text", text: parts.join('\n') }],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigour-labs/mcp",
3
- "version": "5.4.0",
3
+ "version": "5.5.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, Bayesian learning Brain, hallucinated import detection, multi-agent governance. Works with Claude, Cursor, VS Code, ChatGPT, Goose, Windsurf. Industry presets for HIPAA, SOC2, FedRAMP.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://rigour.run",
@@ -62,7 +62,7 @@
62
62
  "execa": "^8.0.1",
63
63
  "fs-extra": "^11.2.0",
64
64
  "yaml": "^2.8.2",
65
- "@rigour-labs/core": "5.4.0"
65
+ "@rigour-labs/core": "5.5.1"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@types/node": "^25.0.3",