You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The TypeScript lint gate has holes, but not the ones this issue was filed about — and the shipped library is already clean. PR #755 (merged 2026-07-09) added a lint:lib CI step that gates javascript/src/** excluding tests. That surface is now 0 problems. The remaining ungated debt is 230 problems, and it lives in test files (209), one example package that CI silently skips (19), and two root-level files (2).
Ask: ratify the revised acceptance criteria below (two of the original five are now wrong) before implementation starts.
The "~269 problems" figure re-measures to 253 today, and 0 of them are in the shipped library.
The "18 import/no-unresolved" are not root-package debt and not a resolver bug — they are a nested React example that lints green under its own config.
New: the lint gate silently depends on pnpm build having run, and pnpm -r run lintsilently skips any package without a lint script.
Status: refactor, P3 - low, tech-debt, investigating. Awaiting AC ratification. Full spec below.
For Agents — full spec
Investigation
Root finding: the issue's premise was correct when filed and is now half-obsolete. The shipped library is already gated and already clean; the real remaining debt is test files, one silently-skipped example package, and two structural gaps in how the gate is composed.
Measurement environment
All numbers below were measured this turn, not inherited:
Repo state
langwatch/scenario @ 88aec40 (main, 2026-07-30)
Command
pnpm install --frozen-lockfile → pnpm build → eslint . --format json, run in javascript/
ESLint
9.39.4 (with nested-config lookup active — it picks up examples/openai-realtime-demo/realtime-client/eslint.config.js)
Node / pnpm
v24.13.0 / 10.22.0
Headline numbers (post-build)
pnpm lint (= eslint . from javascript/) → 253 problems (241 errors, 12 warnings). 106 are autofixable; 145 remain after eslint . --fix.
Rule composition of the 209 in src/** tests: 106 @typescript-eslint/no-explicit-any, 98 import/order, 5 unused-eslint-disable directives. 105 of the 106 any are in a single file, src/agents/__tests__/red-team.test.ts.
Finding 1 — AC 1 is already half-shipped
lint:all is pnpm -r --parallel run lint. pnpm -rexcludes the workspace root even though pnpm-workspace.yaml lists . in packages: — verified empirically (pnpm -r --parallel exec node -e "console.log(process.cwd())" prints only the four examples/* paths). So the issue's "lint:all is examples-only" claim is correct.
But #755 added a second CI step, lint:lib (eslint 'src/**/*.ts' --ignore-pattern '**/*.test.ts' --ignore-pattern '**/__tests__/**'), in .github/workflows/javascript-ci.yml:92-94. That step is green and the surface it covers is 0 problems. The gap AC 1 describes is now: test files, root-level files, and examples/custom-observability — not "the root package."
Finding 2 — AC 2 targets a non-problem (the 18 import/no-unresolved)
All 18 post-build import/no-unresolved errors are in examples/openai-realtime-demo/realtime-client/**.tsx — @/lib/utils, @/components/ui/* (its Vite @/* path alias) and @openai/agents/realtime (its own dependency). They appear only when the root ESLint is pointed at that subtree, because the root tsconfig.json has no @/* paths and that package's deps live in its own node_modules.
That package has its own eslint.config.js and its own lint script, and pnpm lint inside it exits 0. So this is not "an eslint-import-resolver config gap" in the root package — it is a cross-package-cwd artifact of linting a nested app from the parent. The correct action is to exclude that subtree from any root-level gate (it is already covered by lint:all), not to add resolver config or suppressions.
Finding 3 — NEW: the lint gate silently depends on build state
Before pnpm build, pnpm lint:allfails — 63 import/no-unresolved errors in examples/vitest, all Unable to resolve path to module '@langwatch/scenario', because the workspace self-reference resolves through dist/. After pnpm build it is green (exit 0). Same effect on the root: eslint . is 321 problems pre-build vs 253 post-build.
CI passes only because Lint runs after Build and Smoke-load dist in javascript-ci.yml. A contributor running pnpm lint:all on a fresh clone gets 63 spurious errors, and any future reordering of CI steps turns the gate red for a reason unrelated to code quality.
Finding 4 — NEW: pnpm -r run lint silently skips packages with no lint script
examples/custom-observability has no lint script in its package.json. pnpm -r --parallel run lint skips it without erroring, so its 19 problems (8 no-explicit-any, 4 @typescript-eslint/no-unused-vars, 3 import/order, 3 unused-imports/no-unused-vars, 1 unused-imports/no-unused-imports) are invisible. This is the same invisibility failure the issue is about, one level up — and it re-opens the moment anyone adds a sixth workspace package.
examples/openai-realtime-demo's script is eslint agents/ index.ts, which happens to cover everything it owns today, but is enumerated rather than glob-complete and will drift the same way.
Finding 5 — config bug: Node globals not applied to scripts/
eslint.config.mjs applies globals.browser to **/*.{js,mjs,cjs,ts} and globals.node only to **/*.config.{js,mjs,cjs,ts} + eslint.config.mjs. So scripts/generate-noise-samples.mjs — a Node script — is linted with browser globals and reports no-undef on Buffer. This is one of only two root-level problems, and it is a real config defect, not source debt.
Challenge findings
Ran per create-issue/steps/challenge.md against the proposal as written. Three flips:
Challenge
Effect on the proposal
"Just add pnpm lint to CI" is the obvious implementation of AC 1 and it is wrong — it duplicates lint:all over examples, re-imports the 18 false positives from Finding 2, and inherits the build-order coupling from Finding 3.
AC 1 must specify the gate's scope, not just its existence.
AC 4 (no-explicit-any → zero) is the largest cost and the weakest value: 106 of 114 are in test files, 105 in one file. Retyping test mocks changes what the test exercises — real regression risk, near-zero user-visible benefit, and the type contract that actually ships is already at 0.
Scope AC 4 to "no NEW any" / warn in tests, rather than zero.
A gate that is red on day one gets disabled. If the gate lands before the cleanup, the next contributor adds --max-warnings or deletes the step.
Order matters: the gate must be green at the moment it lands.
✅ Recommended — no duplication, no cross-package false positives
Fix resolver config for the 18
❌ Rejected — not a resolver bug (Finding 2)
Add a workspace check that fails when a package has no lint script
✅ Recommended — closes Finding 4 permanently
Findings for the implementer
Do not measure with eslint . pre-build. Always pnpm build first, or the numbers are inflated by 68.
--fix clears 106 of 253: import/order goes 102 → 1, and the 5 unused-eslint-disable directives go to 0. Everything left is no-explicit-any (114), import/no-unresolved (18, all Finding-2 false positives), unused-vars (7), react-hooks (4), no-undef (1).
red-team.test.ts alone is 105 of the 106 test-file any. Treat it as its own unit of work; do not bundle it with the gate change.
Ruled out: the import/no-unresolved errors are not caused by eslint-import-resolver-typescript misconfiguration. tsc --noEmit resolving fine is consistent with Finding 2 (different cwd, different tsconfig), not with a resolver gap.
Caveats
Nested-config lookup behaviour is ESLint-version-dependent (measured on 9.39.4). If the pinned ESLint changes, re-measure before trusting the examples/ column.
examples/openai-realtime-demo has pnpm.overrides in a non-root package.json; pnpm warns it has no effect. Unrelated to lint, but it surfaces on every -r run and will show up in any new gate's logs.
Proposed AC changes
Ratified under dec.2026-08-01-scenario-565-lint-ac-set and graded by ac-reviewer, which returned 7 Must-Fix against the first draft; all are folded in below.
#
Original AC
Disposition
Basis
1
Root-package lint runs in CI
Rewritten to name the scope
#755 already gates src/** non-test (0 problems). The residual is tests + root-level files + one skipped example.
2
Fix the resolver so the 18 import/no-unresolved clear
DROPPED
Measured: all 18 are in examples/…/realtime-client/**.tsx (its own @/* Vite alias) and that package exits 0 under its own config. Not root-package debt, not a resolver bug.
3
import/order auto-fixed
Kept, sharpened
98 in src/** tests + 1 root-level.
4
no-explicit-any driven to zero
Scoped to a severity policy + ratchet
106 of 114 are tests, 105 in one file, and they are (agent as any).privateMember. Typing them away means widening the shipped API.
5
Lint green and stays enforced
Split into AC9 + AC11
"Green" is meaningless on a draft PR whose ci-checks job was skipped.
—
—
Added AC5–AC8, AC10, AC11
The two new findings, plus the regression/ripple/escape-hatch surface.
Acceptance Criteria
AC1 — The gate covers every root-owned file, and each sub-scope is proven gated. One CI step runs pnpm lint:root, covering src/**/*.ts (including **/*.test.ts and **/__tests__/**), root-level *.ts, and scripts/**, and excluding examples/**. Evidence, both: (a) pnpm lint:root --format json | jq -r '[.[].filePath] | .[]' lists at least one path from each of src/ non-test, src/**/__tests__/, the root-level *.ts set, and scripts/, and zero paths under examples/; (b) three injected defects, each reverted — an import/order swap in red-team.test.ts, an unused variable in demo-sliding-deadline.ts, and an undefined global in scripts/generate-noise-samples.mjs — each turning the step red on its own.
AC2 — import/order is clean across the newly-gated surface.pnpm lint:root --format json | jq '[.[].messages[] | select(.ruleId=="import/order")] | length' prints 0 (baseline: 98 in src/** tests, 1 in demo-sliding-deadline.ts). --fix resolves 99; the residual one in src/voice/__tests__/playback.test.ts is fixed by moving the import, not by suppressing the rule.
AC3 — no-explicit-any is error in src/** non-test and ratcheted, not unbounded, in tests. The rule stays error for src/**/*.ts excluding tests (0 today). In tests it is warn under a hard ceiling: lint:root runs --max-warnings=106, so a 107th warning fails CI. This is the one --max-warnings use AC9 exempts. Evidence, both: (a) red on an any added to a non-test src/ file; (b) red on a 107th any in red-team.test.ts. Explicitly not required: driving the existing 106 to zero.
AC4 — Linting without a build reports the real cause instead of 63 resolver errors.Reframed from "the gate must not depend on build state": the examples consume @langwatch/scenario through its published exports map into dist/, so needing a build is legitimate consumer behaviour — the defect was that its absence produced 63 misleading import/no-unresolved errors. Evidence: with dist/ removed, pnpm lint:all exits non-zero, names javascript/dist as missing, tells the reader to run pnpm build, and emits no@langwatch/scenario resolver errors.
AC5 — No workspace package can escape the gate, including one added tomorrow. A guard enumerates packages at runtime from pnpm list -r --depth -1 --json, never a hardcoded list, and fails naming the package when any lacks a lint script or carries an enumerated one (eslint agents/ index.ts) that a new sibling file would slip past. Evidence, three mutations: (a) red when lint is deleted from examples/vitest; (b) red when a new package with no lint script is added; (c) red on an enumerated lint command — each green again on revert.
AC6 — examples/custom-observability is gated, clean, and its cleanup provably did not gut a probe. It gains "lint": "eslint ." and reports 0 problems (baseline 19). Because nothing in CI executes this package — no lint, no typecheck, no test invocation — the cleanup is proven by running it. Evidence, three: (a) pnpm -F custom-observability-example lint exits 0 with zero messages; (b) pnpm -F custom-observability-example test:all exits 0 with its PASS lines quoted; (c) test-no-auto-init.ts still performs await import("@langwatch/scenario") for its side effect — the unused binding was dropped, the import was not.
AC7 — scripts/** is linted with Node globals.scripts/generate-noise-samples.mjs reports 0 problems (baseline: 1 × no-undef on Buffer) with no inline suppression. Proven in-scope by AC1(b)'s third mutation, not merely by a local run.
Consequence & failure-mode coverage
AC8 — lint:lib and lint:all still cover at least what they covered before. Exit 0 alone would also pass for a gutted script, so the criterion is set-based: each command's linted-file set on this branch is a superset of its set on main, and both exit 0.
AC9 — The gate is green on a run that actually executed it, with no escape hatch added. ⚠ A draft PR yields a green javascript-ci with the lint steps never run — ci-checks is gated on draft == false (javascript-ci.yml:47), there is no ready_for_review trigger, and javascript-complete (:127) reports success when an upstream job is skipped. So the evidence is the ci-checks job's own conclusion, never the aggregate. Plus: no added line introduces eslint-disable, --quiet, continue-on-error, or no-error-on-unmatched-pattern, and no --max-warnings beyond the AC3 ratchet. Added lines only — --fixremoves 5 pre-existing eslint-disable directives that an unqualified diff grep would falsely flag.
AC10 — The import reorder changed no runtime behaviour. A differential, not merely "green": pnpm test before and after the autofix reports identical passed/failed/skipped counts, and pnpm typecheck:all exits 0.
AC11 — The baseline was cleared by fixing code, not by disabling rules or widening ignores. Every bare "reports 0 problems" count above is also satisfiable by import/order: "off", a new ignores: entry, or an --ignore-pattern. Evidence, both: (a) the eslint.config.mjs diff shows no rule moved from error to warn/off and no new top-level ignores: entry, with the single AC3-sanctioned exception; (b) lint:root's linted-file count is ≥ the count on main — the file count must not shrink while the problem count falls.
Plan
Landed as two commits on issue565/enable-clean-root-package-eslint, split so the 40-file mechanical reorder is reviewable apart from the design change.
Commit 1 — the autofix.eslint . --ignore-pattern 'examples/**' --fix over src/** and demo-sliding-deadline.ts. 43 files, import statements only (plus one obsolete comment block in playback.test.ts whose subject — an import placed below vitest — no longer exists). Clears 99 import/order and 5 unused eslint-disable directives.
Commit 2 — the gate.
package.json: new lint:root = eslint . --ignore-pattern 'examples/**' --max-warnings=106; lint:all gains the precondition guard.
eslint.config.mjs: globals.node extended to scripts/**; new warn-severity no-explicit-any block scoped to src/** tests.
scripts/check-lint-coverage.mjs: runtime workspace enumeration; fails on a missing lint script, an enumerated lint script, or a missing dist/.
javascript-ci.yml: new Lint (root package) step; the existing two relabelled and re-commented.
examples/custom-observability: gains "lint": "eslint ."; 19 problems fixed by typing (AgentInput, a ScopedSpan cross-version type) rather than suppression — the langwatch: "disabled" as any casts turned out to typecheck without the cast.
examples/openai-realtime-demo: lint moved from the enumerated eslint agents/ index.ts to eslint . --ignore-pattern 'realtime-client/**'.
specs/typescript-lint-gate-coverage.feature: the AC coverage map.
Deliberately out of scope
Two findings belong to the repo, not to #565, and are named here rather than silently absorbed:
The draft-gate hole is repo-wide.ci-checks skipping on draft + javascript-complete treating skipped as success + no ready_for_review trigger means every required JS check is bypassable by opening as draft and marking ready without a push. AC9 works around it; it does not fix it.
examples/custom-observability is executed by nothing in CI. AC6 gates its lint. Its typecheck and test gaps remain — it has no typecheck script, and the workflow runs only pnpm -F vitest-examples test.
Evidence
Raw measurement artifacts for this investigation (this box, session-local): /tmp/claude-1000/565-lint-full.json (pre-build, 321), /tmp/claude-1000/565-lint-full-postbuild.json (post-build, 253), /tmp/claude-1000/565-lint-postfix.json (post---fix, 145). All reproducible from 88aec40 with the commands in "Measurement environment".
For Humans
The TypeScript lint gate has holes, but not the ones this issue was filed about — and the shipped library is already clean. PR #755 (merged 2026-07-09) added a
lint:libCI step that gatesjavascript/src/**excluding tests. That surface is now 0 problems. The remaining ungated debt is 230 problems, and it lives in test files (209), one example package that CI silently skips (19), and two root-level files (2).Ask: ratify the revised acceptance criteria below (two of the original five are now wrong) before implementation starts.
import/no-unresolved" are not root-package debt and not a resolver bug — they are a nested React example that lints green under its own config.pnpm buildhaving run, andpnpm -r run lintsilently skips any package without alintscript.Status:
refactor,P3 - low,tech-debt,investigating. Awaiting AC ratification. Full spec below.For Agents — full spec
Investigation
Root finding: the issue's premise was correct when filed and is now half-obsolete. The shipped library is already gated and already clean; the real remaining debt is test files, one silently-skipped example package, and two structural gaps in how the gate is composed.
Measurement environment
All numbers below were measured this turn, not inherited:
langwatch/scenario@88aec40(main, 2026-07-30)pnpm install --frozen-lockfile→pnpm build→eslint . --format json, run injavascript/examples/openai-realtime-demo/realtime-client/eslint.config.js)Headline numbers (post-build)
pnpm lint(=eslint .fromjavascript/) → 253 problems (241 errors, 12 warnings). 106 are autofixable; 145 remain aftereslint . --fix.Coverage map — what CI actually gates today
--fixsrc/**non-test — the shipped librarylint:lib, added by #755src/**tests (__tests__/,*.test.ts)demo-sliding-deadline.ts,scripts/generate-noise-samples.mjs)examples/vitestlint:allexamples/openai-realtime-demolint:all(onlyagents/ index.ts)examples/…/realtime-clientlint:all(own config, exit 0)examples/custom-observabilitylintscript — silently skippedRule composition of the 209 in
src/**tests: 106@typescript-eslint/no-explicit-any, 98import/order, 5 unused-eslint-disabledirectives. 105 of the 106anyare in a single file,src/agents/__tests__/red-team.test.ts.Finding 1 — AC 1 is already half-shipped
lint:allispnpm -r --parallel run lint.pnpm -rexcludes the workspace root even thoughpnpm-workspace.yamllists.inpackages:— verified empirically (pnpm -r --parallel exec node -e "console.log(process.cwd())"prints only the fourexamples/*paths). So the issue's "lint:allis examples-only" claim is correct.But #755 added a second CI step,
lint:lib(eslint 'src/**/*.ts' --ignore-pattern '**/*.test.ts' --ignore-pattern '**/__tests__/**'), in.github/workflows/javascript-ci.yml:92-94. That step is green and the surface it covers is 0 problems. The gap AC 1 describes is now: test files, root-level files, andexamples/custom-observability— not "the root package."Finding 2 — AC 2 targets a non-problem (the 18
import/no-unresolved)All 18 post-build
import/no-unresolvederrors are inexamples/openai-realtime-demo/realtime-client/**.tsx—@/lib/utils,@/components/ui/*(its Vite@/*path alias) and@openai/agents/realtime(its own dependency). They appear only when the root ESLint is pointed at that subtree, because the roottsconfig.jsonhas no@/*paths and that package's deps live in its ownnode_modules.That package has its own
eslint.config.jsand its ownlintscript, andpnpm lintinside it exits 0. So this is not "an eslint-import-resolver config gap" in the root package — it is a cross-package-cwd artifact of linting a nested app from the parent. The correct action is to exclude that subtree from any root-level gate (it is already covered bylint:all), not to add resolver config or suppressions.Finding 3 — NEW: the lint gate silently depends on build state
Before
pnpm build,pnpm lint:allfails — 63import/no-unresolvederrors inexamples/vitest, allUnable to resolve path to module '@langwatch/scenario', because the workspace self-reference resolves throughdist/. Afterpnpm buildit is green (exit 0). Same effect on the root:eslint .is 321 problems pre-build vs 253 post-build.CI passes only because
Lintruns afterBuildandSmoke-load distinjavascript-ci.yml. A contributor runningpnpm lint:allon a fresh clone gets 63 spurious errors, and any future reordering of CI steps turns the gate red for a reason unrelated to code quality.Finding 4 — NEW:
pnpm -r run lintsilently skips packages with nolintscriptexamples/custom-observabilityhas nolintscript in itspackage.json.pnpm -r --parallel run lintskips it without erroring, so its 19 problems (8no-explicit-any, 4@typescript-eslint/no-unused-vars, 3import/order, 3unused-imports/no-unused-vars, 1unused-imports/no-unused-imports) are invisible. This is the same invisibility failure the issue is about, one level up — and it re-opens the moment anyone adds a sixth workspace package.examples/openai-realtime-demo's script iseslint agents/ index.ts, which happens to cover everything it owns today, but is enumerated rather than glob-complete and will drift the same way.Finding 5 — config bug: Node globals not applied to
scripts/eslint.config.mjsappliesglobals.browserto**/*.{js,mjs,cjs,ts}andglobals.nodeonly to**/*.config.{js,mjs,cjs,ts}+eslint.config.mjs. Soscripts/generate-noise-samples.mjs— a Node script — is linted with browser globals and reportsno-undefonBuffer. This is one of only two root-level problems, and it is a real config defect, not source debt.Challenge findings
Ran per
create-issue/steps/challenge.mdagainst the proposal as written. Three flips:pnpm lintto CI" is the obvious implementation of AC 1 and it is wrong — it duplicateslint:allover examples, re-imports the 18 false positives from Finding 2, and inherits the build-order coupling from Finding 3.no-explicit-any→ zero) is the largest cost and the weakest value: 106 of 114 are in test files, 105 in one file. Retyping test mocks changes what the test exercises — real regression risk, near-zero user-visible benefit, and the type contract that actually ships is already at 0.any" /warnin tests, rather than zero.--max-warningsor deletes the step.Strategies considered
pnpm lint(eslint .) as a CI steplint:all, re-imports the 18 false positives, build-order coupledlint:libto includesrc/**tests--fixsrc/**incl. tests, root-level*.ts/scripts/), excludingexamples/lintscriptFindings for the implementer
eslint .pre-build. Alwayspnpm buildfirst, or the numbers are inflated by 68.--fixclears 106 of 253:import/ordergoes 102 → 1, and the 5 unused-eslint-disabledirectives go to 0. Everything left isno-explicit-any(114),import/no-unresolved(18, all Finding-2 false positives), unused-vars (7), react-hooks (4),no-undef(1).red-team.test.tsalone is 105 of the 106 test-fileany. Treat it as its own unit of work; do not bundle it with the gate change.import/no-unresolvederrors are not caused byeslint-import-resolver-typescriptmisconfiguration.tsc --noEmitresolving fine is consistent with Finding 2 (different cwd, different tsconfig), not with a resolver gap.Caveats
examples/column.examples/openai-realtime-demohaspnpm.overridesin a non-rootpackage.json; pnpm warns it has no effect. Unrelated to lint, but it surfaces on every-rrun and will show up in any new gate's logs.Proposed AC changes
Ratified under
dec.2026-08-01-scenario-565-lint-ac-setand graded byac-reviewer, which returned 7 Must-Fix against the first draft; all are folded in below.src/**non-test (0 problems). The residual is tests + root-level files + one skipped example.import/no-unresolvedclearexamples/…/realtime-client/**.tsx(its own@/*Vite alias) and that package exits 0 under its own config. Not root-package debt, not a resolver bug.import/orderauto-fixedsrc/**tests + 1 root-level.no-explicit-anydriven to zero(agent as any).privateMember. Typing them away means widening the shipped API.ci-checksjob was skipped.Acceptance Criteria
pnpm lint:root, coveringsrc/**/*.ts(including**/*.test.tsand**/__tests__/**), root-level*.ts, andscripts/**, and excludingexamples/**. Evidence, both: (a)pnpm lint:root --format json | jq -r '[.[].filePath] | .[]'lists at least one path from each ofsrc/non-test,src/**/__tests__/, the root-level*.tsset, andscripts/, and zero paths underexamples/; (b) three injected defects, each reverted — animport/orderswap inred-team.test.ts, an unused variable indemo-sliding-deadline.ts, and an undefined global inscripts/generate-noise-samples.mjs— each turning the step red on its own.import/orderis clean across the newly-gated surface.pnpm lint:root --format json | jq '[.[].messages[] | select(.ruleId=="import/order")] | length'prints0(baseline: 98 insrc/**tests, 1 indemo-sliding-deadline.ts).--fixresolves 99; the residual one insrc/voice/__tests__/playback.test.tsis fixed by moving the import, not by suppressing the rule.no-explicit-anyiserrorinsrc/**non-test and ratcheted, not unbounded, in tests. The rule stayserrorforsrc/**/*.tsexcluding tests (0 today). In tests it iswarnunder a hard ceiling:lint:rootruns--max-warnings=106, so a 107th warning fails CI. This is the one--max-warningsuse AC9 exempts. Evidence, both: (a) red on ananyadded to a non-testsrc/file; (b) red on a 107thanyinred-team.test.ts. Explicitly not required: driving the existing 106 to zero.@langwatch/scenariothrough its publishedexportsmap intodist/, so needing a build is legitimate consumer behaviour — the defect was that its absence produced 63 misleadingimport/no-unresolvederrors. Evidence: withdist/removed,pnpm lint:allexits non-zero, namesjavascript/distas missing, tells the reader to runpnpm build, and emits no@langwatch/scenarioresolver errors.pnpm list -r --depth -1 --json, never a hardcoded list, and fails naming the package when any lacks alintscript or carries an enumerated one (eslint agents/ index.ts) that a new sibling file would slip past. Evidence, three mutations: (a) red whenlintis deleted fromexamples/vitest; (b) red when a new package with nolintscript is added; (c) red on an enumeratedlintcommand — each green again on revert.examples/custom-observabilityis gated, clean, and its cleanup provably did not gut a probe. It gains"lint": "eslint ."and reports 0 problems (baseline 19). Because nothing in CI executes this package — no lint, no typecheck, no test invocation — the cleanup is proven by running it. Evidence, three: (a)pnpm -F custom-observability-example lintexits 0 with zero messages; (b)pnpm -F custom-observability-example test:allexits 0 with its PASS lines quoted; (c)test-no-auto-init.tsstill performsawait import("@langwatch/scenario")for its side effect — the unused binding was dropped, the import was not.scripts/**is linted with Node globals.scripts/generate-noise-samples.mjsreports 0 problems (baseline: 1 ×no-undefonBuffer) with no inline suppression. Proven in-scope by AC1(b)'s third mutation, not merely by a local run.Consequence & failure-mode coverage
lint:libandlint:allstill cover at least what they covered before. Exit 0 alone would also pass for a gutted script, so the criterion is set-based: each command's linted-file set on this branch is a superset of its set onmain, and both exit 0.javascript-ciwith the lint steps never run —ci-checksis gated ondraft == false(javascript-ci.yml:47), there is noready_for_reviewtrigger, andjavascript-complete(:127) reports success when an upstream job isskipped. So the evidence is theci-checksjob's own conclusion, never the aggregate. Plus: no added line introduceseslint-disable,--quiet,continue-on-error, orno-error-on-unmatched-pattern, and no--max-warningsbeyond the AC3 ratchet. Added lines only —--fixremoves 5 pre-existingeslint-disabledirectives that an unqualified diff grep would falsely flag.pnpm testbefore and after the autofix reports identical passed/failed/skipped counts, andpnpm typecheck:allexits 0.import/order: "off", a newignores:entry, or an--ignore-pattern. Evidence, both: (a) theeslint.config.mjsdiff shows no rule moved fromerrortowarn/offand no new top-levelignores:entry, with the single AC3-sanctioned exception; (b)lint:root's linted-file count is ≥ the count onmain— the file count must not shrink while the problem count falls.Plan
Landed as two commits on
issue565/enable-clean-root-package-eslint, split so the 40-file mechanical reorder is reviewable apart from the design change.eslint . --ignore-pattern 'examples/**' --fixoversrc/**anddemo-sliding-deadline.ts. 43 files, import statements only (plus one obsolete comment block inplayback.test.tswhose subject — an import placed belowvitest— no longer exists). Clears 99import/orderand 5 unusedeslint-disabledirectives.package.json: newlint:root=eslint . --ignore-pattern 'examples/**' --max-warnings=106;lint:allgains the precondition guard.eslint.config.mjs:globals.nodeextended toscripts/**; newwarn-severityno-explicit-anyblock scoped tosrc/**tests.scripts/check-lint-coverage.mjs: runtime workspace enumeration; fails on a missinglintscript, an enumeratedlintscript, or a missingdist/.javascript-ci.yml: new Lint (root package) step; the existing two relabelled and re-commented.examples/custom-observability: gains"lint": "eslint ."; 19 problems fixed by typing (AgentInput, aScopedSpancross-version type) rather than suppression — thelangwatch: "disabled" as anycasts turned out to typecheck without the cast.examples/openai-realtime-demo:lintmoved from the enumeratedeslint agents/ index.tstoeslint . --ignore-pattern 'realtime-client/**'.specs/typescript-lint-gate-coverage.feature: the AC coverage map.Deliberately out of scope
Two findings belong to the repo, not to #565, and are named here rather than silently absorbed:
ci-checksskipping on draft +javascript-completetreatingskippedas success + noready_for_reviewtrigger means every required JS check is bypassable by opening as draft and marking ready without a push. AC9 works around it; it does not fix it.examples/custom-observabilityis executed by nothing in CI. AC6 gates its lint. Its typecheck and test gaps remain — it has notypecheckscript, and the workflow runs onlypnpm -F vitest-examples test.Evidence
Raw measurement artifacts for this investigation (this box, session-local):
/tmp/claude-1000/565-lint-full.json(pre-build, 321),/tmp/claude-1000/565-lint-full-postbuild.json(post-build, 253),/tmp/claude-1000/565-lint-postfix.json(post---fix, 145). All reproducible from88aec40with the commands in "Measurement environment".