0007 — Codemods emit text edits, never a reprinted AST
A two-line change buried in a nine-hundred-line reformat is a diff nobody can review, and a diff nobody can review is a diff nobody trusts.
Context
When a release breaks an API, someone has to rewrite customer code. The standard technique is parse → transform the AST → print.
Decision
A codemod produces text edits with byte offsets, not a rewritten file. Matches are located with the TypeScript parser; edits are applied by offset.
the obvious approach — parse, transform the AST, print — reformats everything it touches. A customer who upgrades and finds a two-line change buried in a nine-hundred line reformat cannot review it, and a diff nobody can review is a diff nobody trusts.
Locating with a real parser and editing with offsets gives both halves: from '@/lib/errors' appears inside strings, comments and template literals in a real
codebase, and a regex that rewrites those produces a file that still parses and
is subtly wrong. Every line the codemod did not care about is byte-identical
afterwards.
Consequences
Edits apply back-to-front. Doing it forwards and adjusting positions as you go is the same computation with an accumulator that is wrong the first time somebody adds a case.
Overlapping edits throw rather than resolve. Silently applying both produces mangled source that still parses often enough to reach a customer.
The registry shipped empty, honestly. Registering a made-up codemod to prove the pipeline works would be testing against imagined inputs — the failure this project keeps catching in its own work. The engine was tested with fixtures instead, so the first real codemod was a few lines and a test rather than a design exercise.
The first real one, and what it declines to do
design/use-format-helpers rewrites x.toLocaleDateString() to formatDate(x)
and adds the import. It deliberately leaves toLocaleString() alone: on a Date
that renders date-and-time, on a number a formatted number, and without type
information a codemod cannot tell which. Rewriting a Date to formatNumber
would produce plausible garbage — the worst outcome an automatic edit can have,
because plausible garbage does not get re-read.
Four of its six fixture cases assert that it declines. That ratio is the point: a codemod that is right ninety percent of the time is worse than one that refuses, because nobody re-reads the ninety.
0006 — Managed files carry a marker, the hash lives elsewhere
A comment survives every edit that matters. A version inside the file goes stale and becomes a confident lie.
0008 — There is one validation verb
Five commands means an agent remembers zero. lq check is what the hook runs, what the rules mandate, and what CI runs — the same code path in all three.