@rigour-labs/mcp 6.5.2 → 6.5.3

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
@@ -48,8 +48,8 @@ Rigour moves code quality enforcement from "Post-Commit" to "In-Progress." By ru
48
48
 
49
49
  | Tool | Description |
50
50
  |:---|:---|
51
- | `rigour_remember` | DLP-gated persistent memory — scans values before storing. |
52
- | `rigour_recall` | DLP-gated recall — blocks tainted memories on read. |
51
+ | `rigour_remember` | Memory for this repository, all your repositories (`scope: "user"`), or your team (`scope: "team"`, served once promoted). Refuses values containing credentials. |
52
+ | `rigour_recall` | `query`: the memories that match by meaning, plus promoted team knowledge; `key`: one memory. Withholds any memory containing a credential. |
53
53
  | `rigour_forget` | Removes a stored memory by key. |
54
54
 
55
55
  ### Real-Time Hooks & DLP
@@ -111,21 +111,17 @@ Hallucinated import detection with full stdlib whitelists and dependency manifes
111
111
 
112
112
  ## 📦 Installation
113
113
 
114
- ### 1. Install via npm
115
- ```bash
116
- npm install -g @rigour-labs/mcp
117
- ```
114
+ No global install: npx fetches the server when your agent starts it. Keep a version in the package name (`@latest`, or a major such as `@6`); with a bare `@rigour-labs/mcp`, npx runs any older copy installed globally instead. If you once ran `npm install -g @rigour-labs/mcp`, remove it with `npm rm -g @rigour-labs/mcp`.
118
115
 
119
- ### 2. Configure your IDE
116
+ **Claude Code:** use the plugin (`/plugin marketplace add rigour-labs/rigour-plugin`, then `/plugin install rigour@rigour-labs`).
120
117
 
121
- #### Cursor / Claude Desktop
122
- Add the following to your MCP settings:
118
+ **Cursor, Claude Desktop and other MCP clients:**
123
119
  ```json
124
120
  {
125
121
  "mcpServers": {
126
122
  "rigour": {
127
123
  "command": "npx",
128
- "args": ["-y", "@rigour-labs/mcp@5.3.2"],
124
+ "args": ["-y", "@rigour-labs/mcp@latest"],
129
125
  "env": {
130
126
  "RIGOUR_CWD": "/path/to/your/project"
131
127
  }
@@ -134,6 +130,8 @@ Add the following to your MCP settings:
134
130
  }
135
131
  ```
136
132
 
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
+
137
135
  ---
138
136
 
139
137
  ## 📖 Documentation
