Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | 6x 6x 14x 14x 7x 14x 6x 6x 7x 5x 12x 12x 5x 5x 5x 12x 5x 5x 5x 7x 7x 7x 7x 5x 5x 5x 12x 12x 5x 5x 6x 5x 5x 5x 3x 5x 1x 5x | import type { DependencyGraph, ModuleCluster } from './types';
import { calculateFragmentation, calculateEnhancedCohesion } from './metrics';
/**
* Group files by domain to detect module clusters
*/
export function detectModuleClusters(
graph: DependencyGraph,
options?: { useLogScale?: boolean }
): ModuleCluster[] {
const domainMap = new Map<string, string[]>();
for (const [file, node] of graph.nodes.entries()) {
const primaryDomain = node.exports[0]?.inferredDomain || 'unknown';
if (!domainMap.has(primaryDomain)) {
domainMap.set(primaryDomain, []);
}
domainMap.get(primaryDomain)!.push(file);
}
const clusters: ModuleCluster[] = [];
for (const [domain, files] of domainMap.entries()) {
if (files.length < 2 || domain === 'unknown') continue;
const totalTokens = files.reduce((sum, file) => {
const node = graph.nodes.get(file);
return sum + (node?.tokenCost || 0);
}, 0);
// Calculate shared import ratio for coupling discount
let sharedImportRatio = 0;
Eif (files.length >= 2) {
const allImportSets = files.map(
(f) => new Set(graph.nodes.get(f)?.imports || [])
);
let intersection = new Set(allImportSets[0]);
let union = new Set(allImportSets[0]);
for (let i = 1; i < allImportSets.length; i++) {
const nextSet = allImportSets[i];
intersection = new Set([...intersection].filter((x) => nextSet.has(x)));
for (const x of nextSet) union.add(x);
}
sharedImportRatio = union.size > 0 ? intersection.size / union.size : 0;
}
const fragmentation = calculateFragmentation(files, domain, {
...options,
sharedImportRatio,
});
// Average cohesion for the cluster
let totalCohesion = 0;
files.forEach((f) => {
const node = graph.nodes.get(f);
Eif (node) totalCohesion += calculateEnhancedCohesion(node.exports);
});
const avgCohesion = totalCohesion / files.length;
clusters.push({
domain,
files,
totalTokens,
fragmentationScore: fragmentation,
avgCohesion,
suggestedStructure: generateSuggestedStructure(
files,
totalTokens,
fragmentation
),
});
}
return clusters;
}
function generateSuggestedStructure(
files: string[],
tokens: number,
fragmentation: number
) {
const targetFiles = Math.max(1, Math.ceil(tokens / 10000));
const plan: string[] = [];
if (fragmentation > 0.5) {
plan.push(
`Consolidate ${files.length} files scattered across multiple directories into ${targetFiles} core module(s)`
);
}
if (tokens > 20000) {
plan.push(
`Domain logic is very large (${Math.round(tokens / 1000)}k tokens). Ensure clear sub-domain boundaries.`
);
}
return { targetFiles, consolidationPlan: plan };
}
|