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/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
- "Search indexed code, symbols, decisions, patterns, and failures. Supports multiple search scopes simultaneously with scope-specific filters and output formats.",
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 (default: 'full')",
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
- const output = (p.output as string) || "full";
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,