refactor(lint): gate every root-owned TypeScript file and close two silent-skip holes (#565) - #865
refactor(lint): gate every root-owned TypeScript file and close two silent-skip holes (#565)#865drewdrewthis wants to merge 3 commits into
Conversation
Mechanical `eslint --fix` output over the surface that PR #755's lint:lib step does not reach: src/** test files and the root-level demo. 99 of the 100 import/order violations were autofixed; the one in playback.test.ts was moved by hand because its builtin import sat below the vitest import behind a comment block explaining a hoisting concern that vi.hoisted() already handles — the comment went with it. No non-import changes. Verified byte-identical test results before and after: 92 files / 1079 passed / 4 skipped, and typecheck:all exits 0. Refs #565
Measured at 88aec40 (post `pnpm install --frozen-lockfile` + `pnpm build`, eslint 9.39.4): `eslint .` in javascript/ reports 253 problems, not the ~269 the issue estimated, and 0 of them are in the shipped library — PR #755's lint:lib step already gates src/ non-test and it is clean. The real ungated debt was 230: 209 in src/** tests, 19 in an example package CI never linted, and 2 root-level. Two of the issue's original five ACs described the wrong surface and were revised under dec.2026-08-01-scenario-565-lint-ac-set: - The 18 import/no-unresolved are NOT root-package debt. Every one is in examples/openai-realtime-demo/realtime-client, from its own `@/*` Vite alias, and that package exits 0 under its own eslint config. Nothing to fix; the root gate excludes examples/ instead. - no-explicit-any is not driven to zero. 106 of the 114 are in tests and 105 of those are `(agent as any).privateMember` in one file; typing them away would mean widening the production API to satisfy a linter. It stays `error` on the library (0 today) and drops to `warn` in tests behind a --max-warnings=106 ratchet, so the count cannot grow silently. New lint:root step covers src/** INCLUDING tests, root-level *.ts and scripts/, excluding examples/. New check-lint-coverage.mjs runs before lint:all and turns two previously-silent failures loud: - `pnpm -r run lint` skips a package with no lint script without erroring. examples/custom-observability sat that way with 19 problems; it now has a lint script and reports 0. The guard enumerates packages at runtime from `pnpm list -r`, so a package added tomorrow cannot escape either. - Linting before a build emitted 63 import/no-unresolved errors, because the examples consume @langwatch/scenario through its exports map into dist/. That dependency is legitimate; the misleading output was not. The guard now names dist/ and says to run `pnpm build`. Also fixes a config defect: globals.node was scoped to *.config.* only, so scripts/ was linted with browser globals and reported no-undef on Buffer. All 16 mutations in the falsifiability battery behave correctly — each gate goes red on its own injected defect and green again on revert. Nothing was cleared by disabling a rule or widening an ignore. Closes #565
WalkthroughThe JavaScript workspace adds root lint coverage, validates package lint scripts and build prerequisites, updates CI lint steps, and cleans import ordering and TypeScript lint findings across tests and examples. ChangesTypeScript lint coverage
Possibly related issues
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
javascript/examples/custom-observability/test-config-file.ts (1)
22-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated
ScopedSpan/getScopeNamehelper across three example files. All three files define the identical type and function to resolve the OTel instrumentation-scope name across SDK v1/v2 shapes. The shared root cause is the missing shared helper module; a future OTel API change requires editing all three files in lockstep.
javascript/examples/custom-observability/test-config-file.ts#L22-L42: importScopedSpan/getScopeNamefrom a new shared module instead of redefining them.javascript/examples/custom-observability/test-custom-scopes.ts#L16-L38: importScopedSpan/getScopeNamefrom the same shared module instead of redefining them.javascript/examples/custom-observability/test-scenario-only.ts#L14-L38: importScopedSpan/getScopeNamefrom the same shared module instead of redefining them.♻️ Proposed shared helper
// javascript/examples/custom-observability/otel-scope.ts import type { ReadableSpan } from "`@opentelemetry/sdk-trace-base`"; /** The two shapes OTel has used for the instrumentation scope, across SDK majors. */ export type ScopedSpan = ReadableSpan & { instrumentationScope?: { name?: string }; instrumentationLibrary?: { name?: string }; }; /** * Returns the instrumentation scope name for a span, handling both * OTel SDK v1 (instrumentationLibrary) and v2 (instrumentationScope). */ export function getScopeName(span: ReadableSpan): string { const s = span as ScopedSpan; return ( s.instrumentationScope?.name ?? s.instrumentationLibrary?.name ?? "unknown" ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@javascript/examples/custom-observability/test-config-file.ts` around lines 22 - 42, Create the shared otel-scope.ts helper containing ScopedSpan and getScopeName, then remove the duplicate definitions and import both symbols in javascript/examples/custom-observability/test-config-file.ts#L22-L42, javascript/examples/custom-observability/test-custom-scopes.ts#L16-L38, and javascript/examples/custom-observability/test-scenario-only.ts#L14-L38. Preserve the existing OTel v1/v2 scope-name fallback behavior in getScopeName.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@javascript/examples/custom-observability/test-config-file.ts`:
- Around line 22-42: Create the shared otel-scope.ts helper containing
ScopedSpan and getScopeName, then remove the duplicate definitions and import
both symbols in
javascript/examples/custom-observability/test-config-file.ts#L22-L42,
javascript/examples/custom-observability/test-custom-scopes.ts#L16-L38, and
javascript/examples/custom-observability/test-scenario-only.ts#L14-L38. Preserve
the existing OTel v1/v2 scope-name fallback behavior in getScopeName.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 37c36f5a-cabb-4ae6-bad7-aeeb419b2899
📒 Files selected for processing (54)
.github/workflows/javascript-ci.ymljavascript/demo-sliding-deadline.tsjavascript/eslint.config.mjsjavascript/examples/custom-observability/package.jsonjavascript/examples/custom-observability/test-config-file.tsjavascript/examples/custom-observability/test-custom-scopes.tsjavascript/examples/custom-observability/test-no-auto-init.tsjavascript/examples/custom-observability/test-scenario-only.tsjavascript/examples/openai-realtime-demo/package.jsonjavascript/package.jsonjavascript/scripts/check-lint-coverage.mjsjavascript/src/agents/__tests__/realtime-adapter-frames.test.tsjavascript/src/agents/__tests__/realtime-adapter-key-resolution.test.tsjavascript/src/agents/__tests__/realtime-echo-noleak.test.tsjavascript/src/agents/__tests__/realtime-echo-safe.test.tsjavascript/src/agents/__tests__/realtime-response-formatter.test.tsjavascript/src/agents/__tests__/realtime-response-guard.test.tsjavascript/src/agents/__tests__/red-team.test.tsjavascript/src/agents/__tests__/user-simulator-tts.test.tsjavascript/src/agents/__tests__/user-simulator-voice.test.tsjavascript/src/agents/__tests__/voice-assistant-role.test.tsjavascript/src/agents/judge/__tests__/judge-agent.test.tsjavascript/src/agents/judge/__tests__/judge-span-collector.test.tsjavascript/src/agents/judge/__tests__/judge-span-digest-formatter.test.tsjavascript/src/agents/judge/__tests__/judge-utils.test.tsjavascript/src/execution/__tests__/proceed-interruptions.test.tsjavascript/src/execution/__tests__/realtime-user-proceed-guard.test.tsjavascript/src/execution/__tests__/scenario-execution-inline-criteria.test.tsjavascript/src/execution/__tests__/scenario-role-attributes.test.tsjavascript/src/execution/__tests__/scenario-scope-attribute.test.tsjavascript/src/execution/__tests__/user-explicit-content-voice.test.tsjavascript/src/execution/__tests__/voice-agent-transcript.test.tsjavascript/src/runner/__tests__/run.test.tsjavascript/src/script/__tests__/interrupt-after-and-user-overrides.test.tsjavascript/src/voice/__tests__/adapter-lifecycle.test.tsjavascript/src/voice/__tests__/connected-state.test.tsjavascript/src/voice/__tests__/duration-fidelity.test.tsjavascript/src/voice/__tests__/factories.test.tsjavascript/src/voice/__tests__/fixtures/fake-adapter.tsjavascript/src/voice/__tests__/hooks.test.tsjavascript/src/voice/__tests__/judge-stt.test.tsjavascript/src/voice/__tests__/playback.test.tsjavascript/src/voice/__tests__/proceed-interrupt-errors.test.tsjavascript/src/voice/__tests__/proceed-interrupt.test.tsjavascript/src/voice/__tests__/result-audio.test.tsjavascript/src/voice/__tests__/transcribe-stale-transcript.test.tsjavascript/src/voice/__tests__/transcribe.test.tsjavascript/src/voice/__tests__/vad-fallback.test.tsjavascript/src/voice/adapters/__tests__/elevenlabs-agent-hangup.test.tsjavascript/src/voice/adapters/__tests__/elevenlabs.test.tsjavascript/src/voice/adapters/__tests__/gemini-live-spans.test.tsjavascript/src/voice/adapters/__tests__/openai-realtime-response-guard.test.tsjavascript/src/voice/adapters/__tests__/openai-realtime-user-call-guard.test.tsspecs/typescript-lint-gate-coverage.feature
…ssions Review found the ratchet did not do what its own docs claimed. `--max-warnings=106` is a TOTAL budget across every warn-level rule in the scope, not a no-explicit-any ceiling. Reproduced: adding one unused variable to demo-sliding-deadline.ts — not a test, not an `any` — took the count to 110 and failed with "ESLint found too many warnings (maximum: 106)". Two defects follow. The budget is fungible (delete 5 test `any`, add 5 unused vars, still green), and an unrelated violation fails CI with a message pointing at a comment about test `any`. ESLint 9.39 ships the right mechanism and I had hand-rolled a worse one. Verified `--suppress-rule` / `--suppressions-location` / `--prune-suppressions` exist in the pinned version, then switched to it: - no-explicit-any goes back to `error` EVERYWHERE, including tests. The severity carve-out is gone. - The 106 known violations are recorded per-file, per-rule in a committed eslint-suppressions.json (3 files). A new `any` fails pointing at the new `any`; an unrelated warn-level violation no longer consumes the allowance. - `--prune-suppressions` ratchets the baseline down as debt is paid, and because the file is committed, any widening is visible in review. Also from review: - check-lint-coverage.mjs -> check-lint-preconditions.mjs (the name now matches what the file says it does), with the decision half extracted as a pure exported function so the rules are testable without spawning a process. - The `/^eslint \./` detector was unsound both ways: it PASSED `eslint . || true` and `eslint . --ignore-pattern '**/*'` (each fully neutering the gate it was meant to prove) and REJECTED the stricter `eslint --max-warnings=0 .`. Now it parses positional arguments and rejects shell operators outright. - The guard could pass vacuously: an empty package list from `pnpm list` meant the loop never ran and it printed OK. Reproducing the exact failure class the file exists to remove. Now a hard error. - execFileSync/JSON.parse had no error handling, so a pnpm failure surfaced as a raw stack rather than the actionable message everything else in the file is careful to produce. - The dist/ check now looks for dist/index.js — a partial or interrupted build leaves the directory present and still produces the resolver noise. - Dropped a `dec.2026-08-01-...` reference from eslint.config.mjs. That ID resolves only in a personal agent codex and was unresolvable to any other contributor. - `--fix` had stripped five unused `eslint-disable no-console` directives and left five whitespace-only lines behind — a non-import change sitting in the commit whose message says there are none. Removed. - Feature file: fixed tag granularity (@Unit for the diff-only scenario, @e2e for the CI-run one), replaced a Background data table no other spec in the corpus uses, and replaced hardcoded pass counts with the relative form the same file already uses elsewhere. - Deduplicated the ScopedSpan OTel-version shim, which was copied verbatim into three probes, into examples/custom-observability/scope-name.ts. - Matched the sibling script's conventions: no shebang, named node:path imports. Falsifiability battery re-run against the new design: 17/17, now including an explicit non-fungibility assertion (an unrelated warn-level violation must leave lint:root GREEN). Suite unchanged at 92 files / 1079 passed / 4 skipped; typecheck:all, lint:root, lint:lib, lint:all all exit 0. Deliberately NOT done here: adding a typecheck script to examples/custom-observability. `typecheck:all` skips it by the same silent-skip mechanism, but adding it surfaces three pre-existing OTel v1/v2 type errors that are out of scope and would land this PR red. Tracked with the exact errors in #867; REQUIRED_SCRIPTS in the guard carries a pointer. Refs #565
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
javascript/scripts/check-lint-preconditions.mjs (1)
71-74: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value
execFileSync("pnpm", …)fails on Windows.
execFileSyncwithoutshellcannot resolvepnpm.cmd. Windows contributors getENOENT, and the wrapper reports it as a broken pnpm enumeration. If Windows development is supported, setshellon that platform.raw = execFileSync("pnpm", ["list", "-r", "--depth", "-1", "--json"], { cwd: packageRoot, encoding: "utf8", + shell: process.platform === "win32", });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@javascript/scripts/check-lint-preconditions.mjs` around lines 71 - 74, Update the execFileSync call used for pnpm enumeration in the lint precondition script to enable shell resolution on Windows, allowing pnpm.cmd to be found while preserving the existing packageRoot, arguments, and UTF-8 output behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@javascript/scripts/check-lint-preconditions.mjs`:
- Around line 56-62: Extend the value-taking flag allowlist in the token loop
before the positional check, updating the condition associated with the visible
token handling so separate values for ESLint flags such as --ignore-path,
--output-file, --max-warnings, --parser, --plugin, --global, --cache-location,
and --report-unused-disable-directives-severity are consumed. Preserve
positional detection for the package-root argument and ensure valid commands
like eslint . --max-warnings 0 do not treat 0 as an additional positional.
---
Nitpick comments:
In `@javascript/scripts/check-lint-preconditions.mjs`:
- Around line 71-74: Update the execFileSync call used for pnpm enumeration in
the lint precondition script to enable shell resolution on Windows, allowing
pnpm.cmd to be found while preserving the existing packageRoot, arguments, and
UTF-8 output behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 2b926f18-0799-48f9-b49f-036303c51c76
📒 Files selected for processing (11)
.github/workflows/javascript-ci.ymljavascript/eslint-suppressions.jsonjavascript/eslint.config.mjsjavascript/examples/custom-observability/scope-name.tsjavascript/examples/custom-observability/test-config-file.tsjavascript/examples/custom-observability/test-custom-scopes.tsjavascript/examples/custom-observability/test-scenario-only.tsjavascript/package.jsonjavascript/scripts/check-lint-preconditions.mjsjavascript/src/agents/__tests__/realtime-echo-safe.test.tsspecs/typescript-lint-gate-coverage.feature
💤 Files with no reviewable changes (2)
- javascript/src/agents/tests/realtime-echo-safe.test.ts
- javascript/eslint.config.mjs
🚧 Files skipped from review as they are similar to previous changes (4)
- specs/typescript-lint-gate-coverage.feature
- .github/workflows/javascript-ci.yml
- javascript/examples/custom-observability/test-custom-scopes.ts
- javascript/examples/custom-observability/test-config-file.ts
| if (token.startsWith("-")) { | ||
| // A value-taking flag written as two tokens consumes the next one. | ||
| if (!token.includes("=") && /^--(ignore-pattern|format|config|rulesdir|ext|resolve-plugins-relative-to|suppressions-location)$/.test(token)) i++; | ||
| continue; | ||
| } | ||
| positionals.push(token.replace(/^['"]|['"]$/g, "")); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
ESLint 9 CLI options that take a separate value argument list
💡 Result:
In ESLint 9, several CLI options are designed to accept multiple values [1][2]. These options allow you to provide these values by either repeating the flag multiple times or by providing a comma-delimited list [3][2]. Common CLI options that support this behavior include: --ext: Specifies additional file extensions to lint [1]. --global: Defines global variables [1][2]. --ignore-pattern: Specifies patterns of files to ignore [1]. --plugin: Specifies plugins to be used [4]. --rule: Specifies rules to be used [4][3]. For example, to define multiple global variables, you can use either of the following approaches [2]: npx eslint --global describe --global it tests/ npx eslint --global describe,it tests/ Note that while most options supporting multiple values accept both methods, there have been historical exceptions; for example, --ignore-pattern has previously had limitations regarding the comma-delimited format [3]. Always refer to the output of eslint --help for the most current information regarding specific flag behavior in your installed version [3].
Citations:
- 1: https://eslint.org/docs/latest/use/command-line-interface
- 2: eslint/eslint@7dd402d
- 3: https://archive.eslint.org/docs/7.0.0/user-guide/command-line-interface
- 4: https://archive.eslint.org/docs/user-guide/command-line-interface
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== repo files named check-lint-preconditions =="
fd -a 'check-lint-preconditions\.mjs$' . || true
echo "== relevant file excerpt if present =="
for f in $(fd 'check-lint-preconditions\.mjs$' .); do
echo "--- $f"
wc -l "$f"
ast-grep outline "$f" || true
sed -n '1,140p' "$f" | cat -n
done
echo "== usages of lintsWholePackage =="
rg -n "lintsWholePackage|check-lint-preconditions" .Repository: langwatch/scenario
Length of output: 7942
🌐 Web query:
ESLint CLI help --max-warnings --ignore-path --output-file --parser --plugin --rule --global --cache-location
💡 Result:
The following list details the ESLint CLI options you requested, based on the current official ESLint documentation [1]. --max-warnings Int: Sets the maximum number of warnings allowed. If the number of warnings exceeds this value, ESLint will exit with a non-zero exit code. The default is -1 (no limit) [1][2]. --ignore-path path::String: Specifies the path to a file containing ignore patterns (similar to.gitignore). This allows you to define files and directories that ESLint should ignore during linting [2][3]. -o, --output-file path::String: Specifies a file path to which the linting report will be written. If not provided, the report is typically output to the console [1][2]. --parser String: Specifies the parser to be used for linting (e.g., to handle specific JavaScript versions or non-standard syntax) [1][2]. --plugin [String]: Specifies plugins to be used, which can provide additional rules and configurations to ESLint [1][2]. --rule Object: Explicitly specifies rules to use. This can be used to override configurations defined in your configuration files [1][2]. --global [String]: Defines global variables, allowing you to tell ESLint about variables that should be considered present in the environment [1][2]. --cache-location path::String: Specifies the path to the cache file or directory. This is used in conjunction with the --cache option to store linting results for files that have not changed, improving performance on subsequent runs [1][4][5]. Note that --cache-file is deprecated in favor of this option [1].
Citations:
- 1: https://eslint.org/docs/latest/use/command-line-interface
- 2: https://manpages.ubuntu.com/manpages/focal/man1/eslint.1.html
- 3: https://archive.eslint.org/docs/4.0.0/user-guide/command-line-interface
- 4: https://github.com/eslint/eslint/blob/f9e54f43/docs/src/use/command-line-interface.md
- 5: https://mintlify.wiki/eslint/eslint/use/command-line
🌐 Web query:
ESLint 9 --report-unused-disable-directives-severity CLI option
💡 Result:
The --report-unused-disable-directives-severity CLI option in ESLint allows you to specify the severity level for reporting unused eslint-disable and eslint-enable directives [1][2]. Key details regarding this option: Purpose: It provides the same functionality as the --report-unused-disable-directives flag but allows you to set a specific severity level rather than relying on a default [1][2]. You cannot use both options simultaneously; if both are specified, ESLint will throw an error [1][3]. Argument Values: The option accepts a single string argument, which can be one of the following [1][2]: - off (or 0) - warn (or 1) - error (or 2) Default Behavior: If this option is not explicitly provided, ESLint defaults to the value defined in the linterOptions.reportUnusedDisableDirectives configuration setting, which itself defaults to "warn" [1][4]. Relationship with other settings: - This option is available in both the ESLint CLI and is intended to complement the linterOptions.reportUnusedDisableDirectives configuration setting found in flat configuration files (eslint.config.js) [4][5]. - The --report-unused-disable-directives flag remains available as a shorthand for setting the severity to "error" [1][3].
Citations:
- 1: https://eslint.org/docs/latest/use/command-line-interface
- 2: https://github.com/eslint/eslint/blob/f9e54f43/docs/src/use/command-line-interface.md
- 3: Change Request: Merge --report-unused-disable-directives-severity into --report-unused-disable-directives eslint/eslint#18666
- 4: https://eslint.org/docs/latest/use/configure/configuration-files
- 5: https://eslint.org/blog/2023/12/eslint-v8.56.0-released/
🌐 Web query:
ESLint 9 --stats CLI option
💡 Result:
The --stats CLI option in ESLint 9 is used to include detailed performance statistics in the linting report [1][2]. When this flag is enabled, ESLint collects performance data—such as parse times, fix times, and lint times per rule—and attaches it to the result objects processed by the formatter [1][3]. Key details about the --stats option: - Purpose: It is primarily designed to allow users and developers to create custom visualizations of ESLint's performance [4]. - Usage: The option takes no arguments [1][2]. It is typically used in conjunction with a formatter that can handle the extra data, such as the built-in json formatter [1][3]. - Example: npx eslint --stats --format json file.js [1][3] - Alternative: You can achieve the same result programmatically by setting the stats: true option in the ESLint constructor when using the Node.js API [3]. Note that not all formatters are designed to output this performance data; it is most compatible with custom formatters or the standard JSON output [1][3]. This feature was introduced as part of the performance improvements in ESLint v9.0.0 [4].
Citations:
- 1: https://eslint.org/docs/latest/use/command-line-interface
- 2: https://github.com/eslint/eslint/blob/f9e54f43/docs/src/use/command-line-interface.md
- 3: https://eslint.org/docs/latest/extend/stats
- 4: https://eslint.org/blog/2024/04/eslint-v9.0.0-released/
Extend ESLint value-flag handling before the positional check.
lintsWholePackage only consumes the next token for one regex allowlist. ESLint also accepts separate-value flags such as --ignore-path <path>, --output-file <path>, --max-warnings <int>, --parser <name>, --plugin <name>, --global <name>, --cache-location <path>, and --report-unused-disable-directives-severity <level>. A valid script like eslint . --max-warnings 0 leaves 0 as a positional, so the guard fails and reports the script does not cover the whole package. Include the relevant ESLint value-flag allowlist or parse only whether the single remaining positional is the package root.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@javascript/scripts/check-lint-preconditions.mjs` around lines 56 - 62, Extend
the value-taking flag allowlist in the token loop before the positional check,
updating the condition associated with the visible token handling so separate
values for ESLint flags such as --ignore-path, --output-file, --max-warnings,
--parser, --plugin, --global, --cache-location, and
--report-unused-disable-directives-severity are consumed. Preserve positional
detection for the package-root argument and ensure valid commands like eslint .
--max-warnings 0 do not treat 0 as an additional positional.
Source: Linters/SAST tools
Lane stopped — this PR is NOT ready for review, correcting my own earlier callThe worker on this branch was stopped today when the owner cut the fleet to one lane. I had recorded this PR as "CI-green, awaiting a human reviewer". That was wrong, and I only caught it by running the tool that owns the bar instead of reading
CI being green was the only part I had right, and it is C2 of eight. What actually remains
State of the work itselfThe three commits are sound and fully on origin ( Worth knowing for whoever resumes: the issue's original premise shifted underneath this work. The worker was briefed to re-baseline because merged PR #755 added a BoardMoved off — orchardist |
rogeriochaves
left a comment
There was a problem hiding this comment.
according to @drewdrewthis, not ready for review
|
Automated low-risk assessment This PR was evaluated against the repository's Low-Risk Pull Requests procedure and does not qualify as low risk.
This PR requires a manual review before merging. |
For Humans
The lint gate had holes — but not the ones #565 was filed about, and the shipped library was already clean. PR #755 (merged 2026-07-09) added a
lint:libstep that gatesjavascript/src/**excluding tests; that surface measures 0 problems today. The real ungated debt was 230: test files (209), one example package CI silently skipped (19), and two root-level files (2).What this PR does: widens the gate to every root-owned file, closes two silent-skip holes, and clears the debt by fixing code — not by turning rules off.
Two of the issue's five original ACs described the wrong surface and were revised (
dec.2026-08-01-scenario-565-lint-ac-set);ac-reviewerthen returned 7 Must-Fix against my first draft and all are folded in. Details in #565.For Agents — full detail
The real numbers
The issue estimated ~269 problems. Re-measured at
88aec40, afterpnpm install --frozen-lockfileandpnpm build(this matters — see below), eslint 9.39.4:pnpm lint= 253 problems (241 errors, 12 warnings); 106 autofixable; 145 survive--fix.src/**non-test — the shipped librarylint:lib(#755)src/**testslint:rootdemo-sliding-deadline.ts,scripts/)lint:rootexamples/custom-observabilitylintscript — skipped silentlylint:allexamples/vitest,examples/openai-realtime-demolint:allexamples/…/realtime-clientlint:allPre-build the same command reports 321, not 253. That 68-problem gap is the subject of AC4.
Two AC corrections worth reading
The 18
import/no-unresolvedare not root-package debt. Every one is inexamples/openai-realtime-demo/realtime-client/**.tsx— its@/*Vite alias and its own dependencies. They appear only when the root ESLint is pointed at that subtree. That package has its owneslint.config.jsandpnpm lintinside it exits 0. There was nothing to fix;lint:rootexcludesexamples/**instead.no-explicit-anyis not driven to zero. 114 total: 0 in the shipped library, 8 in an example, 106 in tests — 105 of them inred-team.test.ts, shaped(agent as any).turnScores.set(...)and(agent as any).getAttackPlan = vi.fn(). That is deliberate private-member access from tests. Removing it means widening the production API or mirroring its privates across ~100 sites. Instead:erroron the library (0 today, stays 0),warnin tests behind a--max-warnings=106ratchet so a 107th fails CI. The count is capped, not ignored.Two structural findings the issue did not have
1.
pnpm -r run lintskips a package with nolintscript, without erroring. That is howexamples/custom-observabilityaccumulated 19 invisible problems.scripts/check-lint-coverage.mjsnow enumerates workspace packages at runtime frompnpm list -r --depth -1 --json— never a hardcoded list — so a package added tomorrow cannot escape either. It also rejects enumerated lint scripts (eslint agents/ index.tslints only what someone remembered that day);examples/openai-realtime-demomoved toeslint . --ignore-pattern 'realtime-client/**'.2. Linting before a build emitted 63 misleading errors. The examples import
@langwatch/scenariothrough its publishedexportsmap, which points atdist/. On a fresh clonepnpm lint:allfailed with 63Unable to resolve path to module '@langwatch/scenario'. CI only passed becauseLintruns afterBuild. Thedist/dependency is legitimate — the examples exercise the real consumer path — so this PR does not remove it; it makes the failure honest. The guard now namesdist/and says to runpnpm build.Also fixed:
globals.nodewas scoped to*.config.*only, soscripts/was linted with browser globals and reportedno-undefonBuffer.How I can prove I was successful
Falsifiability battery — 16/16. Each gate goes red on its own injected defect and green again on revert. Script:
/tmp/claude-1000/565-discrimination.sh(session-local; every mutation is reproducible from the descriptions below).The battery caught one flaw in itself on an earlier run: the unused-variable probe was named
__probeUnused, which matches the config'svarsIgnorePattern: ^_, so M4 passed a mutation the gate correctly ignores. Renaming the probe made it discriminate. Worth stating, because a battery that never fails is not evidence.AC1 — the gate's scope, not just its exit code (
eslint . --ignore-pattern 'examples/**' --format json):The file count rose, so the problem count did not fall by narrowing what gets linted (AC11).
AC6 —
custom-observability's probes still work. Nothing in CI executes this package, so lint-clean is not enough; I ran it:The lint error there was
const scenario = await import(...)— "assigned but never used". Deleting the line would have satisfied the linter and destroyed the probe. The binding was dropped; the side-effect import was kept.AC10 — the 43-file import reorder changed nothing. Identical before and after, not merely "green":
AC8 — no coverage was lost.
lint:lib's command is byte-identical in the diff.lint:allgained only a precondition prefix; thepnpm -r --parallel run linthalf is unchanged, and both example changes are strictly widening (one package gained alintscript, one moved from an enumerated list to a glob). The superset property follows from the diff.AC9 — the gate passed in real CI, on a run that actually executed it.
https://github.com/langwatch/scenario/actions/runs/30691499000/job/91346933892
That is the
ci-checksjob's own conclusion, notjavascript-complete's — the latter reports success when an upstream job isskipped, which is exactly what a draft PR produces.Deliberately out of scope
ci-checksis gated ondraft == false(javascript-ci.yml:47), there is noready_for_reviewtrigger, andjavascript-complete(:127) succeeds when an upstream job isskipped— so a draft PR yields a greenjavascript-ciwith lint never run. This PR is deliberately not a draft so its own gate actually executes. Read theci-checksjob conclusion, not the aggregate.examples/custom-observabilityis executed by nothing in CI — filed as test(ci): examples/custom-observability is type-checked and tested by nothing in CI #867. — no lint (fixed here), notypecheckscript, and the workflow runs onlypnpm -F vitest-examples test. The typecheck and test gaps remain.Review notes
--fix(43 files, imports only + one obsolete comment). Commit 2 is the design change. Review commit 2.no-explicit-any: warnblock scoped tosrc/**/*.test.ts+src/**/__tests__/**.--fixremoved 5 pre-existingeslint-disabledirectives (unused ones). A naivegit diff | grep eslint-disablewill match those deletions — check added lines only.Closes #565