Update code
This commit is contained in:
176
dev-assistant-mcp/dist/tools/searchCode.js
vendored
Normal file
176
dev-assistant-mcp/dist/tools/searchCode.js
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import { readdirSync, readFileSync, statSync } from "fs";
|
||||
import { join, extname, relative } from "path";
|
||||
const execAsync = promisify(exec);
|
||||
export const searchCodeTool = {
|
||||
name: "search_code",
|
||||
description: "在项目中搜索代码。支持正则表达式、文本搜索、文件名搜索。返回匹配的文件、行号和上下文。类似 grep/ripgrep。",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
project_path: {
|
||||
type: "string",
|
||||
description: "项目根目录(绝对路径)",
|
||||
},
|
||||
query: {
|
||||
type: "string",
|
||||
description: "搜索内容(支持正则表达式)",
|
||||
},
|
||||
mode: {
|
||||
type: "string",
|
||||
description: "搜索模式:content=代码内容(默认)、filename=文件名、symbol=函数/类名",
|
||||
enum: ["content", "filename", "symbol"],
|
||||
},
|
||||
includes: {
|
||||
type: "string",
|
||||
description: "文件过滤 glob(如 *.ts, *.py)",
|
||||
},
|
||||
case_sensitive: {
|
||||
type: "boolean",
|
||||
description: "是否区分大小写(默认 false)",
|
||||
},
|
||||
max_results: {
|
||||
type: "number",
|
||||
description: "最大结果数(默认 50)",
|
||||
},
|
||||
},
|
||||
required: ["project_path", "query"],
|
||||
},
|
||||
};
|
||||
const SKIP_DIRS = new Set([
|
||||
"node_modules", ".git", "__pycache__", "dist", "build", ".next",
|
||||
".nuxt", "coverage", ".cache", ".tsbuildinfo", "vendor",
|
||||
]);
|
||||
const TEXT_EXTS = new Set([
|
||||
".ts", ".tsx", ".js", ".jsx", ".py", ".java", ".go", ".rs", ".c", ".cpp", ".h",
|
||||
".css", ".scss", ".less", ".html", ".vue", ".svelte", ".json", ".yaml", ".yml",
|
||||
".toml", ".md", ".txt", ".sh", ".bat", ".ps1", ".sql", ".graphql", ".prisma",
|
||||
".env", ".gitignore", ".eslintrc", ".prettierrc",
|
||||
]);
|
||||
function searchInDir(dir, rootDir, regex, mode, includeExt, results, maxResults, depth = 0) {
|
||||
if (depth > 10 || results.length >= maxResults)
|
||||
return;
|
||||
try {
|
||||
const entries = readdirSync(dir);
|
||||
for (const entry of entries) {
|
||||
if (results.length >= maxResults)
|
||||
break;
|
||||
if (SKIP_DIRS.has(entry) || entry.startsWith("."))
|
||||
continue;
|
||||
const fullPath = join(dir, entry);
|
||||
try {
|
||||
const stat = statSync(fullPath);
|
||||
if (stat.isDirectory()) {
|
||||
searchInDir(fullPath, rootDir, regex, mode, includeExt, results, maxResults, depth + 1);
|
||||
}
|
||||
else if (stat.isFile()) {
|
||||
const ext = extname(entry).toLowerCase();
|
||||
// 文件名搜索
|
||||
if (mode === "filename") {
|
||||
if (regex.test(entry)) {
|
||||
results.push({ file: relative(rootDir, fullPath), line: 0, content: entry });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// 内容搜索 - 只搜索文本文件
|
||||
if (!TEXT_EXTS.has(ext) && ext !== "")
|
||||
continue;
|
||||
if (includeExt && !entry.endsWith(includeExt.replace("*", "")))
|
||||
continue;
|
||||
if (stat.size > 1024 * 512)
|
||||
continue; // 跳过 >512KB 的文件
|
||||
const content = readFileSync(fullPath, "utf-8");
|
||||
const lines = content.split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (results.length >= maxResults)
|
||||
break;
|
||||
if (mode === "symbol") {
|
||||
// 只匹配函数/类/接口定义
|
||||
const line = lines[i];
|
||||
if (/(?:function|class|interface|def|const|let|var|export)\s/.test(line) && regex.test(line)) {
|
||||
results.push({
|
||||
file: relative(rootDir, fullPath),
|
||||
line: i + 1,
|
||||
content: line.trim(),
|
||||
context_before: i > 0 ? lines[i - 1].trim() : undefined,
|
||||
context_after: i < lines.length - 1 ? lines[i + 1].trim() : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (regex.test(lines[i])) {
|
||||
results.push({
|
||||
file: relative(rootDir, fullPath),
|
||||
line: i + 1,
|
||||
content: lines[i].trim(),
|
||||
context_before: i > 0 ? lines[i - 1].trim() : undefined,
|
||||
context_after: i < lines.length - 1 ? lines[i + 1].trim() : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
export async function executeSearchCode(args) {
|
||||
const { project_path, query, mode = "content", includes, case_sensitive = false, max_results = 50 } = args;
|
||||
const flags = case_sensitive ? "" : "i";
|
||||
let regex;
|
||||
try {
|
||||
regex = new RegExp(query, flags);
|
||||
}
|
||||
catch {
|
||||
regex = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), flags);
|
||||
}
|
||||
const results = [];
|
||||
const includeExt = includes ? includes.replace("*", "") : null;
|
||||
searchInDir(project_path, project_path, regex, mode, includeExt, results, max_results);
|
||||
if (results.length === 0) {
|
||||
return `# 搜索结果\n\n🔍 "${query}" (${mode})\n📂 ${project_path}\n\n_未找到匹配结果_`;
|
||||
}
|
||||
// 按文件分组
|
||||
const grouped = {};
|
||||
for (const r of results) {
|
||||
(grouped[r.file] || (grouped[r.file] = [])).push(r);
|
||||
}
|
||||
const fileCount = Object.keys(grouped).length;
|
||||
const output = [
|
||||
`# 搜索结果`,
|
||||
``,
|
||||
`🔍 "${query}" | 模式: ${mode}${includes ? ` | 过滤: ${includes}` : ""}`,
|
||||
`📂 ${project_path}`,
|
||||
`📊 ${results.length} 个匹配,${fileCount} 个文件`,
|
||||
``,
|
||||
];
|
||||
for (const [file, matches] of Object.entries(grouped)) {
|
||||
output.push(`## 📄 ${file} (${matches.length} 处)`);
|
||||
for (const m of matches) {
|
||||
if (mode === "filename") {
|
||||
output.push(`- ${m.content}`);
|
||||
}
|
||||
else {
|
||||
output.push(`**行 ${m.line}:**`);
|
||||
if (m.context_before)
|
||||
output.push(`\`\`\`\n ${m.context_before}`);
|
||||
else
|
||||
output.push("```");
|
||||
output.push(`→ ${m.content}`);
|
||||
if (m.context_after)
|
||||
output.push(` ${m.context_after}\n\`\`\``);
|
||||
else
|
||||
output.push("```");
|
||||
}
|
||||
}
|
||||
output.push(``);
|
||||
}
|
||||
if (results.length >= max_results) {
|
||||
output.push(`\n⚠️ 结果已截断(最大 ${max_results} 条),可增大 max_results 或缩小搜索范围`);
|
||||
}
|
||||
return output.join("\n");
|
||||
}
|
||||
//# sourceMappingURL=searchCode.js.map
|
||||
Reference in New Issue
Block a user