UNPKG

promachos

Version:

Standardized protocol for human-AI collaboration - making AI assistance predictable, trackable, and scalable

229 lines (200 loc) 6.69 kB
import fs from 'fs'; import path from 'path'; /** * Auto-detect project type and configuration */ export class ProjectDetector { constructor(projectPath = process.cwd()) { this.projectPath = projectPath; } /** * Detect project type based on files and dependencies */ async detectProjectType() { const detections = []; // Check for specific files const fileChecks = { 'package.json': this.detectNodeProject.bind(this), 'requirements.txt': () => ({ type: 'python', confidence: 0.8 }), 'Pipfile': () => ({ type: 'python', confidence: 0.9 }), 'pyproject.toml': () => ({ type: 'python', confidence: 0.7 }), 'Cargo.toml': () => ({ type: 'rust', confidence: 0.9 }), 'go.mod': () => ({ type: 'go', confidence: 0.9 }), 'pom.xml': () => ({ type: 'java-maven', confidence: 0.8 }), 'build.gradle': () => ({ type: 'java-gradle', confidence: 0.8 }), 'composer.json': () => ({ type: 'php', confidence: 0.8 }), 'Gemfile': () => ({ type: 'ruby', confidence: 0.8 }), 'mix.exs': () => ({ type: 'elixir', confidence: 0.9 }), 'pubspec.yaml': () => ({ type: 'dart-flutter', confidence: 0.9 }) }; for (const [file, detector] of Object.entries(fileChecks)) { if (fs.existsSync(path.join(this.projectPath, file))) { const result = await detector(); if (result) { detections.push(result); } } } // Return the detection with highest confidence if (detections.length === 0) { return { type: 'unknown', confidence: 0 }; } return detections.sort((a, b) => b.confidence - a.confidence)[0]; } /** * Detect Node.js project specifics */ async detectNodeProject() { try { const packageJson = JSON.parse( fs.readFileSync(path.join(this.projectPath, 'package.json'), 'utf8') ); const deps = { ...packageJson.dependencies, ...packageJson.devDependencies }; // Framework detection if (deps.react) { if (deps.next) return { type: 'nextjs', confidence: 0.95, framework: 'Next.js' }; if (deps['react-native']) return { type: 'react-native', confidence: 0.95, framework: 'React Native' }; return { type: 'react', confidence: 0.9, framework: 'React' }; } if (deps.vue) return { type: 'vue', confidence: 0.9, framework: 'Vue.js' }; if (deps['@angular/core']) return { type: 'angular', confidence: 0.9, framework: 'Angular' }; if (deps.svelte) return { type: 'svelte', confidence: 0.9, framework: 'Svelte' }; if (deps.express) return { type: 'node-express', confidence: 0.8, framework: 'Express.js' }; if (deps.fastify) return { type: 'node-fastify', confidence: 0.8, framework: 'Fastify' }; // Generic Node.js return { type: 'nodejs', confidence: 0.7, framework: 'Node.js' }; } catch (error) { return { type: 'nodejs', confidence: 0.5 }; } } /** * Get project metadata */ async getProjectMetadata() { const metadata = { name: path.basename(this.projectPath), path: this.projectPath, size: await this.calculateProjectSize(), languages: await this.detectLanguages(), gitInfo: await this.getGitInfo() }; return metadata; } /** * Calculate project size */ async calculateProjectSize() { let fileCount = 0; let lineCount = 0; const extensions = new Map(); const scanDirectory = (dir) => { try { const items = fs.readdirSync(dir); for (const item of items) { // Skip hidden directories and common non-code directories if (item.startsWith('.') || ['node_modules', 'dist', 'build', 'coverage'].includes(item)) { continue; } const fullPath = path.join(dir, item); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { scanDirectory(fullPath); } else if (stat.isFile()) { const ext = path.extname(item); if (this.isCodeFile(ext)) { fileCount++; extensions.set(ext, (extensions.get(ext) || 0) + 1); try { const content = fs.readFileSync(fullPath, 'utf8'); lineCount += content.split('\n').length; } catch { // Skip files that can't be read } } } } } catch { // Skip directories that can't be read } }; scanDirectory(this.projectPath); return { files: fileCount, lines: lineCount, extensions: Object.fromEntries(extensions) }; } /** * Detect programming languages */ async detectLanguages() { const languageMap = { '.js': 'JavaScript', '.jsx': 'JavaScript (JSX)', '.ts': 'TypeScript', '.tsx': 'TypeScript (TSX)', '.py': 'Python', '.java': 'Java', '.cpp': 'C++', '.c': 'C', '.cs': 'C#', '.rb': 'Ruby', '.go': 'Go', '.rs': 'Rust', '.php': 'PHP', '.swift': 'Swift', '.kt': 'Kotlin', '.dart': 'Dart', '.ex': 'Elixir' }; const size = await this.calculateProjectSize(); const languages = []; for (const [ext, count] of Object.entries(size.extensions)) { if (languageMap[ext]) { languages.push({ language: languageMap[ext], files: count, percentage: Math.round((count / size.files) * 100) }); } } return languages.sort((a, b) => b.files - a.files); } /** * Get Git repository information */ async getGitInfo() { try { if (!fs.existsSync(path.join(this.projectPath, '.git'))) { return null; } // Try to get remote URL const gitConfig = path.join(this.projectPath, '.git', 'config'); if (fs.existsSync(gitConfig)) { const config = fs.readFileSync(gitConfig, 'utf8'); const remoteMatch = config.match(/url = (.+)/); if (remoteMatch) { return { isGitRepo: true, remoteUrl: remoteMatch[1] }; } } return { isGitRepo: true }; } catch { return null; } } /** * Check if file extension indicates a code file */ isCodeFile(ext) { const codeExtensions = [ '.js', '.jsx', '.ts', '.tsx', '.py', '.java', '.cpp', '.c', '.cs', '.rb', '.go', '.rs', '.php', '.swift', '.kt', '.dart', '.ex', '.exs', '.html', '.css', '.scss', '.sass', '.less', '.vue', '.svelte', '.json', '.yaml', '.yml', '.toml', '.xml', '.sql' ]; return codeExtensions.includes(ext.toLowerCase()); } }