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

@claudeclaudeBot 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 lintederrorswarningswall
before555700105 s
after555700107 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR 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

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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

@claudeclaudeBot 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 lintederrorswarningswall
before555700105 s
after555700107 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR 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

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@claudeclaudeBot 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 lintederrorswarningswall
before555700105 s
after555700107 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR 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

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@claudeclaudeBot 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 lintederrorswarningswall
before555700105 s
after555700107 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR 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

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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

@claudeclaudeBot 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 lintederrorswarningswall
before555700105 s
after555700107 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR 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

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@claudeclaudeBot 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 lintederrorswarningswall
before555700105 s
after555700107 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR 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

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@claudeclaudeBot 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 lintederrorswarningswall
before555700105 s
after555700107 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR 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

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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

@claudeclaudeBot 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 lintederrorswarningswall
before555700105 s
after555700107 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR 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

@os-project-manager@claude