Features
Sixteen 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 checklittle-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
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 architectureExample output
ui imports infrastructure directly
found: ui -> infrastructure
expected: ui -> application -> infrastructure03
Client/Server Boundary
- What it detects
- 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.
- Why it matters
- 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 all of it is compiled into a page any visitor can read. Server Actions, type-only imports and NEXT_PUBLIC_ variables are correctly left alone — the fix these findings recommend is a Server Action, so recognising one matters as much as finding the leak.
little-owl explain 1little-owl explain 1
$ little-owl explain 1🔴 CRITICAL issue #1
A secret can reach the browser through this component
What happened
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.
Why this matters
Anything a browser runs, a visitor can read. They open developer
tools, search the page source, and the value is there in plain
text — no hacking required. If this is a database URL, an API
key or a service credential, treat it as public from the moment
the page was deployed.
Where
src/components/panel/AdminGate.tsx
Related files
src/lib/auth/session.ts — links the two together
src/lib/db/client.ts — where the problem is
How it connects
src/components/panel/AdminGate.tsx
↓
src/lib/auth/session.ts
↓
src/lib/db/client.ts
What should happen instead
Secrets are read only by code that runs on your server. The
browser receives the result of using them, never the values.
Recommended fix
Move the work into a server component, a route handler or a
server action, and pass only the finished result down as props.
How to check it worked
Build the app and search the generated client files for the
secret value. It must not appear anywhere.
Next step
→ little-owl fix 1
04
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 --detailsExample output
<CheckoutPage> is 486 lines
applyTieredDiscount() has a complexity of 22
→ Flatten early returns, or move each branch group into its own function.05
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 dependenciesExample output
14 declared 11 imported
ℹ Declared but never imported (may be used via config or at runtime)
lodash06
Explain, Fix, Verify
- What it detects
- Every issue gets a number. `explain 1` says what happened and why it matters in plain language, `fix 1` names the files and writes a brief precise enough to hand to an AI assistant, and `verify 1` confirms the fix actually landed.
- Why it matters
- Finding a problem is the easy half. Little Owl re-derives the finding from your source, so an issue can only disappear by genuinely being gone — and it reports anything the fix introduced, because a fix that trades one problem for another is not finished. Add --tests to run your own test command as part of the check.
little-owl verify 1 --testslittle-owl verify 1 --tests
$ little-owl verify 1 --tests🦉 Little Owl
Checking whether the fix landed…
✓ Re-read the project 248 files
✓ Compared with the last check 19 issues then
✓ Ran your tests pnpm test
────────────────────────────────────────────────────────
🟢 Issue #1 is fixed A secret can reach the browser
Health 81 → 87 ↑ +6
Your project looks solid. A few things are worth fixing soon.
NEXT STEP
→ little-owl fix 2 next up: ui imports infrastructure directly
07
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🦉 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
08
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.tslittle-owl impact src/lib/auth/actions.ts
$ little-owl impact src/lib/auth/actions.tsCHANGE 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.
NEXT STEP
→ little-owl tests 12 tests reach this — check what they miss
09
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 highlittle-owl dead-code
$ little-owl dead-codeDEAD 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.
NEXT STEP
→ little-owl explain public/sw.js why it exists, before you delete it
10
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 --changedlittle-owl tests --changed
$ little-owl tests --changedTEST 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.
NEXT STEP
→ little-owl impact src/lib/pricing/discount.ts what breaks if this is wrong
11
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.tslittle-owl explain src/lib/auth/actions.ts
$ little-owl explain src/lib/auth/actions.tsCODE 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.
NEXT STEP
→ little-owl impact src/lib/auth/actions.ts what else would a change here touch?
12
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 maplittle-owl map
$ little-owl mapPROJECT 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
NEXT STEP
→ little-owl check now: what in here needs attention?
13
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 checkExample output
formatCurrency() is implemented in 3 places
src/lib/format.ts
src/components/cart/price.ts14
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 baselineExample output
Architecture 91 → 84 ↓
Since the baseline: +2 circular dependencies, +3 skipped-layer imports15
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 watchExample output
Changed
src/services/orders.ts
3 files import this, directly or indirectly16
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/mainExample output
little-owl: DEGRADED
result: fail (1 error-level finding)
$ echo $? → 1Languages
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.
| Capability | TS | JS | Py | Go |
|---|---|---|---|---|
| Dependency graphGo resolves at package level | supported | supported | supported | supported |
| Circular dependencies | supported | supported | supported | supported |
| Layers and boundariesDriven by directories, so language-agnostic | supported | supported | supported | supported |
| File and function size | supported | supported | supported | supported |
| Cyclomatic complexityKeyword-counted outside TS/JS | supported | supported | supported | supported |
| Duplicate blocksTextual, so it works anywhere | supported | supported | supported | supported |
| Impact and reverse deps | supported | supported | supported | supported |
| Code archaeologyReads git, not code | supported | supported | supported | supported |
| Project map | supported | supported | supported | supported |
| Dead codeConfidence is capped below high for Python and Go | supported | supported | supported | supported |
| Test gapsRecognises test_*.py and *_test.go | supported | supported | supported | supported |
| React component budgets | supported | supported | not supported | not supported |
| Type-safety rulesNeeds types to exist | supported | not supported | not supported | not supported |
| Dependency hygieneReads package.json only | supported | supported | not supported | not supported |
| Structural pattern rulesGo exports are functions only | supported | supported | supported | not 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