/** * Smart Edit Tool - 90% Token Reduction * * Achieves token reduction through: * 1. Line-based editing (edit only specific ranges, not full file) * 2. Return only diffs (show changes, not entire file content) * 3. Pattern-based replacement (regex/search-replace) * 4. Multi-edit batching (apply multiple edits in one operation) * 5. Verification before commit (preview changes before applying) * * Target: 90% reduction vs reading full file + writing changes */ import { CacheEngine } from '../../core/cache-engine.js'; import { TokenCounter } from '../../core/token-counter.js'; import { MetricsCollector } from '../../core/metrics.js'; export { BACKUP_ROOT } from '../../utils/file-backup.js'; export interface EditOperation { type: 'replace' | 'insert' | 'delete'; startLine: number; endLine?: number; content?: string; pattern?: string | RegExp; replacement?: string; } export interface SmartEditOptions { verifyBeforeApply?: boolean; dryRun?: boolean; createBackup?: boolean; batchEdits?: boolean; returnDiff?: boolean; contextLines?: number; updateCache?: boolean; ttl?: number; encoding?: BufferEncoding; } export interface SmartEditResult { success: boolean; path: string; operation: 'applied' | 'preview' | 'unchanged' | 'failed'; metadata: { editsApplied: number; linesChanged: number; originalLines: number; finalLines: number; tokensSaved: number; tokenCount: number; originalTokenCount: number; compressionRatio: number; duration: number; verified: boolean; wasBackedUp: boolean; }; diff?: { added: string[]; removed: string[]; unchanged: number; unifiedDiff: string; }; preview?: string; error?: string; } /** * Coerce whatever arrived as `operations` into an array of operation objects. * * WHY A STRING IS ACCEPTED HERE. `operations` is the only input on this tool * declared as a bare `oneOf` (object OR array) with no top-level `type`, and a * client that decides how to serialise a value from its declared type has * nothing to go on -- so the array arrives as JSON TEXT. `Array.isArray` is * then false, the string gets wrapped as `[theString]`, and validation refuses * it with `Invalid operation type: undefined`, because a string is not an * object. Reproduced 2026-08-28 with a schema-valid payload: * * operations: [{ "type": "replace", "startLine": 1, "endLine": 1, * "content": "x" }] -> Invalid operation type: undefined * operations: { "type": "replace", ... } -> applied * * A single object binds; the array form does not. That made every multi-edit * call fail while single edits worked, which reads as a broken tool rather than * a marshalling quirk. * * Parsing here rather than tightening the schema, because the schema is not * wrong -- both shapes are genuinely accepted -- and because this keeps working * whatever any given client does with a `oneOf`. Anything that is not valid * JSON is passed through untouched, so validateOperations still produces its * own precise error rather than a parse failure. */ export declare function normalizeOperations(operations: EditOperation | EditOperation[] | string): EditOperation[]; export declare class SmartEditTool { private cache; private tokenCounter; private metrics; constructor(cache: CacheEngine, tokenCounter: TokenCounter, metrics: MetricsCollector); /** * Smart edit with line-based operations and diff-only output */ edit(filePath: string, operations: EditOperation | EditOperation[] | string, options?: SmartEditOptions): Promise; /** * Validate edit operations */ private validateOperations; /** * Apply edit operations to lines. * * TWO DEFECTS LIVED HERE, and they compounded into one another. * * The pattern replace ran per LINE (`result[i].replace(...)`), so any pattern * containing a newline could never match anything -- the text it was written * against did not exist on any single line. The replace is now applied to the * JOINED range, which is what a caller passing a multi-line pattern already * believes is happening. * * And a pattern that matched nothing was silently ignored: the edit returned * success with `editsApplied: 0` and `operation: 'unchanged'`, which is * indistinguishable from "your edit was a legitimate no-op". A caller whose * regex was subtly wrong got told it worked. Unmatched patterns now throw, * which the caller renders as `operation: 'failed'` with the offending * patterns named. * * The throw is deliberately all-or-nothing. If three patterns are supplied * and one misses, applying the other two leaves the file in a state the * caller never asked for and did not expect -- worse than doing nothing. * * Note this is only about PATTERN operations. A line-based edit whose content * happens to equal what was already there is a genuine no-op and still * returns success/unchanged. */ private applyEdits; /** * Calculate diff between old and new content */ private calculateDiff; /** * Get edit statistics */ getStats(): { totalEdits: number; unchangedSkips: number; totalTokensSaved: number; averageReduction: number; }; } /** * Get smart edit tool instance */ export declare function getSmartEditTool(cache: CacheEngine, tokenCounter: TokenCounter, metrics: MetricsCollector): SmartEditTool; /** * CLI function - Creates resources and uses factory */ export declare function runSmartEdit(filePath: string, operations: EditOperation | EditOperation[] | string, options?: SmartEditOptions): Promise; /** * MCP Tool Definition */ export declare const SMART_EDIT_TOOL_DEFINITION: { name: string; description: string; inputSchema: { type: string; properties: { path: { type: string; description: string; }; operations: { oneOf: ({ type: string; properties: { type: { type: string; enum: string[]; description: string; }; startLine: { type: string; description: string; }; endLine: { type: string; description: string; }; content: { type: string; description: string; }; pattern: { type: string; description: string; }; replacement: { type: string; description: string; }; }; required: string[]; items?: undefined; } | { type: string; items: { type: string; properties: { type: { type: string; enum: string[]; }; startLine: { type: string; }; endLine: { type: string; }; content: { type: string; }; pattern: { type: string; }; replacement: { type: string; }; }; required: string[]; }; properties?: undefined; required?: undefined; })[]; description: string; }; dryRun: { type: string; description: string; default: boolean; }; returnDiff: { type: string; description: string; default: boolean; }; createBackup: { type: string; description: string; default: boolean; }; verifyBeforeApply: { type: string; description: string; default: boolean; }; batchEdits: { type: string; description: string; default: boolean; }; contextLines: { type: string; description: string; default: number; }; updateCache: { type: string; description: string; default: boolean; }; ttl: { type: string; description: string; default: number; }; encoding: { type: string; description: string; default: string; }; }; required: string[]; }; }; //# sourceMappingURL=smart-edit.d.ts.map