Skip to content

Features

Fourteen things nobody reviews in a diff.

Each one states what it detects, why it matters, and what it prints. Nothing here describes a capability the tool does not have.

01

Codebase Health

What it detects
Six scores over the whole project — architecture, maintainability, complexity, dependencies, type safety, and an overall figure weighted from them.
Why it matters
A score is a summary, not a verdict. Its job is to make drift between two runs of the same project visible, and every point of movement can be traced back to a concrete count.
little-owl check

little-owl check

$ little-owl check
Name            storefront
Stack           Next.js · TypeScript
Package manager pnpm
Files           247
Git             

CODEBASE HEALTH

Overall

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

Architecture     79
Maintainability  44
Complexity       96
Dependencies     76
Type Safety      98

🟡 87 warnings   🔵 63 notes   (150 total)

FINDINGS

🟡 architecture  ui imports infrastructure directly
   src/app/(app)/panel/config/page.tsx:17

   panel/config/page.tsx imports lib/db/client.ts,
   skipping the application layer. The detected
   structure is ui -> application -> infrastructure.

   found:    ui -> infrastructure
   expected: ui -> application -> infrastructure

Route the call through application
     instead of importing infrastructure here.

02

Architecture Analysis

What it detects
Circular dependencies via Tarjan's algorithm, inverted layer dependencies, layers reaching past their neighbour, cross-feature imports, and edges you explicitly forbade.
Why it matters
Cycles make modules impossible to load, test or reason about on their own, and they spread. Layer breaks are how a UI file ends up talking straight to the database.
little-owl architecture

Example output

ui imports infrastructure directly
found:    ui -> infrastructure
expected: ui -> application -> infrastructure

03

Complexity

What it detects
Oversized files, functions and React components, cyclomatic complexity, deep nesting and long parameter lists — measured from a real syntax tree, not a line count.
Why it matters
Every extra branch is another path a change can break and another case a test has to cover. Components get their own budget, because they are legitimately longer than functions.
little-owl check --details

Example output

<CheckoutPage> is 486 lines
applyTieredDiscount() has a complexity of 22
→ Flatten early returns, or move each branch group into its own function.

04

Dependency Hygiene

What it detects
Packages declared but never imported, packages imported but never declared, packages in both dependency lists, major version jumps and newly added dependencies.
Why it matters
Each dependency is code you ship, update and trust. Build tooling that legitimately never appears in an import is filtered out, so the list is short enough to act on.
little-owl dependencies

Example output

14 declared   11 imported
ℹ Declared but never imported (may be used via config or at runtime)
  lodash

05

Change Review

What it detects
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.
Why it matters
This is the difference between a linter and Little Owl. Pre-existing debt is not repeated at you every run — only what this change introduced.
little-owl review --scope 'features/orders/**'

little-owl review

$ little-owl review

🦉 CODEBASE REVIEW

12 files changed (uncommitted changes vs HEAD)
+486 -73 lines   across 3 areas

✗ DEGRADED

Architecture     91 →  84 ↓
Maintainability  87 →  87  ·
Complexity       84 →  71 ↓
Dependencies     95 →  94 ↓
Type Safety      91 →  87 ↓
Overall          89 →  83 ↓

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

🔴 1 critical   🟡 3 warnings

FINDINGS

🔴 architecture  Circular dependency across 3 files
   src/services/orders.ts

   These files depend on each other in a loop.
   Cycles make modules impossible to load, test
   or reason about independently.

   orders.ts -> users.ts -> auth.ts -> orders.ts

06

Impact Analysis

What it detects
Everything that imports a file, directly or through a chain, ranked by distance — plus the routes, tests and external packages involved.
Why it matters
Before you change a shared module you get to see its blast radius. It is reachability, not proof, and it says so; a dynamic import it cannot resolve lowers its own confidence.
little-owl impact src/lib/auth/actions.ts

little-owl impact src/lib/auth/actions.ts

$ little-owl impact src/lib/auth/actions.ts

CHANGE IMPACT

Changed
  src/lib/auth/actions.ts

Potentially affected

  HIGH  (4 files)
    src/app/login/page.tsx
    src/components/panel/AdminGate.tsx
    src/components/panel/PanelMenu.tsx
    src/components/ui/LogoutButton.tsx

  MEDIUM  (4 files)
    src/app/(app)/panel/layout.tsx
    src/app/(app)/panel/metrics/page.tsx
    src/app/admin/layout.tsx

Routes
  /login          src/app/login/page.tsx
  /panel          src/app/(app)/panel/layout.tsx
  /panel/metrics  src/app/(app)/panel/metrics/page.tsx
  /admin          src/app/admin/layout.tsx

These files import the change directly or
indirectly. That makes them worth testing — it
does not mean they are broken.

07

Dead Code

What it detects
Files nothing in the project reaches, plus exported names nothing imports — each graded high, medium or low confidence, with framework entry points excluded.
Why it matters
Static reachability cannot see everything: dynamic imports, plugin registries and file conventions all keep code alive without an import. So it reports candidates with caveats, and never deletes anything.
little-owl dead-code --min-confidence high

little-owl dead-code

$ little-owl dead-code

DEAD CODE

5 candidates — nothing in the project imports them.

MEDIUM CONFIDENCE  (2)

  public/sw.js 158 lines
nothing in the project imports it
it exports nothing
but dynamic imports could not
      be resolved

LOW CONFIDENCE  (3)

  src/components/Mascot.tsx 23 lines
nothing in the project imports it
but it exports 2 names
but dynamic imports could not
      be resolved

Little Owl never deletes anything.

08

Test Gaps

