kotadb 2.1.0 → 2.2.0-next.20260204160632
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/package.json +6 -3
- package/src/cli/args.ts +105 -0
- package/src/cli.ts +72 -4
- package/src/indexer/ast-parser.ts +43 -1
- package/src/mcp/server.ts +26 -56
- package/src/mcp/tools.ts +526 -120
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kotadb",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0-next.20260204160632",
|
|
4
4
|
"description": "Local-only code intelligence tool for CLI agents. SQLite-backed repository indexing and code search via MCP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "src/index.ts",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"@asteasolutions/zod-to-openapi": "^8.4.0",
|
|
55
55
|
"@modelcontextprotocol/sdk": "^1.25.0",
|
|
56
56
|
"@sentry/node": "^10.25.0",
|
|
57
|
-
"@typescript-eslint/parser": "
|
|
58
|
-
"@typescript-eslint/types": "
|
|
57
|
+
"@typescript-eslint/parser": "8.54.0",
|
|
58
|
+
"@typescript-eslint/types": "8.54.0",
|
|
59
59
|
"bcryptjs": "^2.4.3",
|
|
60
60
|
"chokidar": "^5.0.0",
|
|
61
61
|
"cors": "^2.8.6",
|
|
@@ -75,5 +75,8 @@
|
|
|
75
75
|
"lint-staged": "^16.2.4",
|
|
76
76
|
"supertest": "^7.1.4",
|
|
77
77
|
"typescript": "^5.9.3"
|
|
78
|
+
},
|
|
79
|
+
"overrides": {
|
|
80
|
+
"@typescript-eslint/types": "8.54.0"
|
|
78
81
|
}
|
|
79
82
|
}
|
package/src/cli/args.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI argument parsing utilities
|
|
3
|
+
*
|
|
4
|
+
* Extracted for testability. Used by main CLI entry point.
|
|
5
|
+
*
|
|
6
|
+
* @module cli/args
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Valid toolset tiers for MCP tool selection
|
|
11
|
+
* - default: 8 tools (core + sync)
|
|
12
|
+
* - core: 6 tools
|
|
13
|
+
* - memory: 14 tools (core + sync + memory)
|
|
14
|
+
* - full: 20 tools (all)
|
|
15
|
+
*/
|
|
16
|
+
export type ToolsetTier = "default" | "core" | "memory" | "full";
|
|
17
|
+
|
|
18
|
+
const VALID_TOOLSET_TIERS: ToolsetTier[] = ["default", "core", "memory", "full"];
|
|
19
|
+
|
|
20
|
+
export interface CliOptions {
|
|
21
|
+
port: number;
|
|
22
|
+
help: boolean;
|
|
23
|
+
version: boolean;
|
|
24
|
+
stdio: boolean;
|
|
25
|
+
toolset: ToolsetTier;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Type guard for valid toolset tier
|
|
30
|
+
*/
|
|
31
|
+
export function isValidToolsetTier(value: string): value is ToolsetTier {
|
|
32
|
+
return VALID_TOOLSET_TIERS.includes(value as ToolsetTier);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Parse CLI arguments into options object
|
|
37
|
+
*/
|
|
38
|
+
export function parseArgs(args: string[]): CliOptions {
|
|
39
|
+
const options: CliOptions = {
|
|
40
|
+
port: Number(process.env.PORT ?? 3000),
|
|
41
|
+
help: false,
|
|
42
|
+
version: false,
|
|
43
|
+
stdio: false,
|
|
44
|
+
toolset: "default",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
for (let i = 0; i < args.length; i++) {
|
|
48
|
+
const arg = args[i];
|
|
49
|
+
if (arg === undefined) continue;
|
|
50
|
+
|
|
51
|
+
if (arg === "--help" || arg === "-h") {
|
|
52
|
+
options.help = true;
|
|
53
|
+
} else if (arg === "--version" || arg === "-v") {
|
|
54
|
+
options.version = true;
|
|
55
|
+
} else if (arg === "--stdio") {
|
|
56
|
+
options.stdio = true;
|
|
57
|
+
} else if (arg === "--port") {
|
|
58
|
+
const portStr = args[++i];
|
|
59
|
+
if (!portStr || Number.isNaN(Number(portStr))) {
|
|
60
|
+
process.stderr.write("Error: --port requires a valid number\n");
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
options.port = Number(portStr);
|
|
64
|
+
} else if (arg.startsWith("--port=")) {
|
|
65
|
+
const portStr = arg.split("=")[1];
|
|
66
|
+
if (portStr === undefined || Number.isNaN(Number(portStr))) {
|
|
67
|
+
process.stderr.write("Error: --port requires a valid number\n");
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
options.port = Number(portStr);
|
|
71
|
+
} else if (arg === "--toolset") {
|
|
72
|
+
const tierStr = args[++i];
|
|
73
|
+
if (!tierStr) {
|
|
74
|
+
process.stderr.write("Error: --toolset requires a tier value\n");
|
|
75
|
+
process.stderr.write("Valid tiers: default, core, memory, full\n");
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
if (!isValidToolsetTier(tierStr)) {
|
|
79
|
+
process.stderr.write(`Error: Invalid toolset tier '${tierStr}'\n`);
|
|
80
|
+
process.stderr.write("Valid tiers: default, core, memory, full\n");
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
options.toolset = tierStr;
|
|
84
|
+
} else if (arg.startsWith("--toolset=")) {
|
|
85
|
+
const tierStr = arg.split("=")[1];
|
|
86
|
+
if (tierStr === undefined || tierStr === "") {
|
|
87
|
+
process.stderr.write("Error: --toolset requires a tier value\n");
|
|
88
|
+
process.stderr.write("Valid tiers: default, core, memory, full\n");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
if (!isValidToolsetTier(tierStr)) {
|
|
92
|
+
process.stderr.write(`Error: Invalid toolset tier '${tierStr}'\n`);
|
|
93
|
+
process.stderr.write("Valid tiers: default, core, memory, full\n");
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
options.toolset = tierStr;
|
|
97
|
+
} else if (arg.startsWith("-") && arg !== "-") {
|
|
98
|
+
process.stderr.write(`Unknown option: ${arg}\n`);
|
|
99
|
+
process.stderr.write("Use --help for usage information\n");
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return options;
|
|
105
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* kotadb Start the MCP server (default port 3000)
|
|
10
10
|
* kotadb --stdio Start in stdio mode (for Claude Code integration)
|
|
11
11
|
* kotadb --port 4000 Start on custom port
|
|
12
|
+
* kotadb --toolset full Select tool tier (default, core, memory, full)
|
|
12
13
|
* kotadb --version Show version
|
|
13
14
|
* kotadb --help Show help
|
|
14
15
|
* kotadb deps Query dependency information for a file
|
|
@@ -25,11 +26,23 @@ import { fileURLToPath } from "node:url";
|
|
|
25
26
|
|
|
26
27
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Valid toolset tiers for MCP tool selection
|
|
31
|
+
* - default: 8 tools (core + sync)
|
|
32
|
+
* - core: 6 tools
|
|
33
|
+
* - memory: 14 tools (core + sync + memory)
|
|
34
|
+
* - full: 20 tools (all)
|
|
35
|
+
*/
|
|
36
|
+
export type ToolsetTier = "default" | "core" | "memory" | "full";
|
|
37
|
+
|
|
38
|
+
const VALID_TOOLSET_TIERS: ToolsetTier[] = ["default", "core", "memory", "full"];
|
|
39
|
+
|
|
28
40
|
interface CliOptions {
|
|
29
41
|
port: number;
|
|
30
42
|
help: boolean;
|
|
31
43
|
version: boolean;
|
|
32
44
|
stdio: boolean;
|
|
45
|
+
toolset: ToolsetTier;
|
|
33
46
|
}
|
|
34
47
|
|
|
35
48
|
function getVersion(): string {
|
|
@@ -70,6 +83,12 @@ COMMANDS:
|
|
|
70
83
|
OPTIONS:
|
|
71
84
|
--stdio Use stdio transport (for Claude Code integration)
|
|
72
85
|
--port <number> Port to listen on (default: 3000, env: PORT)
|
|
86
|
+
--toolset <tier> Select tool tier (default: default)
|
|
87
|
+
Tiers:
|
|
88
|
+
default 8 tools (core + sync)
|
|
89
|
+
core 6 tools (search, index, deps, impact)
|
|
90
|
+
memory 14 tools (core + sync + memory layer)
|
|
91
|
+
full 20 tools (all available tools)
|
|
73
92
|
--version, -v Show version number
|
|
74
93
|
--help, -h Show this help message
|
|
75
94
|
|
|
@@ -81,6 +100,8 @@ ENVIRONMENT VARIABLES:
|
|
|
81
100
|
|
|
82
101
|
EXAMPLES:
|
|
83
102
|
kotadb --stdio Start in stdio mode (for Claude Code)
|
|
103
|
+
kotadb --stdio --toolset full Start with all tools enabled
|
|
104
|
+
kotadb --stdio --toolset core Start with minimal core tools
|
|
84
105
|
kotadb Start HTTP server on port 3000
|
|
85
106
|
kotadb --port 4000 Start HTTP server on port 4000
|
|
86
107
|
kotadb deps --file src/db/client.ts Query deps for a file (text)
|
|
@@ -99,6 +120,17 @@ MCP CONFIGURATION (stdio mode - RECOMMENDED):
|
|
|
99
120
|
}
|
|
100
121
|
}
|
|
101
122
|
|
|
123
|
+
With toolset selection:
|
|
124
|
+
|
|
125
|
+
{
|
|
126
|
+
"mcpServers": {
|
|
127
|
+
"kotadb": {
|
|
128
|
+
"command": "bunx",
|
|
129
|
+
"args": ["kotadb@next", "--stdio", "--toolset", "full"]
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
102
134
|
MCP CONFIGURATION (HTTP mode - legacy):
|
|
103
135
|
Add to your .mcp.json or Claude Code settings:
|
|
104
136
|
|
|
@@ -126,12 +158,17 @@ function printVersion(): void {
|
|
|
126
158
|
process.stdout.write(`kotadb v${version}\n`);
|
|
127
159
|
}
|
|
128
160
|
|
|
161
|
+
function isValidToolsetTier(value: string): value is ToolsetTier {
|
|
162
|
+
return VALID_TOOLSET_TIERS.includes(value as ToolsetTier);
|
|
163
|
+
}
|
|
164
|
+
|
|
129
165
|
function parseArgs(args: string[]): CliOptions {
|
|
130
166
|
const options: CliOptions = {
|
|
131
167
|
port: Number(process.env.PORT ?? 3000),
|
|
132
168
|
help: false,
|
|
133
169
|
version: false,
|
|
134
170
|
stdio: false,
|
|
171
|
+
toolset: "default",
|
|
135
172
|
};
|
|
136
173
|
|
|
137
174
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -158,6 +195,32 @@ function parseArgs(args: string[]): CliOptions {
|
|
|
158
195
|
process.exit(1);
|
|
159
196
|
}
|
|
160
197
|
options.port = Number(portStr);
|
|
198
|
+
} else if (arg === "--toolset") {
|
|
199
|
+
const tierStr = args[++i];
|
|
200
|
+
if (!tierStr) {
|
|
201
|
+
process.stderr.write("Error: --toolset requires a tier value\n");
|
|
202
|
+
process.stderr.write("Valid tiers: default, core, memory, full\n");
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
if (!isValidToolsetTier(tierStr)) {
|
|
206
|
+
process.stderr.write(`Error: Invalid toolset tier '${tierStr}'\n`);
|
|
207
|
+
process.stderr.write("Valid tiers: default, core, memory, full\n");
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
options.toolset = tierStr;
|
|
211
|
+
} else if (arg.startsWith("--toolset=")) {
|
|
212
|
+
const tierStr = arg.split("=")[1];
|
|
213
|
+
if (tierStr === undefined || tierStr === "") {
|
|
214
|
+
process.stderr.write("Error: --toolset requires a tier value\n");
|
|
215
|
+
process.stderr.write("Valid tiers: default, core, memory, full\n");
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
if (!isValidToolsetTier(tierStr)) {
|
|
219
|
+
process.stderr.write(`Error: Invalid toolset tier '${tierStr}'\n`);
|
|
220
|
+
process.stderr.write("Valid tiers: default, core, memory, full\n");
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
options.toolset = tierStr;
|
|
161
224
|
} else if (arg.startsWith("-") && arg !== "-") {
|
|
162
225
|
process.stderr.write(`Unknown option: ${arg}\n`);
|
|
163
226
|
process.stderr.write("Use --help for usage information\n");
|
|
@@ -168,7 +231,7 @@ function parseArgs(args: string[]): CliOptions {
|
|
|
168
231
|
return options;
|
|
169
232
|
}
|
|
170
233
|
|
|
171
|
-
async function runStdioMode(): Promise<void> {
|
|
234
|
+
async function runStdioMode(toolset: ToolsetTier): Promise<void> {
|
|
172
235
|
// Redirect logger to stderr in stdio mode
|
|
173
236
|
// This is CRITICAL - stdout is reserved for JSON-RPC protocol
|
|
174
237
|
const logger = createLogger({
|
|
@@ -178,11 +241,13 @@ async function runStdioMode(): Promise<void> {
|
|
|
178
241
|
|
|
179
242
|
logger.info("KotaDB MCP server starting in stdio mode", {
|
|
180
243
|
version: getVersion(),
|
|
244
|
+
toolset,
|
|
181
245
|
});
|
|
182
246
|
|
|
183
|
-
// Create MCP server with local-only context
|
|
247
|
+
// Create MCP server with local-only context and toolset
|
|
184
248
|
const context: McpServerContext = {
|
|
185
249
|
userId: "local", // Local-only mode uses fixed user ID
|
|
250
|
+
toolset,
|
|
186
251
|
};
|
|
187
252
|
const server = createMcpServer(context);
|
|
188
253
|
|
|
@@ -192,7 +257,7 @@ async function runStdioMode(): Promise<void> {
|
|
|
192
257
|
// Connect server to transport
|
|
193
258
|
await server.connect(transport);
|
|
194
259
|
|
|
195
|
-
logger.info("KotaDB MCP server connected via stdio");
|
|
260
|
+
logger.info("KotaDB MCP server connected via stdio", { toolset });
|
|
196
261
|
|
|
197
262
|
// Server lifecycle is managed by the transport
|
|
198
263
|
// Process will stay alive until stdin closes (when Claude Code terminates it)
|
|
@@ -234,7 +299,7 @@ async function main(): Promise<void> {
|
|
|
234
299
|
|
|
235
300
|
// Handle stdio mode
|
|
236
301
|
if (options.stdio) {
|
|
237
|
-
await runStdioMode();
|
|
302
|
+
await runStdioMode(options.toolset);
|
|
238
303
|
return; // runStdioMode() keeps process alive
|
|
239
304
|
}
|
|
240
305
|
|
|
@@ -247,6 +312,7 @@ async function main(): Promise<void> {
|
|
|
247
312
|
mode: envConfig.mode,
|
|
248
313
|
port: options.port,
|
|
249
314
|
localDbPath: envConfig.localDbPath,
|
|
315
|
+
toolset: options.toolset,
|
|
250
316
|
});
|
|
251
317
|
|
|
252
318
|
const app = createExpressApp();
|
|
@@ -256,6 +322,7 @@ async function main(): Promise<void> {
|
|
|
256
322
|
port: options.port,
|
|
257
323
|
mcp_endpoint: `http://localhost:${options.port}/mcp`,
|
|
258
324
|
health_endpoint: `http://localhost:${options.port}/health`,
|
|
325
|
+
toolset: options.toolset,
|
|
259
326
|
});
|
|
260
327
|
|
|
261
328
|
// Print user-friendly startup message
|
|
@@ -265,6 +332,7 @@ async function main(): Promise<void> {
|
|
|
265
332
|
process.stdout.write(` MCP Endpoint: http://localhost:${options.port}/mcp\n`);
|
|
266
333
|
process.stdout.write(` Health Check: http://localhost:${options.port}/health\n`);
|
|
267
334
|
process.stdout.write(` Database: ${envConfig.localDbPath}\n`);
|
|
335
|
+
process.stdout.write(` Toolset: ${options.toolset}\n`);
|
|
268
336
|
process.stdout.write(`\n`);
|
|
269
337
|
process.stdout.write(`Press Ctrl+C to stop\n`);
|
|
270
338
|
process.stdout.write(`\n`);
|
|
@@ -42,6 +42,24 @@ export interface ParseError {
|
|
|
42
42
|
column?: number;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Type guard to validate that an AST node is a valid Program.
|
|
47
|
+
* Provides runtime type validation to prevent version compatibility issues.
|
|
48
|
+
*
|
|
49
|
+
* @param ast - Unknown AST node to validate
|
|
50
|
+
* @returns true if ast is a valid Program node
|
|
51
|
+
*/
|
|
52
|
+
function isValidProgram(ast: unknown): ast is TSESTree.Program {
|
|
53
|
+
return (
|
|
54
|
+
ast !== null &&
|
|
55
|
+
typeof ast === "object" &&
|
|
56
|
+
"type" in ast &&
|
|
57
|
+
ast.type === "Program" &&
|
|
58
|
+
"body" in ast &&
|
|
59
|
+
Array.isArray(ast.body)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
45
63
|
/**
|
|
46
64
|
* Result of parsing a file, including partial AST recovery information.
|
|
47
65
|
*/
|
|
@@ -103,7 +121,7 @@ function createParseError(error: unknown): ParseError {
|
|
|
103
121
|
|
|
104
122
|
/**
|
|
105
123
|
* Parse a file with error-tolerant options enabled.
|
|
106
|
-
* Uses allowInvalidAST to attempt partial recovery.
|
|
124
|
+
* Uses allowInvalidAST to attempt partial recovery and validates result.
|
|
107
125
|
*/
|
|
108
126
|
function parseWithRecoveryOptions(filePath: string, content: string): TSESTree.Program | null {
|
|
109
127
|
try {
|
|
@@ -119,6 +137,18 @@ function parseWithRecoveryOptions(filePath: string, content: string): TSESTree.P
|
|
|
119
137
|
allowInvalidAST: true,
|
|
120
138
|
errorOnUnknownASTType: false,
|
|
121
139
|
});
|
|
140
|
+
|
|
141
|
+
// Validate the returned AST is actually a Program node
|
|
142
|
+
if (!isValidProgram(ast)) {
|
|
143
|
+
const astType = typeof ast === "object" && ast !== null && "type" in ast ? (ast as any).type : typeof ast;
|
|
144
|
+
logger.warn(`Parser returned invalid Program type for ${filePath}`, {
|
|
145
|
+
file_path: filePath,
|
|
146
|
+
ast_type: astType,
|
|
147
|
+
recovery: "failed_validation",
|
|
148
|
+
});
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
122
152
|
return ast;
|
|
123
153
|
} catch {
|
|
124
154
|
// Even with recovery options, parsing can still fail
|
|
@@ -159,6 +189,18 @@ export function parseFileWithRecovery(filePath: string, content: string): ParseR
|
|
|
159
189
|
tokens: true,
|
|
160
190
|
filePath,
|
|
161
191
|
});
|
|
192
|
+
|
|
193
|
+
// Validate the returned AST is actually a Program node
|
|
194
|
+
if (!isValidProgram(ast)) {
|
|
195
|
+
const astType = typeof ast === "object" && ast !== null && "type" in ast ? (ast as any).type : typeof ast;
|
|
196
|
+
logger.warn(`Parser returned invalid Program type for ${filePath}`, {
|
|
197
|
+
file_path: filePath,
|
|
198
|
+
ast_type: astType,
|
|
199
|
+
recovery: "failed_validation",
|
|
200
|
+
});
|
|
201
|
+
throw new Error(`Invalid AST type returned: expected Program, got ${astType}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
162
204
|
return {
|
|
163
205
|
ast,
|
|
164
206
|
errors: [],
|
package/src/mcp/server.ts
CHANGED
|
@@ -18,17 +18,14 @@ import {
|
|
|
18
18
|
GENERATE_TASK_CONTEXT_TOOL,
|
|
19
19
|
INDEX_REPOSITORY_TOOL,
|
|
20
20
|
LIST_RECENT_FILES_TOOL,
|
|
21
|
-
|
|
21
|
+
SEARCH_TOOL,
|
|
22
22
|
SEARCH_DEPENDENCIES_TOOL,
|
|
23
23
|
SYNC_EXPORT_TOOL,
|
|
24
24
|
SYNC_IMPORT_TOOL,
|
|
25
25
|
VALIDATE_IMPLEMENTATION_SPEC_TOOL,
|
|
26
26
|
// Memory Layer tools
|
|
27
|
-
SEARCH_DECISIONS_TOOL,
|
|
28
27
|
RECORD_DECISION_TOOL,
|
|
29
|
-
SEARCH_FAILURES_TOOL,
|
|
30
28
|
RECORD_FAILURE_TOOL,
|
|
31
|
-
SEARCH_PATTERNS_TOOL,
|
|
32
29
|
RECORD_INSIGHT_TOOL,
|
|
33
30
|
// Dynamic Expertise tools
|
|
34
31
|
GET_DOMAIN_KEY_FILES_TOOL,
|
|
@@ -40,27 +37,35 @@ import {
|
|
|
40
37
|
executeGenerateTaskContext,
|
|
41
38
|
executeIndexRepository,
|
|
42
39
|
executeListRecentFiles,
|
|
43
|
-
|
|
40
|
+
executeSearch,
|
|
44
41
|
executeSearchDependencies,
|
|
45
42
|
executeSyncExport,
|
|
46
43
|
executeSyncImport,
|
|
47
44
|
executeValidateImplementationSpec,
|
|
48
45
|
// Memory Layer execute functions
|
|
49
|
-
executeSearchDecisions,
|
|
50
46
|
executeRecordDecision,
|
|
51
|
-
executeSearchFailures,
|
|
52
47
|
executeRecordFailure,
|
|
53
|
-
executeSearchPatterns,
|
|
54
48
|
executeRecordInsight,
|
|
55
49
|
// Dynamic Expertise execute functions
|
|
56
50
|
executeGetDomainKeyFiles,
|
|
57
51
|
executeValidateExpertise,
|
|
58
52
|
executeSyncExpertise,
|
|
59
53
|
executeGetRecentPatterns,
|
|
54
|
+
// Tool filtering
|
|
55
|
+
filterToolsByTier,
|
|
60
56
|
} from "./tools";
|
|
61
57
|
|
|
62
58
|
const logger = createLogger({ module: "mcp-server" });
|
|
63
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Valid toolset tiers for MCP tool selection
|
|
62
|
+
* - default: 8 tools (core + sync)
|
|
63
|
+
* - core: 6 tools
|
|
64
|
+
* - memory: 14 tools (core + sync + memory)
|
|
65
|
+
* - full: 20 tools (all)
|
|
66
|
+
*/
|
|
67
|
+
export type ToolsetTier = "default" | "core" | "memory" | "full";
|
|
68
|
+
|
|
64
69
|
/**
|
|
65
70
|
* MCP Server context passed to tool handlers via closure
|
|
66
71
|
*
|
|
@@ -68,6 +73,7 @@ const logger = createLogger({ module: "mcp-server" });
|
|
|
68
73
|
*/
|
|
69
74
|
export interface McpServerContext {
|
|
70
75
|
userId: string;
|
|
76
|
+
toolset?: ToolsetTier;
|
|
71
77
|
}
|
|
72
78
|
|
|
73
79
|
/**
|
|
@@ -107,35 +113,20 @@ export function createMcpServer(context: McpServerContext): Server {
|
|
|
107
113
|
},
|
|
108
114
|
);
|
|
109
115
|
|
|
110
|
-
// Register tools/list handler -
|
|
116
|
+
// Register tools/list handler - filter by toolset tier
|
|
111
117
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
118
|
+
const tier = context.toolset || "default";
|
|
119
|
+
const filteredTools = filterToolsByTier(tier);
|
|
120
|
+
|
|
121
|
+
logger.debug("Listing MCP tools", {
|
|
122
|
+
tier,
|
|
123
|
+
tool_count: filteredTools.length
|
|
124
|
+
});
|
|
125
|
+
|
|
112
126
|
return {
|
|
113
|
-
tools:
|
|
114
|
-
SEARCH_CODE_TOOL,
|
|
115
|
-
INDEX_REPOSITORY_TOOL,
|
|
116
|
-
LIST_RECENT_FILES_TOOL,
|
|
117
|
-
SEARCH_DEPENDENCIES_TOOL,
|
|
118
|
-
ANALYZE_CHANGE_IMPACT_TOOL,
|
|
119
|
-
VALIDATE_IMPLEMENTATION_SPEC_TOOL,
|
|
120
|
-
SYNC_EXPORT_TOOL,
|
|
121
|
-
SYNC_IMPORT_TOOL,
|
|
122
|
-
GENERATE_TASK_CONTEXT_TOOL,
|
|
123
|
-
// Memory Layer tools
|
|
124
|
-
SEARCH_DECISIONS_TOOL,
|
|
125
|
-
RECORD_DECISION_TOOL,
|
|
126
|
-
SEARCH_FAILURES_TOOL,
|
|
127
|
-
RECORD_FAILURE_TOOL,
|
|
128
|
-
SEARCH_PATTERNS_TOOL,
|
|
129
|
-
RECORD_INSIGHT_TOOL,
|
|
130
|
-
// Dynamic Expertise tools
|
|
131
|
-
GET_DOMAIN_KEY_FILES_TOOL,
|
|
132
|
-
VALIDATE_EXPERTISE_TOOL,
|
|
133
|
-
SYNC_EXPERTISE_TOOL,
|
|
134
|
-
GET_RECENT_PATTERNS_TOOL,
|
|
135
|
-
],
|
|
127
|
+
tools: filteredTools,
|
|
136
128
|
};
|
|
137
129
|
});
|
|
138
|
-
|
|
139
130
|
// Register tools/call handler
|
|
140
131
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
141
132
|
const { name, arguments: toolArgs } = request.params;
|
|
@@ -146,8 +137,8 @@ export function createMcpServer(context: McpServerContext): Server {
|
|
|
146
137
|
|
|
147
138
|
try {
|
|
148
139
|
switch (name) {
|
|
149
|
-
case "
|
|
150
|
-
result = await
|
|
140
|
+
case "search":
|
|
141
|
+
result = await executeSearch(
|
|
151
142
|
toolArgs,
|
|
152
143
|
"", // requestId not used
|
|
153
144
|
context.userId,
|
|
@@ -202,13 +193,6 @@ export function createMcpServer(context: McpServerContext): Server {
|
|
|
202
193
|
);
|
|
203
194
|
break;
|
|
204
195
|
// Memory Layer tools
|
|
205
|
-
case "search_decisions":
|
|
206
|
-
result = await executeSearchDecisions(
|
|
207
|
-
toolArgs,
|
|
208
|
-
"", // requestId not used
|
|
209
|
-
context.userId,
|
|
210
|
-
);
|
|
211
|
-
break;
|
|
212
196
|
case "record_decision":
|
|
213
197
|
result = await executeRecordDecision(
|
|
214
198
|
toolArgs,
|
|
@@ -216,13 +200,6 @@ export function createMcpServer(context: McpServerContext): Server {
|
|
|
216
200
|
context.userId,
|
|
217
201
|
);
|
|
218
202
|
break;
|
|
219
|
-
case "search_failures":
|
|
220
|
-
result = await executeSearchFailures(
|
|
221
|
-
toolArgs,
|
|
222
|
-
"", // requestId not used
|
|
223
|
-
context.userId,
|
|
224
|
-
);
|
|
225
|
-
break;
|
|
226
203
|
case "record_failure":
|
|
227
204
|
result = await executeRecordFailure(
|
|
228
205
|
toolArgs,
|
|
@@ -230,13 +207,6 @@ export function createMcpServer(context: McpServerContext): Server {
|
|
|
230
207
|
context.userId,
|
|
231
208
|
);
|
|
232
209
|
break;
|
|
233
|
-
case "search_patterns":
|
|
234
|
-
result = await executeSearchPatterns(
|
|
235
|
-
toolArgs,
|
|
236
|
-
"", // requestId not used
|
|
237
|
-
context.userId,
|
|
238
|
-
);
|
|
239
|
-
break;
|
|
240
210
|
case "record_insight":
|
|
241
211
|
result = await executeRecordInsight(
|
|
242
212
|
toolArgs,
|
package/src/mcp/tools.ts
CHANGED
|
@@ -34,8 +34,14 @@ const logger = createLogger({ module: "mcp-tools" });
|
|
|
34
34
|
/**
|
|
35
35
|
* MCP Tool Definition
|
|
36
36
|
*/
|
|
37
|
+
/**
|
|
38
|
+
* Tool tier for categorizing tools by feature set
|
|
39
|
+
*/
|
|
40
|
+
export type ToolTier = "core" | "sync" | "memory" | "expertise";
|
|
41
|
+
|
|
37
42
|
export interface ToolDefinition {
|
|
38
43
|
name: string;
|
|
44
|
+
tier: ToolTier;
|
|
39
45
|
description: string;
|
|
40
46
|
inputSchema: {
|
|
41
47
|
type: "object";
|
|
@@ -45,38 +51,165 @@ export interface ToolDefinition {
|
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
/**
|
|
54
|
+
* Toolset tier for CLI selection (maps to tool tiers)
|
|
55
|
+
*/
|
|
56
|
+
export type ToolsetTier = "default" | "core" | "memory" | "full";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Filter tools by the requested toolset tier
|
|
60
|
+
*
|
|
61
|
+
* Tier mapping:
|
|
62
|
+
* - core: 6 tools (core tier only)
|
|
63
|
+
* - default: 8 tools (core + sync tiers)
|
|
64
|
+
* - memory: 14 tools (core + sync + memory tiers)
|
|
65
|
+
* - full: all tools
|
|
66
|
+
*
|
|
67
|
+
* @param tier - The toolset tier to filter by
|
|
68
|
+
* @param tools - Optional array of tools (defaults to all tool definitions)
|
|
69
|
+
*/
|
|
70
|
+
export function filterToolsByTier(tier: ToolsetTier, tools?: ToolDefinition[]): ToolDefinition[] {
|
|
71
|
+
const allTools = tools ?? getToolDefinitions();
|
|
72
|
+
switch (tier) {
|
|
73
|
+
case "core":
|
|
74
|
+
return allTools.filter((t) => t.tier === "core");
|
|
75
|
+
case "default":
|
|
76
|
+
return allTools.filter((t) => t.tier === "core" || t.tier === "sync");
|
|
77
|
+
case "memory":
|
|
78
|
+
return allTools.filter((t) => t.tier === "core" || t.tier === "sync" || t.tier === "memory");
|
|
79
|
+
case "full":
|
|
80
|
+
return allTools;
|
|
81
|
+
default:
|
|
82
|
+
// Default to "default" tier if unknown
|
|
83
|
+
return allTools.filter((t) => t.tier === "core" || t.tier === "sync");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
48
86
|
|
|
49
87
|
/**
|
|
50
|
-
*
|
|
88
|
+
* Alias for filterToolsByTier - get tool definitions filtered by toolset
|
|
89
|
+
*
|
|
90
|
+
* @param toolset - The toolset tier to filter by
|
|
51
91
|
*/
|
|
52
|
-
export
|
|
53
|
-
|
|
92
|
+
export function getToolsByTier(toolset: ToolsetTier): ToolDefinition[] {
|
|
93
|
+
return filterToolsByTier(toolset);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Validate if a string is a valid toolset tier
|
|
98
|
+
*/
|
|
99
|
+
export function isValidToolset(value: string): value is ToolsetTier {
|
|
100
|
+
return value === "default" || value === "core" || value === "memory" || value === "full";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// UNIFIED SEARCH TOOL - Replaces search_code, search_symbols, search_decisions, search_patterns, search_failures
|
|
105
|
+
// Issue: #143
|
|
106
|
+
// ============================================================================
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Tool: search (unified)
|
|
110
|
+
*/
|
|
111
|
+
export const SEARCH_TOOL: ToolDefinition = {
|
|
112
|
+
tier: "core",
|
|
113
|
+
name: "search",
|
|
54
114
|
description:
|
|
55
|
-
"Search indexed code
|
|
115
|
+
"Search indexed code, symbols, decisions, patterns, and failures. Supports multiple search scopes simultaneously with scope-specific filters and output formats.",
|
|
56
116
|
inputSchema: {
|
|
57
117
|
type: "object",
|
|
58
118
|
properties: {
|
|
59
|
-
|
|
119
|
+
query: {
|
|
60
120
|
type: "string",
|
|
61
|
-
description: "
|
|
121
|
+
description: "Search query term or phrase",
|
|
62
122
|
},
|
|
63
|
-
|
|
64
|
-
type: "
|
|
65
|
-
|
|
123
|
+
scope: {
|
|
124
|
+
type: "array",
|
|
125
|
+
items: {
|
|
126
|
+
type: "string",
|
|
127
|
+
enum: ["code", "symbols", "decisions", "patterns", "failures"],
|
|
128
|
+
},
|
|
129
|
+
description: "Search scopes to query (default: ['code'])",
|
|
130
|
+
},
|
|
131
|
+
filters: {
|
|
132
|
+
type: "object",
|
|
133
|
+
description: "Scope-specific filters (invalid filters ignored)",
|
|
134
|
+
properties: {
|
|
135
|
+
// Code scope filters
|
|
136
|
+
glob: {
|
|
137
|
+
type: "string",
|
|
138
|
+
description: "File path glob pattern (code scope only)",
|
|
139
|
+
},
|
|
140
|
+
exclude: {
|
|
141
|
+
type: "array",
|
|
142
|
+
items: { type: "string" },
|
|
143
|
+
description: "Exclude patterns (code scope only)",
|
|
144
|
+
},
|
|
145
|
+
language: {
|
|
146
|
+
type: "string",
|
|
147
|
+
description: "Programming language filter (code scope only)",
|
|
148
|
+
},
|
|
149
|
+
// Symbol scope filters
|
|
150
|
+
symbol_kind: {
|
|
151
|
+
type: "array",
|
|
152
|
+
items: {
|
|
153
|
+
type: "string",
|
|
154
|
+
enum: [
|
|
155
|
+
"function",
|
|
156
|
+
"class",
|
|
157
|
+
"interface",
|
|
158
|
+
"type",
|
|
159
|
+
"variable",
|
|
160
|
+
"constant",
|
|
161
|
+
"method",
|
|
162
|
+
"property",
|
|
163
|
+
"module",
|
|
164
|
+
"namespace",
|
|
165
|
+
"enum",
|
|
166
|
+
"enum_member",
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
description: "Symbol kinds to include (symbols scope only)",
|
|
170
|
+
},
|
|
171
|
+
exported_only: {
|
|
172
|
+
type: "boolean",
|
|
173
|
+
description: "Only exported symbols (symbols scope only)",
|
|
174
|
+
},
|
|
175
|
+
// Decision scope filters
|
|
176
|
+
decision_scope: {
|
|
177
|
+
type: "string",
|
|
178
|
+
enum: ["architecture", "pattern", "convention", "workaround"],
|
|
179
|
+
description: "Decision category (decisions scope only)",
|
|
180
|
+
},
|
|
181
|
+
// Pattern scope filters
|
|
182
|
+
pattern_type: {
|
|
183
|
+
type: "string",
|
|
184
|
+
description: "Pattern type filter (patterns scope only)",
|
|
185
|
+
},
|
|
186
|
+
// Common filters
|
|
187
|
+
repository: {
|
|
188
|
+
type: "string",
|
|
189
|
+
description: "Repository ID or full_name filter (all scopes)",
|
|
190
|
+
},
|
|
191
|
+
},
|
|
66
192
|
},
|
|
67
193
|
limit: {
|
|
68
194
|
type: "number",
|
|
69
|
-
description: "
|
|
195
|
+
description: "Max results per scope (default: 20, max: 100)",
|
|
196
|
+
},
|
|
197
|
+
output: {
|
|
198
|
+
type: "string",
|
|
199
|
+
enum: ["full", "paths", "compact"],
|
|
200
|
+
description: "Output format (default: 'full')",
|
|
70
201
|
},
|
|
71
202
|
},
|
|
72
|
-
required: ["
|
|
203
|
+
required: ["query"],
|
|
73
204
|
},
|
|
74
205
|
};
|
|
75
206
|
|
|
207
|
+
|
|
76
208
|
/**
|
|
77
209
|
* Tool: index_repository
|
|
78
210
|
*/
|
|
79
211
|
export const INDEX_REPOSITORY_TOOL: ToolDefinition = {
|
|
212
|
+
tier: "core",
|
|
80
213
|
name: "index_repository",
|
|
81
214
|
description:
|
|
82
215
|
"Index a git repository by cloning/updating it and extracting code files. Performs synchronous indexing and returns immediately with status 'completed' and full indexing stats.",
|
|
@@ -104,6 +237,7 @@ export const INDEX_REPOSITORY_TOOL: ToolDefinition = {
|
|
|
104
237
|
* Tool: list_recent_files
|
|
105
238
|
*/
|
|
106
239
|
export const LIST_RECENT_FILES_TOOL: ToolDefinition = {
|
|
240
|
+
tier: "core",
|
|
107
241
|
name: "list_recent_files",
|
|
108
242
|
description:
|
|
109
243
|
"List recently indexed files, ordered by indexing timestamp. Useful for seeing what code is available.",
|
|
@@ -126,6 +260,7 @@ export const LIST_RECENT_FILES_TOOL: ToolDefinition = {
|
|
|
126
260
|
* Tool: search_dependencies
|
|
127
261
|
*/
|
|
128
262
|
export const SEARCH_DEPENDENCIES_TOOL: ToolDefinition = {
|
|
263
|
+
tier: "core",
|
|
129
264
|
name: "search_dependencies",
|
|
130
265
|
description:
|
|
131
266
|
"Search the dependency graph to find files that depend on (dependents) or are depended on by (dependencies) a target file. Useful for impact analysis before refactoring, test scope discovery, and circular dependency detection.",
|
|
@@ -174,6 +309,7 @@ export const SEARCH_DEPENDENCIES_TOOL: ToolDefinition = {
|
|
|
174
309
|
* Tool: analyze_change_impact
|
|
175
310
|
*/
|
|
176
311
|
export const ANALYZE_CHANGE_IMPACT_TOOL: ToolDefinition = {
|
|
312
|
+
tier: "core",
|
|
177
313
|
name: "analyze_change_impact",
|
|
178
314
|
description:
|
|
179
315
|
"Analyze the impact of proposed code changes by examining dependency graphs, test scope, and potential conflicts. Returns comprehensive analysis including affected files, test recommendations, architectural warnings, and risk assessment. Useful for planning implementations and avoiding breaking changes.",
|
|
@@ -221,6 +357,7 @@ export const ANALYZE_CHANGE_IMPACT_TOOL: ToolDefinition = {
|
|
|
221
357
|
* Tool: validate_implementation_spec
|
|
222
358
|
*/
|
|
223
359
|
export const VALIDATE_IMPLEMENTATION_SPEC_TOOL: ToolDefinition = {
|
|
360
|
+
tier: "expertise",
|
|
224
361
|
name: "validate_implementation_spec",
|
|
225
362
|
description:
|
|
226
363
|
"Validate an implementation specification against KotaDB conventions and repository state. Checks for file conflicts, naming conventions, path alias usage, test coverage, and dependency compatibility. Returns validation errors, warnings, and approval conditions checklist.",
|
|
@@ -303,6 +440,7 @@ export const VALIDATE_IMPLEMENTATION_SPEC_TOOL: ToolDefinition = {
|
|
|
303
440
|
* Tool: kota_sync_export
|
|
304
441
|
*/
|
|
305
442
|
export const SYNC_EXPORT_TOOL: ToolDefinition = {
|
|
443
|
+
tier: "sync",
|
|
306
444
|
name: "kota_sync_export",
|
|
307
445
|
description:
|
|
308
446
|
"Export local SQLite database to JSONL files for git sync. Uses hash-based change detection to skip unchanged tables. Exports to .kotadb/export/ by default.",
|
|
@@ -325,6 +463,7 @@ export const SYNC_EXPORT_TOOL: ToolDefinition = {
|
|
|
325
463
|
* Tool: kota_sync_import
|
|
326
464
|
*/
|
|
327
465
|
export const SYNC_IMPORT_TOOL: ToolDefinition = {
|
|
466
|
+
tier: "sync",
|
|
328
467
|
name: "kota_sync_import",
|
|
329
468
|
description:
|
|
330
469
|
"Import JSONL files into local SQLite database. Applies deletion manifest first, then imports all tables transactionally. Typically run after git pull to sync remote changes.",
|
|
@@ -347,6 +486,7 @@ export const SYNC_IMPORT_TOOL: ToolDefinition = {
|
|
|
347
486
|
* Target: <100ms response time
|
|
348
487
|
*/
|
|
349
488
|
export const GENERATE_TASK_CONTEXT_TOOL: ToolDefinition = {
|
|
489
|
+
tier: "core",
|
|
350
490
|
name: "generate_task_context",
|
|
351
491
|
description:
|
|
352
492
|
"Generate structured context for a set of files including dependency counts, impacted files, test files, and recent changes. Designed for hook-based context injection with <100ms performance target.",
|
|
@@ -383,42 +523,11 @@ export const GENERATE_TASK_CONTEXT_TOOL: ToolDefinition = {
|
|
|
383
523
|
// Memory Layer Tool Definitions
|
|
384
524
|
// ============================================================================
|
|
385
525
|
|
|
386
|
-
/**
|
|
387
|
-
* Tool: search_decisions
|
|
388
|
-
*/
|
|
389
|
-
export const SEARCH_DECISIONS_TOOL: ToolDefinition = {
|
|
390
|
-
name: "search_decisions",
|
|
391
|
-
description:
|
|
392
|
-
"Search past architectural decisions using FTS5. Returns decisions with relevance scores.",
|
|
393
|
-
inputSchema: {
|
|
394
|
-
type: "object",
|
|
395
|
-
properties: {
|
|
396
|
-
query: {
|
|
397
|
-
type: "string",
|
|
398
|
-
description: "Search query for decisions",
|
|
399
|
-
},
|
|
400
|
-
scope: {
|
|
401
|
-
type: "string",
|
|
402
|
-
enum: ["architecture", "pattern", "convention", "workaround"],
|
|
403
|
-
description: "Optional: Filter by decision scope",
|
|
404
|
-
},
|
|
405
|
-
repository: {
|
|
406
|
-
type: "string",
|
|
407
|
-
description: "Optional: Filter to a specific repository ID or full_name",
|
|
408
|
-
},
|
|
409
|
-
limit: {
|
|
410
|
-
type: "number",
|
|
411
|
-
description: "Optional: Max results (default: 20)",
|
|
412
|
-
},
|
|
413
|
-
},
|
|
414
|
-
required: ["query"],
|
|
415
|
-
},
|
|
416
|
-
};
|
|
417
|
-
|
|
418
526
|
/**
|
|
419
527
|
* Tool: record_decision
|
|
420
528
|
*/
|
|
421
529
|
export const RECORD_DECISION_TOOL: ToolDefinition = {
|
|
530
|
+
tier: "memory",
|
|
422
531
|
name: "record_decision",
|
|
423
532
|
description:
|
|
424
533
|
"Record a new architectural decision for future reference. Decisions are searchable via search_decisions.",
|
|
@@ -465,37 +574,11 @@ export const RECORD_DECISION_TOOL: ToolDefinition = {
|
|
|
465
574
|
},
|
|
466
575
|
};
|
|
467
576
|
|
|
468
|
-
/**
|
|
469
|
-
* Tool: search_failures
|
|
470
|
-
*/
|
|
471
|
-
export const SEARCH_FAILURES_TOOL: ToolDefinition = {
|
|
472
|
-
name: "search_failures",
|
|
473
|
-
description:
|
|
474
|
-
"Search failed approaches to avoid repeating mistakes. Returns failures with relevance scores.",
|
|
475
|
-
inputSchema: {
|
|
476
|
-
type: "object",
|
|
477
|
-
properties: {
|
|
478
|
-
query: {
|
|
479
|
-
type: "string",
|
|
480
|
-
description: "Search query for failures",
|
|
481
|
-
},
|
|
482
|
-
repository: {
|
|
483
|
-
type: "string",
|
|
484
|
-
description: "Optional: Filter to a specific repository ID or full_name",
|
|
485
|
-
},
|
|
486
|
-
limit: {
|
|
487
|
-
type: "number",
|
|
488
|
-
description: "Optional: Max results (default: 20)",
|
|
489
|
-
},
|
|
490
|
-
},
|
|
491
|
-
required: ["query"],
|
|
492
|
-
},
|
|
493
|
-
};
|
|
494
|
-
|
|
495
577
|
/**
|
|
496
578
|
* Tool: record_failure
|
|
497
579
|
*/
|
|
498
580
|
export const RECORD_FAILURE_TOOL: ToolDefinition = {
|
|
581
|
+
tier: "memory",
|
|
499
582
|
name: "record_failure",
|
|
500
583
|
description:
|
|
501
584
|
"Record a failed approach for future reference. Helps agents avoid repeating mistakes.",
|
|
@@ -532,44 +615,11 @@ export const RECORD_FAILURE_TOOL: ToolDefinition = {
|
|
|
532
615
|
},
|
|
533
616
|
};
|
|
534
617
|
|
|
535
|
-
/**
|
|
536
|
-
* Tool: search_patterns
|
|
537
|
-
*/
|
|
538
|
-
export const SEARCH_PATTERNS_TOOL: ToolDefinition = {
|
|
539
|
-
name: "search_patterns",
|
|
540
|
-
description:
|
|
541
|
-
"Find codebase patterns by type or file. Returns discovered patterns for consistency.",
|
|
542
|
-
inputSchema: {
|
|
543
|
-
type: "object",
|
|
544
|
-
properties: {
|
|
545
|
-
query: {
|
|
546
|
-
type: "string",
|
|
547
|
-
description: "Optional: Search query for pattern name/description",
|
|
548
|
-
},
|
|
549
|
-
pattern_type: {
|
|
550
|
-
type: "string",
|
|
551
|
-
description: "Optional: Filter by pattern type (e.g., error-handling, api-call)",
|
|
552
|
-
},
|
|
553
|
-
file: {
|
|
554
|
-
type: "string",
|
|
555
|
-
description: "Optional: Filter by file path",
|
|
556
|
-
},
|
|
557
|
-
repository: {
|
|
558
|
-
type: "string",
|
|
559
|
-
description: "Optional: Filter to a specific repository ID or full_name",
|
|
560
|
-
},
|
|
561
|
-
limit: {
|
|
562
|
-
type: "number",
|
|
563
|
-
description: "Optional: Max results (default: 20)",
|
|
564
|
-
},
|
|
565
|
-
},
|
|
566
|
-
},
|
|
567
|
-
};
|
|
568
|
-
|
|
569
618
|
/**
|
|
570
619
|
* Tool: record_insight
|
|
571
620
|
*/
|
|
572
621
|
export const RECORD_INSIGHT_TOOL: ToolDefinition = {
|
|
622
|
+
tier: "memory",
|
|
573
623
|
name: "record_insight",
|
|
574
624
|
description:
|
|
575
625
|
"Store a session insight for future agents. Insights are discoveries, failures, or workarounds.",
|
|
@@ -611,6 +661,7 @@ export const RECORD_INSIGHT_TOOL: ToolDefinition = {
|
|
|
611
661
|
* Tool: get_domain_key_files
|
|
612
662
|
*/
|
|
613
663
|
export const GET_DOMAIN_KEY_FILES_TOOL: ToolDefinition = {
|
|
664
|
+
tier: "expertise",
|
|
614
665
|
name: "get_domain_key_files",
|
|
615
666
|
description:
|
|
616
667
|
"Get the most-depended-on files for a domain. Key files are core infrastructure that many other files depend on.",
|
|
@@ -638,6 +689,7 @@ export const GET_DOMAIN_KEY_FILES_TOOL: ToolDefinition = {
|
|
|
638
689
|
* Tool: validate_expertise
|
|
639
690
|
*/
|
|
640
691
|
export const VALIDATE_EXPERTISE_TOOL: ToolDefinition = {
|
|
692
|
+
tier: "expertise",
|
|
641
693
|
name: "validate_expertise",
|
|
642
694
|
description:
|
|
643
695
|
"Validate that key_files defined in expertise.yaml exist in the indexed codebase. Checks for stale or missing file references.",
|
|
@@ -657,6 +709,7 @@ export const VALIDATE_EXPERTISE_TOOL: ToolDefinition = {
|
|
|
657
709
|
* Tool: sync_expertise
|
|
658
710
|
*/
|
|
659
711
|
export const SYNC_EXPERTISE_TOOL: ToolDefinition = {
|
|
712
|
+
tier: "expertise",
|
|
660
713
|
name: "sync_expertise",
|
|
661
714
|
description:
|
|
662
715
|
"Sync patterns from expertise.yaml files to the patterns table. Extracts pattern definitions and stores them for future reference.",
|
|
@@ -679,6 +732,7 @@ export const SYNC_EXPERTISE_TOOL: ToolDefinition = {
|
|
|
679
732
|
* Tool: get_recent_patterns
|
|
680
733
|
*/
|
|
681
734
|
export const GET_RECENT_PATTERNS_TOOL: ToolDefinition = {
|
|
735
|
+
tier: "expertise",
|
|
682
736
|
name: "get_recent_patterns",
|
|
683
737
|
description:
|
|
684
738
|
"Get recently observed patterns from the patterns table. Useful for understanding codebase conventions.",
|
|
@@ -711,7 +765,7 @@ export const GET_RECENT_PATTERNS_TOOL: ToolDefinition = {
|
|
|
711
765
|
*/
|
|
712
766
|
export function getToolDefinitions(): ToolDefinition[] {
|
|
713
767
|
return [
|
|
714
|
-
|
|
768
|
+
SEARCH_TOOL,
|
|
715
769
|
INDEX_REPOSITORY_TOOL,
|
|
716
770
|
LIST_RECENT_FILES_TOOL,
|
|
717
771
|
SEARCH_DEPENDENCIES_TOOL,
|
|
@@ -721,11 +775,8 @@ export function getToolDefinitions(): ToolDefinition[] {
|
|
|
721
775
|
SYNC_IMPORT_TOOL,
|
|
722
776
|
GENERATE_TASK_CONTEXT_TOOL,
|
|
723
777
|
// Memory Layer tools
|
|
724
|
-
SEARCH_DECISIONS_TOOL,
|
|
725
778
|
RECORD_DECISION_TOOL,
|
|
726
|
-
SEARCH_FAILURES_TOOL,
|
|
727
779
|
RECORD_FAILURE_TOOL,
|
|
728
|
-
SEARCH_PATTERNS_TOOL,
|
|
729
780
|
RECORD_INSIGHT_TOOL,
|
|
730
781
|
// Dynamic Expertise tools
|
|
731
782
|
GET_DOMAIN_KEY_FILES_TOOL,
|
|
@@ -734,7 +785,6 @@ export function getToolDefinitions(): ToolDefinition[] {
|
|
|
734
785
|
GET_RECENT_PATTERNS_TOOL,
|
|
735
786
|
];
|
|
736
787
|
}
|
|
737
|
-
|
|
738
788
|
/**
|
|
739
789
|
|
|
740
790
|
/**
|
|
@@ -749,7 +799,361 @@ function isListRecentParams(params: unknown): params is { limit?: number; reposi
|
|
|
749
799
|
return true;
|
|
750
800
|
}
|
|
751
801
|
|
|
802
|
+
// ============================================================================
|
|
803
|
+
// UNIFIED SEARCH - Helper Functions and Types
|
|
804
|
+
// ============================================================================
|
|
805
|
+
|
|
806
|
+
interface NormalizedFilters {
|
|
807
|
+
// Common
|
|
808
|
+
repositoryId?: string;
|
|
809
|
+
// Code
|
|
810
|
+
glob?: string;
|
|
811
|
+
exclude?: string[];
|
|
812
|
+
language?: string;
|
|
813
|
+
// Symbols
|
|
814
|
+
symbol_kind?: string[];
|
|
815
|
+
exported_only?: boolean;
|
|
816
|
+
// Decisions
|
|
817
|
+
decision_scope?: string;
|
|
818
|
+
// Patterns
|
|
819
|
+
pattern_type?: string;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function normalizeFilters(filters: unknown): NormalizedFilters {
|
|
823
|
+
if (!filters || typeof filters !== "object") {
|
|
824
|
+
return {};
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const f = filters as Record<string, unknown>;
|
|
828
|
+
const normalized: NormalizedFilters = {};
|
|
829
|
+
|
|
830
|
+
// Resolve repository (UUID or full_name)
|
|
831
|
+
if (f.repository && typeof f.repository === "string") {
|
|
832
|
+
const resolved = resolveRepositoryIdentifierWithError(f.repository);
|
|
833
|
+
if (!("error" in resolved)) {
|
|
834
|
+
normalized.repositoryId = resolved.id;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// Extract typed filters (silently ignore invalid)
|
|
839
|
+
if (f.glob && typeof f.glob === "string") {
|
|
840
|
+
normalized.glob = f.glob;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
if (Array.isArray(f.exclude)) {
|
|
844
|
+
normalized.exclude = f.exclude.filter(e => typeof e === "string");
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (f.language && typeof f.language === "string") {
|
|
848
|
+
normalized.language = f.language;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
if (Array.isArray(f.symbol_kind)) {
|
|
852
|
+
normalized.symbol_kind = f.symbol_kind.filter(k => typeof k === "string");
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if (typeof f.exported_only === "boolean") {
|
|
856
|
+
normalized.exported_only = f.exported_only;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
if (f.decision_scope && typeof f.decision_scope === "string") {
|
|
860
|
+
normalized.decision_scope = f.decision_scope;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (f.pattern_type && typeof f.pattern_type === "string") {
|
|
864
|
+
normalized.pattern_type = f.pattern_type;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return normalized;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
interface SymbolResult {
|
|
871
|
+
id: string;
|
|
872
|
+
name: string;
|
|
873
|
+
kind: string;
|
|
874
|
+
signature: string | null;
|
|
875
|
+
documentation: string | null;
|
|
876
|
+
location: {
|
|
877
|
+
file: string;
|
|
878
|
+
line_start: number;
|
|
879
|
+
line_end: number;
|
|
880
|
+
};
|
|
881
|
+
repository_id: string;
|
|
882
|
+
is_exported: boolean;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
async function searchSymbols(
|
|
886
|
+
query: string,
|
|
887
|
+
filters: NormalizedFilters,
|
|
888
|
+
limit: number
|
|
889
|
+
): Promise<SymbolResult[]> {
|
|
890
|
+
const db = getGlobalDatabase();
|
|
891
|
+
|
|
892
|
+
let sql = `
|
|
893
|
+
SELECT
|
|
894
|
+
s.id,
|
|
895
|
+
s.name,
|
|
896
|
+
s.kind,
|
|
897
|
+
s.signature,
|
|
898
|
+
s.documentation,
|
|
899
|
+
s.line_start,
|
|
900
|
+
s.line_end,
|
|
901
|
+
s.metadata,
|
|
902
|
+
f.path as file_path,
|
|
903
|
+
s.repository_id
|
|
904
|
+
FROM indexed_symbols s
|
|
905
|
+
JOIN indexed_files f ON s.file_id = f.id
|
|
906
|
+
WHERE s.name LIKE ?
|
|
907
|
+
`;
|
|
908
|
+
|
|
909
|
+
const params: (string | number)[] = [`%${query}%`];
|
|
910
|
+
|
|
911
|
+
// Apply symbol_kind filter
|
|
912
|
+
if (filters.symbol_kind && filters.symbol_kind.length > 0) {
|
|
913
|
+
const placeholders = filters.symbol_kind.map(() => "?").join(", ");
|
|
914
|
+
sql += ` AND s.kind IN (${placeholders})`;
|
|
915
|
+
params.push(...filters.symbol_kind);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Apply exported_only filter
|
|
919
|
+
if (filters.exported_only) {
|
|
920
|
+
sql += ` AND json_extract(s.metadata, '$.is_exported') = 1`;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Apply repository filter
|
|
924
|
+
if (filters.repositoryId) {
|
|
925
|
+
sql += ` AND s.repository_id = ?`;
|
|
926
|
+
params.push(filters.repositoryId);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
sql += ` ORDER BY s.name LIMIT ?`;
|
|
930
|
+
params.push(limit);
|
|
931
|
+
|
|
932
|
+
const rows = db.query<{
|
|
933
|
+
id: string;
|
|
934
|
+
name: string;
|
|
935
|
+
kind: string;
|
|
936
|
+
signature: string | null;
|
|
937
|
+
documentation: string | null;
|
|
938
|
+
line_start: number;
|
|
939
|
+
line_end: number;
|
|
940
|
+
metadata: string;
|
|
941
|
+
file_path: string;
|
|
942
|
+
repository_id: string;
|
|
943
|
+
}>(sql, params);
|
|
944
|
+
|
|
945
|
+
return rows.map(row => ({
|
|
946
|
+
id: row.id,
|
|
947
|
+
name: row.name,
|
|
948
|
+
kind: row.kind,
|
|
949
|
+
signature: row.signature,
|
|
950
|
+
documentation: row.documentation,
|
|
951
|
+
location: {
|
|
952
|
+
file: row.file_path,
|
|
953
|
+
line_start: row.line_start,
|
|
954
|
+
line_end: row.line_end,
|
|
955
|
+
},
|
|
956
|
+
repository_id: row.repository_id,
|
|
957
|
+
is_exported: JSON.parse(row.metadata || '{}').is_exported || false,
|
|
958
|
+
}));
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
function formatSearchResults(
|
|
962
|
+
query: string,
|
|
963
|
+
scopes: string[],
|
|
964
|
+
scopeResults: Record<string, unknown[]>,
|
|
965
|
+
format: string
|
|
966
|
+
): Record<string, unknown> {
|
|
967
|
+
const response: Record<string, unknown> = {
|
|
968
|
+
query,
|
|
969
|
+
scopes,
|
|
970
|
+
results: {} as Record<string, unknown>,
|
|
971
|
+
counts: { total: 0 } as Record<string, unknown>,
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
for (const scope of scopes) {
|
|
975
|
+
const items = scopeResults[scope] || [];
|
|
976
|
+
|
|
977
|
+
if (format === "paths") {
|
|
978
|
+
// Extract file paths only
|
|
979
|
+
(response.results as Record<string, unknown>)[scope] = items.map((item: any) => {
|
|
980
|
+
if (item.path) return item.path;
|
|
981
|
+
if (item.file_path) return item.file_path;
|
|
982
|
+
if (item.location?.file) return item.location.file;
|
|
983
|
+
return "unknown";
|
|
984
|
+
});
|
|
985
|
+
} else if (format === "compact") {
|
|
986
|
+
// Summary info only
|
|
987
|
+
(response.results as Record<string, unknown>)[scope] = items.map((item: any) => {
|
|
988
|
+
if (scope === "code") {
|
|
989
|
+
return { path: item.path, match_count: 1 };
|
|
990
|
+
} else if (scope === "symbols") {
|
|
991
|
+
return { name: item.name, kind: item.kind, file: item.location.file };
|
|
992
|
+
} else if (scope === "decisions") {
|
|
993
|
+
return { title: item.title, scope: item.scope };
|
|
994
|
+
} else if (scope === "patterns") {
|
|
995
|
+
return { pattern_type: item.pattern_type, file_path: item.file_path };
|
|
996
|
+
} else if (scope === "failures") {
|
|
997
|
+
return { title: item.title, problem: item.problem };
|
|
998
|
+
}
|
|
999
|
+
return item;
|
|
1000
|
+
});
|
|
1001
|
+
} else {
|
|
1002
|
+
// Full details
|
|
1003
|
+
(response.results as Record<string, unknown>)[scope] = items;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
(response.counts as Record<string, unknown>)[scope] = items.length;
|
|
1007
|
+
(response.counts as Record<string, unknown>).total = ((response.counts as Record<string, unknown>).total as number) + items.length;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
return response;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// ============================================================================
|
|
1014
|
+
// UNIFIED SEARCH - Execute Function
|
|
1015
|
+
// ============================================================================
|
|
1016
|
+
|
|
752
1017
|
/**
|
|
1018
|
+
* Execute search tool (unified search across multiple scopes)
|
|
1019
|
+
*/
|
|
1020
|
+
export async function executeSearch(
|
|
1021
|
+
params: unknown,
|
|
1022
|
+
requestId: string | number,
|
|
1023
|
+
userId: string,
|
|
1024
|
+
): Promise<unknown> {
|
|
1025
|
+
// Validate params structure
|
|
1026
|
+
if (typeof params !== "object" || params === null) {
|
|
1027
|
+
throw new Error("Parameters must be an object");
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
const p = params as Record<string, unknown>;
|
|
1031
|
+
|
|
1032
|
+
// Check required parameter: query
|
|
1033
|
+
if (p.query === undefined) {
|
|
1034
|
+
throw new Error("Missing required parameter: query");
|
|
1035
|
+
}
|
|
1036
|
+
if (typeof p.query !== "string") {
|
|
1037
|
+
throw new Error("Parameter 'query' must be a string");
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Validate optional parameters
|
|
1041
|
+
let scopes: string[] = ["code"]; // Default scope
|
|
1042
|
+
if (p.scope !== undefined) {
|
|
1043
|
+
if (!Array.isArray(p.scope)) {
|
|
1044
|
+
throw new Error("Parameter 'scope' must be an array");
|
|
1045
|
+
}
|
|
1046
|
+
const validScopes = ["code", "symbols", "decisions", "patterns", "failures"];
|
|
1047
|
+
for (const s of p.scope) {
|
|
1048
|
+
if (typeof s !== "string" || !validScopes.includes(s)) {
|
|
1049
|
+
throw new Error(`Invalid scope: ${s}. Must be one of: ${validScopes.join(", ")}`);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
scopes = p.scope as string[];
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
if (p.limit !== undefined && typeof p.limit !== "number") {
|
|
1056
|
+
throw new Error("Parameter 'limit' must be a number");
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (p.output !== undefined) {
|
|
1060
|
+
if (typeof p.output !== "string" || !["full", "paths", "compact"].includes(p.output)) {
|
|
1061
|
+
throw new Error("Parameter 'output' must be one of: full, paths, compact");
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
const limit = Math.min(Math.max((p.limit as number) || 20, 1), 100);
|
|
1066
|
+
const output = (p.output as string) || "full";
|
|
1067
|
+
const filters = normalizeFilters(p.filters);
|
|
1068
|
+
|
|
1069
|
+
// Route to scope handlers in parallel
|
|
1070
|
+
const results: Record<string, unknown[]> = {};
|
|
1071
|
+
const searchPromises: Promise<void>[] = [];
|
|
1072
|
+
|
|
1073
|
+
if (scopes.includes("code")) {
|
|
1074
|
+
searchPromises.push(
|
|
1075
|
+
(async () => {
|
|
1076
|
+
// Reuse existing searchFiles logic
|
|
1077
|
+
const codeResults = searchFiles(p.query as string, {
|
|
1078
|
+
repositoryId: filters.repositoryId,
|
|
1079
|
+
limit,
|
|
1080
|
+
});
|
|
1081
|
+
results.code = codeResults;
|
|
1082
|
+
})()
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
if (scopes.includes("symbols")) {
|
|
1087
|
+
searchPromises.push(
|
|
1088
|
+
(async () => {
|
|
1089
|
+
const symbolResults = await searchSymbols(p.query as string, filters, limit);
|
|
1090
|
+
results.symbols = symbolResults;
|
|
1091
|
+
})()
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
if (scopes.includes("decisions")) {
|
|
1096
|
+
searchPromises.push(
|
|
1097
|
+
(async () => {
|
|
1098
|
+
// Reuse existing executeSearchDecisions logic
|
|
1099
|
+
const decisionParams = {
|
|
1100
|
+
query: p.query,
|
|
1101
|
+
scope: filters.decision_scope,
|
|
1102
|
+
repository: filters.repositoryId,
|
|
1103
|
+
limit,
|
|
1104
|
+
};
|
|
1105
|
+
const decisionResults = await executeSearchDecisions(decisionParams, requestId, userId);
|
|
1106
|
+
results.decisions = (decisionResults as { results: unknown[] }).results;
|
|
1107
|
+
})()
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
if (scopes.includes("patterns")) {
|
|
1112
|
+
searchPromises.push(
|
|
1113
|
+
(async () => {
|
|
1114
|
+
// Reuse existing executeSearchPatterns logic
|
|
1115
|
+
const patternParams = {
|
|
1116
|
+
query: p.query,
|
|
1117
|
+
pattern_type: filters.pattern_type,
|
|
1118
|
+
repository: filters.repositoryId,
|
|
1119
|
+
limit,
|
|
1120
|
+
};
|
|
1121
|
+
const patternResults = await executeSearchPatterns(patternParams, requestId, userId);
|
|
1122
|
+
results.patterns = (patternResults as { results: unknown[] }).results;
|
|
1123
|
+
})()
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
if (scopes.includes("failures")) {
|
|
1128
|
+
searchPromises.push(
|
|
1129
|
+
(async () => {
|
|
1130
|
+
// Reuse existing executeSearchFailures logic
|
|
1131
|
+
const failureParams = {
|
|
1132
|
+
query: p.query,
|
|
1133
|
+
repository: filters.repositoryId,
|
|
1134
|
+
limit,
|
|
1135
|
+
};
|
|
1136
|
+
const failureResults = await executeSearchFailures(failureParams, requestId, userId);
|
|
1137
|
+
results.failures = (failureResults as { results: unknown[] }).results;
|
|
1138
|
+
})()
|
|
1139
|
+
);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
await Promise.all(searchPromises);
|
|
1143
|
+
|
|
1144
|
+
// Format output
|
|
1145
|
+
const response = formatSearchResults(p.query as string, scopes, results, output);
|
|
1146
|
+
|
|
1147
|
+
logger.info("Unified search completed", {
|
|
1148
|
+
query: p.query,
|
|
1149
|
+
scopes,
|
|
1150
|
+
total_results: (response.counts as Record<string, unknown>).total,
|
|
1151
|
+
user_id: userId,
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
return response;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
753
1157
|
/**
|
|
754
1158
|
* Execute search_code tool
|
|
755
1159
|
*
|
|
@@ -943,8 +1347,18 @@ export async function executeListRecentFiles(
|
|
|
943
1347
|
? (params.repository as string | undefined)
|
|
944
1348
|
: undefined;
|
|
945
1349
|
|
|
1350
|
+
// Resolve repository ID (supports UUID or full_name)
|
|
1351
|
+
let repositoryId = repository;
|
|
1352
|
+
if (repositoryId) {
|
|
1353
|
+
const repoResult = resolveRepositoryIdentifierWithError(repositoryId);
|
|
1354
|
+
if ("error" in repoResult) {
|
|
1355
|
+
return { results: [], message: repoResult.error };
|
|
1356
|
+
}
|
|
1357
|
+
repositoryId = repoResult.id;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
946
1360
|
// Use SQLite via listRecentFiles with optional repository filter
|
|
947
|
-
const files = listRecentFiles(limit,
|
|
1361
|
+
const files = listRecentFiles(limit, repositoryId);
|
|
948
1362
|
|
|
949
1363
|
return {
|
|
950
1364
|
results: files.map((file) => ({
|
|
@@ -956,8 +1370,6 @@ export async function executeListRecentFiles(
|
|
|
956
1370
|
};
|
|
957
1371
|
}
|
|
958
1372
|
|
|
959
|
-
/**
|
|
960
|
-
|
|
961
1373
|
/**
|
|
962
1374
|
* Execute search_dependencies tool
|
|
963
1375
|
*/
|
|
@@ -2558,8 +2970,8 @@ export async function handleToolCall(
|
|
|
2558
2970
|
userId: string,
|
|
2559
2971
|
): Promise<unknown> {
|
|
2560
2972
|
switch (toolName) {
|
|
2561
|
-
case "
|
|
2562
|
-
return await
|
|
2973
|
+
case "search":
|
|
2974
|
+
return await executeSearch(params, requestId, userId);
|
|
2563
2975
|
case "index_repository":
|
|
2564
2976
|
return await executeIndexRepository(params, requestId, userId);
|
|
2565
2977
|
case "list_recent_files":
|
|
@@ -2577,16 +2989,10 @@ export async function handleToolCall(
|
|
|
2577
2989
|
case "generate_task_context":
|
|
2578
2990
|
return await executeGenerateTaskContext(params, requestId, userId);
|
|
2579
2991
|
// Memory Layer tools
|
|
2580
|
-
case "search_decisions":
|
|
2581
|
-
return await executeSearchDecisions(params, requestId, userId);
|
|
2582
2992
|
case "record_decision":
|
|
2583
2993
|
return await executeRecordDecision(params, requestId, userId);
|
|
2584
|
-
case "search_failures":
|
|
2585
|
-
return await executeSearchFailures(params, requestId, userId);
|
|
2586
2994
|
case "record_failure":
|
|
2587
2995
|
return await executeRecordFailure(params, requestId, userId);
|
|
2588
|
-
case "search_patterns":
|
|
2589
|
-
return await executeSearchPatterns(params, requestId, userId);
|
|
2590
2996
|
case "record_insight":
|
|
2591
2997
|
return await executeRecordInsight(params, requestId, userId);
|
|
2592
2998
|
// Expertise Layer tools
|