Your codebase, mapped for agents.
A small index and an interactive map, generated locally in one pass.
gin | go | 8 modules | 19,537 lines | Gin, GitHub Actions, net/http
test: go test ./... | env: PORT
modules:
.github .github
.github/workflows workflows
binding binding | depends: bytesconv, json
ginS gins
internal/bytesconv bytesconv | depended-by: binding, render, root
internal/json json | depended-by: binding, render, root
render render | depends: bytesconv, json
root root package | depends: binding, bytesconv, json, render
do-not-touch: .github/ Install, then run it in your repo
Pick one. The first run scans the current directory and writes the index.
-
$ npx stacklit initNo install. Downloads the binary on first use. -
$ brew install glincker/tap/stacklitHomebrew tap for macOS and Linux. -
$ go install github.com/glincker/stacklit/cmd/stacklit@latestFrom source, needs Go.
Then commit stacklit.json, and run stacklit setup to wire up your AI tools. Quickstart
What a run looks like
The real output of stacklit generate --graph on gin v1.10.0 (101 files) from a cold cache. It writes stacklit.json, a Mermaid diagram and a self-contained HTML map.
[stacklit] found 101 files
[stacklit] parsed 101 files (0 errors, 0 cached)
[stacklit] done in 37ms -- wrote stacklit.json, DEPENDENCIES.md, stacklit.html What you get
An index agents can read
Modules, what each exports, who depends on whom, and hints for tests and where to add code. Plain JSON you commit next to the source.
{
"render": {
"purpose": "Render",
"language": "go",
"files": 15,
"lines": 1304,
"exports": [
"type JSON",
"type IndentedJSON",
"type SecureJSON",
"type JsonpJSON"
],
"depends_on": [
"internal/bytesconv",
"internal/json"
],
"depended_by": [
"root"
]
}
}Excerpt. Export lists are capped by default, so a module entry is not exhaustive.
A small map to steer the agent
stacklit derive prints a compact map of a few hundred tokens. stacklit setup injects it into the config of your assistant, so a new session starts oriented. The same graph answers token-budgeted queries, also exposed as MCP tools.
query "how are route params bound to a handler"
60 seeds, 83 candidates
context.go
404-406 func (c *Context) Param(key string) string // Param returns the value of the URL param.
gin.go
298-301 func (engine *Engine) NoRoute(handlers ...HandlerFunc) // NoRoute adds handlers for NoRoute.
336-358 func (engine *Engine) addRoute(method, path string, handlers HandlersChain)
calls tree.go::node.addRoute E0.90
605-671 func (engine *Engine) handleHTTPRequest(c *Context)
ginS/gins.go
41-43 func NoRoute(handlers ...gin.HandlerFunc) // NoRoute adds handlers for NoRoute.
calls gin.go::Engine.NoRoute I0.55
routergroup.goOutput cut to the first lines. MCP tools
A map you can open
Every run also writes one HTML file with graph, tree, table and flow views. No server and no build step. Six maps generated from pinned public repos are on the demos page.
Markdown index, GraphML, Cypher, JSON, DOT,
Mermaid, an Obsidian vault, a markdown wikiFrom the commands reference. Commands
The same bytes every time
Output has no timestamps by default, so the file changes only when the code does. Two cold runs on the same checkout gave one hash.
run 1 03b6d695713b8088911157b95a0e5c2303a00007
run 2 03b6d695713b8088911157b95a0e5c2303a00007
$ stacklit diff
Index is up to date. No source changes detected.Works with
Setup covers 26 assistants. These are some of them. Aider has no logo on thesvg.org, so it keeps its letter.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Cline
- Zed
- Codex
- Gemini CLI
- Continue
- OpenCode
- Junie
- Kilo Code
- Aider
Any MCP client works through stacklit serve, and anything else can read stacklit.json directly. Logos load from thesvg.org when you view this page. Without them the letters stay.
Plain terms
- MIT licenseRead it, fork it, ship it.
- FreeNo paid tier, no gated features, no account.
- Local onlyNo telemetry, no upload.
- One Go binaryNothing to run alongside it.
- DeterministicSame code, same bytes.
What we measured
47 "where is this implemented?" tasks across 6 pinned public repos in six languages, run on 2026-11-08. No model was involved, and tokens are bytes divided by four. Intervals are 95% bootstrap intervals over tasks.
| Approach | Found first | Found in top 5 | Tokens to find |
|---|---|---|---|
| grep and read | 89% [81-98] | 100% [100-100] | 5.9k [3.8k-8.7k] |
| BM25 over the question | 57% [43-72] | 89% [81-98] | 11k [6.7k-15k] |
| stacklit map plus lookup | 55% [40-70] | 74% [62-87] | 5.9k [4.6k-7.3k] |
| stacklit query 1000, read in printed order | 81% [70-91] | 96% [89-100] | 9.1k [6.2k-13k] |
- Grep and read leads on accuracy, and its search pattern was written by someone who knew the answer. The query command's gap to it, in percentage points with an interval, is -9 [-23, +6] at found first and -4 [-11, +0] at top 5. Both intervals reach 0, so the gap is suggestive, not settled.
- The token intervals overlap. We claim no token saving over grep or BM25, and when you already know the exact symbol, grep is cheaper than loading a map.
- The query command used to print files sorted by path. Printed by relevance, the order an agent reads it, it beats BM25 on finding the right file first. It is a proxy, not an agent evaluation, and the scoring was tuned after looking at some benchmark failures.
Index size on each repo. Generation times are on the benchmark page, where they are marked as upper bounds from a loaded machine.
| Repo | Language | Source lines | Modules | stacklit.json tokens | derive map tokens |
|---|---|---|---|---|---|
| gin | Go | 19,537 | 8 | 2,491 | 137 |
| flask | Python | 18,515 | 14 | 2,818 | 221 |
| hono | TypeScript | 57,263 | 77 | 16,022 | 1,351 |
| ripgrep | Rust | 52,215 | 34 | 8,586 | 477 |
| gson | Java | 53,490 | 15 | 5,748 | 340 |
| rack | Ruby | 19,805 | 9 | 2,391 | 151 |
Read the full benchmark page, with 58 tasks in all, methodology, the dependents results and limitations.