@rigour-labs/mcp 5.2.9 → 5.3.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/dist/index.js +57 -6
- package/dist/tools/agent-handlers.d.ts +3 -11
- package/dist/tools/agent-handlers.js +171 -11
- package/dist/tools/context-handlers.d.ts +16 -0
- package/dist/tools/context-handlers.js +217 -0
- package/dist/tools/definitions.d.ts +120 -0
- package/dist/tools/definitions.js +124 -3
- package/dist/tools/index-handlers.d.ts +6 -0
- package/dist/tools/index-handlers.js +95 -0
- package/dist/tools/memory-handlers.d.ts +1 -7
- package/dist/tools/memory-handlers.js +64 -5
- package/dist/tools/pattern-handlers.d.ts +1 -7
- package/dist/tools/pattern-handlers.js +71 -7
- package/dist/utils/context-footer.d.ts +6 -0
- package/dist/utils/context-footer.js +19 -0
- package/dist/utils/context-telemetry.d.ts +27 -0
- package/dist/utils/context-telemetry.js +34 -0
- package/package.json +75 -75
- package/LICENSE +0 -21
- package/dist/index.test.d.ts +0 -1
- package/dist/index.test.js +0 -254
- package/dist/smoke.test.d.ts +0 -1
- package/dist/smoke.test.js +0 -6
- package/dist/supervisor.test.d.ts +0 -1
- package/dist/supervisor.test.js +0 -128
- package/dist/tools/deep-handlers.test.d.ts +0 -1
- package/dist/tools/deep-handlers.test.js +0 -48
- package/dist/tools/mcp-settings-handler.test.d.ts +0 -1
- package/dist/tools/mcp-settings-handler.test.js +0 -31
- package/dist/tools/quality-handlers.test.d.ts +0 -1
- package/dist/tools/quality-handlers.test.js +0 -108
|
@@ -316,6 +316,14 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
316
316
|
type: string;
|
|
317
317
|
description: string;
|
|
318
318
|
};
|
|
319
|
+
agentId: {
|
|
320
|
+
type: string;
|
|
321
|
+
description: string;
|
|
322
|
+
};
|
|
323
|
+
taskId: {
|
|
324
|
+
type: string;
|
|
325
|
+
description: string;
|
|
326
|
+
};
|
|
319
327
|
cwd: {
|
|
320
328
|
type: "string";
|
|
321
329
|
description: string;
|
|
@@ -603,4 +611,116 @@ export declare const TOOL_DEFINITIONS: ({
|
|
|
603
611
|
idempotentHint: boolean;
|
|
604
612
|
openWorldHint: boolean;
|
|
605
613
|
};
|
|
614
|
+
} | {
|
|
615
|
+
name: string;
|
|
616
|
+
description: string;
|
|
617
|
+
inputSchema: {
|
|
618
|
+
type: string;
|
|
619
|
+
properties: {
|
|
620
|
+
taskId: {
|
|
621
|
+
type: string;
|
|
622
|
+
description: string;
|
|
623
|
+
};
|
|
624
|
+
cwd: {
|
|
625
|
+
type: "string";
|
|
626
|
+
description: string;
|
|
627
|
+
};
|
|
628
|
+
};
|
|
629
|
+
required: string[];
|
|
630
|
+
};
|
|
631
|
+
annotations: {
|
|
632
|
+
title: string;
|
|
633
|
+
readOnlyHint: boolean;
|
|
634
|
+
destructiveHint: boolean;
|
|
635
|
+
idempotentHint: boolean;
|
|
636
|
+
openWorldHint: boolean;
|
|
637
|
+
};
|
|
638
|
+
} | {
|
|
639
|
+
name: string;
|
|
640
|
+
description: string;
|
|
641
|
+
inputSchema: {
|
|
642
|
+
type: string;
|
|
643
|
+
properties: {
|
|
644
|
+
target: {
|
|
645
|
+
type: string;
|
|
646
|
+
description: string;
|
|
647
|
+
};
|
|
648
|
+
taskId: {
|
|
649
|
+
type: string;
|
|
650
|
+
description: string;
|
|
651
|
+
};
|
|
652
|
+
cwd: {
|
|
653
|
+
type: "string";
|
|
654
|
+
description: string;
|
|
655
|
+
};
|
|
656
|
+
};
|
|
657
|
+
required: string[];
|
|
658
|
+
};
|
|
659
|
+
annotations: {
|
|
660
|
+
title: string;
|
|
661
|
+
readOnlyHint: boolean;
|
|
662
|
+
destructiveHint: boolean;
|
|
663
|
+
idempotentHint: boolean;
|
|
664
|
+
openWorldHint: boolean;
|
|
665
|
+
};
|
|
666
|
+
} | {
|
|
667
|
+
name: string;
|
|
668
|
+
description: string;
|
|
669
|
+
inputSchema: {
|
|
670
|
+
type: string;
|
|
671
|
+
properties: {
|
|
672
|
+
semantic: {
|
|
673
|
+
type: string;
|
|
674
|
+
description: string;
|
|
675
|
+
};
|
|
676
|
+
force: {
|
|
677
|
+
type: string;
|
|
678
|
+
description: string;
|
|
679
|
+
};
|
|
680
|
+
output: {
|
|
681
|
+
type: string;
|
|
682
|
+
description: string;
|
|
683
|
+
};
|
|
684
|
+
cwd: {
|
|
685
|
+
type: "string";
|
|
686
|
+
description: string;
|
|
687
|
+
};
|
|
688
|
+
};
|
|
689
|
+
required: string[];
|
|
690
|
+
};
|
|
691
|
+
annotations: {
|
|
692
|
+
title: string;
|
|
693
|
+
readOnlyHint: boolean;
|
|
694
|
+
destructiveHint: boolean;
|
|
695
|
+
idempotentHint: boolean;
|
|
696
|
+
openWorldHint: boolean;
|
|
697
|
+
};
|
|
698
|
+
} | {
|
|
699
|
+
name: string;
|
|
700
|
+
description: string;
|
|
701
|
+
inputSchema: {
|
|
702
|
+
type: string;
|
|
703
|
+
properties: {
|
|
704
|
+
query: {
|
|
705
|
+
type: string;
|
|
706
|
+
description: string;
|
|
707
|
+
};
|
|
708
|
+
limit: {
|
|
709
|
+
type: string;
|
|
710
|
+
description: string;
|
|
711
|
+
};
|
|
712
|
+
cwd: {
|
|
713
|
+
type: "string";
|
|
714
|
+
description: string;
|
|
715
|
+
};
|
|
716
|
+
};
|
|
717
|
+
required: string[];
|
|
718
|
+
};
|
|
719
|
+
annotations: {
|
|
720
|
+
title: string;
|
|
721
|
+
readOnlyHint: boolean;
|
|
722
|
+
destructiveHint: boolean;
|
|
723
|
+
idempotentHint: boolean;
|
|
724
|
+
openWorldHint: boolean;
|
|
725
|
+
};
|
|
606
726
|
})[];
|
|
@@ -192,7 +192,7 @@ export const TOOL_DEFINITIONS = [
|
|
|
192
192
|
},
|
|
193
193
|
{
|
|
194
194
|
name: "rigour_recall",
|
|
195
|
-
description: "Load project memory and stored conventions. CALL THIS at the
|
|
195
|
+
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.",
|
|
196
196
|
inputSchema: {
|
|
197
197
|
type: "object",
|
|
198
198
|
properties: {
|
|
@@ -332,15 +332,17 @@ export const TOOL_DEFINITIONS = [
|
|
|
332
332
|
},
|
|
333
333
|
{
|
|
334
334
|
name: "rigour_checkpoint",
|
|
335
|
-
description: "Record a quality checkpoint during long-running agent execution. Use periodically (every 15-30 min) to enable drift detection and
|
|
335
|
+
description: "Record a quality checkpoint during long-running agent execution. Use periodically (every 15-30 min) to enable drift detection, quality monitoring, and compact subagent handoffs. Triggers incremental pattern index refresh when filesChanged is provided. Essential for GPT-5.3 coworking mode — call BEFORE rigour_handoff to compress context under 2K tokens.",
|
|
336
336
|
inputSchema: {
|
|
337
337
|
type: "object",
|
|
338
338
|
properties: {
|
|
339
339
|
...cwdParam(),
|
|
340
340
|
progressPct: { type: "number", description: "Estimated progress percentage (0-100)." },
|
|
341
|
-
filesChanged: { type: "array", items: { type: "string" }, description: "List of files modified since last checkpoint." },
|
|
341
|
+
filesChanged: { type: "array", items: { type: "string" }, description: "List of files modified since last checkpoint. Triggers incremental index refresh." },
|
|
342
342
|
summary: { type: "string", description: "Brief description of work done since last checkpoint." },
|
|
343
343
|
qualityScore: { type: "number", description: "Self-assessed quality score (0-100). Be honest - artificially high scores trigger drift detection." },
|
|
344
|
+
agentId: { type: "string", description: "Optional agent ID for checkpoint packet binding." },
|
|
345
|
+
taskId: { type: "string", description: "Optional task ID for checkpoint metrics." },
|
|
344
346
|
},
|
|
345
347
|
required: ["cwd", "progressPct", "summary", "qualityScore"],
|
|
346
348
|
},
|
|
@@ -525,4 +527,123 @@ export const TOOL_DEFINITIONS = [
|
|
|
525
527
|
openWorldHint: false,
|
|
526
528
|
},
|
|
527
529
|
},
|
|
530
|
+
// ─── Context Telemetry & Cost Efficiency ───────────────
|
|
531
|
+
{
|
|
532
|
+
name: "rigour_context_stats",
|
|
533
|
+
description: "Returns context retrieval efficiency, candidate tokens vs returned tokens, potential avoided tokens, cache hit rate, and repeated reads prevented.",
|
|
534
|
+
inputSchema: {
|
|
535
|
+
type: "object",
|
|
536
|
+
properties: {
|
|
537
|
+
...cwdParam(),
|
|
538
|
+
taskId: { type: "string", description: "Optional Task ID (e.g. 'CTP-142') to filter statistics." },
|
|
539
|
+
},
|
|
540
|
+
required: ["cwd"],
|
|
541
|
+
},
|
|
542
|
+
annotations: {
|
|
543
|
+
title: "Context Stats",
|
|
544
|
+
readOnlyHint: true,
|
|
545
|
+
destructiveHint: false,
|
|
546
|
+
idempotentHint: true,
|
|
547
|
+
openWorldHint: false,
|
|
548
|
+
},
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
name: "rigour_task_cost",
|
|
552
|
+
description: "Returns both verified actual model usage/cost (from Cursor Admin API or imported CSV) and Rigour estimated avoided context/cost USD.",
|
|
553
|
+
inputSchema: {
|
|
554
|
+
type: "object",
|
|
555
|
+
properties: {
|
|
556
|
+
...cwdParam(),
|
|
557
|
+
taskId: { type: "string", description: "Optional Task ID (e.g. 'CTP-142') to filter cost stats." },
|
|
558
|
+
},
|
|
559
|
+
required: ["cwd"],
|
|
560
|
+
},
|
|
561
|
+
annotations: {
|
|
562
|
+
title: "Task Cost Breakdown",
|
|
563
|
+
readOnlyHint: true,
|
|
564
|
+
destructiveHint: false,
|
|
565
|
+
idempotentHint: true,
|
|
566
|
+
openWorldHint: false,
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
name: "rigour_cache_stats",
|
|
571
|
+
description: "Returns detailed performance stats across all 4 cache layers (exact hits, semantic hits, partial hits, misses, hit rate, tokens served from cache).",
|
|
572
|
+
inputSchema: {
|
|
573
|
+
type: "object",
|
|
574
|
+
properties: {
|
|
575
|
+
...cwdParam(),
|
|
576
|
+
},
|
|
577
|
+
required: ["cwd"],
|
|
578
|
+
},
|
|
579
|
+
annotations: {
|
|
580
|
+
title: "Cache Efficiency Stats",
|
|
581
|
+
readOnlyHint: true,
|
|
582
|
+
destructiveHint: false,
|
|
583
|
+
idempotentHint: true,
|
|
584
|
+
openWorldHint: false,
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
name: "rigour_context_explain",
|
|
589
|
+
description: "Audits why specific files/services were included or excluded, cache hit/miss status, invalidation reasons, and prior agent requests.",
|
|
590
|
+
inputSchema: {
|
|
591
|
+
type: "object",
|
|
592
|
+
properties: {
|
|
593
|
+
...cwdParam(),
|
|
594
|
+
target: { type: "string", description: "File path, service name, or query term to explain." },
|
|
595
|
+
taskId: { type: "string", description: "Optional Task ID to restrict audit trace." },
|
|
596
|
+
},
|
|
597
|
+
required: ["cwd", "target"],
|
|
598
|
+
},
|
|
599
|
+
annotations: {
|
|
600
|
+
title: "Context Explainability Audit",
|
|
601
|
+
readOnlyHint: true,
|
|
602
|
+
destructiveHint: false,
|
|
603
|
+
idempotentHint: true,
|
|
604
|
+
openWorldHint: false,
|
|
605
|
+
},
|
|
606
|
+
},
|
|
607
|
+
// ─── Pattern Index & Scoped Context ──────────────────
|
|
608
|
+
{
|
|
609
|
+
name: "rigour_index",
|
|
610
|
+
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.",
|
|
611
|
+
inputSchema: {
|
|
612
|
+
type: "object",
|
|
613
|
+
properties: {
|
|
614
|
+
...cwdParam(),
|
|
615
|
+
semantic: { type: "boolean", description: "Generate semantic embeddings for better matching (requires Transformers.js). Default: false." },
|
|
616
|
+
force: { type: "boolean", description: "Force a full rebuild instead of incremental update. Default: false." },
|
|
617
|
+
output: { type: "string", description: "Custom path for the index file." },
|
|
618
|
+
},
|
|
619
|
+
required: ["cwd"],
|
|
620
|
+
},
|
|
621
|
+
annotations: {
|
|
622
|
+
title: "Build Pattern Index",
|
|
623
|
+
readOnlyHint: false,
|
|
624
|
+
destructiveHint: false,
|
|
625
|
+
idempotentHint: true,
|
|
626
|
+
openWorldHint: false,
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
name: "rigour_context_scope",
|
|
631
|
+
description: "CALL THIS BEFORE reading source files. Returns a minimal editScope (3-10 files) with signatures from the pattern index instead of full file bodies. Uses semantic search when embeddings are available. If index is missing, instructs to call rigour_index first. Highest-impact token saver in the Rigour protocol.",
|
|
632
|
+
inputSchema: {
|
|
633
|
+
type: "object",
|
|
634
|
+
properties: {
|
|
635
|
+
...cwdParam(),
|
|
636
|
+
query: { type: "string", description: "Natural-language description of what you need to work on (e.g. 'add priority field to task service')." },
|
|
637
|
+
limit: { type: "number", description: "Maximum number of pattern matches to return (default: 10)." },
|
|
638
|
+
},
|
|
639
|
+
required: ["cwd", "query"],
|
|
640
|
+
},
|
|
641
|
+
annotations: {
|
|
642
|
+
title: "Scoped Context Retrieval",
|
|
643
|
+
readOnlyHint: true,
|
|
644
|
+
destructiveHint: false,
|
|
645
|
+
idempotentHint: true,
|
|
646
|
+
openWorldHint: false,
|
|
647
|
+
},
|
|
648
|
+
},
|
|
528
649
|
];
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pattern Index MCP Tool Handler
|
|
3
|
+
*
|
|
4
|
+
* Wraps PatternIndexer for agent-accessible index build/update.
|
|
5
|
+
*/
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { PatternIndexer, savePatternIndex, loadPatternIndex, getDefaultIndexPath, } from '@rigour-labs/core/pattern-index';
|
|
8
|
+
import { setStaticCache, setComponentCache, } from '@rigour-labs/core';
|
|
9
|
+
import { notifyProgress } from '../utils/notifications.js';
|
|
10
|
+
import { buildTelemetryMeta, getWorkspaceCommitSha } from '../utils/context-telemetry.js';
|
|
11
|
+
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
|
+
export async function handleIndex(cwd, options = {}) {
|
|
51
|
+
const indexPath = options.output || getDefaultIndexPath(cwd);
|
|
52
|
+
const candidateEstimate = 'Full codebase AST scan for pattern extraction';
|
|
53
|
+
try {
|
|
54
|
+
notifyProgress('info', 'Building pattern index...');
|
|
55
|
+
const indexer = new PatternIndexer(cwd, { useEmbeddings: options.semantic ?? false });
|
|
56
|
+
const existingIndex = await loadPatternIndex(indexPath);
|
|
57
|
+
let index;
|
|
58
|
+
if (existingIndex && !options.force) {
|
|
59
|
+
index = await indexer.updateIndex(existingIndex);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
index = await indexer.buildIndex();
|
|
63
|
+
}
|
|
64
|
+
await savePatternIndex(index, indexPath);
|
|
65
|
+
await syncIndexToCache(cwd, index);
|
|
66
|
+
notifyProgress('info', 'Pattern index complete');
|
|
67
|
+
const byType = Object.entries(index.stats.byType)
|
|
68
|
+
.map(([type, count]) => `${type}: ${count}`)
|
|
69
|
+
.join(', ');
|
|
70
|
+
let text = `✅ PATTERN INDEX ${options.force ? 'REBUILT' : 'UPDATED'}\n\n`;
|
|
71
|
+
text += `- Total Patterns: ${index.stats.totalPatterns}\n`;
|
|
72
|
+
text += `- Total Files: ${index.stats.totalFiles}\n`;
|
|
73
|
+
text += `- Index Path: ${indexPath}\n`;
|
|
74
|
+
text += `- Duration: ${index.stats.indexDurationMs}ms\n`;
|
|
75
|
+
if (options.semantic)
|
|
76
|
+
text += `- Semantic Search: Enabled\n`;
|
|
77
|
+
text += `- Types: ${byType}\n\n`;
|
|
78
|
+
text += `Index synced to context cache layers. Use rigour_context_scope before reading files.`;
|
|
79
|
+
const telemetry = buildTelemetryMeta({
|
|
80
|
+
candidateText: candidateEstimate,
|
|
81
|
+
returnedText: text,
|
|
82
|
+
cacheStatus: 'miss',
|
|
83
|
+
});
|
|
84
|
+
return {
|
|
85
|
+
content: [{ type: 'text', text: appendContextFooter(text, telemetry, 'rigour_context_scope("your task")') }],
|
|
86
|
+
_telemetry: telemetry,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
return {
|
|
91
|
+
content: [{ type: 'text', text: `RIGOUR ERROR: Failed to build pattern index: ${error.message}` }],
|
|
92
|
+
isError: true,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
type ToolResult
|
|
2
|
-
content: {
|
|
3
|
-
type: string;
|
|
4
|
-
text: string;
|
|
5
|
-
}[];
|
|
6
|
-
};
|
|
1
|
+
import { type ToolResult } from '../utils/context-telemetry.js';
|
|
7
2
|
export declare function handleRemember(cwd: string, key: string, value: string): Promise<ToolResult>;
|
|
8
3
|
export declare function handleRecall(cwd: string, key?: string): Promise<ToolResult>;
|
|
9
4
|
export declare function handleForget(cwd: string, key: string): Promise<ToolResult>;
|
|
10
|
-
export {};
|
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
* @since v4.2.0 — DLP gate on memory persistence
|
|
11
11
|
*/
|
|
12
12
|
import { loadMemory, saveMemory } from '../utils/config.js';
|
|
13
|
-
import { scanInputForCredentials, formatDLPAlert, createDLPAuditEntry } from '@rigour-labs/core';
|
|
13
|
+
import { scanInputForCredentials, formatDLPAlert, createDLPAuditEntry, getSemanticQueryCache, setSemanticQueryCache, estimateTokenCount, } from '@rigour-labs/core';
|
|
14
|
+
import { loadPatternIndex, getDefaultIndexPath, } from '@rigour-labs/core/pattern-index';
|
|
15
|
+
import { buildTelemetryMeta, getWorkspaceCommitSha } from '../utils/context-telemetry.js';
|
|
16
|
+
import { appendContextFooter } from '../utils/context-footer.js';
|
|
14
17
|
import fs from 'fs-extra';
|
|
15
18
|
import path from 'path';
|
|
16
19
|
/**
|
|
@@ -49,6 +52,29 @@ function extractStrings(obj, out) {
|
|
|
49
52
|
extractStrings(v, out);
|
|
50
53
|
}
|
|
51
54
|
}
|
|
55
|
+
async function getIndexHealthBlock(cwd) {
|
|
56
|
+
const indexPath = getDefaultIndexPath(cwd);
|
|
57
|
+
const index = await loadPatternIndex(indexPath);
|
|
58
|
+
if (!index) {
|
|
59
|
+
return '\n\n📊 Pattern Index: NOT FOUND — call rigour_index to enable scope optimization and reinvention detection.';
|
|
60
|
+
}
|
|
61
|
+
return `\n\n📊 Pattern Index: ${index.stats.totalPatterns} patterns across ${index.stats.totalFiles} files (updated ${index.lastUpdated}).`;
|
|
62
|
+
}
|
|
63
|
+
function wrapRecallResult(text, candidateText, cacheStatus, deduplicatedTokens = 0) {
|
|
64
|
+
const telemetry = buildTelemetryMeta({
|
|
65
|
+
candidateText,
|
|
66
|
+
returnedText: text,
|
|
67
|
+
cacheStatus,
|
|
68
|
+
deduplicatedTokens,
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
content: [{
|
|
72
|
+
type: 'text',
|
|
73
|
+
text: appendContextFooter(text, telemetry, 'rigour_context_scope("your task")'),
|
|
74
|
+
}],
|
|
75
|
+
_telemetry: telemetry,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
52
78
|
export async function handleRemember(cwd, key, value) {
|
|
53
79
|
// Fallback: if key is missing but value exists, auto-generate a key
|
|
54
80
|
if (!key && value) {
|
|
@@ -103,11 +129,21 @@ export async function handleRemember(cwd, key, value) {
|
|
|
103
129
|
};
|
|
104
130
|
}
|
|
105
131
|
export async function handleRecall(cwd, key) {
|
|
132
|
+
const commitSha = await getWorkspaceCommitSha(cwd);
|
|
133
|
+
const cacheQuery = key ? `recall:${key}` : 'recall:all';
|
|
106
134
|
const store = await loadMemory(cwd);
|
|
135
|
+
const candidateText = JSON.stringify(store);
|
|
136
|
+
const cached = await getSemanticQueryCache(cacheQuery, commitSha, cwd);
|
|
137
|
+
if (cached?.evidence?.length) {
|
|
138
|
+
const cachedBody = cached.evidence.join('\n');
|
|
139
|
+
const indexHealth = await getIndexHealthBlock(cwd);
|
|
140
|
+
return wrapRecallResult(`${cachedBody}${indexHealth}`, candidateText, 'semantic-hit', Math.max(0, estimateTokenCount(candidateText) - estimateTokenCount(cachedBody)));
|
|
141
|
+
}
|
|
107
142
|
if (key) {
|
|
108
143
|
const memory = store.memories[key];
|
|
109
144
|
if (!memory) {
|
|
110
|
-
|
|
145
|
+
const text = `NO MEMORY FOUND for key "${key}". Use rigour_remember to store instructions.${await getIndexHealthBlock(cwd)}`;
|
|
146
|
+
return wrapRecallResult(text, candidateText, 'miss');
|
|
111
147
|
}
|
|
112
148
|
// ── DLP Gate on recall: catch credentials stored before DLP existed ──
|
|
113
149
|
const dlpResult = scanInputForCredentials(memory.value);
|
|
@@ -125,11 +161,24 @@ export async function handleRecall(cwd, key) {
|
|
|
125
161
|
}],
|
|
126
162
|
};
|
|
127
163
|
}
|
|
128
|
-
|
|
164
|
+
const body = `RECALLED MEMORY [${key}]:\n${memory.value}\n\n(Stored: ${memory.timestamp})`;
|
|
165
|
+
const indexHealth = await getIndexHealthBlock(cwd);
|
|
166
|
+
const fullText = `${body}${indexHealth}`;
|
|
167
|
+
await setSemanticQueryCache(cacheQuery, commitSha, {
|
|
168
|
+
query: cacheQuery,
|
|
169
|
+
resolvedOwner: 'memory',
|
|
170
|
+
editScope: [],
|
|
171
|
+
validationScope: [],
|
|
172
|
+
evidence: [body],
|
|
173
|
+
commitSha,
|
|
174
|
+
confidence: 1,
|
|
175
|
+
}, cwd);
|
|
176
|
+
return wrapRecallResult(fullText, candidateText, 'miss');
|
|
129
177
|
}
|
|
130
178
|
const keys = Object.keys(store.memories);
|
|
131
179
|
if (keys.length === 0) {
|
|
132
|
-
|
|
180
|
+
const text = `NO MEMORIES STORED. Use rigour_remember to persist important instructions.${await getIndexHealthBlock(cwd)}`;
|
|
181
|
+
return wrapRecallResult(text, candidateText, 'miss');
|
|
133
182
|
}
|
|
134
183
|
// ── DLP scan all memories on bulk recall ──
|
|
135
184
|
const cleanMemories = [];
|
|
@@ -157,7 +206,17 @@ export async function handleRecall(cwd, key) {
|
|
|
157
206
|
else {
|
|
158
207
|
text = "NO MEMORIES STORED. Use rigour_remember to persist important instructions.";
|
|
159
208
|
}
|
|
160
|
-
|
|
209
|
+
text += await getIndexHealthBlock(cwd);
|
|
210
|
+
await setSemanticQueryCache(cacheQuery, commitSha, {
|
|
211
|
+
query: cacheQuery,
|
|
212
|
+
resolvedOwner: 'memory',
|
|
213
|
+
editScope: [],
|
|
214
|
+
validationScope: [],
|
|
215
|
+
evidence: [text],
|
|
216
|
+
commitSha,
|
|
217
|
+
confidence: 1,
|
|
218
|
+
}, cwd);
|
|
219
|
+
return wrapRecallResult(text, candidateText, 'miss', Math.max(0, estimateTokenCount(candidateText) - estimateTokenCount(text)));
|
|
161
220
|
}
|
|
162
221
|
export async function handleForget(cwd, key) {
|
|
163
222
|
const store = await loadMemory(cwd);
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
type ToolResult
|
|
2
|
-
content: {
|
|
3
|
-
type: string;
|
|
4
|
-
text: string;
|
|
5
|
-
}[];
|
|
6
|
-
};
|
|
1
|
+
import { type ToolResult } from '../utils/context-telemetry.js';
|
|
7
2
|
export declare function handleCheckPattern(cwd: string, patternName: string, type?: string, intent?: string, file?: string): Promise<ToolResult>;
|
|
8
3
|
export declare function handleSecurityAudit(cwd: string): Promise<ToolResult>;
|
|
9
|
-
export {};
|
|
@@ -9,8 +9,10 @@ import path from "path";
|
|
|
9
9
|
import yaml from "yaml";
|
|
10
10
|
import fs from "fs-extra";
|
|
11
11
|
import { PatternMatcher, loadPatternIndex, getDefaultIndexPath, StalenessDetector, SecurityDetector, } from "@rigour-labs/core/pattern-index";
|
|
12
|
-
import { ConfigSchema } from "@rigour-labs/core";
|
|
12
|
+
import { ConfigSchema, getSemanticQueryCache, setSemanticQueryCache, estimateTokenCount } from "@rigour-labs/core";
|
|
13
13
|
import { notifyProgress } from '../utils/notifications.js';
|
|
14
|
+
import { buildTelemetryMeta, getWorkspaceCommitSha } from '../utils/context-telemetry.js';
|
|
15
|
+
import { appendContextFooter } from '../utils/context-footer.js';
|
|
14
16
|
/**
|
|
15
17
|
* Check if a file path is protected by safety.protected_paths in rigour.yml.
|
|
16
18
|
* Returns the matched pattern or null.
|
|
@@ -41,7 +43,33 @@ async function checkFileGuard(cwd, filePath) {
|
|
|
41
43
|
return normalized.startsWith(clean + '/');
|
|
42
44
|
}) ?? null;
|
|
43
45
|
}
|
|
46
|
+
function buildCacheQuery(patternName, type, intent, file) {
|
|
47
|
+
return `check_pattern:${patternName}:${type ?? ''}:${intent ?? ''}:${file ?? ''}`;
|
|
48
|
+
}
|
|
44
49
|
export async function handleCheckPattern(cwd, patternName, type, intent, file) {
|
|
50
|
+
const commitSha = await getWorkspaceCommitSha(cwd);
|
|
51
|
+
const cacheQuery = buildCacheQuery(patternName, type, intent, file);
|
|
52
|
+
const indexPath = getDefaultIndexPath(cwd);
|
|
53
|
+
const index = await loadPatternIndex(indexPath);
|
|
54
|
+
const indexScanEstimate = index
|
|
55
|
+
? `Pattern index scan (${index.stats.totalPatterns} patterns) for ${patternName}`
|
|
56
|
+
: `Full pattern discovery for ${patternName}`;
|
|
57
|
+
const cached = await getSemanticQueryCache(cacheQuery, commitSha, cwd);
|
|
58
|
+
if (cached?.evidence?.length) {
|
|
59
|
+
const cachedText = cached.evidence.join('\n');
|
|
60
|
+
const telemetry = buildTelemetryMeta({
|
|
61
|
+
candidateText: indexScanEstimate,
|
|
62
|
+
returnedText: cachedText,
|
|
63
|
+
cacheStatus: 'exact-hit',
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
content: [{
|
|
67
|
+
type: 'text',
|
|
68
|
+
text: appendContextFooter(cachedText, telemetry, 'proceed with implementation or rigour_check when done'),
|
|
69
|
+
}],
|
|
70
|
+
_telemetry: telemetry,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
45
73
|
let resultText = "";
|
|
46
74
|
// 0. File Guard — BLOCK writes to protected paths
|
|
47
75
|
if (file) {
|
|
@@ -52,12 +80,18 @@ export async function handleCheckPattern(cwd, patternName, type, intent, file) {
|
|
|
52
80
|
resultText += `This path matches protected pattern "${matched}" in rigour.yml.\n`;
|
|
53
81
|
resultText += `CI/CD pipelines, governance configs, and protected docs require human review.\n\n`;
|
|
54
82
|
resultText += `RECOMMENDED ACTION: STOP. Do not create or modify this file. Ask the human to make this change manually.`;
|
|
55
|
-
|
|
83
|
+
const telemetry = buildTelemetryMeta({
|
|
84
|
+
candidateText: indexScanEstimate,
|
|
85
|
+
returnedText: resultText,
|
|
86
|
+
cacheStatus: 'miss',
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
content: [{ type: "text", text: appendContextFooter(resultText, telemetry) }],
|
|
90
|
+
_telemetry: telemetry,
|
|
91
|
+
};
|
|
56
92
|
}
|
|
57
93
|
}
|
|
58
94
|
// 1. Check for Reinvention
|
|
59
|
-
const indexPath = getDefaultIndexPath(cwd);
|
|
60
|
-
const index = await loadPatternIndex(indexPath);
|
|
61
95
|
if (index) {
|
|
62
96
|
const matcher = new PatternMatcher(index);
|
|
63
97
|
const matchResult = await matcher.match({ name: patternName, type, intent });
|
|
@@ -68,7 +102,7 @@ export async function handleCheckPattern(cwd, patternName, type, intent, file) {
|
|
|
68
102
|
}
|
|
69
103
|
}
|
|
70
104
|
else {
|
|
71
|
-
resultText += `⚠️ Pattern index not found. Run
|
|
105
|
+
resultText += `⚠️ Pattern index not found. Run rigour_index to enable reinvention detection.\n\n`;
|
|
72
106
|
}
|
|
73
107
|
// 2. Check for Staleness/Best Practices
|
|
74
108
|
const detector = new StalenessDetector(cwd);
|
|
@@ -110,12 +144,42 @@ export async function handleCheckPattern(cwd, patternName, type, intent, file) {
|
|
|
110
144
|
}
|
|
111
145
|
resultText += `\nRECOMMENDED ACTION: ${recommendation}`;
|
|
112
146
|
}
|
|
113
|
-
|
|
147
|
+
await setSemanticQueryCache(cacheQuery, commitSha, {
|
|
148
|
+
query: cacheQuery,
|
|
149
|
+
resolvedOwner: file ? path.dirname(file) : 'patterns',
|
|
150
|
+
editScope: file ? [file] : [],
|
|
151
|
+
validationScope: [],
|
|
152
|
+
evidence: [resultText],
|
|
153
|
+
commitSha,
|
|
154
|
+
confidence: resultText.includes('✅') ? 0.9 : 0.7,
|
|
155
|
+
}, cwd);
|
|
156
|
+
const telemetry = buildTelemetryMeta({
|
|
157
|
+
candidateText: indexScanEstimate,
|
|
158
|
+
returnedText: resultText,
|
|
159
|
+
cacheStatus: 'miss',
|
|
160
|
+
deduplicatedTokens: index
|
|
161
|
+
? Math.max(0, estimateTokenCount(indexScanEstimate) - estimateTokenCount(resultText))
|
|
162
|
+
: 0,
|
|
163
|
+
});
|
|
164
|
+
return {
|
|
165
|
+
content: [{
|
|
166
|
+
type: "text",
|
|
167
|
+
text: appendContextFooter(resultText, telemetry, 'rigour_check before declaring done'),
|
|
168
|
+
}],
|
|
169
|
+
_telemetry: telemetry,
|
|
170
|
+
};
|
|
114
171
|
}
|
|
115
172
|
export async function handleSecurityAudit(cwd) {
|
|
116
173
|
notifyProgress("info", "Running CVE security audit...");
|
|
117
174
|
const security = new SecurityDetector(cwd);
|
|
118
175
|
const summary = await security.getSecuritySummary();
|
|
119
176
|
notifyProgress("info", "Security audit complete");
|
|
120
|
-
return {
|
|
177
|
+
return {
|
|
178
|
+
content: [{ type: "text", text: summary }],
|
|
179
|
+
_telemetry: buildTelemetryMeta({
|
|
180
|
+
candidateText: summary,
|
|
181
|
+
returnedText: summary,
|
|
182
|
+
cacheStatus: 'none',
|
|
183
|
+
}),
|
|
184
|
+
};
|
|
121
185
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Closed-loop context efficiency footers appended to MCP tool responses.
|
|
3
|
+
*/
|
|
4
|
+
import type { TelemetryMeta } from './context-telemetry.js';
|
|
5
|
+
export declare function formatContextFooter(meta: TelemetryMeta, nextStep?: string): string;
|
|
6
|
+
export declare function appendContextFooter(text: string, meta: TelemetryMeta | undefined, nextStep?: string): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function formatContextFooter(meta, nextStep) {
|
|
2
|
+
const saved = Math.max(0, meta.candidateTokens - meta.returnedTokens);
|
|
3
|
+
let footer = `\n\n[Rigour Context] cache=${meta.cacheStatus}`;
|
|
4
|
+
if (saved > 0) {
|
|
5
|
+
footer += ` | saved~${saved.toLocaleString()} tokens`;
|
|
6
|
+
}
|
|
7
|
+
if (meta.deduplicatedTokens && meta.deduplicatedTokens > 0) {
|
|
8
|
+
footer += ` | dedup~${meta.deduplicatedTokens.toLocaleString()} tokens`;
|
|
9
|
+
}
|
|
10
|
+
if (nextStep) {
|
|
11
|
+
footer += ` | Next: ${nextStep}`;
|
|
12
|
+
}
|
|
13
|
+
return footer;
|
|
14
|
+
}
|
|
15
|
+
export function appendContextFooter(text, meta, nextStep) {
|
|
16
|
+
if (!meta)
|
|
17
|
+
return text;
|
|
18
|
+
return text + formatContextFooter(meta, nextStep);
|
|
19
|
+
}
|