arela
Version:
AI-powered CTO with multi-agent orchestration, code summarization, visual testing (web + mobile) for blazing fast development.
135 lines • 5.09 kB
JavaScript
/**
* 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