All files / src summary.ts

61.32% Statements 65/106
30.76% Branches 12/39
82.97% Functions 39/47
62.16% Lines 46/74

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 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141                                      1x       1x 1x     1x 1x   1x 1x       1x 1x     1x     1x 1x 1x 1x 1x 1x     1x 1x 1x   1x 1x 1x 1x 1x 1x 1x   1x   1x                               1x   1x     1x 1x           1x   1x 1x       1x 1x   1x 1x   1x 1x       1x     1x           1x       1x   1x                              
import type {
  ContextAnalysisResult,
  ContextSummary,
  ModuleCluster,
} from './types';
import { GLOBAL_SCAN_OPTIONS } from '@aiready/core';
import { calculatePathEntropy } from './metrics';
 
/**
 * Generate summary of context analysis results
 *
 * @param results - Array of individual file analysis results.
 * @param options - Optional scan configuration for context extraction.
 * @returns A consolidated summary of the entire context scan.
 */
export function generateSummary(
  results: ContextAnalysisResult[],
  options: any = {}
): ContextSummary {
  const config = options
    ? Object.fromEntries(
        Object.entries(options).filter(
          ([key]) => !GLOBAL_SCAN_OPTIONS.includes(key) || key === 'rootDir'
        )
      )
    : {};

  const totalFiles = results.length;
  const totalTokens = results.reduce((sum, r) => sum + r.tokenCost, 0);
  const avgContextBudget =
    totalFiles > 0
      ? results.reduce((sum, r) => sum + r.contextBudget, 0) / totalFiles
      : 0;

  // Find deep files
  const deepFiles = results
    .filter((r) => r.importDepth > 5)
    .map((r) => ({ file: r.file, depth: r.importDepth }));

  const maxImportDepth = Math.max(0, ...results.map((r) => r.importDepth));

  // Find fragmented modules (clusters)
  const moduleMap = new Map<string, ContextAnalysisResult[]>();
  results.forEach((r) => {
    const parts = r.file.split('/');
    // Try to identify domain/module (e.g., packages/core, src/utils)
    let domain = 'root';
    Iif (parts.length > 2) {
      domain = parts.slice(0, 2).join('/');
    }
    Eif (!moduleMap.has(domain)) moduleMap.set(domain, []);
    moduleMap.get(domain)!.push(r);
  });
 
  const fragmentedModules: ModuleCluster[] = [];
  moduleMap.forEach((files, domain) => {
    const clusterTokens = files.reduce((sum, f) => sum + f.tokenCost, 0);
    const filePaths = files.map((f) => f.file);
    const avgEntropy = calculatePathEntropy(filePaths);
 
    // A module is fragmented if it has many files with high directory distance
    // and relatively low cohesion
    const fragmentationScore = Math.min(1, avgEntropy * (files.length / 10));
 
    Iif (fragmentationScore > 0.4) {
      fragmentedModules.push({
        domain,
        files: filePaths,
        fragmentationScore,
        totalTokens: clusterTokens,
        avgCohesion:
          files.reduce((sum, f) => sum + f.cohesionScore, 0) / files.length,
        suggestedStructure: {
          targetFiles: Math.ceil(files.length / 2),
          consolidationPlan: [
            `Consolidate ${files.length} files in ${domain} into fewer modules`,
          ],
        },
      });
    }
  });
 
  fragmentedModules.sort((a, b) => b.fragmentationScore - a.fragmentationScore);
 
  const avgFragmentation =
    fragmentedModules.length > 0
      ? fragmentedModules.reduce((sum, m) => sum + m.fragmentationScore, 0) /
        fragmentedModules.length
      : 0;

  // Cohesion
  const avgCohesion =
    results.reduce((sum, r) => sum + r.cohesionScore, 0) / (totalFiles || 1);

  const lowCohesionFiles = results
    .filter((r) => r.cohesionScore < 0.4)
    .map((r) => ({ file: r.file, score: r.cohesionScore }));
 
  // Issues
  const criticalIssues = results.filter(
    (r) => r.severity === 'critical'
  ).length;
  const majorIssues = results.filter((r) => r.severity === 'major').length;
  const minorIssues = results.filter((r) => r.severity === 'minor').length;
 
  const totalPotentialSavings = results.reduce(
    (sum, r) => sum + (r.potentialSavings || 0),
    0
  );
 
  const topExpensiveFiles = [...results]
    .sort((a, b) => b.contextBudget - a.contextBudget)
    .slice(0, 10)
    .map((r) => ({
      file: r.file,
      contextBudget: r.contextBudget,
      severity: r.severity,
    }));
 
  return {
    totalFiles,
    totalTokens,
    avgContextBudget,
    maxContextBudget: Math.max(0, ...results.map((r) => r.contextBudget)),
    avgImportDepth:
      results.reduce((sum, r) => sum + r.importDepth, 0) / (totalFiles || 1),
    maxImportDepth,
    deepFiles,
    avgFragmentation,
    fragmentedModules,
    avgCohesion,
    lowCohesionFiles,
    criticalIssues,
    majorIssues,
    minorIssues,
    totalPotentialSavings,
    topExpensiveFiles,
    config,
  };
}