/** * Smart Refactor Tool * * Provides intelligent refactoring suggestions with code examples * Analyzes code patterns and suggests improvements */ import { CacheEngine } from '../../core/cache-engine.js'; import { MetricsCollector } from '../../core/metrics.js'; import { TokenCounter } from '../../core/token-counter.js'; import { type Table } from '../shared/table.js'; export interface SmartRefactorOptions { filePath?: string; fileContent?: string; projectRoot?: string; refactorTypes?: Array<'extract-method' | 'simplify-conditional' | 'remove-duplication' | 'improve-naming' | 'reduce-complexity' | 'extract-constant'>; minComplexityForExtraction?: number; force?: boolean; maxCacheAge?: number; } export interface RefactorSuggestion { type: string; severity: 'info' | 'warning' | 'error'; /** * Every place this same finding occurs, as [line, column] pairs. * * ONE FINDING, ALL ITS PLACES -- not one finding per place. * * These were separate suggestions, each carrying its own copy of the * message, the advice, the code example and the impact block. On one * 4937-token source file that produced 100 suggestions holding 26 distinct * messages: "Single-letter variable 'n' is not descriptive." was sent 50 * times and the same sentence for 'f' 24 times, identical in every field * but the line. That is not 50 findings, and answering as though it were * cost 7214 tokens to report on a 4937-token file. * * The previous shape also declared endLine and endColumn, which no * producer ever set. */ locations: Array<[line: number, column: number]>; message: string; suggestion: string; codeExample?: { before: string; after: string; }; impact: { complexity?: number; readability?: 'low' | 'medium' | 'high'; maintainability?: 'low' | 'medium' | 'high'; }; } /** * The advice and worked example for a kind of refactoring, sent once. * * Most of what a suggestion said was a property of its TYPE, not of the place * it was found: the same "Extract complex conditions into descriptively named * boolean variables." and the same before/after example went out with every * simplify-conditional finding. On one fixture the codeExample field alone * was 517 of the 1982 tokens the suggestions cost, most of it the same * textbook snippet repeated. * * Hoisting it here loses nothing: a finding that omits `suggestion` or * `codeExample` means the entry under its own type, which is in this same * response, and a finding whose advice genuinely differs still carries its * own. */ export interface RefactorGuidance { suggestion: string; codeExample?: { before: string; after: string; }; } /** * A finding as it is sent: its own advice only where that differs from the * shared {`link RefactorGuidance} for its type. */ export type RefactorSuggestionRow = Omit & Partial>; export interface SmartRefactorResult { summary: { file: string; /** Distinct findings, after identical ones were folded onto one entry. */ totalSuggestions: number; /** * Places those findings occur, summed over every entry's locations. * * Kept so folding loses no count: this is what totalSuggestions used to * report, when one occurrence was one suggestion. */ totalOccurrences: number; /** * COUNTS PER SEVERITY AND PER TYPE ARE NOT SENT. * * They were, and they were 146 tokens across the three benched files -- * a tenth of the smallest reply -- for sums the caller can take from the * `severity` and `type` columns of `suggestions`, which travel in the * same response either way. A tally of rows the reader already has is * the reader doing less adding, not the tool answering more. */ estimatedImpact: 'low' | 'medium' | 'high'; fromCache: boolean; }; /** * One row per finding, field names sent once. * * Decode with `decodeTable(result.suggestions)`. As * an array of objects this re-labelled every field on every element; on a * 26-finding response that repetition cost 526 tokens. * * A row's `suggestion` and `codeExample` may be absent, which means the * entry for its type in {`link SmartRefactorResult.guidance}. */ suggestions: Table; /** Per-type advice, keyed by `type`, referenced by the rows above. */ guidance: Record; } export declare class SmartRefactorTool { private cache; private metrics; private cacheNamespace; private projectRoot; private complexityTool; constructor(cache: CacheEngine, tokenCounter: TokenCounter, metrics: MetricsCollector, projectRoot?: string); run(options?: SmartRefactorOptions): Promise; private suggestExtractMethod; private suggestSimplifyConditional; private suggestRemoveDuplication; private suggestImproveNaming; private suggestReduceComplexity; private suggestExtractConstant; private countNestedIfs; private countLogicalOperators; private calculateEstimatedImpact; /** * Folds findings that differ only in where they occur onto one entry. * * The key is every field but the locations, so two entries merge only when * their message, advice, code example, severity and impact are all * identical -- a merge can therefore not change what the response says * about any one place. Locations keep first-seen order, and a location * repeated for the same finding is kept once. */ private foldIdenticalFindings; /** * Moves each type's advice into a shared map and drops the copies that * repeat it -- BUT ONLY WHERE SHARING IT SAVES SOMETHING. * * The entry for a type was previously taken from the first finding of that * type whatever its reuse, so a type with one finding paid a dictionary key * and a wrapper object to avoid repeating a value that appeared once. On the * benched fixture four of the five entries served exactly one row each: 70 * tokens spent to deduplicate nothing. * * So each field is hoisted only when AT LEAST TWO findings would rely on it, * counted per field, because a finding can share a type's advice while * carrying its own example. A field that would serve one finding stays on * that finding instead, and a type left with neither field gets no entry. * * Which advice is shareable follows from how it is written, not from this * count: `improve-naming` states a general rule and every finding of that * type repeats it, while `extract-constant` names the literal it found, so * two of those findings never produce the same string and that type is now * never hoisted. A field is still left off a finding only when it is * byte-identical to the entry, so a finding whose advice differs keeps its * own and reads exactly as before. */ private hoistGuidance; private generateCacheKey; private getCachedResult; private cacheResult; } export declare function getSmartRefactorTool(cache: CacheEngine, tokenCounter: TokenCounter, metrics: MetricsCollector, projectRoot?: string): SmartRefactorTool; export declare function runSmartRefactor(options: SmartRefactorOptions): Promise; export { SMART_REFACTOR_TOOL_DEFINITION } from './analysis-tool-definitions.js'; //# sourceMappingURL=smart-refactor.d.ts.map