# All files cluster-detector.ts

100% Statements 43/43
77.27% Branches 17/22
100% Functions 6/6
100% Lines 38/38

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 };
    }