Changelog
Release history and version updates for eslint-plugin-maintainability
Live from GitHub
This changelog is fetched directly from CHANGELOG.md on GitHub and cached for 2 hours.
[3.0.3] - 2026-02-08
3.0.14
Patch Changes
-
#411
d0cc8b6Thanks @ofri-peretz! - Ship the JavaScript without tsc's layout.Every emitted
.jsis re-written through esbuild'sminifyWhitespace, which removes indentation and line breaks. Across the ecosystem that is 3233 kB -> 2023 kB of shipped JavaScript, a 37% cut; on disk a package install drops about 28%. Indentation alone was ~32% of a compiled rule file.This is deliberately NOT minification. Identifiers keep their names, string contents are untouched, and the syntax tree is not rewritten — rule
meta(messages, schema, docs URLs) stays byte-identical, which is what the docs site and--print-configread, and a stack trace from inside a rule still names the function it came from. Full mangling would have bought another 4 kB gzipped and cost both.Verified against the published artifact: identical lint findings including message IDs, identical rule names, and zero differences across every rule's meta, messages, schema and presets.
-
Updated dependencies [
7663cfd,d0cc8b6]:- @interlace/eslint-devkit@1.10.0
3.0.13
Patch Changes
-
#381
74bbf60Thanks @ofri-peretz! - Load rule modules on demand instead of at plugin load.Every plugin barrel used to
requireall of its rules the moment ESLint loaded the plugin, whether or not your config enabled them.plugin.rules[id]is only ever read for rules a config turns on, so the rest was parse-and-compile cost for code that never ran.The published entry now exposes each rule behind a getter, so a rule module is read the first time something asks for it. Measured on a 7-plugin config with 34 rules enabled: 163 rule modules loaded and 251 ms of plugin load, against 34 modules and 8.5 ms — total ESLint wall time 251 ms → 109 ms. On a preset that enables most of a plugin (
node-security/recommended, 25 of 37) it is a wash, 72 ms → 65 ms. It is never slower; the win scales with how many plugins you stack and how few of their rules you use.Nothing about the plugin API changes.
Object.keys(plugin.rules)still lists every rule without loading any of them, repeated reads return the same object, and the./oxlintsub-export is the same plugin object it always was.eslint-plugin-jwtandeslint-plugin-vercel-ai-securityalso re-export their rule objects as named top-level exports, which cannot be deferred — those two keep loading eagerly. -
#381
74bbf60Thanks @ofri-peretz! - Declare what we support, load only what we usetslibis gone from every package. It was a NON-optional peer of@interlace/eslint-devkit, so all 26 plugins declared it as a dependency to satisfy that peer — 124 kB every consumer installed so twelverequire("tslib")calls could resolve. The shipped JavaScript now inlines the TypeScript helpers instead (--importHelpers falseon the emit pass that already re-writes it), costing ~9.5 kB in devkit. Zerotslibrequires remain anywhere; verified by installing every plugin with notslibin the tree and loading all 26 with every rule intact.eslint-plugin-import-nexthad a phantom dependency. Its rulesrequire("typescript")at module load, but it was declared in neitherdependenciesnorpeerDependencies— it worked only because something else in the tree happened to install it. A clean install crashed the whole plugin, not just the type-aware rules.typescriptis now a required peer, which is what the code actually needs.23 "technologies we support" declarations did nothing. Seven plugins listed their target libraries in
peerDependenciesMetawith no matchingpeerDependenciesentry, and npm ignores meta for a package that is not declared a peer — verified by installingeslint-plugin-express-securityand watching nothing install and nothing warn.eslint-plugin-jwtappeared to support six JWT libraries and formally supported none. All 23 are now real optional peers, matching the conventionpg,mongodb,prismaand the other nine already followed:plugin technologies now actually declared eslint-plugin-jwtjsonwebtoken, @nestjs/jwt, express-jwt, jose, jwks-rsa, jwt-decode eslint-plugin-lambda-security@aws-sdk/client-lambda, @middy/core, @middy/http-cors, @middy/http-security-headers, @middy/validator eslint-plugin-express-securityexpress, helmet, cors, csurf, express-rate-limit eslint-plugin-nestjs-security@nestjs/common, @nestjs/throttler, class-validator, class-transformer eslint-plugin-vercel-ai-securityai eslint-plugin-maintainability,eslint-plugin-react-featurestypescript All optional, so nothing is installed on the consumer’s behalf — the declaration is the supported-technology signal, which is exactly what it was meant to be.
A new gate compares declared dependencies against what the emitted JavaScript actually loads, in both directions: a
requirewith no declaration (works until someone installs cleanly) and a declaration nothing requires (weight every consumer pays). It understands that a dependency may exist to satisfy an optional peer of another dependency, which is whyeslint-plugin-import-nextlegitimately declaresoxc-resolverthat devkit lazily loads. -
#335
47cde07Thanks @ofri-peretz! - Fix the./oxlintsubpath export, which pointed atsrc/oxlint.js— a file no build produces.require('<package>/oxlint')threw MODULE_NOT_FOUND on every published package, while every README documented that exact wiring for oxlint'sjsPlugins. The export now points at the build output,dist/src/oxlint.js.The path was hardcoded in
scripts/generate-oxlint-shims.ts, so the generator rewrote any manual correction back to the broken value on the next drift check — fixed there rather than per package.This release also carries npm provenance: the affected packages were last published from a workstation, which has no OIDC token to attest with, so the published tarballs had no attestation. Publishing through the release workflow signs them.
-
#335
47cde07Thanks @ofri-peretz! - Fix SDK peer declarations that npm silently ignoredTwelve plugins listed their target SDKs under
peerDependenciesMetawith{"optional": true}but never declared them inpeerDependencies. npm drops anypeerDependenciesMetaentry that has no matchingpeerDependencieskey, so the metadata was inert — these packages effectively declared no SDK peer at all. Nothing warned: the failure mode of a dependency you never declared is silence.Each SDK now appears in both maps, matching the shape
eslint-plugin-pgandeslint-plugin-mongodb-securityalready use — a supported major range inpeerDependencies,optional: trueinpeerDependenciesMeta:Plugin SDK Range express-securityexpress^4.0.0 || ^5.0.0helmet^6.0.0 || ^7.0.0 || ^8.0.0cors^2.0.0csurf^1.0.0express-rate-limit^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0jwtjsonwebtoken^8.0.0 || ^9.0.0@nestjs/jwt^9.0.0 || ^10.0.0 || ^11.0.0express-jwt^7.0.0 || ^8.0.0jose^4.0.0 || ^5.0.0 || ^6.0.0jwks-rsa^3.0.0 || ^4.0.0jwt-decode^3.0.0 || ^4.0.0lambda-security@aws-sdk/client-lambda^3.0.0@middy/core^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0@middy/http-cors^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0@middy/http-security-headers^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0@middy/validator^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0maintainabilitytypescript>=4.8.4nestjs-security@nestjs/common^9.0.0 || ^10.0.0 || ^11.0.0@nestjs/throttler^4.0.0 || ^5.0.0 || ^6.0.0class-validator^0.14.0 || ^0.15.0class-transformer^0.5.0react-featurestypescript>=4.8.4vercel-ai-securityai^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0jwt-securitysame six as jwt(identical ranges) openai-securityopenai^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0@openai/agents>=0.1.0 <1.0.0anthropic-security@anthropic-ai/sdk>=0.1.0 <1.0.0@anthropic-ai/claude-agent-sdk>=0.1.0 <1.0.0gemini-security@google/genai^1.0.0 || ^2.0.0mcp-sdk-security@modelcontextprotocol/sdk^1.0.0Ranges were taken from each SDK's real release history, bounded below by the oldest major whose call shape the rules still match and above by the current major.
cors,csurf,class-transformerand@aws-sdk/client-lambdahave only ever shipped one usable major. Theairange spans v4 becauserequire-max-stepsdeliberately accepts both the v4maxStepsoption and the v5+stopWhenform. The twotypescriptentries reuse the>=4.8.4bound@interlace/eslint-devkitalready declares, since these are the same type-aware-graceful rules behind the same optional TS program.Every range admits the version this repo's
__compatibility__specs are actually tested against, so the declaration cannot drift from what CI proves.The four SDKs still on
0.x(@openai/agents, both Anthropic packages) use an explicit>=0.1.0 <1.0.0rather than a caret, because^0.115.0resolves to>=0.115.0 <0.116.0— a range narrow enough to warn on almost every real install. These rules match on call shape and never import the SDK, so the honest constraint is the pre-1.0 line, not a single minor.peer-declaration-integrity.test.tsnow locks the invariant across every workspace package: apeerDependenciesMetakey with nopeerDependenciestwin fails the suite and is named in the diff. This class had already been fixed once, in a commit that never merged — nothing went red in its absence, so the bug came back on four newly published packages. A silent failure needs a lock, not review attention.Nothing to migrate. Every entry stays optional, so no install adds a package or emits a warning when the SDK is absent. What changes is that a consumer on an unsupported major now gets a peer warning instead of nothing — which was the point of the metadata in the first place.
-
Updated dependencies [
85e57a7,74bbf60,e5d31ab,1fb1cad,d1a3d8c]:- @interlace/eslint-devkit@1.8.0
3.0.12
Patch Changes
- #364
86baa02Thanks @ofri-peretz! - Add the ecosystem and oxlint marks to the README logo row. Each plugin now leads with Interlace -> its ecosystem (node, nestjs, express, react, mongodb, postgresql, mysql, sqlite, prisma, drizzle, knex, typeorm, sequelize, lambda, vercel, jwt) -> oxlint -> ESLint; the generic quality plugins carry the row without an ecosystem mark. README-only change - no rule behaviour is affected. The patch bump is what carries the new README onto npm, which only refreshes a package README on publish.
3.0.11
Patch Changes
-
#358
1b8c0dfThanks @ofri-peretz! - Fix SDK peer declarations that npm silently ignoredSeven plugins listed their target SDKs under
peerDependenciesMetawith{"optional": true}but never declared them inpeerDependencies. npm drops anypeerDependenciesMetaentry that has no matchingpeerDependencieskey, so the metadata was inert — these packages effectively declared no SDK peer at all. Nothing warned: the failure mode of a dependency you never declared is silence.Each SDK now appears in both maps, matching the shape
eslint-plugin-pgandeslint-plugin-mongodb-securityalready use — a supported major range inpeerDependencies,optional: trueinpeerDependenciesMeta:Plugin SDK Range express-securityexpress^4.0.0 || ^5.0.0helmet^6.0.0 || ^7.0.0 || ^8.0.0cors^2.0.0csurf^1.0.0express-rate-limit^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0jwtjsonwebtoken^8.0.0 || ^9.0.0@nestjs/jwt^9.0.0 || ^10.0.0 || ^11.0.0express-jwt^7.0.0 || ^8.0.0jose^4.0.0 || ^5.0.0 || ^6.0.0jwks-rsa^3.0.0 || ^4.0.0jwt-decode^3.0.0 || ^4.0.0lambda-security@aws-sdk/client-lambda^3.0.0@middy/core^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0@middy/http-cors^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0@middy/http-security-headers^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0@middy/validator^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0maintainabilitytypescript>=4.8.4nestjs-security@nestjs/common^9.0.0 || ^10.0.0 || ^11.0.0@nestjs/throttler^4.0.0 || ^5.0.0 || ^6.0.0class-validator^0.14.0 || ^0.15.0class-transformer^0.5.0react-featurestypescript>=4.8.4vercel-ai-securityai^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0Ranges were taken from each SDK's real release history, bounded below by the oldest major whose call shape the rules still match and above by the current major.
cors,csurf,class-transformerand@aws-sdk/client-lambdahave only ever shipped one usable major. Theairange spans v4 becauserequire-max-stepsdeliberately accepts both the v4maxStepsoption and the v5+stopWhenform. The twotypescriptentries reuse the>=4.8.4bound@interlace/eslint-devkitalready declares, since these are the same type-aware-graceful rules behind the same optional TS program.Every range admits the version this repo's
__compatibility__specs are actually tested against, so the declaration cannot drift from what CI proves.Nothing to migrate. Every entry stays optional, so no install adds a package or emits a warning when the SDK is absent. What changes is that a consumer on an unsupported major now gets a peer warning instead of nothing — which was the point of the metadata in the first place.
-
Updated dependencies [
e8e9ee6]:- @interlace/eslint-devkit@1.7.0
3.0.10
Patch Changes
-
#338
dc25c81Thanks @ofri-peretz! - Re-publish every package so npm carries the optimised artifactNo source changed. This is a no-op patch whose entire purpose is to ship the artifact the current build already produces.
Manifests.
scriptsanddevDependenciesare now stripped from every publishedpackage.json. Neither can do anything in a consumer’s node_modules — npm never runs one and never installs the other — but they shipped in all 27 manifests, cluttered the npm page, and were read by SCA tools scanning installed manifests. No package declares a lifecycle hook, so nothing observable changes. Every published package is bumped so this applies uniformly rather than to a subset.Tarballs. 20 packages were last published before the build pipeline changed and still ship
AGENTS.md,CHANGELOG.md, JSDoc in the emitted.js, and the full generated.d.tstree:package published rebuilt saving eslint-plugin-react-features547 kB 320 kB −227 kB eslint-plugin-secure-coding653 kB 477 kB −176 kB eslint-plugin-conventions241 kB 116 kB −125 kB eslint-plugin-browser-security380 kB 291 kB −89 kB eslint-plugin-maintainability178 kB 116 kB −62 kB eslint-plugin-react-a11y232 kB 173 kB −59 kB eslint-plugin-reliability148 kB 90 kB −58 kB eslint-plugin-vercel-ai-security187 kB 130 kB −57 kB eslint-plugin-operability90 kB 43 kB −47 kB eslint-plugin-jwt140 kB 95 kB −45 kB eslint-plugin-modularity98 kB 58 kB −40 kB eslint-plugin-nestjs-security122 kB 86 kB −36 kB eslint-plugin-sqlite-security54 kB 20 kB −34 kB eslint-plugin-sequelize-security54 kB 21 kB −34 kB eslint-plugin-prisma-security52 kB 19 kB −33 kB eslint-plugin-mysql-security52 kB 19 kB −33 kB eslint-plugin-typeorm-security52 kB 19 kB −33 kB eslint-plugin-drizzle-security52 kB 19 kB −33 kB eslint-plugin-knex-security51 kB 19 kB −32 kB eslint-plugin-modernization45 kB 38 kB −7 kB Those 20 go from 3428 kB to 2169 kB — −36.7%. The remaining 7 were released after the pipeline change and only gain the manifest strip.
A new check in
scripts/check-published-artifacts.tsfails the build ifscriptsordevDependenciesever reappear in a published manifest, so the strip cannot silently regress.The dependency ranges did not need updating: every plugin pins
@interlace/eslint-devkitwith a caret that 1.6.0 satisfies, verified by a clean install of an unchanged plugin resolving devkit 1.6.0 with zero dependencies and notypescriptin the tree. -
Updated dependencies [
dc25c81]:- @interlace/eslint-devkit@1.6.1
3.0.9
Patch Changes
-
#294
659f6dcThanks @ofri-peretz! - Rewritedescriptionandkeywordson every published package for npm search discovery. npm ranks on name, description, and keywords, and the registry only picks up these fields at publish — so this is metadata-only and takes effect for each package on its next release.Descriptions now lead with the search phrase. Every one starts
ESLint plugin for <the thing you'd search>instead of a brand-first or category-first framing, and names the concrete vulnerabilities the plugin actually detects. Three were corrected while doing so:eslint-plugin-import-nextclaimed "100x faster no-cycle detection". No 100x measurement exists:CLAIMS.mdrecords 3.1x end-to-end (8x in pure rule execution) on a 5,483-file React codebase, and the highest number in any benchmark result is 54.9x on the synthetic corpus. The description now states the real-codebase figure.eslint-plugin-secure-codingclaimed SQL injection, XSS and CSRF coverage — none of which are its rules. It now names what it does detect: LDAP, XPath, XXE, GraphQL and template injection, unsafe deserialization, ReDoS, missing authentication, and PII in logs.eslint-plugin-secure-coding("89 rules") andeslint-plugin-react-a11y("37 rules") hard-coded rule counts that had drifted from reality. Counts are generated intointerlace-numbers.json; hand-typed copies are removed rather than corrected.
Keywords now match the vocabulary of the plugins that rank.
eslint-plugin-security,eslint-plugin-jsx-a11y,eslint-plugin-nandeslint-plugin-importall carry theeslint/eslintplugin/eslint-plugintrio — six of our packages were missingeslintplugin, and every one now carries all three plusstatic-analysis,lintingandcode-quality. Security plugins addsast,appsecandvulnerability;node-securityandsecure-codingalso carrynodesecurity, the exact keywordeslint-plugin-securityranks on. Each plugin gained the CWE identifiers and attack names for what it detects (cwe-78command injection,cwe-22path traversal,cwe-89SQL injection,cwe-79XSS,cwe-347JWT algorithm confusion,cwe-352CSRF,cwe-943NoSQL injection), andnode-securitygained the crypto vocabulary it had been missing entirely despite absorbing the crypto rule set (crypto,cryptography,weak-hash,md5,sha1,timing-attack).No rule behavior, exports, or configuration changes.
-
Updated dependencies [
e1cdf83,659f6dc]:- @interlace/eslint-devkit@1.4.3
3.0.8
Patch Changes
-
#269
7028fe2Thanks @ofri-peretz! - docs: dual-logo README header (Interlace mark + ESLint mark side by side) and closing Interlace footer — refreshes the README rendered on npmjs.com. No runtime changes. -
Updated dependencies [
7028fe2]:- @interlace/eslint-devkit@1.4.2
3.0.7
Patch Changes
- #252
d67e395Thanks @ofri-peretz! - Fix Codecov badge showing "unknown" — switch from flag to component URL format
3.0.6
Patch Changes
- #225
34ff5a8Thanks @ofri-peretz! - CI-only: pin all coverage thresholds at 100% (integration target, merges last).
3.0.5
Patch Changes
-
#200
02e0bafThanks @ofri-peretz! - fix: republishrecommendedpreset with the correct plugin namespaceThe published builds of
eslint-plugin-maintainabilityandeslint-plugin-operabilityshipped arecommendedconfig whose plugin KEY (@interlace/maintainability) did not match its rule PREFIX (@interlace/maintainability/maintainability/…— doubled). ESLint cannot resolve that, so spreading...configs.recommendedthrows "could not find plugin" the moment a consumer lints a file — under both ESLint 9 and 10.The source was corrected in the 2026-05-16 namespace cleanup (alongside
react-features, which has since been republished via other changesets), but these two plugins were never bumped — so npm still serves the broken builds and they are the only two doubled-namespace plugins still unfixed downstream. This republishes them from the corrected source.Regression lock:
packages/eslint-config-interlace/src/ecosystem-integrity.test.tsloads every plugin's every config preset into a real ESLint instance and fails if any rule→plugin reference cannot be resolved. Run it against the builtdist/in the release pipeline (pre-publish) to also catch a stale-artifact publish — the failure mode that let these two ship broken.
3.0.4
Patch Changes
-
#197
ecb8491Thanks @ofri-peretz! - fix: republishrecommendedwith correctly-namespaced, unscoped rule idseslint-plugin-maintainability@3.0.3andeslint-plugin-operability@3.0.5shipped arecommendedpreset whose rule ids carried a doubled, scoped plugin segment (@interlace/maintainability/maintainability/cognitive-complexity) that no registered plugin key matched. Enabling the preset alongside any other config made ESLint throw at load:Could not find plugin "@interlace/maintainability/maintainability".(and the equivalent
@interlace/operability/operabilityfor operability.)The source was already corrected to the bare, unscoped form (
maintainability/cognitive-complexityunder amaintainabilityplugin key) but was never republished, so npm still served the broken build. This release ships the corrected build.plugin.meta.nameis also fixed to the unscopedeslint-plugin-maintainability(was@interlace/eslint-plugin-maintainability, which drifted from the package name and every other plugin).Each plugin is configured on its own — there is no unified config. No rule behaviour changes.
New regression locks in each plugin's
index.test.tsreproduce ESLint's rule-id resolution, pin the plugin name and key as unscoped, and load eachrecommendedpreset in a real ESLint instance — failing closed if a scoped or doubly namespaced config could ship again.
Bug Fixes
- align codecov component IDs with full package names (2831b968)
Documentation
- fix changelog header format across all packages (c3a15082)
❤️ Thank You
- Ofri Peretz
[3.0.2] - 2026-02-06
Bug Fixes
- align codecov component names and update docs components (0a59a86c)
❤️ Thank You
- Ofri Peretz
[3.0.1] - 2026-02-02
This was a version bump only for eslint-plugin-maintainability to align it with other projects, there were no code changes.
Changelog
All notable changes to @interlace/eslint-plugin-maintainability will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Documentation
- 📘 Launched new documentation site: eslint.interlace.tools
- 📝 Achieved 100% documentation parity (both .md and .mdx files)
[3.0.0] - 2026-02-02
This was a version bump only for eslint-plugin-maintainability to align it with other projects, there were no code changes.
[1.0.0] - 2026-01-26
Added
- Initial stable release with 12 maintainability rules
- LLM-optimized error messages for AI-assisted development
- 100% test coverage across all rules
- ESLint 9 flat config support
- TypeScript type definitions for all rule options
Rules
Complexity Rules
| Rule | Description |
|---|---|
max-cognitive-complexity | Limit cognitive complexity score |
max-cyclomatic-complexity | Limit cyclomatic complexity |
max-depth | Limit nesting depth |
max-lines | Limit file length |
max-lines-per-function | Limit function length |
max-params | Limit function parameters |
Code Smell Rules
| Rule | Description |
|---|---|
no-magic-numbers | Disallow magic numbers |
no-nested-ternary | Disallow nested ternary expressions |
no-deep-callback-nesting | Disallow deeply nested callbacks |
no-long-parameter-list | Disallow long parameter lists |
Clean Code Rules
| Rule | Description |
|---|---|
prefer-early-return | Prefer early returns over nested conditions |
no-duplicate-logic | Detect duplicated logic blocks (DRY) |
Presets
recommended- Balanced maintainability thresholds
SOLID Principles Mapping
Rules are annotated with SOLID principle alignment:
- Single Responsibility:
max-lines-per-function,max-lines - Open/Closed:
prefer-early-return
Building secure JavaScript with Interlace? Star the repo to get new rules and CWE coverage as we ship them — or follow the AI-code-security benchmarks behind them.