/** * The line mechanics of a comment prune, and the proof that gates every write. * * Split from `comment-prune.ts` because the two change for different reasons: that * module decides WHAT is debt (and follows the classifier), this one decides how a * line is removed from a file without damaging the file (and follows the language). * Keeping them apart is also what lets the proof be tested on a text array, with no * filesystem and no scan in the room. */ import type { PruneAction, PruneSkip } from './comment-prune.js'; /** What the write phase decided, per file. */ export type WriteOutcome = { touched: string[]; notWritten: string[]; fileSkips: PruneSkip[]; }; /** * Read a file, or report that it is not there — a prune never creates one. * * `existsSync` first, and NO catch: a `catch { return null }` answers "present but * unreadable" (permissions, a locked handle, a directory named as a file) with the same * `null` as "absent", and that conflation is the defect this repository already * ratchets under `silentWarningCatchReturnNull` — this very helper was counted by it. * An absent path is a state the caller handles; an unreadable one should be a crash an * operator sees. */ export declare function readFileSafe(root: string, rel: string): string | null; /** * Split a source into lines, remembering what separated them. * * `mixed` is the signal to leave a file alone rather than a detail to paper over: a * file with both CRLF and LF endings cannot be re-joined from one delimiter without * rewriting every line, and a rewrite of every line is not a comment edit — it would * also make the proof below vacuous, because all the lines would differ. */ export declare function splitLines(source: string): { lines: string[]; eol: string; mixed: boolean; }; /** Group a plan by file, in first-seen order. */ export declare function actionsByFile(actions: readonly PruneAction[]): Map; /** * The proof, as data: compare kept lines against the original. * * One string per violation; empty means every difference between the two texts is a * line this plan named, and every line it named that was kept is byte-identical to * what was there before. This is computed whether or not the caller intends to write, * so a dry run reports the refusals a real run would hit. */ export declare function proofViolations(before: readonly string[], after: readonly string[], actions: readonly PruneAction[]): string[]; /** * The blank lines a set of drops ORPHANS. * * A comment standing alone between two blanks is the only thing separating two blocks: * * } * ← blank * ← blank * type AuditEnvelopeGuard = … * * Deleting the comment joins the two blanks into a double blank, which is not what the * file said before and not what any formatter will keep. So the drop owns that blank line * — it is the residue of a comment removal, not an edit of someone's whitespace. Only a * blank that becomes adjacent to another blank THROUGH a drop is claimed here: a double * blank the repository already carries is left exactly where it is, because the pruner did * not make it and has no business "fixing" it. * * ONE HELPER, BOTH CALLERS: `pruneFileLines` removes these and `proofViolations` allows * them. A second copy of this rule is the defect class this whole feature answers, so the * writer and the checker ask the same function the same question. */ export declare function orphanedBlanks(before: readonly string[], dropped: ReadonlySet): Set; /** Apply one file's actions to its lines, in the plan's own terms. */ export declare function pruneFileLines(lines: readonly string[], actions: readonly PruneAction[]): string[]; /** * Run the proof on every planned file, and write only the files that passed it. * * A file that fails is reported and left alone — never written "mostly", because a * partially-pruned file is exactly the state the proof exists to make unreachable. */ export declare function executePlan(projectRoot: string, byFile: Map, apply: boolean): WriteOutcome;