repo-saga
May 9, 2026 · View on GitHub
Render the civilization history of any git repository — eras, events, posters, and JSON, generated locally from heuristics.
Sample poster (epic theme). The same data renders in English and 中文 — see examples/.
repo-saga reads a repo's git history and turns it into a "chronicle": a small set of named eras ("Ancient Era: Initial Chaos", "Migration Era: TypeScript Invasion", "Modern Era: Linting Theocracy", …) and events with evidence pulled directly from the commits. Each meme is grounded in a fact: a tsconfig.json that appeared on a date, a linter that arrived, a 90-day window where 800 files moved.
It runs entirely on your machine. No OpenAI / Anthropic / Gemini calls, no auth, no SaaS, no database.
$ npx repo-saga https://github.com/sindresorhus/execa
▲ repo-saga analysing https://github.com/sindresorhus/execa
init Resolving source: …
git-log Reading git history…
parsing Parsed 1,302 commits 40%
detecting Running heuristic detectors… 70%
eras Carving the timeline into eras… 85%
done Saga complete. 100%
✓ Wrote ./saga.json
✓ Wrote ./saga.md
✓ Wrote ./saga.svg (theme: epic)
Eras: 4 Events: 9 Commits: 1,302
2014–2017 Ancient Era: Initial Chaos
2018–2020 Migration Era: TypeScript Invasion
2021–2022 Federation Era: Monorepo Federation
2023–2025 Modern Era: Linting Theocracy
▲ repo-saga preview at http://127.0.0.1:54323
Install
🚧 The npm package isn't published yet —
npx repo-sagawill land in a future release. For now, run from source:
git clone https://github.com/teee32/repo-saga.git
cd repo-saga
pnpm install
pnpm build
Then run the CLI directly:
node packages/cli/dist/index.js # opens the local web UI
node packages/cli/dist/index.js https://github.com/owner/repo
node packages/cli/dist/index.js ./my-project --theme academic --lang zh --out ./out
node packages/cli/dist/index.js ./my-project --granularity quarter --out ./out
If you'd like a shorter command, alias it:
alias repo-saga="node $(pwd)/packages/cli/dist/index.js"
# or link the CLI globally for this checkout:
pnpm --filter repo-saga link --global
Requires Node.js 18.17+, pnpm, and git available on $PATH.
CLI
| Form | Behaviour |
|---|---|
repo-saga | Start the local server, open the web UI in your browser. You enter repos there. |
repo-saga <repo> | Analyse a repo. Write saga.json, saga.md, saga.svg to the current dir. Open preview in browser. |
repo-saga <repo> --no-open | Same as above, don't auto-open. |
repo-saga <repo> --no-server | Just emit the files; don't start the web server. |
repo-saga <repo> --out ./output | Write outputs into ./output. |
repo-saga <repo> --theme dark-fantasy | Choose an SVG theme (epic, dark-fantasy, academic, minimal). |
repo-saga <repo> --granularity quarter | Slice eras by quarter while keeping the same era/event poster style. |
repo-saga <repo> --granularity month | Slice eras by month for a finer chronicle. |
repo-saga <repo> --granularity days --bucket-days 30 | Slice eras into custom fixed-size day buckets anchored to the repo's first commit. |
repo-saga <repo> --max-commits 10000 | Cap how many commits to read on huge repos. |
repo-saga <repo> --port 5555 | Pin the web server port. |
repo-saga --help | Show all flags. |
<repo> may be:
- An HTTPS GitHub / GitLab / Bitbucket URL (
https://github.com/user/repo) - A
git@…:user/repo.gitSSH URL - A local path to a directory containing a
.gitfolder
When you give a remote URL, repo-saga clones it into $TMPDIR/repo-saga-cache (configurable via --cache-dir).
Outputs
A run produces three artefacts (and a live web view of the same data):
saga.json
A structured snapshot of everything the analysis discovered:
{
"schemaVersion": 1,
"repo": {
"name": "execa",
"source": "https://github.com/sindresorhus/execa",
"commitCount": 1302,
"contributors": 84,
"tagCount": 96,
"firstCommitDate": "2014-...",
"lastCommitDate": "2025-...",
"firstPeriodLabel": "2014 Q2",
"lastPeriodLabel": "2025 Q3",
"timelineGranularity": "quarter"
},
"eras": [ /* 3..7 named eras with evidence */ ],
"events": [ /* 14 detector outputs with evidence */ ],
"stats": { /* commits/year, languages/year, … */ },
"meta": { "generator": "repo-saga", "generatorVersion": "0.1.0", "durationMs": 1234 }
}
When timelineGranularity is not year, renderers should prefer
displayStartLabel / displayEndLabel and startDate / endDate on eras and
events. The legacy startYear / endYear fields are kept for ordering and
overlap compatibility, and may contain synthetic axis values rather than real
calendar years.
saga.md
A printable / pasteable Markdown chronicle:
# The Civilization of execa
## Ancient Era: Initial Chaos, 2014–2015
> _foundations and improvisation_
Evidence:
- 168 commits in the first 4 months
- package.json appeared on 2014-04-19
- src/ and test/ appeared within 11 days
…
saga.svg
A standalone, themable SVG poster with self-contained raster ornaments embedded as PNG data URIs. Looks like a tabletop "history of a project" map.
Use --granularity year|quarter|month|days to change how eras are carved. The renderer keeps the same poster style and swaps in period labels such as 2024 Q1 or 2024-01-01 +30d. Very fine-grained timelines are coalesced to keep the JSON, SVG, and web UI bounded.
Web UI
Running repo-saga (no args) launches a local Fastify server (random port) and opens the UI. Features:
- Paste a repo URL or local path → click Generate
- Live progress events stream over Server-Sent Events
- See eras as cards; click one to view its events with full evidence
- Time-travel slider — drag to any day in the repo's life and watch the active files, top contributors, and in-flight detector events for the ±30-day window snap into focus; the matching era highlights as you scrub
- Contributor perspective — pick a contributor to re-read overlapping events in their voice ("you joined in 2018…"), and see which files they touched most
- Why did this fire? — every event card has a debug panel listing the positive rule the detector matched and the metric values vs. their thresholds
- Switch themes (
epic/dark-fantasy/academic/minimal) - Download the SVG, copy the Markdown, view the JSON
In dev (pnpm --filter @repo-saga/web run dev) the web UI proxies /api/* to a local backend on 127.0.0.1:7878.
How it works
There is no machine-learning model and no LLM. The pipeline is:
┌─────────────┐
url|path ──▶ │ resolve │ (clone if needed, into $TMPDIR/repo-saga-cache)
└─────────────┘
│
▼
┌─────────────┐
│ git log │ --numstat with a sentinel format
└─────────────┘
│
▼
┌─────────────┐
│ analyse │ yearly stats, signal-files index, contributor stats
└─────────────┘
│
▼
┌─────────────┐
│ detectors │ 14 heuristic detectors → DetectedEvent[]
└─────────────┘
│
▼
┌─────────────┐
│ eras │ cluster events into 3..7 named eras
└─────────────┘
│
▼
┌─────────────┐
│ renderers │ saga.json / saga.md / saga.svg
└─────────────┘
Each event carries a confidence (0..1) and severity (minor, notable, major, epoch-defining). Evidence is always a list of concrete facts (paths, dates, counts) — no opinions without receipts.
Heuristic detectors
| Detector | Evidence basis |
|---|---|
initial-chaos | First 60 days: commits, founders, founding files |
typescript-invasion | tsconfig appearance + ts/tsx insertions crossing JS |
great-refactor-war | 90-day window with the most files touched, renamed, deleted |
testing-famine | Multi-year stretch where tests are <5% of insertions |
testing-renaissance | Test-config arrival OR test-files-per-year jump |
linting-theocracy | eslint / prettier / biome / ruff / husky / lint-staged appearance |
container-empire | Dockerfile / compose / k8s / helm / terraform appearance |
monorepo-federation | pnpm-workspace / turbo / nx / lerna / packages/ / apps/ appearance |
dependency-cataclysm | Single commits churning >1k lockfile lines |
founder-exodus | Top early contributors who stop appearing in the final quartile |
new-dynasty | Contributors who only show up in the final quartile and dominate it |
ai-priesthood | openai / anthropic / langchain / claude / cursor / copilot / … appearances |
bug-plague | A month where ≥40% of commits are fix/bug/hotfix/revert |
release-empire | ≥5 dated tags, especially semver-shaped ones |
All detectors are stateless and run in parallel. Add your own by exporting a Detector from @repo-saga/core.
Project layout
repo-saga/
package.json ← workspace root, scripts: build/test/lint/format
pnpm-workspace.yaml
tsconfig.base.json
vitest.config.ts
eslint.config.js
packages/
core/ ← git mining, analyzer, 14 detectors, era grouping
renderer/ ← JSON / Markdown / SVG
server/ ← Fastify + SSE jobs API
web/ ← Vite + React UI
cli/ ← `repo-saga` bin entry
examples/ ← sample saga.json / saga.md / saga.svg
Development
pnpm install
pnpm build # build all packages
pnpm test # vitest across packages
pnpm lint # eslint across packages
pnpm format # prettier write
Run the CLI from the workspace:
node packages/cli/dist/index.js https://github.com/sindresorhus/execa --out ./tmp
Run the dev UI (with backend hot-loop):
# terminal A (backend; proxy target)
node -e 'import("@repo-saga/server").then(m => m.startServer({ port: 7878 }))'
# terminal B
pnpm --filter @repo-saga/web run dev
Roadmap
- Optional plug-in detectors (file paths or npm packages)
- More themes (newspaper, blueprint, comic)
- Multi-repo "civilizations meet" comparisons
- HTML poster export for printing
- Support hg / fossil via the same heuristic core
- Optional AI re-write of narrative copy (opt-in flag, BYO key)
Contributing
Issues and PRs are welcome. The core idea: every chronicle line should have evidence behind it. If a detector adds flavour text without a fact, that's a bug.
pnpm installpnpm build && pnpm test && pnpm lint- Add tests for your detector or renderer change
- Open a PR
License
MIT © repo-saga contributors
Community
Discussion, feedback, and ideas: LinuxDo