196 lines
7.3 KiB
JavaScript
196 lines
7.3 KiB
JavaScript
|
|
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|||
|
|
import { join, extname } from "path";
|
|||
|
|
export const projectScanTool = {
|
|||
|
|
name: "project_scan",
|
|||
|
|
description: "扫描分析项目结构。返回项目类型、技术栈、文件结构、依赖列表、可用脚本、配置文件等全局信息,帮助快速理解项目。",
|
|||
|
|
inputSchema: {
|
|||
|
|
type: "object",
|
|||
|
|
properties: {
|
|||
|
|
project_path: {
|
|||
|
|
type: "string",
|
|||
|
|
description: "项目根目录(绝对路径)",
|
|||
|
|
},
|
|||
|
|
max_depth: {
|
|||
|
|
type: "number",
|
|||
|
|
description: "目录扫描最大深度(默认 3)",
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
required: ["project_path"],
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
function scanDir(dir, depth, maxDepth, prefix = "") {
|
|||
|
|
if (depth > maxDepth)
|
|||
|
|
return [];
|
|||
|
|
const lines = [];
|
|||
|
|
try {
|
|||
|
|
const entries = readdirSync(dir).filter((e) => !e.startsWith(".") && e !== "node_modules" && e !== "__pycache__" && e !== "dist" && e !== "build" && e !== ".git");
|
|||
|
|
for (const entry of entries.slice(0, 30)) { // 限制每层最多30项
|
|||
|
|
const fullPath = join(dir, entry);
|
|||
|
|
try {
|
|||
|
|
const stat = statSync(fullPath);
|
|||
|
|
if (stat.isDirectory()) {
|
|||
|
|
lines.push(`${prefix}📂 ${entry}/`);
|
|||
|
|
lines.push(...scanDir(fullPath, depth + 1, maxDepth, prefix + " "));
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
const size = stat.size;
|
|||
|
|
const sizeStr = size < 1024 ? `${size}B` : size < 1024 * 1024 ? `${(size / 1024).toFixed(0)}KB` : `${(size / 1024 / 1024).toFixed(1)}MB`;
|
|||
|
|
lines.push(`${prefix}📄 ${entry} (${sizeStr})`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch { }
|
|||
|
|
}
|
|||
|
|
if (entries.length > 30) {
|
|||
|
|
lines.push(`${prefix}... 还有 ${entries.length - 30} 个条目`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch { }
|
|||
|
|
return lines;
|
|||
|
|
}
|
|||
|
|
function countFiles(dir, ext, depth = 0) {
|
|||
|
|
if (depth > 5)
|
|||
|
|
return 0;
|
|||
|
|
let count = 0;
|
|||
|
|
try {
|
|||
|
|
for (const entry of readdirSync(dir)) {
|
|||
|
|
if (entry.startsWith(".") || entry === "node_modules" || entry === "__pycache__" || entry === "dist")
|
|||
|
|
continue;
|
|||
|
|
const fullPath = join(dir, entry);
|
|||
|
|
try {
|
|||
|
|
const stat = statSync(fullPath);
|
|||
|
|
if (stat.isDirectory())
|
|||
|
|
count += countFiles(fullPath, ext, depth + 1);
|
|||
|
|
else if (extname(entry) === ext)
|
|||
|
|
count++;
|
|||
|
|
}
|
|||
|
|
catch { }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch { }
|
|||
|
|
return count;
|
|||
|
|
}
|
|||
|
|
export async function executeProjectScan(args) {
|
|||
|
|
const { project_path, max_depth = 3 } = args;
|
|||
|
|
const hasFile = (name) => existsSync(join(project_path, name));
|
|||
|
|
const readJson = (name) => {
|
|||
|
|
try {
|
|||
|
|
return JSON.parse(readFileSync(join(project_path, name), "utf-8"));
|
|||
|
|
}
|
|||
|
|
catch {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
const output = [
|
|||
|
|
`# 项目分析报告`,
|
|||
|
|
``,
|
|||
|
|
`📂 路径: ${project_path}`,
|
|||
|
|
``,
|
|||
|
|
];
|
|||
|
|
// 项目类型和技术栈检测
|
|||
|
|
const techStack = [];
|
|||
|
|
const configs = [];
|
|||
|
|
if (hasFile("package.json"))
|
|||
|
|
techStack.push("Node.js");
|
|||
|
|
if (hasFile("tsconfig.json"))
|
|||
|
|
techStack.push("TypeScript");
|
|||
|
|
if (hasFile("next.config.js") || hasFile("next.config.mjs") || hasFile("next.config.ts"))
|
|||
|
|
techStack.push("Next.js");
|
|||
|
|
if (hasFile("vite.config.ts") || hasFile("vite.config.js"))
|
|||
|
|
techStack.push("Vite");
|
|||
|
|
if (hasFile("webpack.config.js"))
|
|||
|
|
techStack.push("Webpack");
|
|||
|
|
if (hasFile("requirements.txt") || hasFile("pyproject.toml"))
|
|||
|
|
techStack.push("Python");
|
|||
|
|
if (hasFile("Cargo.toml"))
|
|||
|
|
techStack.push("Rust");
|
|||
|
|
if (hasFile("go.mod"))
|
|||
|
|
techStack.push("Go");
|
|||
|
|
if (hasFile("pom.xml") || hasFile("build.gradle"))
|
|||
|
|
techStack.push("Java");
|
|||
|
|
if (hasFile("docker-compose.yml") || hasFile("Dockerfile"))
|
|||
|
|
techStack.push("Docker");
|
|||
|
|
if (hasFile(".eslintrc.js") || hasFile("eslint.config.js"))
|
|||
|
|
configs.push("ESLint");
|
|||
|
|
if (hasFile(".prettierrc") || hasFile(".prettierrc.json"))
|
|||
|
|
configs.push("Prettier");
|
|||
|
|
if (hasFile("jest.config.js") || hasFile("jest.config.ts"))
|
|||
|
|
configs.push("Jest");
|
|||
|
|
if (hasFile("vitest.config.ts"))
|
|||
|
|
configs.push("Vitest");
|
|||
|
|
if (hasFile(".env") || hasFile(".env.example"))
|
|||
|
|
configs.push(".env");
|
|||
|
|
output.push(`## 技术栈`, techStack.length > 0 ? techStack.join(" + ") : "未检测到", ``);
|
|||
|
|
if (configs.length > 0) {
|
|||
|
|
output.push(`## 工具配置`, configs.join(", "), ``);
|
|||
|
|
}
|
|||
|
|
// package.json 分析
|
|||
|
|
const pkg = readJson("package.json");
|
|||
|
|
if (pkg) {
|
|||
|
|
output.push(`## package.json`);
|
|||
|
|
if (pkg.name)
|
|||
|
|
output.push(`- **名称**: ${pkg.name}`);
|
|||
|
|
if (pkg.version)
|
|||
|
|
output.push(`- **版本**: ${pkg.version}`);
|
|||
|
|
if (pkg.description)
|
|||
|
|
output.push(`- **描述**: ${pkg.description}`);
|
|||
|
|
if (pkg.scripts && Object.keys(pkg.scripts).length > 0) {
|
|||
|
|
output.push(``, `### 可用脚本`);
|
|||
|
|
for (const [name, cmd] of Object.entries(pkg.scripts)) {
|
|||
|
|
output.push(`- \`npm run ${name}\` → ${cmd}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (pkg.dependencies) {
|
|||
|
|
const deps = Object.entries(pkg.dependencies);
|
|||
|
|
output.push(``, `### 依赖 (${deps.length})`);
|
|||
|
|
for (const [name, ver] of deps.slice(0, 20)) {
|
|||
|
|
output.push(`- ${name}: ${ver}`);
|
|||
|
|
}
|
|||
|
|
if (deps.length > 20)
|
|||
|
|
output.push(`... 还有 ${deps.length - 20} 个`);
|
|||
|
|
}
|
|||
|
|
if (pkg.devDependencies) {
|
|||
|
|
const devDeps = Object.entries(pkg.devDependencies);
|
|||
|
|
output.push(``, `### 开发依赖 (${devDeps.length})`);
|
|||
|
|
for (const [name, ver] of devDeps.slice(0, 15)) {
|
|||
|
|
output.push(`- ${name}: ${ver}`);
|
|||
|
|
}
|
|||
|
|
if (devDeps.length > 15)
|
|||
|
|
output.push(`... 还有 ${devDeps.length - 15} 个`);
|
|||
|
|
}
|
|||
|
|
output.push(``);
|
|||
|
|
}
|
|||
|
|
// Python 依赖
|
|||
|
|
if (hasFile("requirements.txt")) {
|
|||
|
|
try {
|
|||
|
|
const reqs = readFileSync(join(project_path, "requirements.txt"), "utf-8")
|
|||
|
|
.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|||
|
|
output.push(`## Python 依赖 (${reqs.length})`);
|
|||
|
|
for (const r of reqs.slice(0, 20))
|
|||
|
|
output.push(`- ${r.trim()}`);
|
|||
|
|
if (reqs.length > 20)
|
|||
|
|
output.push(`... 还有 ${reqs.length - 20} 个`);
|
|||
|
|
output.push(``);
|
|||
|
|
}
|
|||
|
|
catch { }
|
|||
|
|
}
|
|||
|
|
// 文件统计
|
|||
|
|
const fileCounts = {};
|
|||
|
|
for (const ext of [".ts", ".tsx", ".js", ".jsx", ".py", ".css", ".html", ".json", ".md"]) {
|
|||
|
|
const count = countFiles(project_path, ext);
|
|||
|
|
if (count > 0)
|
|||
|
|
fileCounts[ext] = count;
|
|||
|
|
}
|
|||
|
|
if (Object.keys(fileCounts).length > 0) {
|
|||
|
|
output.push(`## 文件统计`);
|
|||
|
|
for (const [ext, count] of Object.entries(fileCounts).sort((a, b) => b[1] - a[1])) {
|
|||
|
|
output.push(`- ${ext}: ${count} 个文件`);
|
|||
|
|
}
|
|||
|
|
output.push(``);
|
|||
|
|
}
|
|||
|
|
// 目录树
|
|||
|
|
output.push(`## 目录结构`);
|
|||
|
|
const tree = scanDir(project_path, 0, max_depth);
|
|||
|
|
output.push(...tree);
|
|||
|
|
return output.join("\n");
|
|||
|
|
}
|
|||
|
|
//# sourceMappingURL=projectScan.js.map
|