Skip to content

fix(lint): promote build output to the global ESLint ignore, and level the per-object lists - #13679

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-12334-eslint-global-ignores
Aug 31, 2026
Merged

fix(lint): promote build output to the global ESLint ignore, and level the per-object lists#13679
os-project-manager merged 1 commit into
mainfrom
claude/issue-12334-eslint-global-ignores

Conversation

@claude

@claude claude Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes #12334

Adopts option C from the maintainer ruling on that card (2026-08-26, verbatim 「同意」). Both halves ship.

This is a NAMED CORRECTION, not a neutral change

Three rules are enabled on TypeScript under build/, .next/ and .turbo/ today, and this PR deliberately turns them off on build output:

packages/core/build/x.ts   before -> LINTED, 3 rules ENABLED
                             slot-lookup/no-any-assignment
                             no-restricted-syntax
                             query-options/no-any-erasure
                             parser @typescript-eslint/parser
                           after  -> no config object matches: not linted

It is written up in those words rather than as "semantics-neutral" or "changes nothing", because switching a rule off on generated code and switching it off on source are the same line in a diff. What it repairs is this config's own stated intent — never lint build output. Nobody authors the code under those directories, so linting generated code is the defect being removed, not coverage being cut.

The two halves

  1. Global ignore. **/build/**, **/.next/** and **/.turbo/** join the global ignores object that PR fix(lint): take build output out of the linted population with a global ignore #12332 landed (merged 2026-08-25), which carried **/node_modules/** and **/dist/** only.
  2. Structural half. Every per-object ignores list is levelled onto one shared NEVER_LINTED array, so no config object can match build output on its own. The invariant becomes a property of that array instead of seven lists that happen to agree, and it survives a future edit to the global object by someone who has never read this card.

Why this is live, not latent

Measured on the card's thread, 2026-08-25 20:23Z: **/.next/** is live today. apps/docs/.next is 3.9 GB after pnpm --filter @objectstack/docs build, and linting it is a hard OOM rather than a slowdown:

node --stack-size=4000 node_modules/eslint/bin/eslint.js apps/docs \
    --no-inline-config --ignore-pattern '**/.next/**' --format json
exit 0 · 0.46 s · 32 files linted · 0 errors

node --stack-size=4000 node_modules/eslint/bin/eslint.js apps/docs \
    --no-inline-config --format json
exit 134 · 66 s · FATAL ERROR: Ineffective mark-compacts near heap limit

exit 134 with no eslint output reads as a crashed gate, not as 3.9 GB of chunks being parsed. A tracked-file census cannot see this one, because .next is untracked output. build/ and .turbo/ remain latent on that census — git ls-files matches 0 files under any of the five patterns (control: 4,776 under /src/).

Neutrality measurement — source coverage is unchanged

Repo-wide pnpm lint before and after, same tree, ESLint v10.8.1 on Node 22.22.2. Wall figures are shared-box seconds (parallel agents on the same cores), so read them as "no regression", not as a benchmark.

files linted errors warnings wall
before 5557 0 0 105 s
after 5557 0 0 107 s

Stronger than the count: the two --format json file lists are set-identical — 0 files dropped, 0 added. Per-path confirmation over 14 probes through ESLint#calculateConfigForFile: all 8 build-output probes go from linted to not-linted, and every source control keeps exactly the rules it had.

The structural half is verified independently of the global object: with the global ignore object removed from the array, all 12 build-output probes still resolve to 0 rules, while the 6 source controls keep theirs (6, 6, 4, 2, 6, 5 rules).

The required review step

Option C's own price is one review step: confirm nothing depends on the per-object configs still matching build/ output before levelling them. What was checked:

  • git grep for the three glob literals — they occur only in eslint.config.mjs (now once, in NEVER_LINTED). No workflow, script or test names them.
  • The only consumers of this config are three ratchet gates: check:slot-lookup, check:query-options-erasure, check:verify-stand-in. The first two assert on the rule block's files scope and never on its ignores; the measuringConfig helper that rewrites ignores only ever removes baselined file paths from it.
  • Neither baseline holds a path under any of the three directories (scripts/slot-lookup-baseline.json, 25 files; scripts/query-options-erasure-baseline.json, 3 keys; 0 matches for build/, .next, .turbo).
  • Both ratchets re-measured green after the change with unchanged counts — 107 unswept sites in 25 files, 67 non-test sites in 17 files. A levelling that had removed any measured site would have failed them as a ratchet-DOWN, so this is a mechanical confirmation and not only a grep.
  • scripts/check-verify-stand-in-erasure.mjs already runs its own census with SKIP_DIRS containing build, .next and .turbo. A sibling gate was already assuming build output is out of the population; this levelling brings the ESLint config into agreement with it.

One correction to the card's arithmetic: it is four objects, not three

The card and the ruling both say three per-object ignores lists need levelling. Measured on current main, it is four. The fourth is the packages/cli/src/** object, whose ignores list is node_modules, dist, **/*.test.ts — the card's phrase "list only node_modules and dist" excluded it by wording, but it matches build output exactly as the other three do (packages/cli/src/build/x.ts measured as LINTED with 3 rules enabled before this change). All four are levelled here, and the three objects that already carried the globs read from the same array, so all seven now share one list. The operative clause of the ruling — "so no config object can match build output" — is what is implemented.

Gates

Run on e39276b31, the head of this branch, after the final commit:

  • pnpm lint — exit 0, 5557 files, 0 errors, 0 warnings
  • pnpm check:slot-lookup — "slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new, and every file in the population parsed"
  • pnpm check:query-options-erasure — "query-options-erasure ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new, and every file measured parsed"
  • pnpm check:verify-stand-in — "2 stand-in check(s) guarded, 7 candidate(s) explicitly exempt, 10 call site(s) reached, 0 asserted driver arguments"
  • pnpm check:nul-bytes — "OK (scanned 7555 text file(s) ... no raw ASCII control bytes)"

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack names no check family for this path; the four gates above are the ones that import this config, plus pnpm lint, which CI runs on every PR.

No changeset, and the PR carries skip-changeset: the diff is one root config file, it publishes nothing from any package, and it is the same disposition as PR #12332, which changed the same file.

Draft on purpose — the dispatching PM seat arms it after review.

Generated by Claude Code


Generated by Claude Code

…r-object lists

`**/build/**`, `**/.next/**` and `**/.turbo/**` join the global `ignores` object
alongside `**/node_modules/**` and `**/dist/**`, and every per-object `ignores`
list is levelled onto one shared `NEVER_LINTED` array so no config object can
match build output on its own.

NAMED CORRECTION, not a neutral change: three rules
(`slot-lookup/no-any-assignment`, `no-restricted-syntax`,
`query-options/no-any-erasure`) are enabled on TypeScript under those
directories today and this deliberately turns them off on build output. That
repairs the config's own stated intent — never lint build output — rather than
cutting coverage: nobody authors the code there.

Maintainer ruling 2026-08-26 (option C), verbatim 「同意」.

Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Decision] Should **/build/**, **/.next/** and **/.turbo/** join the global ESLint ignore — three rules are enabled there today

2 participants