@crowley/rag-mcp 1.0.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.
Files changed (49) hide show
  1. package/dist/api-client.d.ts +4 -0
  2. package/dist/api-client.js +19 -0
  3. package/dist/context-enrichment.d.ts +44 -0
  4. package/dist/context-enrichment.js +190 -0
  5. package/dist/formatters.d.ts +33 -0
  6. package/dist/formatters.js +70 -0
  7. package/dist/index.d.ts +13 -0
  8. package/dist/index.js +109 -0
  9. package/dist/tool-registry.d.ts +20 -0
  10. package/dist/tool-registry.js +123 -0
  11. package/dist/tools/advanced.d.ts +9 -0
  12. package/dist/tools/advanced.js +315 -0
  13. package/dist/tools/agents.d.ts +8 -0
  14. package/dist/tools/agents.js +97 -0
  15. package/dist/tools/analytics.d.ts +9 -0
  16. package/dist/tools/analytics.js +261 -0
  17. package/dist/tools/architecture.d.ts +5 -0
  18. package/dist/tools/architecture.js +720 -0
  19. package/dist/tools/ask.d.ts +9 -0
  20. package/dist/tools/ask.js +256 -0
  21. package/dist/tools/cache.d.ts +5 -0
  22. package/dist/tools/cache.js +98 -0
  23. package/dist/tools/clustering.d.ts +9 -0
  24. package/dist/tools/clustering.js +251 -0
  25. package/dist/tools/confluence.d.ts +9 -0
  26. package/dist/tools/confluence.js +147 -0
  27. package/dist/tools/database.d.ts +5 -0
  28. package/dist/tools/database.js +429 -0
  29. package/dist/tools/feedback.d.ts +9 -0
  30. package/dist/tools/feedback.js +220 -0
  31. package/dist/tools/guidelines.d.ts +5 -0
  32. package/dist/tools/guidelines.js +146 -0
  33. package/dist/tools/indexing.d.ts +9 -0
  34. package/dist/tools/indexing.js +129 -0
  35. package/dist/tools/memory.d.ts +9 -0
  36. package/dist/tools/memory.js +565 -0
  37. package/dist/tools/pm.d.ts +9 -0
  38. package/dist/tools/pm.js +680 -0
  39. package/dist/tools/review.d.ts +8 -0
  40. package/dist/tools/review.js +213 -0
  41. package/dist/tools/search.d.ts +9 -0
  42. package/dist/tools/search.js +377 -0
  43. package/dist/tools/session.d.ts +10 -0
  44. package/dist/tools/session.js +386 -0
  45. package/dist/tools/suggestions.d.ts +9 -0
  46. package/dist/tools/suggestions.js +301 -0
  47. package/dist/types.d.ts +32 -0
  48. package/dist/types.js +4 -0
  49. package/package.json +40 -0
