---
title: Codebase Stats (project health dashboard)
---

# Codebase Stats (project health dashboard)

## What it is

**Codebase Stats** is a multi-tab modal that analyzes a real project and reports on its size, health, git activity, dependency risk, AI spend, and test coverage — everything you'd normally get from a half-dozen CLI tools, gathered and presented in one place. It scans the filesystem, reads git log, checks `package.json`, runs the coverage tool, and aggregates session costs from Omniscio's own database. Results cache for 24 hours so repeat opens are instant, but you can force-refresh anytime. The modal is per-project and only shows for real codebases — virtual projects (Gmail, SMS, OpenClaw, Skills, etc.) don't have a filesystem to analyze so the menu item is hidden there.

## Where to find it

It opens from a project's three-dot menu in the left sidebar, under View Codebase Stats. Because
it needs a folder to scan, the menu item is only there for real projects — the built-in virtual
rows such as Gmail, SMS or Skills have no filesystem behind them and never show it.

## How it behaves

### How to use it

1. **Open the modal.** Click the three-dot menu on any real project in the left sidebar → **View Codebase Stats**. The modal takes over the screen.
2. **Skim the Overview.** The first tab shows 8 headline numbers: total lines, source vs test lines, commits in last 30 days, AI spend to date, test count, TODO count, dependency count, and known vulnerabilities. If any secrets were detected, a red alert banner appears at the top.
3. **Drill into tabs.** Switch between **Files** (language breakdown + extension table), **Health** (TODO/FIXME count, `any` types, detected secrets, large files over 300 lines), **Git** (activity, churn hotspots, knowledge silos, stale files), **Deps** (outdated and vulnerable packages by severity), **Costs** (input/output token usage and total dollars), and **Coverage** (donut chart + untested file list). If the file scan hit an aggregate cap — too many files, too much cumulative content, or too deep a folder tree — an amber banner at the top of the **Overview and Files** tabs explains which cap stopped it and that the analysis is partial.
4. **Run coverage manually.** The Coverage tab doesn't auto-run — click **Run Coverage** and Omniscio invokes your test suite with `@vitest/coverage-v8` instrumentation (and offers a one-click install if the provider is missing). Progress streams live as it works.
5. **Ignore noisy findings or launch a fix-it session.** Click the **X** on any flagged finding (a large file, a TODO, a detected secret) to hide it from future reports — restore later from the "show ignored" toggle. Or click **Fix in Session** on a Health finding to spawn a new Claude session with a pre-written prompt asking it to address that specific problem. Use **Refresh** (bottom-left) to bypass the 24h cache.

## For agents

### How it works

The modal is [/src/renderer/src/features/dashboard/CodebaseStatsModal.tsx](/src/renderer/src/features/dashboard/CodebaseStatsModal.tsx) (~557 lines) with 7 tab IDs defined in its `TABS` array: `overview | files | health | git | deps | costs | coverage`. It's opened from the project's three-dot menu ([/src/renderer/src/features/dashboard/ProjectListItem.tsx](/src/renderer/src/features/dashboard/ProjectListItem.tsx)), gated on `!isVirtualProject(project.folderPath)`. On mount the modal invokes `IPC.CODEBASE_STATS_ANALYZE`, which runs [/src/main/services/codebase-analyzer.ts](/src/main/services/codebase-analyzer.ts) — that scan asks **git** which files belong to the project (`git ls-files --cached --others --exclude-standard`), which is both fast and correct by construction: it honours the project's own ignore rules, so build output and machine junk that merely sits inside the folder are never counted. It falls back to walking the filesystem when the folder is not a git repository root (guarded with `git rev-parse --show-prefix`, so a folder nested inside a larger repo is not reported as that repo). Both sources refuse junk **by shape** rather than by one spelling of its name: any `*.git` directory, any `node_modules*` directory, and — the catch-all — any file whose leading content contains a NUL byte, whatever its extension. A file counts as "test" if its path or name matches `/test|spec/`; files over 300 lines are flagged "large". Files are then read with a bounded fan-out (`threadpoolSize() × 4`) rather than one at a time, and the results are accumulated in list order so the same tree always produces the same numbers. The scan is **bounded** (generous defaults: ~150,000 files, ~2 GB of cumulative content, ~40 folder levels) so a huge monorepo or a mis-pointed folder can never make it unbounded — but the caps are sized to *fully* scan a large real repo (Omniscio itself measures ~61k files / ~878 MB) rather than truncate it. The byte cap is a work/time budget, not a memory guard: the walk reads each file's content transiently and keeps only per-file `{path, ext, lines, bytes}`, so raising it costs scan time, not heap. When a cap does trip, the walk stops and `truncated` is set on the result so **both the Overview and Files tabs** show the amber "analysis is partial" banner (one shared `TruncationBanner`) rather than let a short scan look complete on the default view. A `CODEBASE_STATS_ANALYSIS_VERSION` stamp on the cached blob makes a logic change like a cap raise self-applying: the on-demand handler treats a cache written by an older analyzer as stale and re-scans, instead of serving now-wrong numbers for up to 24h. Secret detection uses a fixed pattern list (AWS keys, GitHub PATs, Slack tokens, Stripe `sk_live_`, PEM private keys) plus a generic high-entropy regex. Results stream back incrementally via `IPC.CODEBASE_STATS_PROGRESS` push events so sections populate as they finish. Git stats come from `git log` shellouts; dependency data from `npm outdated --json` and `npm audit --json`; coverage data from the `coverage-final.json` vitest writes. The 24-hour cache lives in SQLite table `codebase_stats_cache`, and per-project ignored findings live in `codebase_stats_ignores` — both managed by [/src/main/db/queries-codebase-stats.ts](/src/main/db/queries-codebase-stats.ts). The **Costs** tab pulls from `IPC.PROJECT_COST_ROLLUP`, which sums input/output token costs across every session in the project. **Fix in Session** on a Health finding creates a new session with a pre-written prompt asking Claude to address the specific file/line flagged.

