All 30 checks with why-it-matters prose, severity, and cross-references to related audits.
Without TypeScript strict mode, the compiler silently allows implicit `any` types, skips null-safety checks, and misses a family of type errors that only surface at runtime. ISO 25010:2011 §6.5.4 classifies these as reliability defects. A single `null` dereference in an authentication path or payment flow can take down production. Strict mode is the umbrella flag — disabling it means `noImplicitAny`, `strictNullChecks`, and `strictFunctionTypes` are all off simultaneously.
Why this severity: Critical because disabling strict mode silently opts out of the compiler's entire null-safety and implicit-any detection family, letting whole classes of runtime crashes ship undetected.
code-quality-essentials.type-safety.strict-modeSee full pattern`noUncheckedIndexedAccess` is not covered by the `strict` umbrella — it must be set explicitly, and most TypeScript projects never do. Without it, `array[0]` is typed as `T` even when the array is empty, so `array[0].name` compiles fine and throws `Cannot read properties of undefined` at runtime. ISO 25010:2011 §6.5.4 flags this as a reliability defect. This is one of the highest-signal, lowest-friction compiler flags available: it adds `| undefined` to every index access and forces you to handle the empty case.
Why this severity: Critical because every array index access in the codebase is a potential undefined crash that the compiler is actively hiding from you — one empty API response is enough to trigger it.
code-quality-essentials.type-safety.unchecked-indexed-accessSee full patternImplicit `any` is TypeScript's escape hatch — every untyped function parameter defaults to `any`, which propagates through the call chain and silently disables type checking for all downstream code that touches it. ISO 25010:2011 §6.5.4 classifies this as a maintainability defect. When `noImplicitAny` is off, a renamed field in an API response or database schema causes zero compile-time errors and surfaces only as a runtime crash or silent data corruption in production.
Why this severity: Critical because implicit `any` on a single function parameter can propagate silently through an entire call chain, making TypeScript's type guarantees meaningless for that code path.
code-quality-essentials.type-safety.no-implicit-anySee full patternA missing lockfile means `npm install` resolves dependency versions at install time rather than pinning them. Two developers on the same project — or the same developer on two machines — can get different dependency trees. CWE-1357 and SLSA L1 both identify non-deterministic builds as a supply-chain risk: a transitive dependency can silently introduce a breaking change or a security regression between developer and CI, making bugs impossible to reproduce. SSDF SP 800-218 PW.4.1 requires reproducible builds as a baseline software security practice.
Why this severity: High because non-deterministic installs make bugs non-reproducible between environments and open the door to transitive dependency drift introducing security vulnerabilities undetected.
code-quality-essentials.type-safety.lockfile-committedSee full patternA bare `@ts-ignore` tells TypeScript to stop checking the next line with no record of why. When the underlying problem is later fixed (a third-party type definition is updated, a library is upgraded), the suppression remains silently in place — a form of type-safety debt that accumulates until something breaks. ISO 25010:2011 §6.5.2 classifies unexplained suppressions as a maintainability defect. More than three `@ts-ignore` comments in a codebase is a signal that developers are routinely bypassing the compiler rather than fixing the underlying type issues.
Why this severity: High because unexplained type suppressions accumulate as maintenance debt and mask real type errors that would otherwise be caught before they reach production.
code-quality-essentials.type-safety.no-ts-ignoreSee full patternHigh `any` density in core business logic means TypeScript's safety guarantees are opt-out rather than opt-in across the most critical code paths. ISO 25010:2011 §6.5.5 treats this as a reliability defect: when an API response shape changes, a database column is renamed, or a third-party SDK updates its return type, `any`-saturated code compiles without complaint and fails silently or noisily at runtime. A practical threshold is fewer than 15% of typed positions using `any` — above that, the type checker is too porous to catch refactoring regressions.
Why this severity: High because pervasive `any` usage renders TypeScript's compile-time safety effectively optional, meaning refactoring regressions and API shape changes surface only at runtime.
code-quality-essentials.type-safety.type-coverageSee full patternWith `strictNullChecks` disabled, `null` and `undefined` are subtypes of every type — `string` implicitly includes `null`. This means the compiler cannot warn when you call `.trim()` on a value that could be `null`, or access `.user.name` on a possibly-undefined response. ISO 25010:2011 §6.5.4 classifies null-safety gaps as reliability defects. Null pointer exceptions are one of the most common runtime crashes in production JavaScript: nearly always preventable, never fun to debug from a Sentry alert.
Why this severity: High because disabling strictNullChecks allows null and undefined to flow into any typed value silently, making null-dereference crashes invisible to the compiler until they hit production.
code-quality-essentials.type-safety.strict-null-checksSee full patternA codebase with no test files has no automated verification that the core logic works correctly after a change. Every refactor, dependency upgrade, or new feature becomes a manual regression risk. ISO 25010:2011 §6.5.5 identifies absence of test infrastructure as a reliability defect. For AI-built projects specifically, the lack of tests is a compounding risk: the original author may not remember the edge cases, and future changes can silently break behavior that was never encoded as an assertion.
Why this severity: Medium because absent tests mean every change ships with no automated regression check, making regressions in business logic invisible until a user reports them.
code-quality-essentials.testing.test-files-presentSee full patternHaving test files is necessary but not sufficient — a test suite that only renders components without crashing and ignores business logic in `src/lib/` provides false confidence. ISO 25010:2011 §6.5.5 requires that reliability verification covers the critical paths. A payment calculation function, a permission check, or a data transformation that is never tested will eventually receive a change that breaks it; without a coverage gate, no one learns about it until a user reports a wrong order total or a privilege escalation.
Why this severity: Medium because low unit coverage means business logic changes ship without regression verification, turning every refactor into an undetected risk for production correctness.
code-quality-essentials.testing.unit-coverageSee full patternUnit tests verify individual functions in isolation; they cannot catch bugs that only appear when multiple units interact — a misconfigured middleware, a mismatched API contract between frontend and backend, or an authentication check that passes locally but fails in a real browser session. ISO 25010:2011 §6.5.5 requires verification at the workflow level. For user-facing applications, the most costly bugs are the ones that break the critical path — signup, login, checkout — while all the unit tests are green.
Why this severity: Medium because without integration tests, the most visible user-facing workflows — auth, data submission, core product actions — have no automated verification that they work end-to-end.
code-quality-essentials.testing.integration-testsSee full patternDebug `console.log` statements left in production code expose internal data structures, user data, and system internals in the browser console and server logs — visible to anyone who opens DevTools or has log access. ISO 25010:2011 §6.5.2 classifies this as a maintainability defect. Beyond the information exposure risk, uncontrolled log output fills log aggregators with noise, makes filtering for real errors harder, and signals to engineers that the codebase lacks basic hygiene standards.
Why this severity: Low because console.log leakage is primarily a hygiene and information-exposure issue rather than an immediate exploitable vulnerability, but it degrades log quality and can expose sensitive internal state.
code-quality-essentials.testing.no-console-logSee full patternA test suite that only verifies the happy path does not test the code that actually runs when things go wrong. ISO 25010:2011 §6.5.5 requires reliability verification to cover failure modes. Missing error-path tests mean: API 404 and 500 responses produce untested UI states that may crash or show stale data; invalid inputs reach business logic that was never verified to reject them correctly; `catch` blocks that silently swallow errors pass code review because no test ever triggered them.
Why this severity: Low because untested error paths do not break current functionality but guarantee that failure scenarios — which happen in production — have never been automatically verified.
code-quality-essentials.testing.error-handlingSee full patternUnit tests that call real external services — databases, APIs, email providers — are not unit tests: they are slow, flaky, and environment-dependent. ISO 25010:2011 §6.5.5 requires that tests produce deterministic results. Without consistent mocking, a failing test might be a real bug, a network blip, a rate limit hit, or a service outage — and distinguishing between them wastes engineering time. Real API calls in tests also risk leaking test data to production systems or incurring costs from development runs.
Why this severity: Low because unmocked external calls make tests non-deterministic and environment-dependent, which erodes confidence in the test suite and often leads to tests being ignored or disabled.
code-quality-essentials.testing.mocking-strategySee full patternDeprecated packages signal that the maintainer has abandoned the library and will not ship security patches. OWASP A06 (Vulnerable and Outdated Components) identifies unmaintained dependencies as a primary attack vector — CWE-1104 and CWE-1357 classify this as a supply-chain defect. A package like `request` (deprecated 2020) or `moment` (maintenance-mode, bloated, no new security releases) is a liability: any vulnerability discovered in it will not receive a fix, leaving the codebase permanently exposed.
Why this severity: High because deprecated dependencies will not receive security patches, permanently exposing the application to any vulnerability discovered after the deprecation date.
code-quality-essentials.dependencies.no-deprecatedSee full patternOWASP A06 (Vulnerable and Outdated Components) is the most mechanically preventable entry on the OWASP Top 10 — `npm audit` flags high and critical vulnerabilities automatically, yet most projects never run it in CI. SSDF SP 800-218 PW.4.4 requires active vulnerability scanning of dependencies as a baseline software security practice. A high-severity advisory in a direct dependency (e.g., prototype pollution in `lodash`, RCE in an older `serialize-javascript`) can be fixed with a single `npm audit fix` — but only if you know it's there.
Why this severity: High because unscanned dependencies can carry high or critical CVEs silently for months, with a trivial automated fix available that no one runs because auditing was never wired into CI.
code-quality-essentials.dependencies.security-advisoriesSee full patternCircular module dependencies cause subtle initialization-order bugs that appear only in specific import sequences — the kind that works on the developer's machine and fails in a bundled production build or a Jest environment with a different module evaluation order. ISO 25010:2011 §6.5.1 classifies this as a structural maintainability defect. Circular dependencies also prevent effective tree-shaking: bundlers cannot safely eliminate one half of a cycle, so both modules ship to the client even when only one is needed.
Why this severity: High because circular imports introduce initialization-order bugs that are environment-specific and notoriously hard to diagnose, and they prevent the bundler from tree-shaking either module.
code-quality-essentials.dependencies.no-circularSee full patternBuild tools, test frameworks, and type definitions in `dependencies` instead of `devDependencies` bloat production installs and Docker images with packages that serve no runtime purpose. ISO 25010:2011 §6.5.1 classifies this as a structural defect. More concretely: `typescript` in `dependencies` means your production Node.js server or serverless function installs the TypeScript compiler on every cold start — adding hundreds of milliseconds to deploy times and attack surface for a tool that is never invoked at runtime.
Why this severity: Low because miscategorized dependencies bloat production artifacts and extend attack surface without causing functional failures, making it a hygiene issue rather than a security emergency.
code-quality-essentials.dependencies.dev-dependencies-separatedSee full patternUsing `npm install` in CI instead of `npm ci` means the lockfile is advisory rather than enforced — if `package.json` and the lockfile diverge (a common accident after a manual install), CI installs different versions than the lockfile specifies, silently breaking reproducibility. SLSA L2 and SSDF SP 800-218 PW.4.1 both require that the build process is deterministic and traceable. A non-reproducible build makes security audits meaningless: you cannot verify what code shipped without knowing exactly which dependency versions were installed.
Why this severity: Info because non-frozen CI installs are a build hygiene defect that undermines reproducibility guarantees, but they rarely cause immediate functional failures unless a transitive dependency changes in a breaking way.
code-quality-essentials.dependencies.build-reproducibleSee full patternWithout automated dependency update tooling, dependency updates are deferred indefinitely until a breaking version or security incident forces a painful large-batch upgrade. CWE-1104 and SSDF SP 800-218 PW.4.4 identify stale dependencies as a supply-chain risk. Projects that skip minor versions for 12+ months accumulate multiple breaking changes between their installed version and the current release, turning a routine update into a multi-day migration. Dependabot and Renovate send small, reviewable PRs that keep the gap manageable.
Why this severity: Info because absent update automation does not break current functionality but guarantees that the dependency graph drifts toward stale, potentially vulnerable versions without any active detection.
code-quality-essentials.dependencies.update-policySee full patternPublished libraries without a changelog force consumers to diff two arbitrary git SHAs to understand what changed between versions — or to discover breaking changes at runtime after an upgrade. ISO 25010:2011 §6.5.2 classifies undocumented API surface changes as a maintainability defect. For internal applications this is low priority, but any package with `"private": false` or published `"exports"` fields is making an implicit promise to consumers that it does not keep when it ships breaking changes silently.
Why this severity: Info because missing changelog documentation does not affect runtime behavior but removes the audit trail that lets consumers make informed upgrade decisions for published libraries.
code-quality-essentials.dependencies.breaking-changes-docSee full patternAn unconstrained generic `<T>` is effectively `<T extends unknown>` — it accepts any value, and TypeScript cannot enforce meaningful relationships between `T` and the function's behavior. The symptom is forced casts inside the generic body: if you need `(item as any).id`, the generic is not constrained correctly. ISO 25010:2011 §6.5.4 classifies this as a type-safety defect. Unconstrained generics in utility code are particularly insidious because they look safe from the outside while silently bypassing type checks inside.
Why this severity: Medium because unconstrained generics defeat the purpose of generic typing — they provide an API that looks type-safe but requires internal casts to work, masking real type errors.
code-quality-essentials.organization.generic-constraintsSee full patternOptional boolean flags for mutually exclusive states — `isLoading`, `isError`, `isSuccess` on the same object — allow impossible combinations that the type system cannot prevent: `{ isLoading: true, isSuccess: true }` is a valid TypeScript value, and code that doesn't defensively check every combination will produce undefined behavior. ISO 25010:2011 §6.5.4 classifies this as a reliability defect. Discriminated unions encode the constraint that only one state can be active at a time directly in the type, eliminating an entire class of conditional-logic bugs.
Why this severity: Medium because optional boolean state flags allow impossible combinations that TypeScript cannot detect, producing runtime behavior that depends on which flag the developer happened to check first.
code-quality-essentials.organization.discriminated-unionsSee full patternExported functions and types without JSDoc force every consumer to read the implementation to understand what the module expects, what it returns, and when it throws. ISO 25010:2011 §6.5.2 classifies undocumented public API surfaces as a maintainability defect. In practice, this means every integration with an undocumented utility function is a guess — and wrong guesses about parameter contracts (e.g., whether an ID parameter is the database row ID or the external API ID) produce bugs that only surface in production data.
Why this severity: Medium because undocumented public module surfaces force consumers to reverse-engineer behavior from the implementation, increasing integration errors and maintenance cost.
code-quality-essentials.organization.public-types-exportedSee full patternWhen a test fails, the first signal is the test name in the CI output. A name like `"test1"` or `"works"` tells an engineer nothing about what broke or what regression to investigate — they must open the test file, read the code, and reconstruct the intent. ISO 25010:2011 §6.5.5 classifies this as a maintainability defect: diagnostic value is a measurable property of a test suite. Poor naming also correlates with poor test structure — if a developer cannot name a test precisely, they often haven't defined precisely what they're testing.
Why this severity: Low because vague test names do not break functionality but make failing tests significantly harder to triage, slowing down diagnosis and increasing the time bugs spend in production.
code-quality-essentials.organization.test-naming-conventionSee full patternInconsistent naming conventions in TypeScript code — snake_case variables, Hungarian notation, mixed component casing — increase cognitive load for every developer reading the code and signal that the project lacks enforced standards. ISO 25010:2011 §6.5.2 classifies this as a readability and maintainability defect. In practice, mixed conventions also correlate with inconsistent module structure, inconsistent error handling, and higher defect rates: they are a proxy signal for how rigorously the codebase enforces its own standards.
Why this severity: Low because naming inconsistencies do not cause runtime failures but reliably increase cognitive load, make code review harder, and correlate with weaker overall standards enforcement.
code-quality-essentials.organization.naming-conventionsSee full patternCommented-out code blocks and abandoned files (`api-old.ts`, `utils.backup.ts`) create ambiguity about which implementation is authoritative and inflate the mental model required to understand the codebase. ISO 25010:2011 §6.5.2 classifies this as a maintainability defect. For AI-built projects, the risk is compounded: a new coding session may incorrectly treat the abandoned code as the canonical version and reintroduce bugs that were already fixed in the active implementation.
Why this severity: Low because dead code does not cause runtime failures but creates maintenance overhead and, in AI-assisted workflows, risks reintroducing previously fixed bugs from the abandoned version.
code-quality-essentials.organization.dead-code-removedSee full patternThrowing strings, numbers, or plain objects instead of `Error` instances destroys the stack trace entirely — the thrown value arrives at the `catch` handler with no indication of where in the call stack the problem originated. ISO 25010:2011 §6.5.4 classifies this as a reliability defect. Beyond diagnostics, plain-object throws break `instanceof` checks that callers use to distinguish error types: a catch handler checking `if (err instanceof NotFoundError)` silently falls through when the throw was `{ code: 404, text: '...' }`.
Why this severity: Low because non-Error throws do not prevent the exception from being caught, but they destroy stack traces and break instanceof-based error discrimination, making errors significantly harder to diagnose and handle correctly.
code-quality-essentials.organization.custom-error-classesSee full patternWithout ESLint or Biome, code quality issues — unused variables, implicit `any`, unsafe type assertions, missing `await`, console.log in production — are caught only by the developer's own review discipline rather than automatically on every commit. ISO 25010:2011 §6.5.4 classifies absent static analysis as a maintainability defect. The practical effect: every code review must manually check for issues that a linter would flag in milliseconds, and issues that slip through become permanent residents of the codebase.
Why this severity: Low because absent linting does not cause immediate failures but means an entire category of preventable defects — unused variables, unsafe patterns, style violations — is caught only by manual review, if at all.
code-quality-essentials.organization.eslint-configuredSee full patternMixing `.then()` chains and `async/await` in the same codebase — or in the same function — creates inconsistent error propagation: `.then()` chains require `.catch()` while `async/await` uses `try/catch`, and mixing them means some rejections are caught by one mechanism and some by the other. ISO 25010:2011 §6.5.2 classifies this as a maintainability defect. Nested `.then()` chains also re-introduce the callback-pyramid problem that `async/await` was designed to solve, making control flow harder to read and independent async operations sequential when they could run in parallel.
Why this severity: Low because mixed async styles do not cause immediate failures but produce inconsistent error handling that leads to unhandled rejections and makes the control flow significantly harder to read and maintain.
code-quality-essentials.organization.async-consistencySee full patternWithout a PR template or documented code review checklist, review quality is entirely reviewer-dependent — type safety checks, test coverage verification, and breaking change documentation happen only when a reviewer happens to think of them. ISO 25010:2011 §6.5.3 classifies undocumented review processes as a maintainability defect. PR templates enforce a minimum bar on every PR: the author must explicitly confirm that tests pass, lint is clean, and breaking changes are documented — reducing the rate of issues that slip through to main.
Why this severity: Info because absent PR templates do not break functionality but allow preventable defects to ship through code review gaps that a checklist would have caught.
code-quality-essentials.organization.code-review-checklistSee full patternRun this audit in your AI coding tool (Claude Code, Cursor, Bolt, etc.) and submit results here for scoring and benchmarks.
Open Code Quality Essentials Audit