
A geological survey of a git repository. Strata replays a project’s entire history line by line and turns it into one self-contained HTML report. The report shows:
Open a live sample report → (express, 17 years of history)
$ npx strata-survey ~/code/express --open
STRATIGRAPHY · express
2026 Quaternary ██ 583 (2.2%)
2025 Neogene ██ 460 (1.7%)
2024 Paleogene ██████ 1,488 (5.6%)
…
2014 Cambrian ████████████████████████ 6,400 (23.9%)
2013 Neoproterozoic ███ 918 (3.4%)
2012 Mesoproterozoic ██████████ 2,718 (10.1%)
2011 Paleoproterozoic ███████████ 2,871 (10.7%)
2010 Archean ███ 892 (3.3%)
2009 Hadean █ 6 (<0.1%)
FIELD NOTES
01 17 years of sediment: 5,688 commits by 381 people since Jun 26, 2009. Today it holds 26,798 lines in 214 files.
04 A line of code here has a half-life of 5 months (source: 4 months, tests: 1.4 years) — half of all
lines ever written were rewritten or deleted within that time.
07 Bus factor: 1 person holds more than half of all lines. test is a knowledge silo — Douglas
Christopher Wilson wrote 61% of it. 94% of the code was written by people inactive for over a year.
…
git blame --first-parent line for line (see Accuracy).Strata is published on npm as strata-survey. It needs git and Node 18 or newer.
Run it once without installing anything:
npx strata-survey /path/to/repo --open
Or install the strata command globally:
npm install -g strata-survey
strata --version
The rest of this README uses strata. With npx, write npx strata-survey instead.
Point Strata at any local git repository, or run it inside one:
cd ~/code/my-project
strata --open # survey the current repository, write strata-my-project.html and open it
It reads history with ordinary git commands and never changes the repository. A survey takes seconds for most projects and about two minutes for git/git.
strata ~/code/app --lang ru # report and terminal summary in Russian
strata ~/code/app -o ~/reports/app.html # choose where the report goes
strata ~/code/app --exclude 'fixtures/**' --exclude '**/*.snap' # leave out noise
strata ~/code/app --include 'packages/core/**' # survey one part of a monorepo
strata ~/code/app --no-report # terminal summary only
strata ~/code/app --fast # huge merge-heavy repo: skip the merge content pass
Save the raw survey and render reports from it later, without git or the repository:
strata ~/code/app --json app.survey.json
strata --from-json app.survey.json --lang ru -o app-ru.html
Check how closely the line replay matches git blame on your repository:
strata verify ~/code/app # 60 random files
strata verify ~/code/app --files 200 # a bigger sample
| Option | What it does |
|---|---|
-o, --out <file> |
report path (default strata-<repo>.html in the current folder) |
--lang en\|ru |
language of the report and of the terminal summary (the report also has an EN/RU switch) |
--json <file> |
also save the raw survey as JSON |
--from-json <file> |
render a report from a saved survey; no git needed |
--exclude <glob>, --include <glob> |
narrow the survey (repeatable), e.g. --exclude 'fixtures/**' |
--no-default-excludes |
keep lockfiles, vendored, generated and minified files, and .po catalogs |
--name <name> |
display name for the repository |
--fast |
skip the content pass that credits merged lines to side-branch authors |
--open |
open the report in the default browser when done |
--no-report |
print the terminal summary only |
-q, --quiet |
no progress output |
Run strata --help for the full list.
git clone --depth …, common in CI) everything older than the first available commit is squashed into the bottom layer. Strata warns about it; run git fetch --unshallow first.--no-default-excludes to keep them..mailmap. If one person still shows up twice, add a .mailmap entry.git clone https://github.com/mxmvshnvsk/strata-survey.git
cd strata-survey
node bin/strata.ts /path/to/repo --open # Node 22.18+ runs the TypeScript sources directly
On older Node versions (18+), build the JavaScript once:
npm install # installs TypeScript and builds dist/
node dist/bin/strata.js /path/to/repo --open
To get a global strata command from your checkout, run npm install && npm link.
Open a live sample report in your browser:
| Report | What to look at |
|---|---|
| express | Layers by author: the project changing hands from TJ Holowaychuk to Douglas Wilson |
| flask | Long-lived maintenance branches and a mass reformatting |
| vite | A young monorepo: half of the code was written in 2024 or later |
| git | 21 years, 75k commits; the oldest fossil is a 2005 line by Linus Torvalds |
The files themselves are in examples/.
| Plate | What it answers |
|---|---|
| Stratigraphic column + field notes | How old is the code alive today? A time scrubber (▶) replays how the column grew and eroded. Ten auto-written findings sit next to it. |
| I · Stratigraphy | Every living line over time, coloured by the period it was written in. Switch to layers by author to watch ownership change hands, or pick a single module. |
| II · Geological map | A zoomable treemap of all files, coloured by median line age, hotspot score, owner, knowledge at risk or recent change. Click a file to drill a borehole: you get its core sample (age layers), stats, owners and the files that usually change with it. |
| III · Half-life | Kaplan–Meier survival of lines, overall and for source, tests, docs and config. |
| IV · Hotspots | Change frequency × indentation complexity. The upper right corner is where to refactor first. |
| V · Fault lines | Temporal coupling between modules (chord ring) and between files (pairs table). |
| VI · Knowledge & bus factor | Who wrote today’s lines in each module, bus factor, and the share written by people inactive for over a year. |
| VII · Fossil record | The oldest non-trivial lines still untouched at HEAD. |
| VIII · Extinctions & big bangs | Commits that deleted the most old code, and commits that added the most at once. |
| IX · Seismograph | Weekly commit trace and monthly active people. |
Strata are coloured with the official ICS chronostratigraphic palette. The oldest layer gets Hadean magenta and the topsoil Quaternary yellow.
Strata reads every commit’s graph data with git log --numstat. It then walks the first-parent history with git log -p -U0, keeping one array per file with the id of the commit that wrote each line. Hunk headers alone tell which lines die and which are born, so line contents are never stored. Lines that arrive through merges are credited back to the side-branch commits that wrote them. One git cat-file --batch pass over HEAD adds sizes, complexity and fossils.
The full walkthrough is in docs/GUIDE.md: the idea, the architecture, the algorithms, the metric definitions and the limitations.
strata verify checks every text file’s replayed length at HEAD against the real file. It also compares the commit and the author of each line, for a sample of files, with git blame.
| Repository | Commits | Files with wrong length | Commit per line vs blame --first-parent |
Author per line vs blame |
…if merges were credited to the merger |
|---|---|---|---|---|---|
| expressjs/express | 6.2k | 0 / 214 | 100.000% | 98.5% | 81.4% |
| psf/requests | 6.5k | 0 / 122 | 100.000% | 98.4% | 62.0% |
| jqlang/jq | 2.0k | 0 / 396 | 100.000% | 99.8% | 98.7% |
| tj/commander.js | 1.5k | 0 / 215 | 99.982% ¹ | 99.6% | 95.1% |
| vitejs/vite | 9.7k | 0 / 2721 | 100.000% | 99.0% | 97.5% |
| pallets/flask | 5.6k | 0 / 230 | 100.000% | 85.0% | 70.6% |
| git/git | 75k | 0 / 4782 | 100.000% | 95.5% | 10.1% |
¹ Two lines in byte-identical files where blame credits a different copy of the same content.
Survey time on a 2-vCPU machine:
| Repository | Time |
|---|---|
| express | 2.6 s |
| flask | 3.2 s |
| vite | 13 s |
| git/git (1.7M lines, 75k commits) | about 2 minutes (75 s with --fast) |
bin/strata.ts entry point
src/cli.ts argument parsing, terminal summary, `verify` output
src/types.ts every shared type, ending with the `Survey` the report is drawn from
src/git.ts streaming git runner, cat-file batch, path unquoting, pinned diff config
src/filters.ts default excludes, globs, file roles (source, tests, docs, config)
src/history.ts pass 1: commit graph, identities, first-parent chain, side commits
src/patch-parser.ts streaming parser for `git log -p -U0` / `git diff-tree` output
src/replay.ts pass 2: the line replay, strata snapshots, deaths, moves in merges
src/mergeblame.ts content-level credit for merged lines
src/headscan.ts HEAD blobs: LOC, indentation complexity, fossils
src/survival.ts Kaplan–Meier estimator
src/period.ts strata width: years, quarters or months
src/paths.ts time-aware rename resolution, adaptive modules
src/coupling.ts temporal coupling (files and modules)
src/analyze.ts orchestration and all aggregate analyses → Survey
src/narrative.ts findings as structured facts
src/i18n.ts EN/RU templates, plurals, ICS palette (shared by CLI and report)
src/verify.ts replay vs git blame
src/report/render.ts builds the single HTML file
src/report/template.html page layout and styles
src/report/client/*.ts browser code: one file per group of plates
test/ unit tests, replay-vs-blame integration, edge cases
examples/ sample reports
docs/GUIDE.md idea, architecture, algorithms, metrics, limitations
A good order for a first read:
src/types.ts. The shape of everything Strata produces. Survey at the end is the whole result.src/patch-parser.ts → src/replay.ts. The core idea in about 750 lines: hunk headers alone are enough to replay every line’s life. Start at Replay.run and Replay.applyFile.src/analyze.ts. How the passes are wired together (prepare, then survey), and how each plate’s numbers are computed from the replay.src/mergeblame.ts. The most intricate part: crediting merged lines back to the side-branch commits that wrote them.src/report/client/. Start at main.ts; each other file draws one or a few plates.Every file opens with a comment that says what it does and why.
npm install # TypeScript + @types/node; also builds dist/
npm test # 18 tests, a few seconds (runs the .ts files directly, Node 22.18+)
npm run typecheck # tsc --noEmit, strict
npm run build # compile to dist/ for older Node versions and for npm
The sources use only erasable TypeScript (no enums, namespaces or parameter properties), so Node can run them as they are. The browser code is bundled the simplest possible way: render.ts strips the types, drops import/export, and concatenates the client files into one script inside the page.
Strata started as an open-ended overnight experiment: Claude (Anthropic) was given a free evening and a sandbox folder, and this is what it built.