Static Analysis Engine
50-rule static analysis for TypeScript that sees structure, not just syntax. Replaces SonarQube with a 3-layer pipeline: AST, KERN IR, and optional LLM analysis.
$ npx kern review src/
Why kern review
machine, config, event, and fn nodes structurally.
--graph resolves the full import tree from entry files. Findings on upstream dependencies include their distance from the change, so you can prioritize what matters.
# Trace feature path from entry points kern review --graph src/index.ts # Only review files changed in current diff kern review --diff --graph
Sonar-compatible S3776 implementation. Scores every function independently. Handles else if chains, nested functions, logical operator sequences, recursion, and the ?? operator correctly.
Default threshold: 15. Override with --max-complexity.
--enforce fails the build when thresholds are exceeded. Configure max errors, max warnings, max complexity, and minimum KERN coverage.
kern review --enforce \ --max-errors 0 \ --max-warnings 10 \ --max-complexity 20 \ --min-coverage 60
--sarif outputs SARIF 2.1.0 for GitHub Advanced Security integration. Findings appear as code scanning alerts directly in PRs.
Also supports --json for custom tooling and dashboards.
Set your target and get framework-specific rules automatically. React hooks violations, Vue reactivity bugs, Next.js Server Component errors, Express security issues.
Rule Reference
Base, security, and dead-logic rules are always active. Framework rules activate based on your build target.
| Rule ID | Description | Severity | |
|---|---|---|---|
| Base — Always Active (12 rules) | |||
| floating-promise | Async function or .then() chain called without await, return, or void |
error | |
| state-mutation | Direct mutation of state via .push(), .splice(), delete on state-like objects |
error | |
| empty-catch | Empty catch block silently swallows exceptions | warning | |
| machine-gap | Unreachable states, dead-end states, or transitions referencing unknown states in KERN machine nodes | warning / error | |
| config-default-mismatch | Config interface fields missing from defaults object, or default keys not in the interface | warning | |
| event-map-mismatch | EventType values without handlers in EventMap, or map keys without matching event types | warning | |
| non-exhaustive-switch | Switch over a union or machine state type that misses cases (no default clause) | warning | |
| cognitive-complexity | Function exceeds cognitive complexity threshold (Sonar S3776-compatible) | warning | |
| template-available | Library pattern detected that has a registered KERN template | info | |
| handler-extraction | Large inline handler (>300 tokens) that should be extracted to a separate fn node |
info | |
| memory-leak | Effect creates subscription (addEventListener, setInterval, WebSocket, etc.) without cleanup return |
error | |
| unhandled-async | Async function with await but no try/catch or .catch() — unhandled rejection risk |
warning | |
| Security — Always Active (8 rules) | |||
| xss-unsafe-html | dangerouslySetInnerHTML, v-html, or direct .innerHTML assignment — XSS vector |
error | |
| hardcoded-secret | String literals matching API key, JWT, AWS, GitHub, Slack, SendGrid, or Google key patterns | error | |
| command-injection | exec() / spawn() with template literals or string concatenation |
error | |
| no-eval | eval() or new Function() — code injection risk |
error | |
| insecure-random | Math.random() in security-sensitive context (token, password, key generation) |
warning | |
| cors-wildcard | cors() with no options or origin: '*' — allows any domain |
warning | |
| helmet-missing | Express app created without helmet() middleware — missing security headers |
warning | |
| open-redirect | res.redirect() with unvalidated user input from req.query / req.params |
error | |
| Security v2 — Always Active (6 rules) | |||
| jwt-weak-verification | jwt.decode() for auth decisions, jwt.verify() without algorithms allowlist, or accepting "none" algorithm |
warning / error | |
| cookie-hardening | Auth cookies missing httpOnly, secure, or sameSite flags |
error / warning | |
| csrf-detection | Cookie-auth app with state-changing routes but no CSRF protection middleware | warning / error | |
| csp-strength | Weak CSP directives: unsafe-inline, unsafe-eval, wildcard script-src, missing frame-ancestors |
warning / info | |
| path-traversal | File system operations or res.sendFile() with user input without path validation |
error | |
| weak-password-hashing | MD5/SHA1/SHA256 for passwords, low bcrypt rounds (<10), low PBKDF2 iterations (<100k) | error / warning | |
| Dead Logic — Always Active (8 rules) | |||
| identical-conditions | Same condition repeated in an if / else if chain |
error | |
| identical-expressions | Both sides of a binary operator are identical: a === a, x - x, x && x |
error | |
| all-identical-branches | All branches of if/else or ternary produce the same code — condition has no effect |
error / warning | |
| constant-condition | if (true), if (false), while (false), ternary with boolean literal |
warning / error | |
| one-iteration-loop | Loop body always exits on first iteration (unconditional break/return/throw) |
warning | |
| unused-collection | Array, Map, or Set is populated but never read | warning | |
| empty-collection-access | Collection initialized empty and read but never populated — reads always return empty | warning | |
| redundant-jump | Unnecessary continue at end of loop body or bare return at end of void function |
info | |
| React — nextjs | tailwind | web | native (6 rules) | |||
| async-effect | useEffect(async () => ...) — React does not support async effect callbacks |
error | |
| render-side-effect | setState or fetch() called directly in component render body, outside hooks or handlers |
error | |
| unstable-key | Missing key prop or key={index} in .map() JSX expressions |
warning | |
| stale-closure | Timer in useEffect with empty [] deps may capture stale state values |
warning | |
| state-explosion | Component has >5 useState calls — should use useReducer or a state machine |
warning | |
| hook-order | React hook called inside if, loop, or after early return — violates Rules of Hooks |
error | |
| Vue — vue | nuxt (4 rules) | |||
| missing-ref-value | ref() result used in expression without .value access |
warning | |
| missing-onUnmounted | watch() or addEventListener without cleanup in onUnmounted |
error | |
| setup-side-effect | Top-level await in <script setup> without onMounted wrapper — SSR issue |
warning | |
| reactive-destructure | Destructuring reactive() loses reactivity — use toRefs() |
warning | |
| Next.js — nextjs only (3 rules) | |||
| server-hook | Client hooks (useState, useEffect, etc.) used in Server Component without 'use client' |
error | |
| hydration-mismatch | Nondeterministic expressions (Date.now(), Math.random(), new Date()) in render cause hydration mismatch |
warning | |
| missing-use-client | Event handlers (onClick, onChange, etc.) in Server Component — needs 'use client' |
warning | |
| Express — express only (3 rules) | |||
| unvalidated-input | req.body / req.params / req.query used without validation library (zod, joi, yup) |
error | |
| missing-error-middleware | Express app without error handling middleware (4-param function) | warning | |
| sync-in-handler | Synchronous file system or crypto operations in request handlers block the event loop | warning | |
CLI Reference
# Basic usage — review a file or directory kern review src/server.ts kern review src/ # Recursive scan of all .ts/.tsx files kern review --recursive src/ # Only review files changed in current git diff kern review --diff # Include import graph tracing (feature-path analysis) kern review --graph src/index.ts # Enable LLM layer (requires KERN_LLM_KEY or configured provider) kern review --llm src/ # Run as linter (exit 1 on any error, for pre-commit hooks) kern review --lint src/
# JSON output for custom tooling kern review --json src/ # SARIF 2.1.0 output for GitHub Advanced Security kern review --sarif src/ > results.sarif
# Enforce quality gate — fail if thresholds are exceeded kern review --enforce src/ # Custom thresholds kern review --enforce \ --max-errors 0 # Zero errors allowed (default) --max-warnings 10 # Max 10 warnings --max-complexity 20 # Max cognitive complexity per function --min-coverage 60 # Min KERN coverage percentage
| Flag | Description |
|---|---|
| --json | Output findings as JSON |
| --sarif | Output SARIF 2.1.0 for GitHub code scanning |
| --enforce | Enable quality gate enforcement (exit 1 on failure) |
| --max-complexity | Maximum cognitive complexity per function |
| --max-errors | Maximum errors allowed (enforcement mode) |
| --max-warnings | Maximum warnings allowed (enforcement mode) |
| --min-coverage | Minimum KERN coverage percentage (enforcement mode) |
| --graph | Enable import graph tracing from entry files |
| --llm | Enable LLM analysis layer (architectural review) |
| --diff | Only review files changed in current git diff |
| --recursive | Recursively scan directories for .ts/.tsx files |
| --lint | Lint mode: exit 1 on any error finding |
CI Integration
Upload SARIF results to GitHub Advanced Security. Findings appear as code scanning alerts inline on pull requests.
# .github/workflows/kern-review.yml name: kern review on: [push, pull_request] jobs: review: runs-on: ubuntu-latest permissions: security-events: write steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci # Run kern review with SARIF output + enforcement - name: kern review run: | npx kern review --sarif --enforce \ --max-errors 0 \ --max-warnings 20 \ --max-complexity 20 \ --recursive src/ > results.sarif # Upload SARIF to GitHub Advanced Security - name: Upload SARIF if: always() uses: github/codeql-action/upload-sarif@v3 with: sarif_file: results.sarif category: kern-review
Use --diff to only review files changed in the PR. Combine with --graph to also trace upstream dependencies.
# Only review changed files + their import graph npx kern review --diff --graph --sarif > results.sarif