/** * # Canonical Resolver — Intelligent duplicate resolution * * ## Problem * * When duplicate exports are detected (same name in multiple files), * we need to determine which is the "canonical" version — the one the AI should use * and the others ignore. Without this, the AI doesn't know which to pick or creates a third. * * The naive approach (pick the first alphabetically) fails because * filesystem order has no relation to code quality or intent. * * ## Solution: Multi-signal scoring * * Each location of a duplicate is scored with 8 independent signals. * The location with the highest score is canonical. The difference between first * and second determines the confidence level. * * ## Signals and weights * * | # | Signal | Range | Logic | * |----|--------------------------|------------|-----------------------------------------------------------| * | S1 | Semantic name | +15 to +40 | The filename matches the export. | * | | | | +40 if exact (formatCurrency.ts → formatCurrency) | * | | | | +15 per partial word (validation.ts → validateEmail) | * | S2 | Semantic folder | +12 | The folder contains a word from the export. | * | | | | E.g.: formatting/ for formatCurrency | * | S3 | shared/common location | +15 | Code intentionally placed as shared. | * | S4 | Dependency relationship | +25 / -15 | If another duplicate's file imports THIS file, | * | | | | this is the original (+25). If THIS imports from other, | * | | | | this is the copy (-15). | * | S5 | Popularity (importers) | 0 to +18 | More files import this = more established. | * | | | | Formula: min(importedByCount * 3, 18) | * | S6 | Generic file | -25 | utils.ts, helpers.ts penalized. | * | | | | They are "junk drawers", not intentional locations. | * | S7 | File focus | -12 to +18 | Small files with few exports = purpose-built. | * | | | | Large files with many exports = grab bag. | * | | | | Lines: >500 → -12, >300 → -6, <=80 → +10 | * | | | | Exports: >10 → -12, <=3 → +8 | * | S8 | Function cluster | 0 to +15 | If the file has other functions with common words | * | | | | (e.g.: formatDate alongside formatCurrency), it's the | * | | | | "home" for that type of functionality. +5 per fn, max 15. | * * ## Tiebreakers (when scores are equal) * * 1. More importers wins * 2. Fewer lines wins (more focused file) * * ## Confidence levels * * Based on the difference (gap) between #1 and #2 scores: * * | Gap | Confidence | AI action | * |--------|------------|------------------------------------------------| * | >= 20 | high | Use canonical directly, IGNORE alternatives | * | 10-19 | medium | Use canonical, but mention uncertainty | * | < 10 | low | Ask user which is canonical | * * ## Resolved example * * Given: formatCurrency exists in 3 files: * * | File | S1 | S2 | S3 | S4 | S5 | S6 | S7 | S8 | Total | * |---------------------------------------|-----|-----|-----|-----|-----|-----|---------|-----|-------| * | src/shared/formatting/formatCurrency.ts| +40 | +12 | +15 | 0 | +9 | 0 | +10,+8 | 0 | 94 | * | src/utils.ts | 0 | 0 | 0 | +25 | +18 | -25 | -12,-12 | +15 | 9 | * | src/helpers.ts | 0 | 0 | 0 | -15 | +6 | -25 | -6,0 | +10 | -30 | * * Result: formatCurrency.ts wins with high confidence (gap: 85). * The dedicated file beats the generic one, even though utils.ts has more importers. * * ## Known limitations * * - S4 depends on DependencyData.mostImported (top 30). Rarely imported files * won't have this signal. * - S1 uses substring matching, which can produce false positives with short names. * Words of <=2 characters are filtered to mitigate this. * - S8 compares exact camelCase words. "format" matches "formatDate" but * not "formatter" (stemming would be needed for that). * - The first run has no prior data for comparison. The resolver works * with current data only. * * ## Integration * * Used by: * - unifiedTemplate.ts: Output with confidence tiers or simplified based on flags * * Receives DuplicateData.duplicates that is pre-filtered (only 'accidental' category), * without barrels or cross-stack mirrors. */ import { DuplicateData, InventoryData, DependencyData } from '../types'; export type CanonicalConfidence = 'high' | 'medium' | 'low'; export interface ScoredLocation { file: string; line: number; score: number; reasons: string[]; signature?: string; } export interface ResolvedDuplicate { name: string; type: string; canonical: ScoredLocation; alternatives: ScoredLocation[]; confidence: CanonicalConfidence; } /** * Resolves which location of each duplicate export is the "canonical" version. * * Uses 8 signals to score each location: * 1. Semantic name match (filename ↔ export name) * 2. Directory semantic match * 3. shared/common location bonus * 4. Dependency relationship (is another dup's file importing this one?) * 5. General import count (popularity) * 6. Generic file penalty (utils.ts, helpers.ts) * 7. File focus (size + export count) * 8. Cluster detection (related exports in same file) * * Returns results with confidence levels: * - high: score gap ≥ 20 — clear winner, AI can act confidently * - medium: score gap 10-19 — likely winner, AI should mention uncertainty * - low: score gap < 10 — ambiguous, AI should ask user to decide */ export declare function resolveCanonicals(duplicates: DuplicateData, inventory: InventoryData, deps: DependencyData): ResolvedDuplicate[];