kotadb 2.2.0-next.20260204235102 → 2.2.0-next.20260205005118
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 +1 -1
- package/src/api/queries.ts +973 -868
- package/src/mcp/tools.ts +76 -9
package/src/mcp/tools.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
resolveFilePath,
|
|
14
14
|
runIndexingWorkflow,
|
|
15
15
|
searchFiles,
|
|
16
|
+
extractLineSnippets,
|
|
16
17
|
} from "@api/queries";
|
|
17
18
|
import { getDomainKeyFiles } from "@api/expertise-queries.js";
|
|
18
19
|
import { getGlobalDatabase } from "@db/sqlite/index.js";
|
|
@@ -112,8 +113,20 @@ export function isValidToolset(value: string): value is ToolsetTier {
|
|
|
112
113
|
export const SEARCH_TOOL: ToolDefinition = {
|
|
113
114
|
tier: "core",
|
|
114
115
|
name: "search",
|
|
115
|
-
description:
|
|
116
|
-
|
|
116
|
+
description: `Search indexed code, symbols, decisions, patterns, and failures.
|
|
117
|
+
|
|
118
|
+
OUTPUT MODES:
|
|
119
|
+
- 'paths': File paths only (~100 bytes/result)
|
|
120
|
+
- 'compact': Summary info (~200 bytes/result) - DEFAULT for code scope
|
|
121
|
+
- 'snippet': Matching lines with context (~2KB/result)
|
|
122
|
+
- 'full': Complete content (~100KB/result) - Use with caution for code scope
|
|
123
|
+
|
|
124
|
+
TIPS:
|
|
125
|
+
- Use 'snippet' for code exploration (shows matches in context)
|
|
126
|
+
- Use 'compact' for quick file discovery
|
|
127
|
+
- Use 'full' only for small result sets (symbols, decisions, etc.)
|
|
128
|
+
|
|
129
|
+
Supports multiple search scopes simultaneously with scope-specific filters.`,
|
|
117
130
|
inputSchema: {
|
|
118
131
|
type: "object",
|
|
119
132
|
properties: {
|
|
@@ -197,8 +210,14 @@ export const SEARCH_TOOL: ToolDefinition = {
|
|
|
197
210
|
},
|
|
198
211
|
output: {
|
|
199
212
|
type: "string",
|
|
200
|
-
enum: ["full", "paths", "compact"],
|
|
201
|
-
description: "Output format (
|
|
213
|
+
enum: ["full", "paths", "compact", "snippet"],
|
|
214
|
+
description: "Output format: 'paths' (file paths only), 'compact' (summary), 'snippet' (matches with context), 'full' (complete content). Default varies by scope: code='compact', others='full'. WARNING: 'full' + code scope = large results.",
|
|
215
|
+
},
|
|
216
|
+
context_lines: {
|
|
217
|
+
type: "number",
|
|
218
|
+
description: "Lines of context before/after matches (snippet mode only, default: 3, max: 10)",
|
|
219
|
+
minimum: 0,
|
|
220
|
+
maximum: 10,
|
|
202
221
|
},
|
|
203
222
|
},
|
|
204
223
|
required: ["query"],
|
|
@@ -1089,7 +1108,8 @@ function formatSearchResults(
|
|
|
1089
1108
|
scopes: string[],
|
|
1090
1109
|
scopeResults: Record<string, unknown[]>,
|
|
1091
1110
|
format: string,
|
|
1092
|
-
filters: NormalizedFilters
|
|
1111
|
+
filters: NormalizedFilters,
|
|
1112
|
+
contextLines?: number
|
|
1093
1113
|
): Record<string, unknown> {
|
|
1094
1114
|
const response: Record<string, unknown> = {
|
|
1095
1115
|
query,
|
|
@@ -1125,6 +1145,36 @@ function formatSearchResults(
|
|
|
1125
1145
|
}
|
|
1126
1146
|
return item;
|
|
1127
1147
|
});
|
|
1148
|
+
} else if (format === "snippet") {
|
|
1149
|
+
// Snippet extraction with context
|
|
1150
|
+
if (scope === "code") {
|
|
1151
|
+
(response.results as Record<string, unknown>)[scope] = items.map((item: any) => {
|
|
1152
|
+
const matches = extractLineSnippets(
|
|
1153
|
+
item.content || "",
|
|
1154
|
+
query,
|
|
1155
|
+
contextLines || 3
|
|
1156
|
+
);
|
|
1157
|
+
return {
|
|
1158
|
+
path: item.path,
|
|
1159
|
+
matches: matches
|
|
1160
|
+
};
|
|
1161
|
+
});
|
|
1162
|
+
} else {
|
|
1163
|
+
// For non-code scopes, fall back to compact format
|
|
1164
|
+
// (snippets only meaningful for code files)
|
|
1165
|
+
(response.results as Record<string, unknown>)[scope] = items.map((item: any) => {
|
|
1166
|
+
if (scope === "symbols") {
|
|
1167
|
+
return { name: item.name, kind: item.kind, file: item.location.file };
|
|
1168
|
+
} else if (scope === "decisions") {
|
|
1169
|
+
return { title: item.title, scope: item.scope };
|
|
1170
|
+
} else if (scope === "patterns") {
|
|
1171
|
+
return { pattern_type: item.pattern_type, file_path: item.file_path };
|
|
1172
|
+
} else if (scope === "failures") {
|
|
1173
|
+
return { title: item.title, problem: item.problem };
|
|
1174
|
+
}
|
|
1175
|
+
return item;
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1128
1178
|
} else {
|
|
1129
1179
|
// Full details
|
|
1130
1180
|
(response.results as Record<string, unknown>)[scope] = items;
|
|
@@ -1191,13 +1241,30 @@ export async function executeSearch(
|
|
|
1191
1241
|
}
|
|
1192
1242
|
|
|
1193
1243
|
if (p.output !== undefined) {
|
|
1194
|
-
if (typeof p.output !== "string" || !["full", "paths", "compact"].includes(p.output)) {
|
|
1195
|
-
throw new Error("Parameter 'output' must be one of: full, paths, compact");
|
|
1244
|
+
if (typeof p.output !== "string" || !["full", "paths", "compact", "snippet"].includes(p.output)) {
|
|
1245
|
+
throw new Error("Parameter 'output' must be one of: full, paths, compact, snippet");
|
|
1196
1246
|
}
|
|
1197
1247
|
}
|
|
1198
1248
|
|
|
1249
|
+
if (p.context_lines !== undefined && typeof p.context_lines !== "number") {
|
|
1250
|
+
throw new Error("Parameter 'context_lines' must be a number");
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
if (p.context_lines !== undefined && (p.context_lines < 0 || p.context_lines > 10)) {
|
|
1254
|
+
throw new Error("Parameter 'context_lines' must be between 0 and 10");
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1199
1257
|
const limit = Math.min(Math.max((p.limit as number) || 20, 1), 100);
|
|
1200
|
-
|
|
1258
|
+
// Determine default output based on scopes
|
|
1259
|
+
let defaultOutput = "full";
|
|
1260
|
+
if (scopes.length === 1 && scopes[0] === "code") {
|
|
1261
|
+
defaultOutput = "compact"; // Code-only searches default to compact
|
|
1262
|
+
} else if (scopes.includes("code") && scopes.length > 1) {
|
|
1263
|
+
defaultOutput = "compact"; // Multi-scope including code defaults to compact
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
const output = (p.output as string) || defaultOutput;
|
|
1267
|
+
const contextLines = Math.min(Math.max((p.context_lines as number) || 3, 0), 10);
|
|
1201
1268
|
const filters = normalizeFilters(p.filters);
|
|
1202
1269
|
|
|
1203
1270
|
// Route to scope handlers in parallel
|
|
@@ -1276,7 +1343,7 @@ export async function executeSearch(
|
|
|
1276
1343
|
await Promise.all(searchPromises);
|
|
1277
1344
|
|
|
1278
1345
|
// Format output
|
|
1279
|
-
const response = formatSearchResults(p.query as string, scopes, results, output, filters);
|
|
1346
|
+
const response = formatSearchResults(p.query as string, scopes, results, output, filters, contextLines);
|
|
1280
1347
|
|
|
1281
1348
|
logger.info("Unified search completed", {
|
|
1282
1349
|
query: p.query,
|