Skip to content
Local, deterministic, open source

Keep your codebase healthy while AI writes your code.

Little Owl is a local, deterministic codebase analyzer that helps developers keep architecture, maintainability and code health under control in AI-assisted development.

npx little-owl-code

Node.js 18.18 or newer. No account, no API key, nothing to configure first.View on npm

little-owl check

$ little-owl check
🦉 Little Owl

storefront — Next.js · TypeScript
Next.js, React detected
247 files  — 209 source, 38 test
Git repository — change reviews will work

Read the project             247 files
Mapped how files connect     677 connections
Checked the architecture     3 layers: ui → application → infrastructure
Checked for common problems  79ms

────────────────────────────────────────────────────────

Health   81 / 100   ████████████████░░░░

Your project needs attention.

🔴   1  critical    Fix before your app goes live.
🟠   6  important   Fix soon — this gets more expensive the longer it waits.
🟡  12  minor       Improve when you have time. Nothing is broken.

Start with the 1 critical issue. The rest can wait.

WHERE TO START

🔴 #1  A secret can reach the browser through this component
       src/components/panel/AdminGate.tsx
       Following the imports out of AdminGate.tsx leads to code
       that reads a password or key from your environment — and
       everything on that path is sent to the browser.

🟠 #2  ui imports infrastructure directly
       src/app/panel/config/page.tsx:17
       page.tsx reaches straight past the level directly below
       it and talks to the one after that.

… and 16 more. Run `little-owl check --all` to see every one.

NEXT STEP

little-owl explain 1   the full story of the first issue

The problem

AI changes code well. It does not watch the shape of your project.

Ask for a refactor and twenty files change. Another pass changes thirty more. Every individual diff looks reasonable — and the architecture is somewhere else by Friday.

Your assistant

Changes individual files

Fast, local, and correct in the diff. Each edit reads as reasonable on its own.

  • + src/components/Orders.tsx
  • + src/lib/format.ts
  • + src/services/orders.ts
  • … 17 more
feeds into

Little Owl

Reads the whole project

The shape of the codebase is not visible in any one diff. This is the part nobody was watching.

  • architecture
  • complexity
  • dependencies
  • tests
  • dead code
  • impact
  • health

Abstractions multiply

The same helper gets written a third time, in a third place.

Boundaries dissolve

A component starts importing the database client directly.

The graph knots up

Two modules import each other and nothing can be tested alone.

Scope creeps

Files change that had nothing to do with what you asked for.

How it works

Three ideas, and nothing else to learn.

Baseline
You record what healthy looks like for this project. Little Owl never moves that mark on its own — a baseline that follows the code downhill would hide the very drift it exists to catch.
Scope
You say what the change was supposed to touch. If files outside that area changed, you hear about it.
Drift
Every score movement is traced back to the counts behind it. A number you cannot explain is not worth showing.

After this change

↓ -6

  • Architecture9184dropped by 7 points
  • Maintainability8787
  • Complexity8471dropped by 13 points
  • Dependencies9594dropped by 1 points
  • Type Safety9187dropped by 4 points
  • Overall8983dropped by 6 points

Since the baseline: +1 circular dependency, +2 skipped-layer imports, +812 lines

What it looks at

Sixteen things nobody reviews in a diff.

Each one reports what it found, why that matters, and what to do about it — with the confidence it actually has.

  • Architecture Analysis

    Circular dependencies via Tarjan's algorithm, inverted layer dependencies, layers reaching past their neighbour, cross-feature imports, and edges you explicitly forbade.

    little-owl architecture
  • Client/Server Boundary

    Secrets and server-only code that a browser component can reach through a chain of imports — not just by importing them directly. Little Owl walks the real import graph, so it sees the leak that no single file reveals.

    little-owl explain 1
  • Change Review

    What the current git change did: files touched, lines added and removed, how each score moved, which findings are new, and whether the edit stayed inside the area you scoped it to.

    little-owl review --scope 'features/orders/**'
  • Impact Analysis

    Everything that imports a file, directly or through a chain, ranked by distance — plus the routes, tests and external packages involved.

    little-owl impact src/lib/auth/actions.ts
  • Test Gaps

    Modules with real logic that no test file reaches through imports, and modules a test reaches without naming every exported behaviour.

    little-owl tests --changed
  • AI Structural Patterns

    The same helper implemented in several files, two modules quietly implementing one concept, modules that only forward a call, and directories full of single-use abstractions.

    little-owl check

Client / server boundary

The leak that no single file reveals.

A component imports a helper. The helper imports the database module. The database module reads a key. Nothing in any one file is wrong — and everything on that path is compiled into a page any visitor can open and read.

Little Owl already holds the import graph, so it can answer the question a file-at-a-time linter structurally cannot: from this browser component, is there any route to code that was never meant to leave the server? The finding names the exact chain, the variable, and the fact that a deployed secret has to be rotated as well as removed.

It stays quiet about the things that only look like this. A "use server" module is called over the network, not bundled — that is the correct fix, so recognising one matters as much as finding the leak. Type-only imports and NEXT_PUBLIC_ variables are left alone for the same reason.

How the walk works, and what it will not follow
little-owl check
🔴   1  critical    Fix before your app goes live.

WHERE TO START

🔴 #1   A secret can reach the browser
       through this component
       components/Profile.tsx

       components/Profile.tsx
          ↓
       lib/user.ts
          ↓
       lib/db.ts
       reads process.env.DATABASE_URL