@@ -0,0 +1,4 @@
1
+ /**
2
+ * API Client - Shared axios instance for RAG API calls.
3
+ */
4
+ export declare function createApiClient(ragApiUrl: string, projectName: string, projectPath: string, apiKey?: string): import("axios").AxiosInstance;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * API Client - Shared axios instance for RAG API calls.
3
+ */
4
+ import axios from "axios";
5
+ export function createApiClient(ragApiUrl, projectName, projectPath, apiKey) {
6
+ const headers = {
7
+ "Content-Type": "application/json",
8
+ "X-Project-Name": projectName,
9
+ "X-Project-Path": projectPath,
10
+ };
11
+ if (apiKey) {
12
+ headers["Authorization"] = `Bearer ${apiKey}`;
13
+ }
14
+ return axios.create({
15
+ baseURL: ragApiUrl,
16
+ timeout: 120000,
17
+ headers,
18
+ });
19
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Context Enrichment Middleware
3
+ *
4
+ * Automatically enriches MCP tool responses with relevant project context
5
+ * (memories, patterns, ADRs) by performing semantic recall before each tool call.
6
+ */
7
+ import type { ToolContext } from "./types.js";
8
+ export interface EnrichmentConfig {
9
+ enrichableTools: Set<string>;
10
+ skipTools: Set<string>;
11
+ maxAutoRecall: number;
12
+ minRelevance: number;
13
+ timeoutMs: number;
14
+ }
15
+ /**
16
+ * Default sets of tools that should/shouldn't be enriched.
17
+ */
18
+ export declare const DEFAULT_ENRICHABLE_TOOLS: Set<string>;
19
+ export declare const DEFAULT_SKIP_TOOLS: Set<string>;
20
+ export declare class ContextEnricher {
21
+ private config;
22
+ constructor(config?: Partial<EnrichmentConfig>);
23
+ /**
24
+ * Before hook: auto-recall relevant memories/patterns/ADRs.
25
+ * Returns a context prefix string or null if nothing relevant found.
26
+ */
27
+ before(name: string, args: Record<string, unknown>, ctx: ToolContext): Promise<string | null>;
28
+ /**
29
+ * After hook: fire-and-forget session activity tracking.
30
+ */
31
+ after(name: string, _args: Record<string, unknown>, _result: string, ctx: ToolContext): void;
32
+ /**
33
+ * Extract a semantic query string from tool arguments.
34
+ */
35
+ private extractQuery;
36
+ /**
37
+ * Recall memories with a hard timeout.
38
+ */
39
+ private recallWithTimeout;
40
+ /**
41
+ * Format recalled memories into a compact context prefix.
42
+ */
43
+ private formatContext;
44
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Context Enrichment Middleware
3
+ *
4
+ * Automatically enriches MCP tool responses with relevant project context
5
+ * (memories, patterns, ADRs) by performing semantic recall before each tool call.
6
+ */
7
+ /**
8
+ * Default sets of tools that should/shouldn't be enriched.
9
+ */
10
+ export const DEFAULT_ENRICHABLE_TOOLS = new Set([
11
+ "search_codebase",
12
+ "hybrid_search",
13
+ "ask_codebase",
14
+ "explain_code",
15
+ "find_feature",
16
+ "review_code",
17
+ "generate_tests",
18
+ "suggest_implementation",
19
+ "suggest_related_code",
20
+ "check_architecture",
21
+ "run_agent",
22
+ ]);
23
+ export const DEFAULT_SKIP_TOOLS = new Set([
24
+ "get_cache_stats",
25
+ "warm_cache",
26
+ "get_prediction_stats",
27
+ "get_tool_analytics",
28
+ "list_aliases",
29
+ "backup_collection",
30
+ "enable_quantization",
31
+ "list_backups",
32
+ "get_index_status",
33
+ "get_project_stats",
34
+ "get_rag_guidelines",
35
+ "list_memories",
36
+ "get_behavior_patterns",
37
+ "merge_memories",
38
+ "feedback_search",
39
+ "feedback_memory",
40
+ "get_quality_metrics",
41
+ "get_knowledge_gaps",
42
+ "get_agent_types",
43
+ "get_platform_stats",
44
+ ]);
45
+ export class ContextEnricher {
46
+ config;
47
+ constructor(config = {}) {
48
+ this.config = {
49
+ enrichableTools: config.enrichableTools ?? DEFAULT_ENRICHABLE_TOOLS,
50
+ skipTools: config.skipTools ?? DEFAULT_SKIP_TOOLS,
51
+ maxAutoRecall: config.maxAutoRecall ?? 3,
52
+ minRelevance: config.minRelevance ?? 0.6,
53
+ timeoutMs: config.timeoutMs ?? 2000,
54
+ };
55
+ }
56
+ /**
57
+ * Before hook: auto-recall relevant memories/patterns/ADRs.
58
+ * Returns a context prefix string or null if nothing relevant found.
59
+ */
60
+ async before(name, args, ctx) {
61
+ // Skip non-enrichable tools
62
+ if (this.config.skipTools.has(name))
63
+ return null;
64
+ if (!this.config.enrichableTools.has(name))
65
+ return null;
66
+ // Extract semantic query from args
67
+ const query = this.extractQuery(args);
68
+ if (!query)
69
+ return null;
70
+ try {
71
+ const memories = await this.recallWithTimeout(query, ctx);
72
+ if (memories.length === 0)
73
+ return null;
74
+ return this.formatContext(memories);
75
+ }
76
+ catch {
77
+ // Enrichment should never break tool calls
78
+ return null;
79
+ }
80
+ }
81
+ /**
82
+ * After hook: fire-and-forget session activity tracking.
83
+ */
84
+ after(name, _args, _result, ctx) {
85
+ if (!ctx.activeSessionId)
86
+ return;
87
+ // Fire-and-forget: track tool usage in session
88
+ ctx.api
89
+ .post(`/api/session/${ctx.activeSessionId}/activity`, {
90
+ projectName: ctx.projectName,
91
+ type: "tool",
92
+ value: name,
93
+ })
94
+ .catch(() => {
95
+ // Silently ignore tracking errors
96
+ });
97
+ }
98
+ /**
99
+ * Extract a semantic query string from tool arguments.
100
+ */
101
+ extractQuery(args) {
102
+ // Try common argument patterns in priority order
103
+ if (typeof args.query === "string" && args.query.length > 0) {
104
+ return args.query;
105
+ }
106
+ if (typeof args.question === "string" && args.question.length > 0) {
107
+ return args.question;
108
+ }
109
+ if (typeof args.description === "string" && args.description.length > 0) {
110
+ return args.description;
111
+ }
112
+ if (typeof args.feature === "string" && args.feature.length > 0) {
113
+ return args.feature;
114
+ }
115
+ if (typeof args.task === "string" && args.task.length > 0) {
116
+ return args.task;
117
+ }
118
+ if (typeof args.code === "string" && args.code.length > 0) {
119
+ return args.code.slice(0, 200);
120
+ }
121
+ return null;
122
+ }
123
+ /**
124
+ * Recall memories with a hard timeout.
125
+ */
126
+ async recallWithTimeout(query, ctx) {
127
+ const controller = new AbortController();
128
+ const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs);
129
+ try {
130
+ // Parallel recall: general memories + decisions/ADRs
131
+ const [memoriesRes, decisionsRes] = await Promise.all([
132
+ ctx.api
133
+ .post("/api/memory/recall-durable", {
134
+ projectName: ctx.projectName,
135
+ query,
136
+ limit: this.config.maxAutoRecall,
137
+ type: "all",
138
+ }, { signal: controller.signal })
139
+ .catch(() => null),
140
+ ctx.api
141
+ .post("/api/memory/recall-durable", {
142
+ projectName: ctx.projectName,
143
+ query,
144
+ limit: 2,
145
+ type: "decision",
146
+ }, { signal: controller.signal })
147
+ .catch(() => null),
148
+ ]);
149
+ const memories = [];
150
+ const seenIds = new Set();
151
+ // Process general memories
152
+ if (memoriesRes?.data?.memories) {
153
+ for (const m of memoriesRes.data.memories) {
154
+ if (m.score >= this.config.minRelevance && !seenIds.has(m.memory?.id)) {
155
+ seenIds.add(m.memory?.id);
156
+ memories.push({
157
+ type: m.memory?.type || "note",
158
+ content: m.memory?.content || "",
159
+ score: m.score,
160
+ });
161
+ }
162
+ }
163
+ }
164
+ // Process decisions/ADRs
165
+ if (decisionsRes?.data?.memories) {
166
+ for (const m of decisionsRes.data.memories) {
167
+ if (m.score >= this.config.minRelevance && !seenIds.has(m.memory?.id)) {
168
+ seenIds.add(m.memory?.id);
169
+ memories.push({
170
+ type: m.memory?.type || "decision",
171
+ content: m.memory?.content || "",
172
+ score: m.score,
173
+ });
174
+ }
175
+ }
176
+ }
177
+ return memories.slice(0, this.config.maxAutoRecall + 2);
178
+ }
179
+ finally {
180
+ clearTimeout(timeout);
181
+ }
182
+ }
183
+ /**
184
+ * Format recalled memories into a compact context prefix.
185
+ */
186
+ formatContext(memories) {
187
+ const lines = memories.map((m) => `- [${m.type}] ${m.content.slice(0, 150).replace(/\n/g, " ")}`);
188
+ return `--- Project Context ---\n${lines.join("\n")}\n---`;
189
+ }
190
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Shared formatters and content helpers for tool output.
3
+ */
4
+ /** Content preview length constants */
5
+ export declare const PREVIEW: {
6
+ readonly SHORT: 100;
7
+ readonly MEDIUM: 300;
8
+ readonly LONG: 500;
9
+ readonly FULL: 800;
10
+ };
11
+ /** Truncate text to max length with ellipsis */
12
+ export declare function truncate(text: string, max: number): string;
13
+ /** Format a score as percentage */
14
+ export declare function pct(score: number): string;
15
+ /** Format code search results into markdown */
16
+ export declare function formatCodeResults(results: Array<{
17
+ file: string;
18
+ content: string;
19
+ score: number;
20
+ language?: string;
21
+ startLine?: number;
22
+ endLine?: number;
23
+ }>, contentLimit?: number): string;
24
+ /** Format memory results into markdown */
25
+ export declare function formatMemoryResults(results: Array<{
26
+ memory: Record<string, unknown>;
27
+ score: number;
28
+ }>, emptyMessage?: string): string;
29
+ /** Format a simple list of files with scores */
30
+ export declare function formatFileList(files: Array<{
31
+ file: string;
32
+ score?: number;
33
+ }>, emptyMessage?: string): string;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Shared formatters and content helpers for tool output.
3
+ */
4
+ /** Content preview length constants */
5
+ export const PREVIEW = {
6
+ SHORT: 100,
7
+ MEDIUM: 300,
8
+ LONG: 500,
9
+ FULL: 800,
10
+ };
11
+ /** Truncate text to max length with ellipsis */
12
+ export function truncate(text, max) {
13
+ if (!text || text.length <= max)
14
+ return text;
15
+ return text.slice(0, max) + "...";
16
+ }
17
+ /** Format a score as percentage */
18
+ export function pct(score) {
19
+ return (score * 100).toFixed(1) + "%";
20
+ }
21
+ /** Format code search results into markdown */
22
+ export function formatCodeResults(results, contentLimit = PREVIEW.LONG) {
23
+ if (!results || results.length === 0)
24
+ return "No results found.";
25
+ return results
26
+ .map((r) => `**${r.file}** (${pct(r.score)} match)\n` +
27
+ (r.startLine ? `Lines ${r.startLine}-${r.endLine || "?"}\n` : "") +
28
+ "```" + (r.language || "") + "\n" +
29
+ truncate(r.content, contentLimit) +
30
+ "\n```")
31
+ .join("\n\n---\n\n");
32
+ }
33
+ /** Format memory results into markdown */
34
+ export function formatMemoryResults(results, emptyMessage = "No memories found.") {
35
+ if (!results || results.length === 0)
36
+ return emptyMessage;
37
+ const typeEmojis = {
38
+ decision: "🎯",
39
+ insight: "💡",
40
+ context: "📌",
41
+ todo: "📋",
42
+ conversation: "💬",
43
+ note: "📝",
44
+ };
45
+ let result = "";
46
+ results.forEach((r, i) => {
47
+ const m = r.memory;
48
+ const type = m.type;
49
+ const emoji = type === "todo" && m.status === "done" ? "✅" : (typeEmojis[type] || "📝");
50
+ result += `### ${i + 1}. ${emoji} ${(type || "note").toUpperCase()}\n`;
51
+ result += `**Relevance:** ${pct(r.score)}\n`;
52
+ result += `${m.content}\n`;
53
+ if (m.relatedTo)
54
+ result += `*Related to: ${m.relatedTo}*\n`;
55
+ if (m.tags?.length > 0)
56
+ result += `*Tags: ${m.tags.join(", ")}*\n`;
57
+ if (m.status)
58
+ result += `*Status: ${m.status}*\n`;
59
+ result += `*${new Date(m.createdAt).toLocaleDateString()}*\n\n`;
60
+ });
61
+ return result;
62
+ }
63
+ /** Format a simple list of files with scores */
64
+ export function formatFileList(files, emptyMessage = "No files found.") {
65
+ if (!files || files.length === 0)
66
+ return emptyMessage;
67
+ return files
68
+ .map((f) => `- ${f.file}${f.score ? ` (${pct(f.score)})` : ""}`)
69
+ .join("\n");
70
+ }
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Universal RAG MCP Server
4
+ *
5
+ * A shared MCP server that can be used by any project.
6
+ * Each project has its own namespace/collection in Qdrant.
7
+ *
8
+ * Environment variables:
9
+ * - PROJECT_NAME: Unique project identifier (e.g., "cypro", "myproject")
10
+ * - PROJECT_PATH: Path to project codebase for indexing
11
+ * - RAG_API_URL: URL of the shared RAG API (default: http://localhost:3100)
12
+ */
13
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Universal RAG MCP Server
4
+ *
5
+ * A shared MCP server that can be used by any project.
6
+ * Each project has its own namespace/collection in Qdrant.
7
+ *
8
+ * Environment variables:
9
+ * - PROJECT_NAME: Unique project identifier (e.g., "cypro", "myproject")
10
+ * - PROJECT_PATH: Path to project codebase for indexing
11
+ * - RAG_API_URL: URL of the shared RAG API (default: http://localhost:3100)
12
+ */
13
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
14
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
16
+ import { createApiClient } from "./api-client.js";
17
+ import { ToolRegistry } from "./tool-registry.js";
18
+ import { ContextEnricher } from "./context-enrichment.js";
19
+ // Tool modules
20
+ import { createSearchTools } from "./tools/search.js";
21
+ import { createAskTools } from "./tools/ask.js";
22
+ import { createIndexingTools } from "./tools/indexing.js";
23
+ import { createMemoryTools } from "./tools/memory.js";
24
+ import { createArchitectureTools } from "./tools/architecture.js";
25
+ import { createDatabaseTools } from "./tools/database.js";
26
+ import { createConfluenceTools } from "./tools/confluence.js";
27
+ import { createPmTools } from "./tools/pm.js";
28
+ import { createReviewTools } from "./tools/review.js";
29
+ import { createAnalyticsTools } from "./tools/analytics.js";
30
+ import { createClusteringTools } from "./tools/clustering.js";
31
+ import { createSessionTools } from "./tools/session.js";
32
+ import { createFeedbackTools } from "./tools/feedback.js";
33
+ import { createSuggestionTools } from "./tools/suggestions.js";
34
+ import { createCacheTools } from "./tools/cache.js";
35
+ import { createGuidelinesTools } from "./tools/guidelines.js";
36
+ import { createAdvancedTools } from "./tools/advanced.js";
37
+ import { createAgentTools } from "./tools/agents.js";
38
+ // Configuration from environment
39
+ const PROJECT_NAME = process.env.PROJECT_NAME || "default";
40
+ const PROJECT_PATH = process.env.PROJECT_PATH || process.cwd();
41
+ const RAG_API_URL = process.env.RAG_API_URL || "http://localhost:3100";
42
+ const RAG_API_KEY = process.env.RAG_API_KEY;
43
+ const COLLECTION_PREFIX = `${PROJECT_NAME}_`;
44
+ // API client
45
+ const api = createApiClient(RAG_API_URL, PROJECT_NAME, PROJECT_PATH, RAG_API_KEY);
46
+ // Mutable tool context shared by all handlers (session state updates in-place)
47
+ const ctx = {
48
+ api,
49
+ projectName: PROJECT_NAME,
50
+ projectPath: PROJECT_PATH,
51
+ collectionPrefix: COLLECTION_PREFIX,
52
+ enrichmentEnabled: true,
53
+ };
54
+ // Build tool registry from modules
55
+ const registry = new ToolRegistry();
56
+ registry.register(createSearchTools(PROJECT_NAME));
57
+ registry.register(createAskTools(PROJECT_NAME));
58
+ registry.register(createIndexingTools(PROJECT_NAME));
59
+ registry.register(createMemoryTools(PROJECT_NAME));
60
+ registry.register(createArchitectureTools(PROJECT_NAME));
61
+ registry.register(createDatabaseTools(PROJECT_NAME));
62
+ registry.register(createConfluenceTools(PROJECT_NAME));
63
+ registry.register(createPmTools(PROJECT_NAME));
64
+ registry.register(createReviewTools(PROJECT_NAME));
65
+ registry.register(createAnalyticsTools(PROJECT_NAME));
66
+ registry.register(createClusteringTools(PROJECT_NAME));
67
+ registry.register(createSessionTools(PROJECT_NAME, ctx));
68
+ registry.register(createFeedbackTools(PROJECT_NAME));
69
+ registry.register(createSuggestionTools(PROJECT_NAME));
70
+ registry.register(createCacheTools(PROJECT_NAME));
71
+ registry.register(createGuidelinesTools(PROJECT_NAME));
72
+ registry.register(createAdvancedTools(PROJECT_NAME));
73
+ registry.register(createAgentTools(PROJECT_NAME));
74
+ // Initialize context enrichment middleware
75
+ const enricher = new ContextEnricher({
76
+ maxAutoRecall: 3,
77
+ minRelevance: 0.6,
78
+ timeoutMs: 2000,
79
+ });
80
+ registry.setEnricher(enricher);
81
+ // MCP Server
82
+ const server = new Server({
83
+ name: `${PROJECT_NAME}-rag`,
84
+ version: "1.0.0",
85
+ }, {
86
+ capabilities: {
87
+ tools: {},
88
+ },
89
+ });
90
+ // List tools handler
91
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
92
+ tools: registry.getTools(),
93
+ }));
94
+ // Call tool handler
95
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
96
+ const { name, arguments: args } = request.params;
97
+ const result = await registry.handle(name, args || {}, ctx);
98
+ return {
99
+ content: [{ type: "text", text: result }],
100
+ };
101
+ });
102
+ // Start server
103
+ async function main() {
104
+ const transport = new StdioServerTransport();
105
+ await server.connect(transport);
106
+ console.error(`${PROJECT_NAME} RAG MCP server running (collection prefix: ${COLLECTION_PREFIX})`);
107
+ console.error(`Registered ${registry.getTools().length} tools from 18 modules`);
108
+ }
109
+ main().catch(console.error);
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Tool Registry - Registration and dispatch for tool modules.
3
+ */
4
+ import type { ToolDefinition, ToolModule, ToolContext } from "./types.js";
5
+ import type { ContextEnricher } from "./context-enrichment.js";
6
+ export declare class ToolRegistry {
7
+ private tools;
8
+ private handlers;
9
+ private enricher?;
10
+ /** Set the context enricher */
11
+ setEnricher(enricher: ContextEnricher): void;
12
+ /** Register a tool module */
13
+ register(module: ToolModule): void;
14
+ /** Get all registered tool definitions */
15
+ getTools(): ToolDefinition[];
16
+ /** Fire-and-forget usage tracking */
17
+ private trackUsage;
18
+ /** Dispatch a tool call */
19
+ handle(name: string, args: Record<string, unknown>, ctx: ToolContext): Promise<string>;
20
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Tool Registry - Registration and dispatch for tool modules.
3
+ */
4
+ /** Tools that should NOT be tracked (meta/admin tools, avoid recursive tracking) */
5
+ const TRACKING_EXCLUDE = new Set([
6
+ "get_tool_analytics",
7
+ "get_knowledge_gaps",
8
+ "analyze_usage_patterns",
9
+ "get_behavior_patterns",
10
+ "get_quality_metrics",
11
+ "get_cache_stats",
12
+ "get_prediction_stats",
13
+ "get_rag_guidelines",
14
+ ]);
15
+ /** Summarize tool args into a short string for analytics */
16
+ function summarizeInput(name, args) {
17
+ // Common patterns: query, question, content, feature, code, file
18
+ const q = args.query || args.question || args.feature || args.description || args.task || "";
19
+ if (q && typeof q === "string")
20
+ return q.slice(0, 200);
21
+ const content = args.content || args.code || args.diff || "";
22
+ if (content && typeof content === "string")
23
+ return content.slice(0, 100);
24
+ const file = args.file || args.filePath || args.currentFile || "";
25
+ if (file && typeof file === "string")
26
+ return file;
27
+ // Fallback: first string arg
28
+ for (const v of Object.values(args)) {
29
+ if (typeof v === "string" && v.length > 0)
30
+ return v.slice(0, 150);
31
+ }
32
+ return name;
33
+ }
34
+ /** Count results from a tool response string */
35
+ function countResults(result) {
36
+ // Heuristic: count numbered list items, file matches, or "No results" = 0
37
+ if (result.includes("No results") || result.includes("No matches") || result.includes("not found"))
38
+ return 0;
39
+ const numbered = result.match(/^\d+\./gm);
40
+ if (numbered)
41
+ return numbered.length;
42
+ const bullets = result.match(/^[-*] /gm);
43
+ if (bullets)
44
+ return bullets.length;
45
+ return 1;
46
+ }
47
+ export class ToolRegistry {
48
+ tools = [];
49
+ handlers = new Map();
50
+ enricher;
51
+ /** Set the context enricher */
52
+ setEnricher(enricher) {
53
+ this.enricher = enricher;
54
+ }
55
+ /** Register a tool module */
56
+ register(module) {
57
+ this.tools.push(...module.tools);
58
+ for (const [name, handler] of Object.entries(module.handlers)) {
59
+ this.handlers.set(name, handler);
60
+ }
61
+ }
62
+ /** Get all registered tool definitions */
63
+ getTools() {
64
+ return this.tools;
65
+ }
66
+ /** Fire-and-forget usage tracking */
67
+ trackUsage(name, args, startTime, success, result, errorMessage, ctx) {
68
+ if (TRACKING_EXCLUDE.has(name))
69
+ return;
70
+ ctx.api
71
+ .post("/api/track-usage", {
72
+ projectName: ctx.projectName,
73
+ sessionId: ctx.activeSessionId,
74
+ toolName: name,
75
+ inputSummary: summarizeInput(name, args),
76
+ startTime,
77
+ resultCount: success ? countResults(result) : 0,
78
+ success,
79
+ errorMessage,
80
+ })
81
+ .catch(() => {
82
+ // Silent — tracking must never break tool execution
83
+ });
84
+ }
85
+ /** Dispatch a tool call */
86
+ async handle(name, args, ctx) {
87
+ const handler = this.handlers.get(name);
88
+ if (!handler) {
89
+ return `Unknown tool: ${name}`;
90
+ }
91
+ const startTime = Date.now();
92
+ try {
93
+ // Before: auto-enrich context
94
+ const contextPrefix = ctx.enrichmentEnabled && this.enricher
95
+ ? await this.enricher.before(name, args, ctx)
96
+ : null;
97
+ // Execute original handler
98
+ const result = await handler(args, ctx);
99
+ // After: track interaction (fire-and-forget)
100
+ if (this.enricher) {
101
+ this.enricher.after(name, args, result, ctx);
102
+ }
103
+ // Track usage (fire-and-forget)
104
+ this.trackUsage(name, args, startTime, true, result, undefined, ctx);
105
+ // Prepend context if available
106
+ return contextPrefix ? contextPrefix + "\n\n" + result : result;
107
+ }
108
+ catch (error) {
109
+ const err = error;
110
+ const errorMessage = err.message || String(error);
111
+ // Track failed usage (fire-and-forget)
112
+ this.trackUsage(name, args, startTime, false, "", errorMessage, ctx);
113
+ if (err.code === "ECONNREFUSED") {
114
+ return `Error: Cannot connect to RAG API at ${ctx.api.defaults.baseURL}. Is it running?\n` +
115
+ `Start with: cd docker && docker-compose up -d`;
116
+ }
117
+ if (err.response) {
118
+ return `API Error (${err.response.status}): ${JSON.stringify(err.response.data)}`;
119
+ }
120
+ return `Error: ${errorMessage}`;
121
+ }
122
+ }
123
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Advanced tools module - memory merging, code completion context,
3
+ * import suggestions, type context, and behavior patterns.
4
+ */
5
+ import type { ToolModule } from "../types.js";
6
+ /**
7
+ * Create the advanced tools module with project-specific descriptions.
8
+ */
9
+ export declare function createAdvancedTools(projectName: string): ToolModule;