Spaces:
Running
Running
| /* | |
| ELYSIA CODE COMPANION v1.2.2 - Code Analyzer | |
| Static analysis tools for code insights | |
| */ | |
| import FileSystem from "./filesystem.js"; | |
| import Utils from "./utils.js"; | |
| const Analyzer = { | |
| // Analyze Entire Project | |
| 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: [] | |
| }; | |
| // Generate 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.` | |
| }); | |
| } | |
| // Check for common patterns | |
| 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; | |
| }, | |
| // Analyze Single File | |
| 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: [] | |
| }; | |
| // Code-specific analysis | |
| if (Utils.isCodeFile(fileEntry.name)) { | |
| // Count lines of code (non-empty, non-comment) | |
| const codeLines = content.split("\n").filter(line => { | |
| const trimmed = line.trim(); | |
| return trimmed.length > 0 && !trimmed.startsWith("//") && !trimmed.startsWith("#"); | |
| }); | |
| analysis.linesOfCode = codeLines.length; | |
| // Detect TODO/FIXME comments (optimized regex) | |
| 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)` | |
| }); | |
| } | |
| // Check file size | |
| if (fileEntry.size > 1024 * 100) { | |
| // 100KB | |
| analysis.insights.push({ | |
| type: "warning", | |
| message: "Large file detected - consider splitting into smaller modules" | |
| }); | |
| } | |
| // Check for very long lines | |
| 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; | |
| }, | |
| // Generate Project Tree (Text Format) | |
| generateTreeText(tree, indent = "", isRoot = true) { | |
| let text = ""; | |
| // Only show root node name | |
| 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`; | |
| // Recursively process children | |
| if (child.children && child.children.length > 0) { | |
| text += this.generateTreeText(child, childIndent, false); | |
| } | |
| }); | |
| } | |
| return text; | |
| }, | |
| // Get Files for AI Context (smart selection) | |
| async getContextFiles(query = "", maxFiles = 5) { | |
| const mentionedFiles = []; | |
| const configFiles = []; | |
| // PRIORITY 1: Files explicitly mentioned in query (highest relevance) | |
| if (query) { | |
| const mentioned = FileSystem.files.filter( | |
| f => | |
| query.toLowerCase().includes(f.name.toLowerCase()) || | |
| query.toLowerCase().includes(f.path.toLowerCase()) | |
| ); | |
| mentionedFiles.push(...mentioned); | |
| } | |
| // PRIORITY 2: Important config files (context) | |
| 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); | |
| // Merge with priority: mentioned files FIRST, then config files | |
| const relevantFilesMap = new Map(); | |
| mentionedFiles.forEach(f => relevantFilesMap.set(f.path, f)); // Priority 1 | |
| configFiles.forEach(f => { | |
| if (!relevantFilesMap.has(f.path)) { | |
| // Don't add duplicates | |
| relevantFilesMap.set(f.path, f); // Priority 2 | |
| } | |
| }); | |
| // Convert Map to array and limit | |
| 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) // Limit to 5000 chars per file | |
| }; | |
| } catch (err) { | |
| console.error(`Failed to read ${file.path}:`, err); | |
| return null; | |
| } | |
| }) | |
| ); | |
| return filesWithContent.filter(f => f !== null); | |
| }, | |
| // Parse Command from User Input | |
| 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; | |