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#
| Check | Limit | Scope |
|---|---|---|
ESLint complexity | 10 independent paths per function | All authored JS/TS, including tests |
ESLint max-depth | 3 nested control-flow blocks | All authored JS/TS, including tests |
ESLint max-params | 4 parameters | All authored JS/TS, including tests |
ESLint max-lines | 300 code lines | Authored JS/TS except test files |
check:css | 300 code lines | Authored 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.