All files analyzer.ts

94.28% Statements 33/35
80% Branches 16/20
100% Functions 8/8
96.55% Lines 28/29

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                                            3x 3x     3x                       3x 3x 4x 4x               4x 4x 4x 36x         3x                         3x 20x 6x 6x 2x         4x 3x 4x   3x 4x   3x 4x       3x 4x   4x     3x                              
/**
 * Main analyzer for AI signal clarity.
 * Scans all TS/JS files in a directory and aggregates signals.
 */
 
import {
  scanFiles,
  calculateAiSignalClarity,
  Severity,
  emitProgress,
} from '@aiready/core';
import { scanFile } from './scanner';
import type {
  AiSignalClarityOptions,
  AiSignalClarityReport,
  FileAiSignalClarityResult,
} from './types';
 
export async function analyzeAiSignalClarity(
  options: AiSignalClarityOptions
): Promise<AiSignalClarityReport> {
  // Use core scanFiles which respects .gitignore recursively
  const files = await scanFiles(options);
  const results: FileAiSignalClarityResult[] = [];
 
  // Aggregate signals
  const aggregate = {
    magicLiterals: 0,
    booleanTraps: 0,
    ambiguousNames: 0,
    undocumentedExports: 0,
    implicitSideEffects: 0,
    deepCallbacks: 0,
    overloadedSymbols: 0,
    totalSymbols: 0,
    totalExports: 0,
  };
 
  let processed = 0;
  for (const filePath of files) {
    processed++;
    emitProgress(
      processed,
      files.length,
      'ai-signal-clarity',
      'analyzing files',
      options.onProgress
    );
 
    const result = await scanFile(filePath, options);
    results.push(result);
    for (const key of Object.keys(aggregate) as Array<keyof typeof aggregate>) {
      aggregate[key] += result.signals[key] ?? 0;
    }
  }
 
  // Calculate grounding score using core math (statically imported)
  const riskResult = calculateAiSignalClarity({
    overloadedSymbols: aggregate.overloadedSymbols,
    magicLiterals: aggregate.magicLiterals,
    booleanTraps: aggregate.booleanTraps,
    implicitSideEffects: aggregate.implicitSideEffects,
    deepCallbacks: aggregate.deepCallbacks,
    ambiguousNames: aggregate.ambiguousNames,
    undocumentedExports: aggregate.undocumentedExports,
    totalSymbols: Math.max(1, aggregate.totalSymbols),
    totalExports: Math.max(1, aggregate.totalExports),
  });
 
  // Helper for severity mapping
  const getLevel = (s: any): number => {
    if (s === Severity.Critical || s === 'critical') return 4;
    Iif (s === Severity.Major || s === 'major') return 3;
    if (s === Severity.Minor || s === 'minor') return 2;
    Eif (s === Severity.Info || s === 'info') return 1;
    return 0;
  };
 
  // Count severities
  const allIssues = results.flatMap((r) => r.issues);
  const criticalSignals = allIssues.filter(
    (i) => getLevel(i.severity) === 4
  ).length;
  const majorSignals = allIssues.filter(
    (i) => getLevel(i.severity) === 3
  ).length;
  const minorSignals = allIssues.filter(
    (i) => getLevel(i.severity) === 2
  ).length;
 
  // Filter by minSeverity
  const minSev = options.minSeverity ?? Severity.Info;
  const filteredResults = results.map((r) => ({
    ...r,
    issues: r.issues.filter((i) => getLevel(i.severity) >= getLevel(minSev)),
  }));
 
  return {
    summary: {
      filesAnalyzed: files.length,
      totalSignals: allIssues.length,
      criticalSignals,
      majorSignals,
      minorSignals,
      topRisk: riskResult.topRisk,
      rating: riskResult.rating,
    },
    results: filteredResults,
    aggregateSignals: aggregate,
    recommendations: riskResult.recommendations,
  };
}