@@ -154,6 +154,11 @@ export declare function getAdvertisedToolDefinitions(spec?: string): ({
154
154
  type: string;
155
155
  description: string;
156
156
  };
157
+ scope: {
158
+ type: string;
159
+ enum: string[];
160
+ description: string;
161
+ };
157
162
  cwd: {
158
163
  type: "string";
159
164
  description: string;
@@ -178,6 +183,39 @@ export declare function getAdvertisedToolDefinitions(spec?: string): ({
178
183
  type: string;
179
184
  description: string;
180
185
  };
186
+ query: {
187
+ type: string;
188
+ description: string;
189
+ };
190
+ cwd: {
191
+ type: "string";
192
+ description: string;
193
+ };
194
+ };
195
+ required: string[];
196
+ };
197
+ annotations: {
198
+ title: string;
199
+ readOnlyHint: boolean;
200
+ destructiveHint: boolean;
201
+ idempotentHint: boolean;
202
+ openWorldHint: boolean;
203
+ };
204
+ } | {
205
+ name: string;
206
+ description: string;
207
+ inputSchema: {
208
+ type: string;
209
+ properties: {
210
+ key: {
211
+ type: string;
212
+ description: string;
213
+ };
214
+ scope: {
215
+ type: string;
216
+ enum: string[];
217
+ description: string;
218
+ };
181
219
  cwd: {
182
220
  type: "string";
183
221
  description: string;
@@ -210,6 +248,17 @@ export declare function getAdvertisedToolDefinitions(spec?: string): ({
210
248
  type: string;
211
249
  description: string;
212
250
  };
251
+ signature: {
252
+ type: string;
253
+ description: string;
254
+ };
255
+ keywords: {
256
+ type: string;
257
+ items: {
258
+ type: string;
259
+ };
260
+ description: string;
261
+ };
213
262
  file: {
214
263
  type: string;
215
264
  description: string;
@@ -12,4 +12,4 @@
12
12
  *
13
13
  * No external dependencies. Initial state injected via __INITIAL_STATE__.
14
14
  */
15
- export declare const DASHBOARD_HTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>Rigour Governance</title>\n<style>\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0d1117;color:#c9d1d9;padding:16px;font-size:13px;line-height:1.5}\n.header{display:flex;align-items:center;gap:8px;margin-bottom:16px}\n.header h1{font-size:15px;font-weight:600;color:#f0f6fc}\n.header .badge{font-size:11px;padding:2px 8px;border-radius:12px;font-weight:500}\n.badge-pass{background:#1a7f37;color:#fff}\n.badge-fail{background:#da3633;color:#fff}\n.badge-scanning{background:#9e6a03;color:#fff}\n.badge-idle{background:#30363d;color:#8b949e}\n.top{display:flex;gap:16px;margin-bottom:16px}\n.gauge-wrap{flex:0 0 100px;text-align:center}\n.gauge-label{font-size:11px;color:#8b949e;margin-top:4px}\n.severity-wrap{flex:1;display:flex;flex-direction:column;justify-content:center;gap:6px}\n.sev-row{display:flex;align-items:center;gap:6px;font-size:12px}\n.sev-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}\n.sev-critical{background:#da3633}\n.sev-high{background:#db6d28}\n.sev-medium{background:#9e6a03}\n.sev-low{background:#388bfd}\n.sev-info{background:#8b949e}\n.sev-count{color:#f0f6fc;font-weight:600;min-width:18px}\n.section-title{font-size:11px;color:#8b949e;text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px;font-weight:600}\n.timeline{max-height:200px;overflow-y:auto;margin-bottom:16px}\n.timeline::-webkit-scrollbar{width:4px}\n.timeline::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}\n.tl-entry{display:flex;gap:8px;padding:4px 0;border-bottom:1px solid #21262d;font-size:12px}\n.tl-time{color:#8b949e;flex:0 0 55px;font-family:'SF Mono',Monaco,monospace;font-size:11px}\n.tl-tool{color:#58a6ff;flex:0 0 auto}\n.tl-arrow{color:#484f58}\n.tl-detail{color:#c9d1d9;flex:1}\n.tl-status-success .tl-detail{color:#3fb950}\n.tl-status-error .tl-detail{color:#da3633}\n.tl-status-warning .tl-detail{color:#d29922}\n.brain{display:flex;align-items:center;gap:8px;padding:8px 12px;background:#161b22;border:1px solid #30363d;border-radius:6px;font-size:12px}\n.brain-icon{font-size:16px}\n.brain-text{color:#c9d1d9}\n.brain-trend{color:#3fb950;font-weight:600}\n.brain-trend.down{color:#da3633}\n.empty{text-align:center;padding:32px 16px;color:#484f58;font-size:12px}\n</style>\n</head>\n<body>\n<div class=\"header\">\n <h1>Rigour Governance</h1>\n <span class=\"badge badge-idle\" id=\"statusBadge\">idle</span>\n</div>\n\n<div class=\"top\">\n <div class=\"gauge-wrap\">\n <svg viewBox=\"0 0 100 100\" width=\"90\" height=\"90\">\n <circle cx=\"50\" cy=\"50\" r=\"42\" fill=\"none\" stroke=\"#21262d\" stroke-width=\"8\"/>\n <circle cx=\"50\" cy=\"50\" r=\"42\" fill=\"none\" stroke=\"#30363d\" stroke-width=\"8\"\n stroke-dasharray=\"264\" stroke-dashoffset=\"264\" stroke-linecap=\"round\"\n transform=\"rotate(-90 50 50)\" id=\"gaugeArc\"/>\n <text x=\"50\" y=\"46\" text-anchor=\"middle\" fill=\"#f0f6fc\" font-size=\"22\" font-weight=\"700\" id=\"gaugeText\">--</text>\n <text x=\"50\" y=\"62\" text-anchor=\"middle\" fill=\"#8b949e\" font-size=\"9\">SCORE</text>\n </svg>\n </div>\n <div class=\"severity-wrap\" id=\"severityWrap\">\n <div class=\"sev-row\"><span class=\"sev-dot sev-critical\"></span><span class=\"sev-count\" id=\"sevCritical\">0</span>critical</div>\n <div class=\"sev-row\"><span class=\"sev-dot sev-high\"></span><span class=\"sev-count\" id=\"sevHigh\">0</span>high</div>\n <div class=\"sev-row\"><span class=\"sev-dot sev-medium\"></span><span class=\"sev-count\" id=\"sevMedium\">0</span>medium</div>\n <div class=\"sev-row\"><span class=\"sev-dot sev-low\"></span><span class=\"sev-count\" id=\"sevLow\">0</span>low</div>\n </div>\n</div>\n\n<div class=\"section-title\">Activity</div>\n<div class=\"timeline\" id=\"timeline\">\n <div class=\"empty\">Waiting for agent activity...</div>\n</div>\n\n<div class=\"brain\" id=\"brainSection\">\n <span class=\"brain-icon\">&#x1F9E0;</span>\n <span class=\"brain-text\" id=\"brainText\">Brain: waiting for data</span>\n <span class=\"brain-trend\" id=\"brainTrend\"></span>\n</div>\n\n<script>\nconst state = JSON.parse('__INITIAL_STATE__');\n\nfunction scoreColor(s) {\n if (s === null) return '#30363d';\n if (s < 50) return '#da3633';\n if (s < 80) return '#d29922';\n return '#3fb950';\n}\n\nfunction updateGauge(score) {\n const arc = document.getElementById('gaugeArc');\n const text = document.getElementById('gaugeText');\n if (score === null) {\n arc.setAttribute('stroke-dashoffset', '264');\n arc.setAttribute('stroke', '#30363d');\n text.textContent = '--';\n return;\n }\n const offset = 264 - (264 * Math.min(score, 100) / 100);\n arc.setAttribute('stroke-dashoffset', String(offset));\n arc.setAttribute('stroke', scoreColor(score));\n text.textContent = String(Math.round(score));\n}\n\nfunction updateBadge(status) {\n const badge = document.getElementById('statusBadge');\n badge.className = 'badge badge-' + (status || 'idle');\n const labels = {pass:'PASS',fail:'FAIL',scanning:'SCANNING'};\n badge.textContent = labels[status] || 'IDLE';\n}\n\nfunction updateSeverity(sev) {\n document.getElementById('sevCritical').textContent = sev.critical || 0;\n document.getElementById('sevHigh').textContent = sev.high || 0;\n document.getElementById('sevMedium').textContent = sev.medium || 0;\n document.getElementById('sevLow').textContent = sev.low || 0;\n}\n\nfunction formatTime(iso) {\n const d = new Date(iso);\n return d.toLocaleTimeString('en-US', {hour12:false,hour:'2-digit',minute:'2-digit',second:'2-digit'});\n}\n\nfunction renderTimeline(entries) {\n const el = document.getElementById('timeline');\n if (!entries.length) {\n el.innerHTML = '<div class=\"empty\">Waiting for agent activity...</div>';\n return;\n }\n el.innerHTML = entries.map(e => {\n const cls = e.status === 'success' ? 'tl-status-success' : e.status === 'error' ? 'tl-status-error' : 'tl-status-warning';\n return '<div class=\"tl-entry ' + cls + '\">' +\n '<span class=\"tl-time\">' + formatTime(e.timestamp) + '</span>' +\n '<span class=\"tl-tool\">' + e.tool + '</span>' +\n '<span class=\"tl-arrow\">\u2192</span>' +\n '<span class=\"tl-detail\">' + e.details + '</span></div>';\n }).join('');\n el.scrollTop = el.scrollHeight;\n}\n\nfunction updateBrain(patterns, trend) {\n const text = document.getElementById('brainText');\n const trendEl = document.getElementById('brainTrend');\n text.textContent = 'Brain: ' + patterns + ' patterns learned';\n if (trend) {\n const arrows = {improving:'\u2191',stable:'\u2194',declining:'\u2193'};\n trendEl.textContent = (arrows[trend] || '') + ' ' + trend;\n trendEl.className = 'brain-trend' + (trend === 'declining' ? ' down' : '');\n }\n}\n\nfunction render() {\n updateGauge(state.currentScore);\n updateBadge(state.status);\n updateSeverity(state.severityBreakdown || {});\n renderTimeline(state.timeline || []);\n updateBrain(state.brainPatterns || 0, state.brainTrend);\n}\n\nrender();\n</script>\n</body>\n</html>";
15
+ export declare const DASHBOARD_HTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>Rigour Governance</title>\n<style>\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;background:#0d1117;color:#c9d1d9;padding:16px;font-size:13px;line-height:1.5}\n.header{display:flex;align-items:center;gap:8px;margin-bottom:16px}\n.header h1{font-size:15px;font-weight:600;color:#f0f6fc}\n.header .badge{font-size:11px;padding:2px 8px;border-radius:12px;font-weight:500}\n.badge-pass{background:#1a7f37;color:#fff}\n.badge-fail{background:#da3633;color:#fff}\n.badge-scanning{background:#9e6a03;color:#fff}\n.badge-idle{background:#30363d;color:#8b949e}\n.top{display:flex;gap:16px;margin-bottom:16px}\n.gauge-wrap{flex:0 0 100px;text-align:center}\n.gauge-label{font-size:11px;color:#8b949e;margin-top:4px}\n.severity-wrap{flex:1;display:flex;flex-direction:column;justify-content:center;gap:6px}\n.sev-row{display:flex;align-items:center;gap:6px;font-size:12px}\n.sev-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}\n.sev-critical{background:#da3633}\n.sev-high{background:#db6d28}\n.sev-medium{background:#9e6a03}\n.sev-low{background:#388bfd}\n.sev-info{background:#8b949e}\n.sev-count{color:#f0f6fc;font-weight:600;min-width:18px}\n.section-title{font-size:11px;color:#8b949e;text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px;font-weight:600}\n.timeline{max-height:200px;overflow-y:auto;margin-bottom:16px}\n.timeline::-webkit-scrollbar{width:4px}\n.timeline::-webkit-scrollbar-thumb{background:#30363d;border-radius:2px}\n.tl-entry{display:flex;gap:8px;padding:4px 0;border-bottom:1px solid #21262d;font-size:12px}\n.tl-time{color:#8b949e;flex:0 0 55px;font-family:'SF Mono',Monaco,monospace;font-size:11px}\n.tl-tool{color:#58a6ff;flex:0 0 auto}\n.tl-arrow{color:#484f58}\n.tl-detail{color:#c9d1d9;flex:1}\n.tl-status-success .tl-detail{color:#3fb950}\n.tl-status-error .tl-detail{color:#da3633}\n.tl-status-warning .tl-detail{color:#d29922}\n.brain{display:flex;align-items:center;gap:8px;padding:8px 12px;background:#161b22;border:1px solid #30363d;border-radius:6px;font-size:12px}\n.brain-icon{font-size:16px}\n.brain-text{color:#c9d1d9}\n.brain-trend{color:#3fb950;font-weight:600}\n.brain-trend.down{color:#da3633}\n.empty{text-align:center;padding:32px 16px;color:#484f58;font-size:12px}\n</style>\n</head>\n<body>\n<div class=\"header\">\n <h1>Rigour Governance</h1>\n <span class=\"badge badge-idle\" id=\"statusBadge\">idle</span>\n</div>\n\n<div class=\"top\">\n <div class=\"gauge-wrap\">\n <svg viewBox=\"0 0 100 100\" width=\"90\" height=\"90\">\n <circle cx=\"50\" cy=\"50\" r=\"42\" fill=\"none\" stroke=\"#21262d\" stroke-width=\"8\"/>\n <circle cx=\"50\" cy=\"50\" r=\"42\" fill=\"none\" stroke=\"#30363d\" stroke-width=\"8\"\n stroke-dasharray=\"264\" stroke-dashoffset=\"264\" stroke-linecap=\"round\"\n transform=\"rotate(-90 50 50)\" id=\"gaugeArc\"/>\n <text x=\"50\" y=\"46\" text-anchor=\"middle\" fill=\"#f0f6fc\" font-size=\"22\" font-weight=\"700\" id=\"gaugeText\">--</text>\n <text x=\"50\" y=\"62\" text-anchor=\"middle\" fill=\"#8b949e\" font-size=\"9\">SCORE</text>\n </svg>\n </div>\n <div class=\"severity-wrap\" id=\"severityWrap\">\n <div class=\"sev-row\"><span class=\"sev-dot sev-critical\"></span><span class=\"sev-count\" id=\"sevCritical\">0</span>critical</div>\n <div class=\"sev-row\"><span class=\"sev-dot sev-high\"></span><span class=\"sev-count\" id=\"sevHigh\">0</span>high</div>\n <div class=\"sev-row\"><span class=\"sev-dot sev-medium\"></span><span class=\"sev-count\" id=\"sevMedium\">0</span>medium</div>\n <div class=\"sev-row\"><span class=\"sev-dot sev-low\"></span><span class=\"sev-count\" id=\"sevLow\">0</span>low</div>\n </div>\n</div>\n\n<div class=\"section-title\">Activity</div>\n<div class=\"timeline\" id=\"timeline\">\n <div class=\"empty\">Waiting for agent activity...</div>\n</div>\n\n<div class=\"brain\" id=\"brainSection\">\n <span class=\"brain-icon\">&#x1F9E0;</span>\n <span class=\"brain-text\" id=\"brainText\">Knowledge: waiting for the first tool call</span>\n <span class=\"brain-trend\" id=\"brainTrend\"></span>\n</div>\n\n<script>\nconst state = JSON.parse('__INITIAL_STATE__');\n\nfunction scoreColor(s) {\n if (s === null) return '#30363d';\n if (s < 50) return '#da3633';\n if (s < 80) return '#d29922';\n return '#3fb950';\n}\n\nfunction updateGauge(score) {\n const arc = document.getElementById('gaugeArc');\n const text = document.getElementById('gaugeText');\n if (score === null) {\n arc.setAttribute('stroke-dashoffset', '264');\n arc.setAttribute('stroke', '#30363d');\n text.textContent = '--';\n return;\n }\n const offset = 264 - (264 * Math.min(score, 100) / 100);\n arc.setAttribute('stroke-dashoffset', String(offset));\n arc.setAttribute('stroke', scoreColor(score));\n text.textContent = String(Math.round(score));\n}\n\nfunction updateBadge(status, source) {\n const badge = document.getElementById('statusBadge');\n badge.className = 'badge badge-' + (status || 'idle');\n const labels = {pass:'PASS',fail:'FAIL',scanning:'SCANNING'};\n badge.textContent = (labels[status] || 'IDLE') + (source === 'last-report' && status !== 'scanning' ? ' \u00B7 LAST CHECK' : '');\n}\n\nfunction updateSeverity(sev) {\n document.getElementById('sevCritical').textContent = sev.critical || 0;\n document.getElementById('sevHigh').textContent = sev.high || 0;\n document.getElementById('sevMedium').textContent = sev.medium || 0;\n document.getElementById('sevLow').textContent = sev.low || 0;\n}\n\nfunction formatTime(iso) {\n const d = new Date(iso);\n return d.toLocaleTimeString('en-US', {hour12:false,hour:'2-digit',minute:'2-digit',second:'2-digit'});\n}\n\nfunction renderTimeline(entries) {\n const el = document.getElementById('timeline');\n if (!entries.length) {\n el.innerHTML = '<div class=\"empty\">Waiting for agent activity...</div>';\n return;\n }\n el.innerHTML = entries.map(e => {\n const cls = e.status === 'success' ? 'tl-status-success' : e.status === 'error' ? 'tl-status-error' : 'tl-status-warning';\n return '<div class=\"tl-entry ' + cls + '\">' +\n '<span class=\"tl-time\">' + formatTime(e.timestamp) + '</span>' +\n '<span class=\"tl-tool\">' + e.tool + '</span>' +\n '<span class=\"tl-arrow\">\u2192</span>' +\n '<span class=\"tl-detail\">' + e.details + '</span></div>';\n }).join('');\n el.scrollTop = el.scrollHeight;\n}\n\nfunction updateBrain(k) {\n const text = document.getElementById('brainText');\n const detail = document.getElementById('brainTrend');\n if (!k) { text.textContent = 'Knowledge: waiting for the first tool call'; detail.textContent = ''; return; }\n const parts = [];\n if (k.memories) parts.push(k.memories + (k.memories === 1 ? ' memory' : ' memories'));\n if (k.lessons) parts.push(k.lessons + (k.lessons === 1 ? ' lesson' : ' lessons') + ' (' + k.trustedLessons + ' trusted)');\n if (k.learnedRules) parts.push(k.learnedRules + ' learned ' + (k.learnedRules === 1 ? 'rule' : 'rules'));\n if (k.indexedPatterns) parts.push(k.indexedPatterns + ' patterns indexed');\n text.textContent = parts.length ? 'Knows: ' + parts.join(' \u00B7 ') : 'Nothing learned yet';\n detail.textContent = k.mutedChecks ? k.mutedChecks + ' noisy check(s) muted' : parts.length ? '' : 'learns from fixes, dismissals and rigour_remember';\n detail.className = 'brain-trend';\n}\n\nfunction render() {\n updateGauge(state.currentScore);\n updateBadge(state.status, state.scoreSource);\n updateSeverity(state.severityBreakdown || {});\n renderTimeline(state.timeline || []);\n updateBrain(state.knowledge);\n}\n\nrender();\n</script>\n</body>\n</html>";
@@ -92,7 +92,7 @@ body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;b
92
92
 
93
93
  <div class="brain" id="brainSection">
94
94
  <span class="brain-icon">&#x1F9E0;</span>
95
- <span class="brain-text" id="brainText">Brain: waiting for data</span>
95
+ <span class="brain-text" id="brainText">Knowledge: waiting for the first tool call</span>
96
96
  <span class="brain-trend" id="brainTrend"></span>
97
97
  </div>
98
98
 
@@ -121,11 +121,11 @@ function updateGauge(score) {
121
121
  text.textContent = String(Math.round(score));
122
122
  }
123
123
 
124
- function updateBadge(status) {
124
+ function updateBadge(status, source) {
125
125
  const badge = document.getElementById('statusBadge');
126
126
  badge.className = 'badge badge-' + (status || 'idle');
127
127
  const labels = {pass:'PASS',fail:'FAIL',scanning:'SCANNING'};
128
- badge.textContent = labels[status] || 'IDLE';
128
+ badge.textContent = (labels[status] || 'IDLE') + (source === 'last-report' && status !== 'scanning' ? ' \u00b7 LAST CHECK' : '');
129
129
  }
130
130
 
131
131
  function updateSeverity(sev) {
@@ -157,23 +157,26 @@ function renderTimeline(entries) {
157
157
  el.scrollTop = el.scrollHeight;
158
158
  }
159
159
 
160
- function updateBrain(patterns, trend) {
160
+ function updateBrain(k) {
161
161
  const text = document.getElementById('brainText');
162
- const trendEl = document.getElementById('brainTrend');
163
- text.textContent = 'Brain: ' + patterns + ' patterns learned';
164
- if (trend) {
165
- const arrows = {improving:'\u2191',stable:'\u2194',declining:'\u2193'};
166
- trendEl.textContent = (arrows[trend] || '') + ' ' + trend;
167
- trendEl.className = 'brain-trend' + (trend === 'declining' ? ' down' : '');
168
- }
162
+ const detail = document.getElementById('brainTrend');
163
+ if (!k) { text.textContent = 'Knowledge: waiting for the first tool call'; detail.textContent = ''; return; }
164
+ const parts = [];
165
+ if (k.memories) parts.push(k.memories + (k.memories === 1 ? ' memory' : ' memories'));
166
+ if (k.lessons) parts.push(k.lessons + (k.lessons === 1 ? ' lesson' : ' lessons') + ' (' + k.trustedLessons + ' trusted)');
167
+ if (k.learnedRules) parts.push(k.learnedRules + ' learned ' + (k.learnedRules === 1 ? 'rule' : 'rules'));
168
+ if (k.indexedPatterns) parts.push(k.indexedPatterns + ' patterns indexed');
169
+ text.textContent = parts.length ? 'Knows: ' + parts.join(' \u00b7 ') : 'Nothing learned yet';
170
+ detail.textContent = k.mutedChecks ? k.mutedChecks + ' noisy check(s) muted' : parts.length ? '' : 'learns from fixes, dismissals and rigour_remember';
171
+ detail.className = 'brain-trend';
169
172
  }
170
173
 
171
174
  function render() {
172
175
  updateGauge(state.currentScore);
173
- updateBadge(state.status);
176
+ updateBadge(state.status, state.scoreSource);
174
177
  updateSeverity(state.severityBreakdown || {});
175
178
  renderTimeline(state.timeline || []);
176
- updateBrain(state.brainPatterns || 0, state.brainTrend);
179
+ updateBrain(state.knowledge);
177
180
  }
178
181
 
179
182
  render();
@@ -1,4 +1,5 @@
1
1
  export declare const DASHBOARD_URI = "ui://rigour/dashboard";
2
2
  /** Get dashboard HTML with current state injected. */
3
3
  export declare function getDashboardHtml(): string;
4
- export { getState, pushTimelineEntry, updateScore, updateBrainStatus, setScanning } from './state.js';
4
+ export { getState, pushTimelineEntry, updateScore, updateKnowledge, seedFromLastReport, setScanning } from './state.js';
5
+ export { summarizeKnowledge, LEARNING_TOOLS, type KnowledgeSummary } from './knowledge.js';
@@ -13,4 +13,5 @@ export function getDashboardHtml() {
13
13
  const stateJson = JSON.stringify(getState()).replace(/'/g, "\\'");
14
14
  return DASHBOARD_HTML.replace('__INITIAL_STATE__', stateJson);
15
15
  }
16
- export { getState, pushTimelineEntry, updateScore, updateBrainStatus, setScanning } from './state.js';
16
+ export { getState, pushTimelineEntry, updateScore, updateKnowledge, seedFromLastReport, setScanning } from './state.js';
17
+ export { summarizeKnowledge, LEARNING_TOOLS } from './knowledge.js';
@@ -0,0 +1,15 @@
1
+ export interface KnowledgeSummary {
2
+ memories: number;
3
+ lessons: number;
4
+ /** Lessons agents may act on: validated by repeated fixes, or promoted by a person. */
5
+ trustedLessons: number;
6
+ learnedRules: number;
7
+ indexedPatterns: number;
8
+ mutedChecks: number;
9
+ }
10
+ /** Tools that change what Rigour knows: after them the summary is recomputed, never served from cache. */
11
+ export declare const LEARNING_TOOLS: Set<string>;
12
+ export declare function summarizeKnowledge(cwd: string, options?: {
13
+ fresh?: boolean;
14
+ now?: number;
15
+ }): Promise<KnowledgeSummary>;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * What Rigour knows about this repository, for the dashboard's knowledge line:
3
+ * read from the stores the learning features write, after every tool call.
4
+ * Each count is cheap (small JSON files, one local lesson query, the index
5
+ * stats) and any store that cannot be read counts as zero.
6
+ */
7
+ import { checkPrecisions, listKnowledgeLessons, loadLearnedRules } from '@rigour-labs/core';
8
+ import { getDefaultIndexPath, loadPatternIndex } from '@rigour-labs/core/pattern-index';
9
+ import { loadMemory } from '../utils/config.js';
10
+ /** Summaries per repository for a short while: tool calls come in bursts and the counts change slowly. */
11
+ const SUMMARY_TTL_MS = 30_000;
12
+ const summaries = new Map();
13
+ /** Tools that change what Rigour knows: after them the summary is recomputed, never served from cache. */
14
+ export const LEARNING_TOOLS = new Set(['rigour_remember', 'rigour_forget', 'rigour_index', 'rigour_review', 'rigour_review_ack']);
15
+ export async function summarizeKnowledge(cwd, options = {}) {
16
+ const now = options.now ?? Date.now();
17
+ const cached = summaries.get(cwd);
18
+ if (!options.fresh && cached && now - cached.at < SUMMARY_TTL_MS)
19
+ return cached.summary;
20
+ const summary = await computeSummary(cwd);
21
+ summaries.set(cwd, { at: now, summary });
22
+ return summary;
23
+ }
24
+ async function computeSummary(cwd) {
25
+ const [repo, user, lessons, index] = await Promise.all([
26
+ loadMemory(cwd, 'repo').catch(() => ({ memories: {} })),
27
+ loadMemory(cwd, 'user').catch(() => ({ memories: {} })),
28
+ listKnowledgeLessons(cwd).catch(() => []),
29
+ loadPatternIndex(getDefaultIndexPath(cwd)).catch(() => null),
30
+ ]);
31
+ return {
32
+ memories: new Set([...Object.keys(repo.memories), ...Object.keys(user.memories)]).size,
33
+ lessons: lessons.length,
34
+ trustedLessons: lessons.filter(l => l.state === 'validated' || l.state === 'promoted').length,
35
+ learnedRules: safeCount(() => loadLearnedRules(cwd).length),
36
+ indexedPatterns: index?.stats.totalPatterns ?? 0,
37
+ mutedChecks: safeCount(() => checkPrecisions(cwd).filter(c => c.muted).length),
38
+ };
39
+ }
40
+ function safeCount(count) {
41
+ try {
42
+ return count();
43
+ }
44
+ catch {
45
+ return 0;
46
+ }
47
+ }
@@ -5,6 +5,7 @@
5
5
  * the MCP App dashboard HTML on each resource fetch.
6
6
  * Single-threaded Node.js — no locking needed.
7
7
  */
8
+ import type { KnowledgeSummary } from './knowledge.js';
8
9
  export interface TimelineEntry {
9
10
  timestamp: string;
10
11
  tool: string;
@@ -14,13 +15,26 @@ export interface TimelineEntry {
14
15
  export interface DashboardState {
15
16
  currentScore: number | null;
16
17
  status: "pass" | "fail" | "scanning" | null;
18
+ /** Where the score came from: a check in this session, or the last report on disk. */
19
+ scoreSource: "session" | "last-report" | null;
17
20
  timeline: TimelineEntry[];
18
21
  severityBreakdown: Record<string, number>;
19
- brainPatterns: number;
20
- brainTrend: string | null;
22
+ /** What Rigour knows about this repository (knowledge.ts); null until the first tool call. */
23
+ knowledge: KnowledgeSummary | null;
21
24
  }
22
25
  export declare function getState(): DashboardState;
23
26
  export declare function pushTimelineEntry(tool: string, status: string, details: string): void;
24
27
  export declare function updateScore(score: number, status: "pass" | "fail", severity?: Record<string, number>): void;
25
- export declare function updateBrainStatus(patterns: number, trend?: string): void;
28
+ /**
29
+ * Start from the last check run anywhere (CLI, hook, CI) while this session has not run one:
30
+ * otherwise the dashboard shows "--" next to a fix packet that came from that very report.
31
+ */
32
+ export declare function seedFromLastReport(report: {
33
+ status?: string;
34
+ stats?: {
35
+ score?: number;
36
+ severity_breakdown?: Record<string, number>;
37
+ };
38
+ } | null): void;
39
+ export declare function updateKnowledge(knowledge: KnowledgeSummary | null): void;
26
40
  export declare function setScanning(): void;
@@ -9,10 +9,10 @@ const MAX_TIMELINE = 50;
9
9
  const state = {
10
10
  currentScore: null,
11
11
  status: null,
12
+ scoreSource: null,
12
13
  timeline: [],
13
14
  severityBreakdown: {},
14
- brainPatterns: 0,
15
- brainTrend: null,
15
+ knowledge: null,
16
16
  };
17
17
  export function getState() {
18
18
  return state;
@@ -31,13 +31,26 @@ export function pushTimelineEntry(tool, status, details) {
31
31
  export function updateScore(score, status, severity) {
32
32
  state.currentScore = score;
33
33
  state.status = status;
34
+ state.scoreSource = "session";
34
35
  if (severity)
35
36
  state.severityBreakdown = severity;
36
37
  }
37
- export function updateBrainStatus(patterns, trend) {
38
- state.brainPatterns = patterns;
39
- if (trend)
40
- state.brainTrend = trend;
38
+ /**
39
+ * Start from the last check run anywhere (CLI, hook, CI) while this session has not run one:
40
+ * otherwise the dashboard shows "--" next to a fix packet that came from that very report.
41
+ */
42
+ export function seedFromLastReport(report) {
43
+ if (state.scoreSource === "session" || typeof report?.stats?.score !== "number")
44
+ return;
45
+ state.currentScore = report.stats.score;
46
+ state.status = report.status === "PASS" ? "pass" : "fail";
47
+ state.scoreSource = "last-report";
48
+ if (report.stats.severity_breakdown)
49
+ state.severityBreakdown = report.stats.severity_breakdown;
50
+ }
51
+ export function updateKnowledge(knowledge) {
52
+ if (knowledge)
53
+ state.knowledge = knowledge;
41
54
  }
42
55
  export function setScanning() {
43
56
  state.status = "scanning";
package/dist/index.js CHANGED
@@ -7,6 +7,8 @@
7
7
  *
8
8
  * @since v2.17.0 — refactored from 1,487-line monolith
9
9
  */
10
+ import fs from 'fs-extra';
11
+ import path from 'path';
10
12
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
11
13
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12
14
  import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
@@ -18,12 +20,13 @@ import { bindServer } from './utils/notifications.js';
18
20
  import { getMcpVersion } from './utils/package-version.js';
19
21
  import { buildMcpResultMeta, buildStudioImpact } from './utils/impact-receipt.js';
20
22
  // Dashboard (MCP App)
21
- import { DASHBOARD_URI, getDashboardHtml, pushTimelineEntry, updateScore } from './dashboard/index.js';
23
+ import { DASHBOARD_URI, getDashboardHtml, LEARNING_TOOLS, pushTimelineEntry, seedFromLastReport, summarizeKnowledge, updateKnowledge, updateScore } from './dashboard/index.js';
22
24
  // Tool definitions & advertised registry
23
25
  import { getAdvertisedToolDefinitions } from './advertised-tools.js';
24
26
  // Tool handlers
25
27
  import { handleCheck, handleExplain, handleStatus, handleGetFixPacket, handleListGates, handleGetConfig } from './tools/quality-handlers.js';
26
- import { handleRemember, handleRecall, handleForget } from './tools/memory-handlers.js';
28
+ import { handleRemember, handleForget } from './tools/memory-handlers.js';
29
+ import { handleRecall } from './tools/memory-recall.js';
27
30
  import { handleCheckPattern, handleSecurityAudit } from './tools/pattern-handlers.js';
28
31
  import { handleRun, handleRunSupervised } from './tools/execution-handlers.js';
29
32
  import { handleAgentRegister, handleCheckpoint, handleHandoff, handleAgentDeregister, handleHandoffAccept } from './tools/agent-handlers.js';
@@ -33,7 +36,7 @@ import { handleCheckDeep, handleDeepStats } from './tools/deep-handlers.js';
33
36
  import { handleMcpGetSettings, handleMcpSetSettings } from './tools/mcp-settings-handler.js';
34
37
  import { handleContextStats, handleTaskCost, handleCacheStats, handleContextExplain, handleContextScope } from './tools/context-handlers.js';
35
38
  import { handleIndex } from './tools/index-handlers.js';
36
- import { recordContextEvent, recordInteractionEvidence, recordInteractionLesson, syncTeamOutbox, estimateTokenCount } from '@rigour-labs/core';
39
+ import { recordContextEvent, recordInteractionEvidence, syncTeamOutbox, estimateTokenCount } from '@rigour-labs/core';
37
40
  // ─── Server Setup ─────────────────────────────────────────────────
38
41
  const server = new Server({ name: "rigour-mcp", version: getMcpVersion('5.5.0') }, { capabilities: { tools: {}, prompts: {}, logging: {}, resources: {} } });
39
42
  function interactionMetadata(args) {
@@ -144,17 +147,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
144
147
  break;
145
148
  // Memory
146
149
  case "rigour_remember":
147
- result = await handleRemember(cwd, args.key, args.value || args.content);
150
+ result = await handleRemember(cwd, args.key, args.value || args.content, args.scope);
148
151
  break;
149
152
  case "rigour_recall":
150
- result = await handleRecall(cwd, args.key);
153
+ result = await handleRecall(cwd, { key: args.key, query: args.query });
151
154
  break;
152
155
  case "rigour_forget":
153
- result = await handleForget(cwd, args.key);
156
+ result = await handleForget(cwd, args.key, args.scope);
154
157
  break;
155
158
  // Pattern intelligence
156
159
  case "rigour_check_pattern":
157
- result = await handleCheckPattern(cwd, args.name, args.type, args.intent, args.file);
160
+ result = await handleCheckPattern(cwd, args.name, args.type, args.intent, args.file, args.signature, args.keywords);
158
161
  break;
159
162
  case "rigour_security_audit":
160
163
  result = await handleSecurityAudit(cwd);
@@ -286,15 +289,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
286
289
  summary: result?._guidance?.recommendation,
287
290
  ...interactionMetadata(args),
288
291
  });
289
- await recordInteractionLesson(cwd, {
290
- tool: name,
291
- outcome,
292
- requestId,
293
- deterministic,
294
- verifiedOutcome: deterministic,
295
- summary: result?._guidance?.recommendation,
296
- ...interactionMetadata(args),
297
- });
298
292
  void syncTeamOutbox().catch(() => undefined);
299
293
  }
300
294
  catch {
@@ -320,6 +314,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
320
314
  ? `${report.status.toUpperCase()} — Score: ${report.stats?.score ?? '?'}/100`
321
315
  : "completed";
322
316
  pushTimelineEntry(name, result.isError ? "error" : "success", details);
317
+ updateKnowledge(await summarizeKnowledge(cwd, { fresh: LEARNING_TOOLS.has(name) }).catch(() => null));
318
+ seedFromLastReport(await readLastReport(cwd));
323
319
  if (report?.stats) {
324
320
  updateScore(report.stats.score ?? 0, report.status === "PASS" ? "pass" : "fail", report.stats.severity_breakdown);
325
321
  }
@@ -470,6 +466,16 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
470
466
  throw new Error(`Unknown prompt: ${name}`);
471
467
  }
472
468
  });
469
+ /** The last report `rigour check` wrote here (output.report_path), or null. */
470
+ async function readLastReport(cwd) {
471
+ try {
472
+ const config = await loadConfig(cwd);
473
+ return await fs.readJson(path.join(cwd, config.output?.report_path ?? 'rigour-report.json'));
474
+ }
475
+ catch {
476
+ return null;
477
+ }
478
+ }
473
479
  // ─── Start ────────────────────────────────────────────────────────
474
480
  async function main() {
475
481
  if (process.argv.includes('--gateway')) {
@@ -482,7 +488,7 @@ async function main() {
482
488
  const transport = new StdioServerTransport();
483
489
  await server.connect(transport);
484
490
  void flushDailyUsage(); // a day's agent activity, if a day has passed; never awaited by the agent
485
- console.error("Rigour MCP server v4.0.0 running on stdio");
491
+ console.error(`Rigour MCP server v${getMcpVersion('unknown')} running on stdio`);
486
492
  }
487
493
  main().catch((error) => {
488
494
  console.error("Fatal error in Rigour MCP server:", error);
@@ -157,6 +157,11 @@ export declare const TOOL_DEFINITIONS: ({
157
157
  type: string;
158
158
  description: string;
159
159
  };
160
+ scope: {
161
+ type: string;
162
+ enum: string[];
163
+ description: string;
164
+ };
160
165
  cwd: {
161
166
  type: "string";
162
167
  description: string;
@@ -181,6 +186,39 @@ export declare const TOOL_DEFINITIONS: ({
181
186
  type: string;
182
187
  description: string;
183
188
  };
189
+ query: {
190
+ type: string;
191
+ description: string;
192
+ };
193
+ cwd: {
194
+ type: "string";
195
+ description: string;
196
+ };
197
+ };
198
+ required: string[];
199
+ };
200
+ annotations: {
201
+ title: string;
202
+ readOnlyHint: boolean;
203
+ destructiveHint: boolean;
204
+ idempotentHint: boolean;
205
+ openWorldHint: boolean;
206
+ };
207
+ } | {
208
+ name: string;
209
+ description: string;
210
+ inputSchema: {
211
+ type: string;
212
+ properties: {
213
+ key: {
214
+ type: string;
215
+ description: string;
216
+ };
217
+ scope: {
218
+ type: string;
219
+ enum: string[];
220
+ description: string;
221
+ };
184
222
  cwd: {
185
223
  type: "string";
186
224
  description: string;
@@ -213,6 +251,17 @@ export declare const TOOL_DEFINITIONS: ({
213
251
  type: string;
214
252
  description: string;
215
253
  };
254
+ signature: {
255
+ type: string;
256
+ description: string;
257
+ };
258
+ keywords: {
259
+ type: string;
260
+ items: {
261
+ type: string;
262
+ };
263
+ description: string;
264
+ };
216
265
  file: {
217
266
  type: string;
218
267
  description: string;
@@ -176,13 +176,14 @@ export const TOOL_DEFINITIONS = [
176
176
  // ─── Memory Persistence ───────────────────────────────
177
177
  {
178
178
  name: "rigour_remember",
179
- description: "Store a persistent instruction or context that the AI should remember across sessions. Use this to persist user preferences, project conventions, or critical instructions. IMPORTANT: You must provide both 'key' (a short snake_case identifier) and 'value' (the full text to remember).",
179
+ description: "Store an instruction or convention to remember across sessions. Provide 'key' (short snake_case) and 'value' (the full text). scope: 'repo' (default, this repository), 'user' (all your repositories), or 'team' (this repository, and shared as a team candidate that teammates' agents receive once a person promotes it). Values containing credentials are refused.",
180
180
  inputSchema: {
181
181
  type: "object",
182
182
  properties: {
183
183
  ...cwdParam(),
184
184
  key: { type: "string", description: "A short snake_case identifier for this memory, e.g. 'api_response_format', 'naming_convention', 'testing_strategy'. This is used to retrieve the memory later." },
185
185
  value: { type: "string", description: "The full instruction or convention text to persist. This is the content that will be recalled in future sessions." },
186
+ scope: { type: "string", enum: ["repo", "user", "team"], description: "Where the memory applies. Default: repo." },
186
187
  },
187
188
  required: ["cwd", "key", "value"],
188
189
  },
@@ -196,12 +197,13 @@ export const TOOL_DEFINITIONS = [
196
197
  },
197
198
  {
198
199
  name: "rigour_recall",
199
- description: "Load project memory and stored conventions. CALL THIS at the START of every coding task (before reading files) to restore team decisions, naming conventions, and architectural preferences. Returns index health status and uses semantic cache on repeat calls — second recall with the same key is served from cache.",
200
+ description: "Load stored conventions. At the START of a task, call it with 'query' describing the task to get the few memories that match by meaning, plus promoted team knowledge when team mode is on. With 'key' it returns that memory; with neither, every repository and user memory.",
200
201
  inputSchema: {
201
202
  type: "object",
202
203
  properties: {
203
204
  ...cwdParam(),
204
205
  key: { type: "string", description: "Optional. Key of specific memory to retrieve." },
206
+ query: { type: "string", description: "Optional. What you are about to do, e.g. 'add retry to the payments client'. Returns the closest memories by meaning." },
205
207
  },
206
208
  required: ["cwd"],
207
209
  },
@@ -221,6 +223,7 @@ export const TOOL_DEFINITIONS = [
221
223
  properties: {
222
224
  ...cwdParam(),
223
225
  key: { type: "string", description: "Key of the memory to remove." },
226
+ scope: { type: "string", enum: ["repo", "user"], description: "Which memory to remove it from. Default: repo." },
224
227
  },
225
228
  required: ["cwd", "key"],
226
229
  },
@@ -243,6 +246,8 @@ export const TOOL_DEFINITIONS = [
243
246
  name: { type: "string", description: "The name of the function, class, or component you want to create." },
244
247
  type: { type: "string", description: "The type of pattern (e.g., 'function', 'component', 'hook', 'type')." },
245
248
  intent: { type: "string", description: "What the code is for (e.g., 'format dates', 'user authentication')." },
249
+ signature: { type: "string", description: "Optional. The signature you plan to write, e.g. '(items: T[], size: number) => T[][]'. Matches existing code by shape, not only by name." },
250
+ keywords: { type: "array", items: { type: "string" }, description: "Optional. Words for what it does, e.g. ['retry', 'backoff']." },
246
251
  file: { type: "string", description: "Target file path (relative to cwd) where the code will be written. Used to enforce protected path rules — writes to .github/, rigour.yml, etc. will be BLOCKED." },
247
252
  },
248
253
  required: ["cwd", "name"],
@@ -633,12 +638,12 @@ export const TOOL_DEFINITIONS = [
633
638
  // ─── Pattern Index & Scoped Context ──────────────────
634
639
  {
635
640
  name: "rigour_index",
636
- description: "Build or update the Rigour pattern index (.rigour/patterns.json). CALL THIS when the index is missing or stale — before rigour_context_scope or rigour_check_pattern. One AST pass extracts functions, classes, routes, and signatures for reuse. Use semantic=true for embedding-based search.",
641
+ description: "Build or update the Rigour pattern index (.rigour/patterns.json). CALL THIS when the index is missing or stale — before rigour_context_scope or rigour_check_pattern. One AST pass extracts functions, classes, routes, and signatures for reuse, embedded locally so rigour_check_pattern can match by intent; semantic=false skips the embeddings.",
637
642
  inputSchema: {
638
643
  type: "object",
639
644
  properties: {
640
645
  ...cwdParam(),
641
- semantic: { type: "boolean", description: "Generate semantic embeddings for better matching (requires Transformers.js). Default: false." },
646
+ semantic: { type: "boolean", description: "Embed patterns so an intent can find them (local Transformers.js model). Default: true; false records an explicit opt-out." },
642
647
  force: { type: "boolean", description: "Force a full rebuild instead of incremental update. Default: false." },
643
648
  output: { type: "string", description: "Custom path for the index file." },
644
649
  },