5.0.0: auditing our own output

10 August 2026 · covers 4.4.0 through 5.0.0

A tool that tells you where your codebase wastes effort should be able to survive the same question. So 4.4.0 through 5.0.0 came out of pointing that question at clines itself — first at what it spends, then at what it puts on your screen. Both audits were done by measuring rather than by reading the code and forming opinions, which turned out to matter.

4.4.0 — three kinds of repeated work

Profiling every command against facebook/react found three redundancies, none of which were obvious from reading the source.

Glob patterns were recompiled per file, per pattern. isIgnoredFile called globToRegExp inside a .some(), so react's 44 .gitignore entries produced 304,216 RegExp constructions on every run.

isIgnoredFile over 6,914 files:  171 ms
precompiled equivalent:           12 ms

Every file was classified twice. The collector worked out which lines were code, comment or blank in order to summarise file roles — then tokenize, measureContext and toDupFile each did the identical work again on the same content.

comments spent a third of a second on data it never used. It ran the full context analysis, estimating tokens for every line of every file, purely to rank candidates by comment volume — then re-read and re-classified the fifty files it picked.

command before after
count 1.13s 0.84s
dup 1.23s 0.90s
cx 1.20s 0.82s
ctx 1.28s 0.88s
comments 7.33s 6.81s

The check that mattered was not the stopwatch but the diff: output from count, dup, cx and ctx is byte-identical before and after.

4.5.0 — the output did not fit

Nothing in the codebase read process.stdout.columns, and four separate renderers each hardcoded a 68-character truncation. On a standard 80-column terminal a fifth of the output wrapped, which destroys the alignment the tables exist for.

lines over 80 columns before after
ctx 24 0
cx 22 0
comments 21 0
dup 12 0

Truncation also threw away the wrong end. In a monorepo the package name is the part that tells you where you are, and a leading ellipsis deleted exactly that:

before   …plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts
after    compiler/packages/babel-…/InferMutationAliasingEffects.ts

And a repository that dwarfs a context window now reads as a multiple: 16.5× a 200,000-token window rather than 1651.5% of one.

One finding in this pass was wrong. An early measurement reported a 219-character line in count, which looked like a serious overflow. It was awk counting bytes: the box-drawing character is three bytes in UTF-8, and the divider was 73 characters all along. Re-measuring by code point put every command under 100. Worth stating plainly, because a metrics tool has no business reporting a number it has not checked the units of.

5.0.0 — the rough edges

A walk through the actual journeys — piping, typos, empty directories, malformed config — turned up six problems.

The banner leaked escape codes when redirected. clines > notes.txt wrote ^[[1m^[[38;5;38m… into the file. Nothing checked for a terminal, and NO_COLOR was ignored. Both are now honoured.

One mistyped config key produced ten lines of validator internals:

before   Invalid config in …/clines.json: [
           { "code": "unrecognized_keys", "keys": [ "ignor" ], "path": [], … } ]

after    Invalid config in …/clines.json:
           Unknown key "ignor" — did you mean "ignore"?

--top did not do what it looked like it did. clines cx --top 3 still printed twenty-one rows, because the flag only sized the HTML report. It now sets how many files the terminal lists, on every command — including dup, which had the most results and no limit at all.

--html opened a browser by itself. Writing a file is expected of a command-line tool; launching a graphical application unasked is not. It now takes --open.

Alongside those: comments says what it is doing before spending seven seconds in git blame, an empty directory reports that no files were found instead of drawing an empty table and calling the project a Meteoroid, and the --html hint is dropped when the output is piped, because advice addressed to a human does not belong in a pipeline.

Upgrading

npm went straight from 3.11.1 to 5.0.0, so an upgrade crosses two sets of breaking changes at once.

The intermediate versions were never published. There is nothing between 3.11.1 and 5.0.0 on the registry, and nothing missing from 5.0.0 because of it.