feat: add smart-explore AST-based code navigation (#1244)
* feat: add smart-file-read module for token-optimized semantic code search - Created package.json for the smart-file-read module with dependencies and scripts. - Implemented parser.ts for code structure parsing using tree-sitter, supporting multiple languages. - Developed search.ts for searching code files and symbols with grep-style and structural matching. - Added test-run.mjs for testing search and outline functionalities. - Configured TypeScript with tsconfig.json for strict type checking and module resolution. * fix: update .gitignore to include _tree-sitter and remove unused subproject * feat: add preliminary results and skill recommendation for smart-explore module * chore: remove outdated plan.md file detailing session start hook issues * feat: update Smart File Read integration plan and skill documentation for smart-explore * feat: migrate Smart File Read to web-tree-sitter WASM for cross-platform compatibility * refactor: switch to tree-sitter CLI for parsing and enhance search functionality - Updated `parser.ts` to utilize the tree-sitter CLI for AST extraction instead of native bindings, improving compatibility and performance. - Removed grammar loading logic and replaced it with a path resolution for grammar packages. - Implemented batch parsing in `parseFilesBatch` to handle multiple files in a single CLI call, enhancing search speed. - Refactored `searchCodebase` to collect files and parse them in batches, streamlining the search process. - Adjusted symbol extraction logic to accommodate the new parsing method and ensure accurate symbol matching. * feat: update Smart File Read integration plan to utilize tree-sitter CLI for improved performance and cross-platform compatibility * feat: add smart-file-read parser and search to src/services Copy validated tree-sitter CLI-based parser and search modules from smart-file-read prototype into the claude-mem source tree for MCP tool integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: register smart_search, smart_unfold, smart_outline MCP tools Add 3 tree-sitter AST-based code exploration tools to the MCP server. Direct execution (no HTTP delegation) — they call parser/search functions directly for sub-second response times. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add tree-sitter CLI deps to build system and plugin runtime Externalize tree-sitter packages in esbuild MCP server build. Add 10 grammar packages + CLI to plugin package.json for runtime install. Remove unused @chroma-core/default-embed from plugin deps. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: create smart-explore skill with 3-layer workflow docs Progressive disclosure workflow: search -> outline -> unfold. Documents all 3 MCP tools with parameters and token economics. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add comprehensive documentation for the smart-explore feature - Introduced a detailed technical reference covering the architecture, parser, search engine, and tool registration for the smart-explore feature in claude-mem. - Documented the three-layer workflow: search, outline, and unfold, along with their respective MCP tools. - Explained the parsing process using tree-sitter, including language support, query patterns, and symbol extraction. - Outlined the search module's functionality, including file discovery, batch parsing, and relevance scoring. - Provided insights into build system integration and token economics for efficient code exploration. * chore: remove experiment artifacts, prototypes, and plan files Remove A/B test docs, prototype smart-file-read directory, and implementation plans. Keep only production code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: simplify hooks configuration and remove setup script * fix: use execFileSync to prevent command injection in tree-sitter parser Replaces execSync shell string with execFileSync + argument array, eliminating shell interpretation of file paths. Also corrects file_pattern description from "Glob pattern" to "Substring filter". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,10 @@ import {
|
||||
ListToolsRequestSchema,
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
import { getWorkerPort, getWorkerHost } from '../shared/worker-utils.js';
|
||||
import { searchCodebase, formatSearchResults } from '../services/smart-file-read/search.js';
|
||||
import { parseFile, formatFoldedView, unfoldSymbol } from '../services/smart-file-read/parser.js';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
/**
|
||||
* Worker HTTP API configuration
|
||||
@@ -233,6 +237,118 @@ NEVER fetch full details without filtering first. 10x token savings.`,
|
||||
handler: async (args: any) => {
|
||||
return await callWorkerAPIPost('/api/observations/batch', args);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'smart_search',
|
||||
description: 'Search codebase for symbols, functions, classes using tree-sitter AST parsing. Returns folded structural views with token counts. Use path parameter to scope the search.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: {
|
||||
type: 'string',
|
||||
description: 'Search term — matches against symbol names, file names, and file content'
|
||||
},
|
||||
path: {
|
||||
type: 'string',
|
||||
description: 'Root directory to search (default: current working directory)'
|
||||
},
|
||||
max_results: {
|
||||
type: 'number',
|
||||
description: 'Maximum results to return (default: 20)'
|
||||
},
|
||||
file_pattern: {
|
||||
type: 'string',
|
||||
description: 'Substring filter for file paths (e.g. ".ts", "src/services")'
|
||||
}
|
||||
},
|
||||
required: ['query']
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const rootDir = resolve(args.path || process.cwd());
|
||||
const result = await searchCodebase(rootDir, args.query, {
|
||||
maxResults: args.max_results || 20,
|
||||
filePattern: args.file_pattern
|
||||
});
|
||||
const formatted = formatSearchResults(result, args.query);
|
||||
return {
|
||||
content: [{ type: 'text' as const, text: formatted }]
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'smart_unfold',
|
||||
description: 'Expand a specific symbol (function, class, method) from a file. Returns the full source code of just that symbol. Use after smart_search or smart_outline to read specific code.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
file_path: {
|
||||
type: 'string',
|
||||
description: 'Path to the source file'
|
||||
},
|
||||
symbol_name: {
|
||||
type: 'string',
|
||||
description: 'Name of the symbol to unfold (function, class, method, etc.)'
|
||||
}
|
||||
},
|
||||
required: ['file_path', 'symbol_name']
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const filePath = resolve(args.file_path);
|
||||
const content = await readFile(filePath, 'utf-8');
|
||||
const unfolded = unfoldSymbol(content, filePath, args.symbol_name);
|
||||
if (unfolded) {
|
||||
return {
|
||||
content: [{ type: 'text' as const, text: unfolded }]
|
||||
};
|
||||
}
|
||||
// Symbol not found — show available symbols
|
||||
const parsed = parseFile(content, filePath);
|
||||
if (parsed.symbols.length > 0) {
|
||||
const available = parsed.symbols.map(s => ` - ${s.name} (${s.kind})`).join('\n');
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Symbol "${args.symbol_name}" not found in ${args.file_path}.\n\nAvailable symbols:\n${available}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Could not parse ${args.file_path}. File may be unsupported or empty.`
|
||||
}]
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'smart_outline',
|
||||
description: 'Get structural outline of a file — shows all symbols (functions, classes, methods, types) with signatures but bodies folded. Much cheaper than reading the full file.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
file_path: {
|
||||
type: 'string',
|
||||
description: 'Path to the source file'
|
||||
}
|
||||
},
|
||||
required: ['file_path']
|
||||
},
|
||||
handler: async (args: any) => {
|
||||
const filePath = resolve(args.file_path);
|
||||
const content = await readFile(filePath, 'utf-8');
|
||||
const parsed = parseFile(content, filePath);
|
||||
if (parsed.symbols.length > 0) {
|
||||
return {
|
||||
content: [{ type: 'text' as const, text: formatFoldedView(parsed) }]
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{
|
||||
type: 'text' as const,
|
||||
text: `Could not parse ${args.file_path}. File may use an unsupported language or be empty.`
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user