What it detects
Modules with real logic that no test file reaches through imports, and modules a test reaches without naming every exported behaviour.
Why it matters
The question is not what your coverage percentage is — a coverage tool answers that better. It is whether anything is watching the behaviour you just changed.
little-owl tests --changed

little-owl tests --changed

$ little-owl tests --changed

TEST GAPS

Limited to the 12 files in the current change.

50 test files reaching 48 modules

✗ NO TEST REACHES THESE  (3)

  src/lib/pricing/discount.ts
    applyTieredDiscount() has 14 branches

  src/lib/auth/actions.ts
    signIn() has 9 branches

⚠ PARTIALLY COVERED  (1)

  src/services/orders.ts
    reached by src/services/orders.test.ts
    no test names: cancelOrder, refundOrder

Little Owl never generates tests. It points at
the gap and stops.

09

Code Archaeology

What it detects
The commit that introduced a file, the messages that record a reason, who maintains it, what changes alongside it, and what imports it today.
Why it matters
Every answer comes from evidence already in your repository. Where the history is silent it says so, rather than filling the gap with a plausible story.
little-owl explain src/lib/auth/actions.ts

little-owl explain src/lib/auth/actions.ts

$ little-owl explain src/lib/auth/actions.ts

CODE ARCHAEOLOGY

src/lib/auth/actions.ts

Evidence: strong

Introduced in 0532b4c (29 days ago).
It has been touched in 11 commits.
4 files import it today.

Commits that explain why

fix: getCurrentProfile fell open (f1de4da)
fix: superadmin owner settings (874242e)

Maintained by
  A. Rivera (11 commits)

Little Owl only reports what the repository
records. Where the history is silent it says so
rather than guessing at a reason.

10

Project Map

What it detects
Areas and their size, detected layers, entry points with route paths, the modules most files depend on, and external services grouped by what they are.
Why it matters
Listing every directory alphabetically would be accurate and useless. The ordering principle is what you would read first.
little-owl map

little-owl map

$ little-owl map

PROJECT MAP

247 files, 37,816 lines, 677 internal imports

START HERE

  1. src/app/
  2. src/components/
  3. src/lib/

Layers

  ui

  application

  infrastructure

Areas

  src/lib          80 files, 12,862 lines  ←211
  src/components   65 files, 10,806 lines  ←120
  src/app          34 files, 6,861 lines

Most depended on

  src/lib/db/schema.ts        39 dependents
  src/lib/auth/session.ts     20 dependents
  src/lib/format.ts           14 dependents

11

AI Structural Patterns

What it detects
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.
Why it matters
These shapes appear whenever changes are made without seeing the whole codebase — which is most of what AI-assisted editing does. Little Owl reports the shape. It never claims to know who wrote the code.
little-owl check

Example output

formatCurrency() is implemented in 3 places
  src/lib/format.ts
  src/components/cart/price.ts

12

Baseline & Drift

What it detects
The difference between now and the state you declared healthy — per score, with the counts that explain each move.
Why it matters
Little Owl never refreshes the baseline on its own. A baseline that follows the code downhill would quietly redefine healthy as whatever the code is today, and steady degradation would become invisible.
little-owl baseline

Example output

Architecture     91 →  84 ↓
Since the baseline: +2 circular dependencies, +3 skipped-layer imports

13

Watch Mode

What it detects
Drift as it happens, grouped by whether a new finding is in the file you just saved, in something that imports it, or somewhere unrelated.
Why it matters
It measures against a fixed reference rather than the state a second ago, and it will not blame your last keystroke for a problem in a file you have not touched.
little-owl watch

Example output

Changed
  src/services/orders.ts
  3 files import this, directly or indirectly

14

CI

What it detects
Whether the change under review introduced new problems, with a configurable severity gate and a maximum acceptable score drop.
Why it matters
By default only new findings can fail a build, so a project with existing debt can adopt Little Owl without fixing everything first.
little-owl ci --base origin/main

Example output

little-owl: DEGRADED
result: fail (1 error-level finding)
$ echo $?  → 1

Languages

Not every feature reaches every language.

Anything driven by the import graph or by git works the same in all four. The rules that need a real syntax tree do not.

Analysis capabilities by language. TypeScript and JavaScript are parsed into a full syntax tree; Python and Go are analysed line by line.
CapabilityTSJSPyGo
Dependency graphGo resolves at package levelsupportedsupportedsupportedsupported
Circular dependenciessupportedsupportedsupportedsupported
Layers and boundariesDriven by directories, so language-agnosticsupportedsupportedsupportedsupported
File and function sizesupportedsupportedsupportedsupported
Cyclomatic complexityKeyword-counted outside TS/JSsupportedsupportedsupportedsupported
Duplicate blocksTextual, so it works anywheresupportedsupportedsupportedsupported
Impact and reverse depssupportedsupportedsupportedsupported
Code archaeologyReads git, not codesupportedsupportedsupportedsupported
Project mapsupportedsupportedsupportedsupported
Dead codeConfidence is capped below high for Python and Gosupportedsupportedsupportedsupported
Test gapsRecognises test_*.py and *_test.gosupportedsupportedsupportedsupported
React component budgetssupportedsupportednot supportednot supported
Type-safety rulesNeeds types to existsupportednot supportednot supportednot supported
Dependency hygieneReads package.json onlysupportedsupportednot supportednot supported
Structural pattern rulesGo exports are functions onlysupportedsupportedsupportednot supported

Full detail, including where Python and Go stop seeing things, is in the documentation.

See it on your own project

Nothing to configure. It infers your structure on the first run and tells you when it is guessing.

Get Started