From 5.4.0 to 5.7.0

10 August 2026 · covers 5.4.1 through 5.7.0

Four releases. One adds the command that answers the question the other four commands only gesture at. Two are bugs found by running clines on real repositories rather than by reading its source — including one where clines was measuring its own output. The last removes 95.7% of what npm install clines downloads.

5.4.1 — clines -v

It printed unknown option and exited 1. Commander reserves -V for the version and leaves -v free, but -v is what node, npm and git accept, so it is what people type.

The fix rewrites -v only in the global position, so clines count --config -v still treats -v as a path rather than a request for the version. -V and --version are unchanged.

5.5.0 — clines was counting its own reports

--html defaults to writing into the working directory, which is where people put it. That file is then part of the repository, so the next run measures it. On facebook/react, a dup.html (1.2 MB) and a cx.html left over from earlier runs added 11,403 phantom code lines — 3.4% of the repo — and took HTML from 19 files to 21.

HTML   21 files   12,260 code     <- with two clines reports present
HTML   19 files      857 code     <- actual

Nothing about this was exotic: it is reachable by following the documented usage. Reports now carry <meta name="generator" content="clines" />, and the role classifier treats that — or a clines report <title> — as generated, which is excluded from headline figures by default.

Detection is deliberately narrow. A page that merely mentions clines, or whose title is clines usage guide, stays source. Verified two ways: against the real leftover artifacts in react, and by round-trip — writing all three report types into a fixture leaves that fixture's count byte-identical.

5.6.0 — clines refactor

Complexity is a poor priority list. It ranks a file you never open above one you edit every week, because it is largely a proxy for size. Measured over two years of react's history:

So clines refactor (alias rf) reads git log --name-only once — one cheap call, no per-file blame — joins the change count to complexity density and token cost, and returns a verdict per file.

verdict meaning
refactor complex and changed often — you pay for this repeatedly
split expensive to read and changed often, though the logic is simple
watch changed often but cheap to read
quiet rarely touched
inert untouched in this window — leave it alone

The ranking is by tokens × changes: what reading that file for every change to it has already cost, and what it will keep costing. --price 3 turns that into dollars per million tokens.

$ clines refactor ../react --price 3

Refactor: 1,113 files weighed against 2,367 commits since 2 years ago

Judged against this repo: changed often means 3+ changes, dense means 16.7 cx per 100 lines, costly
means 2,420 tokens.

  refactor     122 files   complex and changed often — you pay for this repeatedly
  split        162 files   expensive to read and changed often, though the logic is simple
  watch        257 files   changed often but cheap to read
  quiet        322 files   rarely touched
  inert        250 files   untouched in this window — leave it alone

Ranked by what re-reading them has cost, in tokens
  File                                        Verdict   Changes   Cx/100   Tokens   Re-read     Cost
  packages/react-…ackend/fiber/renderer.js   refactor       151     20.0      64k      9.6M   $28.83
  packages/react-…src/ReactFlightServer.js   refactor       134     17.3      56k      7.5M   $22.41
  packages/react-…t/ReactFiberConfigDOM.js   refactor       114     16.7      54k      6.1M   $18.39

The thresholds are quantiles of the repository being measured, not fixed numbers: dense and costly are its 75th percentile of density and tokens, and changed often is the median change count among files that changed, floored at 2. Verdicts are therefore relative — every repository has a top quartile — and the thresholds are printed so any verdict can be checked against them.

Two things were cut after measuring them. Temporal coupling — which files change together — found 432 pairs on react and zero on bootstrap, too repository-dependent to put in default output. And files with no decision points at all are excluded, because before that react's scripts/error-codes/codes.json came back as split: true in the arithmetic, useless as advice.

5.7.0 — 95.7% of the install was a validator

zod existed in clines to validate clines.json: thirteen fixed fields, twelve string[] and one boolean. It used six of zod's exports — array, string, boolean, object().strict(), .default() and ZodError. No refinements, transforms, unions, coercion or async.

A clean npm install --omit=dev, before and after:

install files config import heap
5.6.0 5,368 KB 611 4.1 ms 1,263 KB
5.7.0 232 KB 15 1.1 ms 204 KB

Part of that was drift nobody noticed: from 3.25, zod's v3 package began shipping the entire v4 and v4-mini codebases plus TypeScript sources, taking it from 0.67 MB to 3.43 MB packed, and a ^3.24.1 range floated onto it.

Pinning back to ~3.24.4 was the obvious cheap fix, and it was measured and rejected. For a consumer already on zod 3.25 or 4.x, npm cannot dedupe a non-overlapping range, so it installs both copies — 5,968 KB across two zods, worse than doing nothing — and 3.24.x has had no release since May 2025, so a future advisory would land above the pinned range.

The replacement is 133 lines with no dependency. The interesting part is how it was checked: a 59-case table generated from the zod build before zod was deleted — 14 accepted configs compared field by field, 45 rejected ones compared against the exact error string clines prints. Building it caught three divergences that would otherwise have shipped:

input zod first attempt
{"respectGitignore": null} rejects silently defaulted to true
{ignore: {folders: […], dirs: "bad"}} field error, then unknown key unknown key first
Object.create({inherited: true}) flags the inherited key did not see it

The first is the one that mattered: ?? treats null as absent, while zod's .default() only fires on undefined. A config saying do not respect .gitignore — written wrongly, but written deliberately — would have been accepted and inverted. Every config error message clines prints is byte-identical to what 5.6.0 printed, and the exit code is still 1.

Upgrading

npm install --save-dev clines@5.7.0

No breaking changes. Two things to expect: if you had left clines' own HTML reports inside a repository you measure, your counts will drop to their correct values (5.5.0), and userConfigSchema is still exported for anyone using clines as a library, alongside the new parseUserConfig and ConfigError.

clines refactor needs a git repository. Everything else works without one.