SnagBadgerA little help for every snag.

Static quality gates#

Run pnpm verify before handing off a change. Production code, examples, tooling, fixtures, and prototypes receive the same checks. Generated output and dependencies are excluded. Checks fail on violations; there is no grandfathered baseline or warning-only rollout.

Commits use pnpm check:commit for parallel staged formatting, strict typed linting and tagged critical tests within an eight-second shared deadline. Timeouts and failures block the commit. The full gate remains required in CI and before handoff; see commit checks.

Complexity and size#

CheckLimitScope
ESLint complexity10 independent paths per functionAll authored JS/TS, including tests
ESLint max-depth3 nested control-flow blocksAll authored JS/TS, including tests
ESLint max-params4 parametersAll authored JS/TS, including tests
ESLint max-lines300 code linesAuthored JS/TS except test files
check:css300 code linesAuthored CSS except test files

File limits exclude blank and comment-only lines. A line containing code and a comment still counts. CSS uses PostCSS comment locations, preserving quoted comment-like text and source line numbers. Malformed CSS fails the check.

Test files use .test or .spec before the extension, or live under tests/. They are exempt only from file length. A fixture under testing/fixtures/, browser harness helper, or prototype remains subject to the limit. ESLint covers .js, .mjs, .cjs, .jsx, .ts, .mts, .cts, and .tsx.

Cyclomatic complexity counts branching paths, including conditional expressions, logical operators, defaults, and optional chains. The selected classic variant counts switch cases independently. Nesting measures control-flow blocks rather than JSX indentation. These are guardrails, not measures of design quality. Extract cohesive responsibilities and simplify control flow; avoid arbitrary file chunks, forwarding wrappers, compressed formatting, or moving production logic into tests to satisfy a number. Do not add lint suppressions to bypass the policy. If a limit needs revision, change the documented policy, enforcement, and regression tests together.

The JS/TS and CSS file limits share tooling/quality/limits.mts. Enforcement configuration lives in eslint.config.mjs; CSS checks live in tooling/quality/css-lines.mts.

Inline lint policy#

The root ESLint noInlineConfig option makes authored inline directives inert. Block eslint-disable, eslint-disable-line, eslint-disable-next-line, and inline eslint rule configuration cannot hide violations or override severity or rule options. ESLint reports the directive's location with a has no effect diagnostic, alongside the original violation. Remove the directive and fix the code, or obtain human approval for an explicit documented and tested policy revision. There is no per-location waiver or automatic suppression removal.

Both pnpm lint (--max-warnings 0) and the supported staged lint checker reject warnings as well as errors, so even a directive without an underlying violation fails. ESLint configuration comments such as global and exported are also inert; declare legitimate scope in the maintained configuration. Strings, template literals and explanatory comments that merely mention directives remain ordinary source text. Existing configuration scope distinctions, including the test-file length override and generated/dependency exclusions, remain unchanged. The staged commit gate retains its eight-second shared deadline.

The passing/failing fixtures in tooling/eslint/suppression.test.mts exercise the real root configuration and both lint paths, including a used suppression hiding a violation when the safeguard is absent. This uses ESLint's maintained directive parser rather than a second custom comment scanner.

Names and architecture#

pnpm check:architecture already enforces lowercase kebab-case file and directory names, explicit conventional-name exceptions, and registered extensions/suffixes. Use profile-scenario.tsx, profile.test.ts, and vite.config.mts; PascalCase React symbols are fine, PascalCase filenames fail. New names, root files, packages, public entries, and runtime dependency classifications require deliberate registration in tooling/architecture/policy.mts. Windows-reserved device names are rejected even through conventional-name exceptions. Case-folded file and directory collisions are checked independently of the host filesystem.

The same gate checks module dependency directions, import cycles, unresolved and incorrectly cased local imports, private imports, browser/server separation, and deferred optional UI/capture loading. Runtime child_process loading is confined to explicitly registered tooling adapters and test files; the check covers imports, re-exports, dynamic imports and require. See architecture enforcement.

Unused code and dependencies#

pnpm check:unused runs the pinned Knip configuration in knip.config.mts. Unused files, exports, types, and dependencies, unlisted dependencies, and configuration hints fail the gate. Public SDK entries are explicitly registered; exports from supported public entries remain available to external consumers. Internal exports are checked for actual consumers.

The guarded browser workflow allocates its server and run directory before loading Playwright configuration. Knip's Playwright plugin is disabled because it executes those configurations without an owned run. Both Playwright configs, browser journeys, workflow tests, Vite configs, and the subprocess consumer checker are explicit analysis entries; their imports remain checked. This does not permit running browser tests outside the guarded commands.

The root entries include browser examples, test fixtures, and the standalone consumer evaluator launched as a subprocess. Tool scripts and standard tool configuration are also discovered from package scripts and Knip plugins. The shared PostCSS tooling in tooling/stylesheets/imports.mts follows quoted CSS imports, including quoted url() paths and Tailwind imports with modifiers, so unused local stylesheet files remain visible to Knip. Remote resources are excluded from its dependency output.

Reporting's build configuration lives in tooling/build/reporting.config.mts and uses the same import interpretation to emit standalone optional skins. Skin expansion accepts unconditional quoted relative .css imports within the reporting source tree, preserving nested and repeated import order. Missing files, cycles, source-tree escapes, package/remote paths, query strings, and conditional imports fail with diagnostics. Analysis retains conditional and package imports for Knip; expansion deliberately supports the smaller local skin grammar. Keep build tooling outside SDK modules to preserve dependency direction checks.

The two root dependency exceptions are @snagbadger/capture and @snagbadger/embedding: tooling/consumers/check.mts writes their imports into generated consumer fixtures. Knip cannot infer those string-generated imports; the consumer verification suite resolves and builds them. This is not an exception for SDK source or general unused dependencies. Revisit these entries when changing the consumer harness. Do not broaden ignore patterns to silence new failures; register genuine entry points or remove abandoned code.

Commands and regression checks#

pnpm lint
pnpm check:css
pnpm check:unused
pnpm build
pnpm test:quality
pnpm test:rules
pnpm check:architecture
pnpm test:architecture
pnpm verify

test:quality includes the existing baseline checks and requires a preceding build. Its additional fixtures test exact complexity/size/nesting/parameter boundaries, test-file overrides, CSS comments/strings/Unicode and exclusions, CSS dependency extraction and nested skin expansion/failure diagnostics, and Knip's rejection of abandoned files, exports, and dependencies. Existing custom-rule and architecture regression suites remain separate required checks.

References: ESLint complexity, file length, nesting, and Knip configuration.