Concept Rules

kern review doesn't just lint syntax. It builds a concept graph of your code — entrypoints, effects, guards, state mutations, boundaries — and checks the relationships between them.

How concept rules work

Traditional linters check patterns line by line. Concept rules first infer what your code does (is this an effect? a guard? a state mutation?) then check if the relationships are correct (is the effect guarded? is the mutation within its boundary?).

This catches bugs that no regex-based linter can find — the kind of bugs that take senior engineers hours to debug.

The 5 concept rules

unguarded-effect

A side effect (network, database, filesystem) without try/catch or error handling.

Why it matters: Unhandled network calls crash at runtime. A fetch() without try/catch is a ticking bomb in production.

// kern review flags this:
async function loadUser(id: string) {
  const res = await fetch(`/api/users/${id}`);  // ← unguarded
  return res.json();
}

// Fix: wrap the effect
async function loadUser(id: string) {
  try {
    const res = await fetch(`/api/users/${id}`);
    return res.json();
  } catch (err) {
    return null;
  }
}

boundary-mutation

State mutated across a module boundary — one module directly changes another module's state.

Why it matters: Cross-boundary mutations create invisible dependencies. When module A silently changes module B's state, debugging becomes impossible.

// kern review flags this:
import { cartState } from '../stores/cart';

function addDiscount() {
  cartState.items[0].price *= 0.8;  // ← mutating across boundary
}

// Fix: expose a mutation function from the owning module
import { applyDiscount } from '../stores/cart';
applyDiscount(0, 0.8);

ignored-error

An error is caught but not handled — empty catch block or catch with no action.

Why it matters: Swallowed errors hide bugs. The application continues in a broken state without anyone knowing.

// kern review flags this:
try {
  await saveOrder(order);
} catch (err) {
  // empty — error silently swallowed
}

// Fix: at minimum, log it
try {
  await saveOrder(order);
} catch (err) {
  console.error('Failed to save order:', err);
  throw err;
}

illegal-dependency

A cross-module dependency that bypasses the intended architecture — importing internals instead of public API.

Why it matters: Internal imports break when the dependency refactors. They create tight coupling that prevents independent evolution.

// kern review flags this:
import { _internalParser } from '../core/src/parser';  // ← internal

// Fix: use the public export
import { parse } from '../core';

unrecovered-effect

An effect (database write, API call) can fail but has no recovery path — no retry, no fallback, no error propagation.

Why it matters: Network calls fail. Disk writes fail. If there's no recovery path, the failure cascades silently.

// kern review flags this:
async function syncData() {
  await db.write(data);       // ← can fail, no recovery
  await api.notify(webhook);  // ← can fail, no recovery
}

// Fix: add recovery
async function syncData() {
  try {
    await db.write(data);
  } catch (err) {
    await db.write(data);  // retry once
  }
  await api.notify(webhook).catch(() => {
    queue.push({ type: 'retry-notify', webhook });
  });
}

Concept node types

kern review classifies code into 6 concept types:

  • entrypoint — Route handler, main function, event listener, exported function
  • effect — Network call, database query, filesystem operation, process spawn
  • state_mutation — Direct state change (local, module, or global scope)
  • error_raise — throw, reject, error return
  • error_handle — catch, .catch(), error boundary
  • guard — Auth check, validation, rate limit, policy check

The concept rules check relationships between these: "Is every effect guarded? Is every error handled? Do mutations stay within their boundary?"