|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import FileSystem from "./filesystem.js";
|
|
|
import Utils from "./utils.js";
|
|
|
|
|
|
const Analyzer = {
|
|
|
|
|
|
analyzeProject() {
|
|
|
const stats = FileSystem.getStats();
|
|
|
const tree = FileSystem.buildTree();
|
|
|
|
|
|
const analysis = {
|
|
|
summary: {
|
|
|
name: FileSystem.folderName,
|
|
|
totalFiles: stats.totalFiles,
|
|
|
totalSize: Utils.formatFileSize(stats.totalSize),
|
|
|
languages: stats.languages,
|
|
|
fileTypes: stats.fileTypes
|
|
|
},
|
|
|
structure: tree,
|
|
|
insights: []
|
|
|
};
|
|
|
|
|
|
|
|
|
if (stats.totalFiles === 0) {
|
|
|
analysis.insights.push({
|
|
|
type: "warning",
|
|
|
message: "No files found in this folder"
|
|
|
});
|
|
|
}
|
|
|
|
|
|
if (stats.totalFiles > 500) {
|
|
|
analysis.insights.push({
|
|
|
type: "info",
|
|
|
message: `Large project detected (${stats.totalFiles} files). Consider analyzing specific files instead of the whole project.`
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
const hasPackageJson = FileSystem.files.some(f => f.name === "package.json");
|
|
|
const hasRequirementsTxt = FileSystem.files.some(f => f.name === "requirements.txt");
|
|
|
const hasCargoToml = FileSystem.files.some(f => f.name === "Cargo.toml");
|
|
|
|
|
|
if (hasPackageJson) {
|
|
|
analysis.insights.push({
|
|
|
type: "success",
|
|
|
message: "JavaScript/Node.js project detected (package.json found)"
|
|
|
});
|
|
|
}
|
|
|
|
|
|
if (hasRequirementsTxt) {
|
|
|
analysis.insights.push({
|
|
|
type: "success",
|
|
|
message: "Python project detected (requirements.txt found)"
|
|
|
});
|
|
|
}
|
|
|
|
|
|
if (hasCargoToml) {
|
|
|
analysis.insights.push({
|
|
|
type: "success",
|
|
|
message: "Rust project detected (Cargo.toml found)"
|
|
|
});
|
|
|
}
|
|
|
|
|
|
return analysis;
|
|
|
},
|
|
|
|
|
|
|
|
|
async analyzeFile(filePath) {
|
|
|
const fileEntry = FileSystem.getFileByPath(filePath);
|
|
|
if (!fileEntry) {
|
|
|
throw new Error("File not found");
|
|
|
}
|
|
|
|
|
|
const content = await FileSystem.readFile(fileEntry);
|
|
|
|
|
|
const analysis = {
|
|
|
name: fileEntry.name,
|
|
|
path: filePath,
|
|
|
size: Utils.formatFileSize(fileEntry.size),
|
|
|
extension: fileEntry.extension,
|
|
|
language: fileEntry.language,
|
|
|
lines: content.split("\n").length,
|
|
|
content,
|
|
|
insights: []
|
|
|
};
|
|
|
|
|
|
|
|
|
if (Utils.isCodeFile(fileEntry.name)) {
|
|
|
|
|
|
const codeLines = content.split("\n").filter(line => {
|
|
|
const trimmed = line.trim();
|
|
|
return trimmed.length > 0 && !trimmed.startsWith("//") && !trimmed.startsWith("#");
|
|
|
});
|
|
|
analysis.linesOfCode = codeLines.length;
|
|
|
|
|
|
|
|
|
const todoRegex = /(?:TODO|FIXME|HACK|XXX|NOTE):/gi;
|
|
|
const todos = content.match(todoRegex);
|
|
|
if (todos && todos.length > 0) {
|
|
|
analysis.insights.push({
|
|
|
type: "info",
|
|
|
message: `Found ${todos.length} TODO/FIXME comment(s)`
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
if (fileEntry.size > 1024 * 100) {
|
|
|
|
|
|
analysis.insights.push({
|
|
|
type: "warning",
|
|
|
message: "Large file detected - consider splitting into smaller modules"
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
const longLines = content.split("\n").filter(line => line.length > 120);
|
|
|
if (longLines.length > 5) {
|
|
|
analysis.insights.push({
|
|
|
type: "info",
|
|
|
message: `${longLines.length} lines exceed 120 characters - consider refactoring for readability`
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return analysis;
|
|
|
},
|
|
|
|
|
|
|
|
|
generateTreeText(tree, indent = "", isRoot = true) {
|
|
|
let text = "";
|
|
|
|
|
|
|
|
|
if (isRoot && tree.name) {
|
|
|
const icon = tree.type === "directory" ? "π" : "π";
|
|
|
text += `${icon} ${tree.name}\n`;
|
|
|
}
|
|
|
|
|
|
if (tree.children) {
|
|
|
tree.children.forEach((child, index) => {
|
|
|
const isLast = index === tree.children.length - 1;
|
|
|
const prefix = indent + (isLast ? "ββ " : "ββ ");
|
|
|
const childIndent = indent + (isLast ? " " : "β ");
|
|
|
|
|
|
const icon = child.type === "directory" ? "π" : "π";
|
|
|
text += `${prefix}${icon} ${child.name}\n`;
|
|
|
|
|
|
|
|
|
if (child.children && child.children.length > 0) {
|
|
|
text += this.generateTreeText(child, childIndent, false);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
return text;
|
|
|
},
|
|
|
|
|
|
|
|
|
async getContextFiles(query = "", maxFiles = 5) {
|
|
|
const mentionedFiles = [];
|
|
|
const configFiles = [];
|
|
|
|
|
|
|
|
|
if (query) {
|
|
|
const mentioned = FileSystem.files.filter(
|
|
|
f =>
|
|
|
query.toLowerCase().includes(f.name.toLowerCase()) ||
|
|
|
query.toLowerCase().includes(f.path.toLowerCase())
|
|
|
);
|
|
|
mentionedFiles.push(...mentioned);
|
|
|
}
|
|
|
|
|
|
|
|
|
const configs = FileSystem.files.filter(f =>
|
|
|
[
|
|
|
"package.json",
|
|
|
"tsconfig.json",
|
|
|
"requirements.txt",
|
|
|
"Cargo.toml",
|
|
|
"README.md",
|
|
|
".env.example",
|
|
|
"docker-compose.yml"
|
|
|
].includes(f.name)
|
|
|
);
|
|
|
configFiles.push(...configs);
|
|
|
|
|
|
|
|
|
const relevantFilesMap = new Map();
|
|
|
mentionedFiles.forEach(f => relevantFilesMap.set(f.path, f));
|
|
|
configFiles.forEach(f => {
|
|
|
if (!relevantFilesMap.has(f.path)) {
|
|
|
|
|
|
relevantFilesMap.set(f.path, f);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
|
|
|
const relevantFiles = Array.from(relevantFilesMap.values());
|
|
|
const selectedFiles = relevantFiles.slice(0, maxFiles);
|
|
|
const filesWithContent = await Promise.all(
|
|
|
selectedFiles.map(async file => {
|
|
|
try {
|
|
|
const content = await FileSystem.readFile(file);
|
|
|
return {
|
|
|
name: file.name,
|
|
|
path: file.path,
|
|
|
language: file.language,
|
|
|
content: content.substring(0, 5000)
|
|
|
};
|
|
|
} catch (err) {
|
|
|
console.error(`Failed to read ${file.path}:`, err);
|
|
|
return null;
|
|
|
}
|
|
|
})
|
|
|
);
|
|
|
|
|
|
return filesWithContent.filter(f => f !== null);
|
|
|
},
|
|
|
|
|
|
|
|
|
parseCommand(input) {
|
|
|
const trimmed = input.trim();
|
|
|
|
|
|
if (!trimmed.startsWith("/")) {
|
|
|
return { type: "message", content: trimmed };
|
|
|
}
|
|
|
|
|
|
const parts = trimmed.substring(1).split(" ");
|
|
|
const command = parts[0].toLowerCase();
|
|
|
const args = parts.slice(1).join(" ");
|
|
|
|
|
|
return { type: "command", command, args };
|
|
|
}
|
|
|
};
|
|
|
|
|
|
export default Analyzer;
|
|
|
|