UNPKG

arela

Version:

AI-powered CTO with multi-agent orchestration, code summarization, visual testing (web + mobile) for blazing fast development.

135 lines 5.09 kB
/** * Main slice detection orchestrator */ import path from "node:path"; import fs from "fs-extra"; import { detectCommunitiesInfomap } from "./infomap.js"; import { loadGraph, loadMultiRepoGraph } from "./graph-loader.js"; import { calculateCohesion } from "./modularity.js"; import { suggestSliceName, getFeatureEmoji } from "./slice-namer.js"; import { generateRecommendations } from "./reporter.js"; /** * Detect slices in one or more repositories */ export async function detectSlices(repoPaths, cwd = process.cwd(), options) { // Resolve graph DB path - always check cwd first since that's where ingestion runs let dbPath = null; const searchedPaths = []; // 1. Try cwd first (most common case) const cwdPath = path.join(cwd, ".arela", "memory", "graph.db"); searchedPaths.push(cwdPath); if (await fs.pathExists(cwdPath)) { dbPath = cwdPath; } // 2. If not found and repo paths provided, try those if (!dbPath && repoPaths.length > 0) { const absoluteRepoPaths = repoPaths.map(p => path.resolve(cwd, p)); for (const repoPath of absoluteRepoPaths) { const candidatePath = path.join(repoPath, ".arela", "memory", "graph.db"); searchedPaths.push(candidatePath); if (await fs.pathExists(candidatePath)) { dbPath = candidatePath; break; } } } if (!dbPath) { throw new Error(`Graph DB not found. Searched:\n ${searchedPaths.join('\n ')}\n\nRun 'arela ingest codebase' first.`); } // Load graph // If repoPaths is ["."] or empty, load all files (no filtering) // Otherwise, filter by specific repo paths const shouldFilter = repoPaths.length > 0 && !repoPaths.includes("."); const graph = shouldFilter ? loadMultiRepoGraph(dbPath, repoPaths.map(p => path.resolve(cwd, p))) : loadGraph(dbPath); if (graph.nodes.length === 0) { throw new Error("No files found in graph. Run 'arela ingest codebase' first."); } // Run Infomap clustering (information flow-based, conceptually superior for codebases) const communities = detectCommunitiesInfomap(graph, { directed: true, twoLevel: true, numTrials: 20, seed: 42, }); // Convert communities to slices const slices = communitiesToSlices(communities, graph); // Sort by cohesion (best first) slices.sort((a, b) => b.cohesion - a.cohesion); // Apply minimum cohesion filter if specified let filteredSlices = slices; if (options?.minCohesion !== undefined) { filteredSlices = slices.filter((s) => s.cohesion >= options.minCohesion); } // Apply max slices limit if specified if (options?.maxSlices !== undefined && filteredSlices.length > options.maxSlices) { filteredSlices = filteredSlices.slice(0, options.maxSlices); } // Generate recommendations const recommendations = generateRecommendations(filteredSlices); const report = { totalFiles: graph.nodes.length, totalImports: graph.edges.length, sliceCount: filteredSlices.length, slices: filteredSlices, recommendations, }; // Save to file if requested if (options?.json) { const outputPath = path.isAbsolute(options.json) ? options.json : path.join(cwd, options.json); await fs.ensureDir(path.dirname(outputPath)); await fs.writeJSON(outputPath, report, { spaces: 2 }); } return report; } /** * Convert communities to slices */ function communitiesToSlices(communities, graph) { const slices = []; for (const community of communities) { if (community.nodes.length === 0) continue; // Get file paths for this community const files = community.nodes .map((nodeId) => { const node = graph.nodes.find((n) => n.id === nodeId); return node?.path || ""; }) .filter((p) => p !== "") .sort(); // Calculate cohesion const cohesion = calculateCohesion(community, graph); // Count internal and external imports const nodeSet = new Set(community.nodes); let internalImports = 0; let externalImports = 0; for (const edge of graph.edges) { const fromIn = nodeSet.has(edge.from); const toIn = nodeSet.has(edge.to); if (fromIn && toIn) { internalImports += edge.weight; } else if ((fromIn && !toIn) || (!fromIn && toIn)) { externalImports += edge.weight; } } // Suggest name const baseName = suggestSliceName(files); const emoji = getFeatureEmoji(baseName); const name = `${emoji} ${baseName}`; slices.push({ name, files, fileCount: files.length, cohesion, internalImports, externalImports, }); } return slices; } //# sourceMappingURL=index.js.map