Skip to content

Releases: isamu/ever-better

ever-better@1.0.0

Choose a tag to compare

@isamu isamu released this 17 Oct 00:05

No command or flag was added or removed since 0.5.0. This release tightens how the drain loop verifies its own work, moves three decisions out of code that needed real ESLint or tsc into pure, tested modules, and brings the plugin manifest's version, which had stayed at 0.4.0, in line with the package.

Skills

  • Breaking the code to check a test has to put it back (#87, #86). ever-better-drain step 6 now says how to break several things safely in an unattended run: one break at a time, each an exact replacement asserted to match once; a copy of each file compared before every break and after every restore; restore from that copy, never git checkout; file lists in arrays in shell loops (zsh does not split an unquoted variable). When a PR added tests, it counts twice — with every test, and with only the tests that existed before it.
  • Run what CI runs before every push (#88, #86). ever-better-drain step 8 and ever-better-run now say to run the whole suite with CI's flags before pushing, and to read the diff for the platforms that cannot run locally (POSIX paths in test expectations, assumed \n, POSIX-only scripts). One rule per PR means many PRs, and each one that goes red costs a fix, a push and another review.

Internal

Each move below was proved by running the old code beside the new over generated inputs, and the generator and property were kept as a permanent test.

  • check's verdict is a pure module (#79). src/checkVerdict.ts: errors fail first, then counts above their ceiling, else pass. It was reachable only through real ESLint before.
  • The strictness-flag decision is pinned and extracted (#80). Guard tests on freeFlags / pricedFlags, and the report moved into src/strictnessReport.ts. A wrong call here breaks the user's typecheck at once, since type errors cannot be suppressed.
  • One counter for tsc type errors (#81). src/typeErrorCount.ts replaces two copies in migrate and the strictness probe.

Dependencies

Plugin

  • .claude-plugin/plugin.json and marketplace.json now carry the package version; they had stayed at 0.4.0 through 0.4.1 and 0.5.0.

📦 ever-better@1.0.0

What's Changed

  • ever-better@0.5.0 by @isamu in #68
  • docs: ChangeLog for 0.5.0 by @isamu in #69
  • chore(deps-dev): Bump the dev group with 2 updates by @dependabot[bot] in #71
  • chore(deps-dev): Bump the eslint group with 2 updates by @dependabot[bot] in #70
  • chore(deps-dev): Bump the eslint group with 2 updates by @dependabot[bot] in #72
  • chore(deps-dev): Bump the dev group with 3 updates by @dependabot[bot] in #73
  • chore(deps-dev): Bump the dev group across 1 directory with 3 updates by @dependabot[bot] in #76
  • chore(deps-dev): Bump the dev group with 3 updates by @dependabot[bot] in #78
  • chore(deps-dev): Bump the eslint group across 1 directory with 3 updates by @dependabot[bot] in #77
  • refactor: move the check verdict into a pure checkVerdict module by @isamu in #79
  • refactor: pin and extract the strictness-flag decision by @isamu in #80
  • refactor: count tsc type errors in one pure module by @isamu in #81
  • docs(drain): breaking the code to check a test has to put it back by @isamu in #87
  • chore(deps-dev): Bump the dev group across 1 directory with 4 updates by @dependabot[bot] in #85
  • chore(deps-dev): Bump the eslint group across 1 directory with 4 updates by @dependabot[bot] in #84
  • docs(drain): run what CI runs before every push by @isamu in #88

Full Changelog: 0.5.0...1.0.0

0.5.0

Choose a tag to compare

@isamu isamu released this 23 Aug 22:41

Highlights

Three new commands, and every one of them exists because a repository can pass check and still be unsafe.

ever-better tier — every rule an error, and the list may only shrink

freeze records today's violations and stays silent about them. That is the right default and it cannot put a squiggle under the line somebody is editing.

tier answers the same question the other way. Every rule stays an error; the file-and-rule pairs failing today become warnings in a generated eslint-tier.config.mjs. Fix what a file is listed for, run it again, and the entry disappears — the rules get stricter with nobody editing a config.

ever-better tier            # take a tier, and record it
ever-better tier --check    # compare against the ledger, write nothing  (for CI)

.ever-better/tier.json records a count per pair, because a files-scoped block downgrades the whole pair — a second violation of a rule a file is already excused for would otherwise be a warning nobody fails on. tier --check is the CI gate: it writes nothing into the repository, so it runs on a read-only checkout and leaves git status clean. Before recording anything, both paths run ESLint and confirm no rule the list excuses is still reported as an error — text that looks like wiring is not the same as wiring that works.

ever-better secrets — the one check with no baseline

Scans the whole git history with gitleaks. A leaked key cannot be grandfathered: it is either in the history or it is not, so this command has no ceiling to raise and no baseline to pin. bootstrap also generates a secret-scan.yml workflow.

ever-better report — where the findings are, not just how many

status says how big the backlog is. report says where it is — per rule and per area, as markdown, for a CI job comment.

What else changed

  • eslint-disable is now banned in generated configs (#57). An inline disable comment is an exemption the ratchet cannot see or reclaim, so a repo could drain its baseline to zero while the violations moved into comments. Generated configs now set noInlineConfig alongside reportUnusedDisableDirectives, and ban any, as casts, @ts-ignore and @ts-expect-error.
  • Generated files are what Prettier would write (#67). The generated eslint.config.js failed the prettier/prettier rule it switches on, so every repository that ran bootstrap started with a violation the tool itself wrote. Fixed, and pinned by a test that runs Prettier over every generated sample.
  • bootstrap no longer reformats package.json (#48), and will not overwrite a knip script somebody else wrote.
  • CI detection understands yarn run lint (#62). A workflow step written as yarn run lint rather than yarn lint was read as "no lint in CI", so diagnose reported a gap that was not there.
  • node:test is detected whatever launches node (#63) — node --test, a wrapper script, or a runner flag buried in a longer command.
  • A solution-style tsconfig.json is followed to the project that compiles the code (#64). A root tsconfig holding only references has no options to read, and reading it as the project reported every strictness flag as absent.
  • Dependency updates: eslint group (#54, #59), knip (#55), js-yaml (#60).
  • ChangeLog for 0.4.1 (#46).

Upgrade notes

Nothing in this release changes an existing freeze/check workflow. tier is an alternative to freeze, not a layer on it — do not run both, as a violation downgraded to warn moves out of the suppression ledger into the warning population. Configs generated by earlier versions are unchanged until you re-run bootstrap.

📦 npm: ever-better@0.5.0


What's Changed

  • docs: ChangeLog for 0.4.1 by @isamu in #46
  • fix: bootstrap reformatted package.json, and could overwrite a knip script by @isamu in #48
  • feat: ever-better report — where the findings are, not just how many by @isamu in #52
  • feat: secret scanning — the one check with no baseline by @isamu in #53
  • chore(deps-dev): Bump knip from 6.32.0 to 6.32.2 in the dev group by @dependabot[bot] in #55
  • chore(deps-dev): Bump the eslint group with 2 updates by @dependabot[bot] in #54
  • fix: an eslint-disable comment is an exemption the ratchet cannot see by @isamu in #57
  • fix: a script run as yarn run lint is still lint running in CI by @isamu in #62
  • fix: node:test is the runner whatever launches node by @isamu in #63
  • fix: a solution-style tsconfig holds no options, so read the project that compiles the code by @isamu in #64
  • chore(deps-dev): Bump globals from 17.10.0 to 17.11.0 in the eslint group by @dependabot[bot] in #59
  • chore(deps-dev): Bump js-yaml from 5.2.3 to 5.3.0 in the dev group by @dependabot[bot] in #60
  • feat: ever-better tier — every rule an error, the files that trip one downgraded to warn by @isamu in #65
  • fix: generated files are what Prettier would write by @isamu in #67

Full Changelog: 0.4.1...0.5.0

ever-better@0.4.1

Choose a tag to compare

@isamu isamu released this 11 Aug 00:53
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 ...
Read more