(args: z.infer<typeof ExplainSymbolArgs>, ctx: ToolContext)
| 353 | argsSchema = ExplainSymbolArgs; |
| 354 | |
| 355 | async execute(args: z.infer<typeof ExplainSymbolArgs>, ctx: ToolContext): Promise<ToolResult> { |
| 356 | const db = requireDB(); |
| 357 | if ('error' in db) return { content: db.error, isError: true }; |
| 358 | |
| 359 | const rows = db.findSymbolsByName(args.name, args.kind); |
| 360 | if (rows.length === 0) { |
| 361 | const hints = db.searchSymbolsByPrefix(args.name, args.kind, 8); |
| 362 | if (hints.length > 0) { |
| 363 | const hintList = hints.map(h => ` ${h.kind} ${h.name} → ${path.relative(ctx.cwd, h.file_path)}:${h.start_line}`).join('\n'); |
| 364 | return { content: `[NOT_FOUND] No exact match for "${args.name}". Similar:\n${hintList}` }; |
| 365 | } |
| 366 | return { content: `[NOT_FOUND] No symbol named "${args.name}" in the indexed codebase.` }; |
| 367 | } |
| 368 | |
| 369 | const maxBody = args.max_body_lines ?? 60; |
| 370 | const sections: string[] = []; |
| 371 | const MAX_MATCHES = 5; |
| 372 | |
| 373 | for (const row of rows.slice(0, MAX_MATCHES)) { |
| 374 | const rel = path.relative(ctx.cwd, row.file_path); |
| 375 | let body: string[] = []; |
| 376 | let leading: string[] = []; |
| 377 | |
| 378 | try { |
| 379 | const buf = await fs.readFile(row.file_path, 'utf-8'); |
| 380 | const lines = buf.split('\n'); |
| 381 | const startIdx = Math.max(0, row.start_line - 1); |
| 382 | const endIdx = Math.min(lines.length - 1, row.end_line - 1); |
| 383 | |
| 384 | // Capture leading docstring/comment block (look up to 15 lines above for comments) |
| 385 | for (let i = startIdx - 1; i >= Math.max(0, startIdx - 15); i--) { |
| 386 | const ln = lines[i] ?? ''; |
| 387 | const trimmed = ln.trim(); |
| 388 | if (trimmed === '') { |
| 389 | if (leading.length > 0) break; // blank line ends the comment block |
| 390 | continue; |
| 391 | } |
| 392 | if ( |
| 393 | trimmed.startsWith('//') || |
| 394 | trimmed.startsWith('*') || |
| 395 | trimmed.startsWith('/*') || |
| 396 | trimmed.startsWith('#') || |
| 397 | trimmed.startsWith('"""') || |
| 398 | trimmed.endsWith('*/') || |
| 399 | trimmed.startsWith("'''") |
| 400 | ) { |
| 401 | leading.unshift(ln); |
| 402 | } else { |
| 403 | break; |
| 404 | } |
| 405 | } |
| 406 | |
| 407 | // Body, capped at maxBody lines |
| 408 | const totalLines = endIdx - startIdx + 1; |
| 409 | const sliceEnd = Math.min(endIdx, startIdx + maxBody - 1); |
| 410 | body = lines.slice(startIdx, sliceEnd + 1); |
| 411 | if (sliceEnd < endIdx) { |
| 412 | body.push(` /* ... ${totalLines - maxBody} more lines (use read_file to see the rest) */`); |
nothing calls this directly
no test coverage detected