### Background warming (pre-computed so the modal opens instantly)

The 24h cache is also **pre-warmed in the background**, so opening the modal is instant instead of triggering a cold scan. A main-process service — [/src/main/services/codebase-stats/codebase-stats-warmer.ts](/src/main/services/codebase-stats/codebase-stats-warmer.ts) (scheduler) + [/src/main/services/codebase-stats/codebase-stats-warmer-logic.ts](/src/main/services/codebase-stats/codebase-stats-warmer-logic.ts) (pure, unit-tested decision rules) — periodically warms a small batch of projects into `codebase_stats_cache`, reusing the same `analyzeCodebase` engine. No new table, no migration, no new view.

Built to never load the machine, even at 1000 projects:

- **Change-detection.** Probes `git rev-parse HEAD` and skips any project whose cached `git_commit_hash` still matches (within a 7-day backstop). Steady-state work is ~zero, since most projects don't change on a given day.
- **Light mode.** Calls `analyzeCodebase(…, { skipDeps: true })`, skipping the only network step (`npm audit` + `npm outdated`). `deps` stays null (modal shows "No dependency data available"); vulnerability/outdated numbers fill in only on an on-demand open + **Refresh**. A per-file size cap also prevents one giant file from OOMing a sweep.
- **Throttle.** ONE PROJECT AT A TIME, a small batch per ~5-min tick, a pause between projects, first run deferred ~2 min after boot, and a load gate re-checked between projects — free RAM always, plus CPU and disk pressure. The CPU/disk terms can hold ticks back for at most ~4 h: past that a starvation floor forces one small batch whose first project always runs, so even a permanently busy box keeps its stats refreshing (a box short of RAM is never forced). HEAD probes use a rotating slice so a fresh commit is caught over time without probing all 1000 each tick. Inside a single project the file reads use a bounded fan-out (`threadpoolSize() × 4`) — a serial scan measured ~10 files/s, which put a large repo at ~19 minutes and made the 5-minute per-project budget unreachable.
- **Never degrades.** Merges onto the prior cache row, preserving any richer `coverage`/`deps` a full on-demand scan produced.
- **Control.** The `codebaseStatsBackgroundEnabled` setting (Settings → Diagnostics, default on) + the `AMC_DISABLE_CODEBASE_STATS_WARMER` env kill switch. Start/stop is wired at startup ([/src/main/startup/registry.ts](/src/main/startup/registry.ts)) and on toggle ([/src/main/services/settings-apply.ts](/src/main/services/settings-apply.ts)).

Invariants are locked in [/.claude/memory/contracts/background-codebase-stats-warmer-contract.md](/.claude/memory/contracts/background-codebase-stats-warmer-contract.md).

## Related

Running an analysis on a schedule rather than by hand is a job for the step machinery described on
the [use recipes](use-recipes.md) page. The projects that are exempt from stats because they have
no local folder are covered on [OpenClaw provider](openclaw-provider.md).

- [/.claude/memory/contracts/background-codebase-stats-warmer-contract.md](/.claude/memory/contracts/background-codebase-stats-warmer-contract.md) — the background warmer's test-locked invariants
- [use-recipes.md](use-recipes.md) — if you want to run codebase analysis on a schedule across many projects, wire it into a recipe step
- [openclaw-provider.md](openclaw-provider.md) — OpenClaw projects are exempt from stats (no local filesystem)