Skip to content

ever-better@0.4.1

Choose a tag to compare

@isamu isamu released this 11 Aug 00:53
· 93 commits to main since this release
bd85d8a

Highlights

Four bug fixes, every one of them found by using the tool rather than reading it — three while writing the article about it, and one by running the whole loop on a fresh clone of debug-js/debug.

The security tier is no longer dropped on browser bundles (#43)

security.configs.recommended was removed entirely for runtime: "browser", on the ground that everything it looks for is Node-side. Two of its rules are not:

  • detect-object-injection is the only rule in the whole generated config that sees a lookup answering out of the prototype chain. A key of "constructor" returns an inherited function, the if (!found) guard never fires, and no type objects — Record<string, T> claims that key holds a T. Six of those were found in a browser TypeScript UI, and two more in a 13-year-old JavaScript CLI.
  • A super-linear regex freezes the tab, not the server.

The plugin now loads everywhere, with the seven genuinely Node-side rules turned off on browser bundles instead. A rule that cannot fire costs nothing; a rule that is missing cost six bugs.

mocha is recognised as a test runner (#43)

detectTestRunner knew only vitest, jest and node:test, so a repository testing with mocha came back "none" — and "none" is what bootstrap installs vitest on. The cost was not a wrong label in a report; it was a second test runner added to a repository that already had one. mocha, ava, tap and jasmine are now detected from dependencies. A bare test script is still not evidence: "test": "echo nope" names no runner.

ever-better strictness no longer appears in the migrate skill (#43)

The skill told the agent to run a command that does not exist, mid-migration. Strictness pricing is something bootstrap already does — it enables each flag in a temporary config extending yours, counts the errors, and reports which it turned on for free and what the rest would cost.

freeze says where the unsuppressable errors are (#43)

Before: WARNING: N errors could not be suppressed and will fail CI. Fix the config before committing. — no rule, no file. These errors have no rule id by definition, so the message sent the reader to the config they had just generated rather than to the file that was failing. Now:

WARNING: 5 error(s) could not be suppressed and will fail CI:
  5  (parse error)

Where they are:
  src/browser.js:1  /* eslint-env */ comments are no longer supported.
  src/browser.js:134  Unused eslint-disable directive (no problems were reported from 'no-return-assign').
  src/common.js:205  Unused eslint-disable directive (no problems were reported from 'no-negated-condition').

The sample is capped at three in the formatter, which keeps its output constant-size the way the counts already are.

Also in this release

ever-better next --fan-in (#42)

next ranks the backlog by how much work a rule is; it said nothing about how far that work travels. A no-explicit-any in a module twenty files import changes an exported shape and lands errors in files the diff never opened. The same finding in a leaf is one edit. --fan-in counts importers per row. It is a flag rather than a default because it reads every source file, and it changes no ordering — a widely imported file is only expensive for the rules that move types, and that judgement stays with you.

Skill guidance: what a real drain actually looks like (#43, #44)

ever-better-drain gained the operational half that only shows up on a repository measured in thousands:

  • Two bug families worth hunting by hand. The prototype lookup, with a note that the type system endorses it — Record<string, Thing> says table[key] is a Thing, so the not-found branch is dead code that typechecks. And super-linear regexes, where the rule was measured wrong in both directions.
  • Leaving no-explicit-any off at the start is sequencing, not surrender. Finding the ten violations that matter inside 1,413 is not a thing anyone does, and every other rule's findings sit underneath it. Clear the mechanical tiers first; taken last it is the largest single reduction available, taken first it is where the effort stops on day one. What off must not become is the end state.
  • The largest any group is annotations written over types the library already publishes — delete, do not write.
  • Directory-level promotion off → warn → error, because a file list goes stale the moment someone adds a sibling.
  • Slicing an identical group is for review accuracy, not reviewer capacity. The reviewer is an agent and will read all 167 hunks without complaint; what falls as the diff grows is recall, and in an identical group every hunk looks plausible.
  • For a refactor too big to characterise with a handful of tests, keep the old body and diff against it. The old implementation already knows every answer, so make it the oracle. Two things make or break it: the inputs must not be ones you invented, and the old body must be copied rather than delegated to.

docs/RESULTS.md and a report-a-run template (#43)

The bug-yield claim rests on two repositories and both are mine. This says so plainly and asks for rows — numbers only, no source. A run that found zero real bugs is the most useful row that table could get, and the least likely to be volunteered.

Verified

  • yarn install / typecheck / lint / build / test — green, 270 tests, on Linux, macOS and Windows
  • claude plugin validate . passes on both the working tree and a fresh clone of main
  • End-to-end on a fresh clone of debug-js/debug: diagnose → bootstrap → freeze → check, with a new violation producing exit 1 while the existing 72 stayed silent

📦 npm: ever-better@0.4.1


What's Changed

  • feat: Vue, React, Next and Nuxt support by @isamu in #1
  • fix: ship the ESLint formatter in the npm package by @isamu in #2
  • docs: badges, verified publish/install, and the Python assessment by @isamu in #3
  • chore: dependabot, duplication and dead-code scans by @isamu in #4
  • chore: remove the dead exports knip found by @isamu in #8
  • feat: the drain and dry skills — the phases that actually clean up by @isamu in #10
  • feat: ever-better-run — the unattended mode, and lead with it by @isamu in #11
  • feat: worklist, work log, and a stale-diagnosis warning by @isamu in #12
  • fix: keep Prettier off what ever-better generates by @isamu in #13
  • feat: measure what is actually enforced, not what the config says by @isamu in #14
  • chore(deps): Bump actions/setup-node from 5 to 7 by @dependabot[bot] in #5
  • chore(deps): Bump github/codeql-action from 3 to 4 by @dependabot[bot] in #9
  • chore(deps): Bump actions/checkout from 5 to 7 by @dependabot[bot] in #6
  • chore(deps-dev): Bump @types/node from 24.13.3 to 26.1.2 in the dev group by @dependabot[bot] in #7
  • feat: generate the whole four-layer setup, and stop the pinned versions rotting by @isamu in #15
  • docs: 0.2.0 — the four layers, the log, and staleness by @isamu in #16
  • fix: wire the gate into repos that already have CI by @isamu in #17
  • feat: turn on the strictness flags that cost nothing, price the rest by @isamu in #18
  • feat: strengthen an ESLint config the repository already wrote by @isamu in #19
  • feat: the security and readability tiers, and why no import plugin by @isamu in #20
  • feat: ever-better-prepare, and a Codex review workflow in CI by @isamu in #21
  • feat: emit-diff — prove a type-only refactor changed nothing by @isamu in #22
  • docs: show what you actually end up with, rendered not transcribed by @isamu in #23
  • test: the lifecycle end to end, against real ESLint by @isamu in #24
  • feat: catalog — the helpers that already exist by @isamu in #25
  • docs: 0.3.0 — refresh the handoff and state the target by @isamu in #26
  • fix: Prettier was never enforced, and casts had four doors left open by @isamu in #27
  • feat: migrate — JavaScript to TypeScript, one file at a time by @isamu in #28
  • docs: 0.4.0 — everything from the original scope has shipped by @isamu in #29
  • docs: size the two remaining directions and link their issues by @isamu in #32
  • docs: move the session's record into the repo by @isamu in #33
  • feat: 160-column Prettier template, one-pass JS migration, and the owner's working rules in the skills by @isamu in #34
  • feat: var → let → const as a lint rule, and a work log the owner can actually read by @isamu in #35
  • docs: make the work log a step in every skill, not only the unattended one by @isamu in #36
  • docs: lead with the Claude Code path, not the CLI by @isamu in #37
  • feat: rank the drain backlog by leverage, with ever-better next by @isamu in #40
  • feat: ever-better next --fan-in — how far a fix in this file reaches by @isamu in #42
  • fix: four things writing the article surfaced by @isamu in #43
  • docs: for a big refactor, keep the old body and diff against it by @isamu in #44
  • ever-better@0.4.1 by @isamu in #45

New Contributors

Full Changelog: https://github.com/isamu/ever-better/commits/0.4.1