import * as fs from "node:fs"; import * as path from "node:path"; export type ProjectType = | "generic" | "git" | "node" | "frontend" | "go" | "wails" | "tauri" | "rust" | "python" | "php" | "docker" | "database"; export interface DetectedProject { root: string; types: ProjectType[]; confidence: number; evidenceFiles: string[]; recommendedProfiles: string[]; } function has(root: string, rel: string): boolean { try { return fs.existsSync(path.join(root, rel)); } catch { return false; } } function pushType(set: Set, evidence: string[], type: ProjectType, ...files: string[]): void { set.add(type); for (const file of files) { if (!evidence.includes(file)) evidence.push(file); } } function sortTypes(types: Set): ProjectType[] { const order: ProjectType[] = [ "generic", "git", "node", "frontend", "go", "wails", "tauri", "rust", "python", "php", "docker", "database", ]; return order.filter((item) => types.has(item)); } function inferProfiles(types: ProjectType[]): string[] { const profiles = new Set(); profiles.add("generic"); for (const type of types) { switch (type) { case "node": case "frontend": case "go": case "wails": case "tauri": case "rust": case "python": case "php": case "docker": case "database": profiles.add(type === "database" ? "postgres" : type); break; default: break; } } return [...profiles]; } function computeConfidence(types: ProjectType[], evidenceFiles: string[]): number { if (types.length === 1 && types[0] === "generic") return 0.1; const markerScore = Math.min(0.9, 0.3 + evidenceFiles.length * 0.12); return Number(markerScore.toFixed(2)); } export function detectProject(root: string): DetectedProject { const types = new Set(["generic"]); const evidenceFiles: string[] = []; if (has(root, ".git")) { pushType(types, evidenceFiles, "git", ".git"); } const hasPackageJson = has(root, "package.json"); if (hasPackageJson) { pushType(types, evidenceFiles, "node", "package.json"); pushType(types, evidenceFiles, "frontend", "package.json"); } const hasGoMod = has(root, "go.mod"); const hasWailsJson = has(root, "wails.json"); if (hasGoMod) { pushType(types, evidenceFiles, "go", "go.mod"); } if (hasGoMod && hasWailsJson) { pushType(types, evidenceFiles, "wails", "go.mod", "wails.json"); } const hasTauriCargo = has(root, "src-tauri/Cargo.toml"); if (hasTauriCargo) { pushType(types, evidenceFiles, "tauri", "src-tauri/Cargo.toml"); pushType(types, evidenceFiles, "rust", "src-tauri/Cargo.toml"); } else if (has(root, "Cargo.toml")) { pushType(types, evidenceFiles, "rust", "Cargo.toml"); } if (has(root, "pyproject.toml")) { pushType(types, evidenceFiles, "python", "pyproject.toml"); } else if (has(root, "requirements.txt")) { pushType(types, evidenceFiles, "python", "requirements.txt"); } if (has(root, "composer.json")) { pushType(types, evidenceFiles, "php", "composer.json"); } if (has(root, "Dockerfile")) { pushType(types, evidenceFiles, "docker", "Dockerfile"); } if (has(root, "docker-compose.yml")) { pushType(types, evidenceFiles, "docker", "docker-compose.yml"); } if (has(root, "schema.sql")) { pushType(types, evidenceFiles, "database", "schema.sql"); } if (has(root, "migrations")) { pushType(types, evidenceFiles, "database", "migrations"); } const detectedTypes = sortTypes(types); const recommendedProfiles = inferProfiles(detectedTypes); const confidence = computeConfidence(detectedTypes, evidenceFiles); return { root, types: detectedTypes, confidence, evidenceFiles, recommendedProfiles, }; }