All files / src summary.ts

83.63% Statements 46/55
52.38% Branches 11/21
72% Functions 18/25
82.6% Lines 38/46

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