NEXT STEP

  → little-owl explain 1

Architecture

It knows which way your dependencies are supposed to point.

Layers are read from your configuration, or inferred from your directory names and labelled as a guess. Then every import is checked against them: inverted dependencies, layers reaching past their neighbour, features reaching into each other, and cycles found with Tarjan's algorithm.

Type-only imports are excluded, because they are erased at build time and never create a runtime cycle.

Every rule and its default severity
A three-layer dependency graph. Six modules across ui, application and infrastructure. Every dependency points downward except one: Cart.tsx in the ui layer imports client.ts in the infrastructure layer directly, skipping the application layer.UIAPPLICATIONINFRASTRUCTUREpage.tsxOrders.tsxCart.tsxorders.tspricing.tsclient.ts
allowed dependency ui imports infrastructure directly
Concentric rings around one changed file. Four files import it directly, four more are two to three hops away, and eight are further out.edit8 more4 files4 files
Distance from the change decides the level: direct importers are high, two to three hops are medium, the rest are low.

Impact

See the blast radius before you commit.

Walk the reverse dependency graph from any file and get back what could be affected, ranked by how many import hops away it is — plus the routes involved, the tests that reach it, and the external services it talks to.

It is reachability, not proof, and it says so. When a dynamic import cannot be resolved, it lowers its own confidence rather than pretending the answer is complete.

The loop

Review the change, not the codebase.

A linter tells you the same thousand things every run. Little Owl compares against your baseline and shows what this change introduced — so existing debt stays quiet and new problems stand out.

Everything it reports is numbered, and the number means the same thing in every command. You never have to describe a finding to Little Owl — you point at it.

  1. 1Your assistant makes a change
  2. 2little-owl review reports what it did to the project
  3. 3little-owl explain 1 says what the first issue means
  4. 4little-owl fix 1 writes a brief precise enough to hand back
  5. 5little-owl verify 1 confirms the fix actually landed

little-owl review

$ little-owl review
🦉 Little Owl

Looking at what changed…

12 files changed   +486 -73, 3 areas
  uncommitted changes vs HEAD
Compared with the baseline recorded 2 days ago

Health   89  83   ↓ -6

This change introduced something that needs fixing before release.

🔴   1  critical    Fix before your app goes live.
🟠   3  important   Fix soon — this gets more expensive the longer it waits.

✓ 2 earlier issues no longer appear.

WHAT THIS CHANGE INTRODUCED

🔴 #1  Circular dependency across 3 files
       src/services/orders.ts
       Some of your files depend on each other in a loop:
       orders.ts -> users.ts -> auth.ts -> orders.ts.
       Each one needs the other to load first.

… and 2 more. Run `little-owl review --details` to see every one.

NEXT STEP

little-owl explain 1   what this issue actually means

Privacy

Your code stays on your machine.

Little Owl reads files from disk, runs git locally to see what changed, and prints to your terminal. That is the entire data flow. There is no network code in the package to audit.

The only thing it writes is a .little-owl/ directory in your project — your config and baseline, which belong in version control, and a local cache, which it adds to .gitignore for you.

  • No source-code uploads

    Your files are read from disk and never leave it.

  • No backend

    There is no service to be down, rate-limit you, or shut off.

  • No authentication

    No account, no login, no seat count.

  • No API key

    Nothing to provision before the first run.

  • No telemetry

    Not opt-out — absent. There is no analytics code to disable.

  • No AI required

    Little Owl never calls a model. It writes prompts; you decide what to do with them.

Languages

Two languages in depth, two more at the surface.

Python and Go are genuinely supported, and genuinely shallower. Pretending otherwise would cost you more than it gained us.

TypeScript

Full AST

.ts .tsx .mts .cts

TypeScript compiler API — full syntax tree

Imports, exports, dynamic imports, path aliases, functions, components, cyclomatic complexity, nesting, `any`, assertions and suppressions.

Rules only this language gets

  • type-safety/explicit-any
  • type-safety/suppression
  • type-safety/unsafe-assertion
  • type-safety/js-in-ts-project
  • next/server-import-in-client
  • react/effect-dependency-risk

JavaScript

Full AST

.js .jsx .mjs .cjs

TypeScript compiler API — full syntax tree

Everything TypeScript gets, minus the type-safety rules that need types to exist. JSX, CommonJS `require` and ESM are all understood.

Rules only this language gets

  • next/server-import-in-client
  • react/effect-dependency-risk

Python

Shallow

.py .pyi

Line and indentation based

Imports and the dependency graph, relative and absolute module resolution, function boundaries by indentation, complexity, module sizes, and three known smells.

Limits: Not a full parse. Re-exports through `__init__.py` are not followed, so a module reached only through a package export can look unreferenced. Decorators, metaclasses and dynamic attribute access are invisible. It will not replace Ruff.

Rules only this language gets

  • python/bare-except
  • python/mutable-default
  • python/global-state

Go

Shallow

.go

Line and brace based

Package clauses, grouped and single imports, module-relative resolution through go.mod, package-level cycles, function boundaries by brace balance, and oversized packages.

Limits: Exports are detected from capitalised functions only, so exported types, constants and variables are invisible to the dead-code and pattern rules. Build tags and generated code are not special-cased. It will not replace golangci-lint.

Rules only this language gets

  • go/ignored-error
  • go/large-package

Keep your codebase healthy while AI writes your code.

One command, no setup. Point it at a project you already have and see what it says.

npx little-owl-code