In one line. greplost derives a committed, deterministic map of a codebase from its syntax tree with no model in the loop, so humans and coding agents read the map instead of searching, and one command proves in continuous integration that the map still matches the code.
Every engineer opening an unfamiliar repository does the same thing. Search for a name, open a file, follow an import, search again, and after a dozen files decide they probably know what breaks if they change this one. Coding agents do the same, except each hop is a billed tool call and a slice of a context window. What both are reconstructing is neither secret nor subjective: the import graph, the call graph, the exported surface, and the set of files that break when one changes. A machine derives all of it in a fifth of a second. Nobody reads it from a file because every architecture document anyone has written went stale, quietly, and nothing said so. greplost writes that document from the syntax tree, commits it beside the code, and ships a command that fails the build the moment it stops being true.
Figure 1. Searching cannot tell you what it missed. A committed map can, and a check fails the build if that map has fallen behind the code.
Why this matters
- For anyone joining a codebase, or paged into one at 2am: the questions that matter first are structural, not semantic. What exists, what depends on what, what breaks if this changes. On hono (248 files) greplost answers all three from one committed file, at import edge precision and recall of 1.000 against the TypeScript compiler.
- For teams paying for coding agents by the token: orientation is the most expensive thing an agent does and the least valuable thing to pay a model to redo. greplost keeps the map current for 0.17 seconds of machine time per commit and $0, because no model is called in the structure layer.
- For anyone burned by a stale diagram: Reproducible Builds defines a build as reproducible when, given the same source, environment and instructions, any party can recreate bit-by-bit identical copies of the artifacts (status as of September 2026). Apply that to a documentation artifact and a new option appears: rebuild the map in memory and compare it byte for byte with the committed copy. Two greplost builds of one commit differ by 0 bytes, which is what makes the check mechanical rather than a matter of judgment.
🧭 If you only read this far: a map regenerated from the syntax tree, committed with the code, and proven current by a command that exits non-zero on drift is a document you can rely on. Refreshing an artifact and checking one are not the same thing, and today only one of these tools does the second.
Terms in 30 seconds
Domain engineers: skip ahead, this is orientation for everyone else.
- tree-sitter: an incremental parser that turns source text into a structured tree, so an import is a node with fields rather than a line to match with a regular expression. Used through the WebAssembly runtime
web-tree-sitter0.27.0, so an install never needs a C++ toolchain. (tree-sitter.github.io) - Grammar ABI: the versioned interface a compiled grammar declares. greplost vendors
tree-sitter-typescript0.23.2 (ABI 14) andtree-sitter-go0.25.0 (ABI 15), pinned because the runtime is backward compatible and not forward compatible. - Precision, recall, F1: of the edges emitted, the share that are real; of the real edges, the share found; their harmonic mean.
- Compiler truth: ground truth from the compiler, never from greplost. Imports and exports come from the TypeScript compiler API at 5.9.3; Go from
go list -json -depsandgolang.org/x/tools/go/callgraph/cha. - Blast radius: the reverse transitive closure of the import graph for one file, the honest answer to "what breaks if I change this".
- Reproducible build: "given the same source code, build environment and build instructions, any party can recreate bit-by-bit identical copies of all specified artifacts" (definition, status as of September 2026).
- Claude Code hooks: named lifecycle events a plugin attaches shell commands to. greplost uses
SessionStart,PreToolUse,PostToolUseandStop, all four current in the hooks reference (status as of September 2026). (reference)
The problem, for engineers who don't live in this space
Every large shopping mall has a floor plan by the entrance, and every one is slightly wrong. A unit changed hands, a corridor closed, the sign was printed in 2019. People keep reading it because it is mostly right, and keep being sent to a shop that is not there. Reprinting weekly costs more than the occasional wrong turn, so the sign rots and nothing at the entrance says it is old. Architecture documents rot the same way, for the same reason.
Here is the concrete version. hono is a web framework of 248 TypeScript files. You are asked to change src/types.ts, and the honest answer to "what else breaks" is a graph walk. Without a map you get it by searching for the filename, opening each importer and repeating; an agent spends a Grep, a Glob and a dozen Reads on the same job, filling the context window it needs for the code it is about to edit. Three other tools address this, each writing an artifact: Graphify 0.9.53 writes graphify-out/graph.json, Understand-Anything 2.9.0 writes .ua/knowledge-graph.json, code-review-graph 2.3.8 keeps a SQLite graph. All three install a hook that rebuilds after a commit.
Figure 2. All of them refresh after a commit; only one can say when the refresh did not happen. Nothing in a refresh-only design compares the artifact to the code, so a hook that silently fails to fire leaves a stale map that reads exactly like a current one.
Why this is genuinely hard. Two problems have to be solved at once. The first is deriving structure with no compiler in the loop: resolving tsconfig path aliases, workspace dependencies, package.json exports maps and Go module paths well enough to be scored against the compiler and survive. The second is making the artifact checkable, which is far stronger than making it rebuildable. The output must be a pure function of the input, with no timestamp, absolute path, machine name or model output anywhere in it, or a rebuild differs from the committed copy for reasons unrelated to drift and the check is worthless.
What exists today (and the gap)
| Tool | Structure derived with no model in the loop | Committed artifact a reviewer reads in the diff | Byte-identical rebuild of one commit | Cross-repo blast radius | Mechanical staleness check: byte comparison against a rebuild, exit 1 on drift |
|---|---|---|---|---|---|
| Graphify 0.9.53 | ⚠️ | ✅ | ✅ | ❌ | ❌ |
| Understand-Anything 2.9.0 | ❌ | ✅ | n/a | ❌ | ❌ |
| code-review-graph 2.3.8 | ✅ | ⚠️ | ❌ | ❌ | ❌ |
| greplost 0.0.1 | ✅ | ✅ | ✅ | ✅ | ✅ |
Reading the rows honestly. Graphify earns ⚠️ because it ships a documented no-model rebuild (graphify update .) alongside a /graphify first pass that puts a model inside extraction and clustering, and its rebuild is byte-identical exactly as greplost's is, so reproducibility is a genuine tie at 0 bytes for both. Understand-Anything is ❌ there because /understand is a multi-agent model pipeline end to end; its other cells read n/a rather than ❌ because it ships only as a Claude Code plugin with no headless entry point, so this harness never ran it and does not get to score it. code-review-graph's structure layer is model-free and its update is a real incremental diff, but its artifact is a SQLite database, so a reviewer never reads the architectural change in the pull request, and two builds of one commit differ by 5,160,286 bytes.
The empty column is the last one, and the claim is exactly this narrow: greplost has verify (byte comparison against a rebuild, exit 1 on drift). None of the three competitors ships an equivalent: their artifacts are refreshed, never checked.
Figure 3. What the empty column is not. Over 100 commits, with each tool's own hook installed and observed firing on 100 of 100, nobody decays: greplost holds 1.000, code-review-graph gains 0.003, Graphify loses 0.005. The distance between the lines is coverage, a different metric. The gap claimed here is not that they go stale, it is that nothing says when they have.
How it works
greplost is a five-stage pass over the source tree, plus a sync layer that keeps its output current and a plugin that points agents at it.
- Extractor: tree-sitter WebAssembly grammars, vendored in the package, producing declarations, imports, exports and call sites per file across TypeScript, TSX, JavaScript, JSX and Go.
- Resolver: turns a raw import specifier into a target through
tsconfigpaths, workspace packages,package.jsonexportsand Go module paths. - Graph and metrics: fan-in, fan-out, import cycles by Tarjan strongly connected components, blast radius as a reverse transitive closure.
- Render:
INDEX.md, a package map and API surface per package, one card per source file,graph/*.jsonlwith one sorted line per edge, and diagrams as Mermaid so GitHub renders them from the fenced block with no image pipeline. - Sync: a
pre-commithook that refreshes the map and stages it, so every commit carries the map of its own tree;post-commit,post-mergeandpost-checkouthooks running an incremental update after the fact; andverifyas the merge gate in continuous integration. - Plugin: a Claude Code plugin with four hooks, six slash commands, a skill documenting the
--jsonshapes, and a read-only navigator subagent.
Figure 4. One pass, five deterministic stages, one committed directory. The sync layer and the plugin both consume that artifact; neither writes anything the extractor did not derive.
Resolution that refuses to guess
A call edge is recorded only when the callee resolves to exactly one declaration: same-file and directly imported declarations at high confidence, a callee reached through re-export chains of any depth to a single declaration at med. An ambiguous name supplied by two star exports, an unresolved hop, or a cycle is dropped. export * chains are followed transitively as a fixpoint, because the compiler's own export set is transitive and a one-hop rule silently lost recall on nested barrel files.
The determinism contract
Every collection is serialized sorted, JSON keys are sorted, JSONL is one compact line per edge sorted by (from, to, kind), and Mermaid node order is the sorted identifier order. No timestamp, absolute path or machine name appears in the structure layer. Incremental update renders the whole map in memory and writes only the changed bytes, so full and incremental builds are byte-identical by construction rather than by careful bookkeeping.
The layer a model never touches
An optional semantic layer writes module summaries and flow diagrams with a model, cached by content hash. It is strictly downstream: content hashes flow into it and nothing flows back. A repository that never runs it is fully navigable, and deleting every summary changes no byte of the structure layer.
Figure 5. The boundary that makes the check possible. verify compares the structure layer and nothing else, and that layer holds no model output, so a byte difference can only mean the code moved.
The decisions that actually mattered
Drop the edge rather than guess it
The fork. A call site whose callee does not resolve to exactly one declaration can be emitted with a guess or dropped. Options. Emit everything and let the consumer filter, maximizing recall; or resolve conservatively, maximizing precision. Chosen. Drop it. On anyq (148 files) greplost emits 346 call edges and the compiler confirms every one: precision 1.000 with 0 false positives, against 0.877 for Graphify and 0.361 for code-review-graph, same corpus, same adapter. Trade-off accepted. Call recall is 0.468 there, so more than half the compiler's call edges are missing. An agent acting on a wrong edge does damage; one that finds nothing falls back to reading the code, which it was going to do anyway.
Rebuild everything in memory, write only what changed
The fork. An incremental update can regenerate only the artifacts it believes are affected, or render the whole map in memory each time. Options. Selective regeneration is less work per commit; whole-map rendering makes full and incremental output byte-identical by construction, which is the property the check depends on. Chosen. Whole-map rendering. Incremental update p50 is 129 ms and p95 164 ms across a 100-commit replay, and 0 of the full-versus-incremental comparisons differed. Trade-off accepted. The diff is noisier than it should be. Adding one import moves 54 artifact lines across 12 files, against a target of 10 and Graphify's 24: a published loss. What greplost's diff does that the others' do not is name both endpoints of the new edge on a changed line, so the architectural change is legible at all.
A check, not just a refresh
The fork. Given a hook that rebuilds after every commit, is a separate staleness check worth building? Options. Trust the hook, as every tool measured here does; or rebuild in memory, compare byte for byte, and treat a difference as a build failure.
Chosen. Both. The hooks run, and verify is the merge gate. Across a replay with drift injected at every step it caught 82 of 82 injected drifts, with 0 false positives after a clean update.
Trade-off accepted. This works only because of the determinism contract, so it constrains every renderer forever. One timestamp, one unordered map iteration, one model-written byte, and the gate becomes a source of red builds nobody trusts.
Figure 6. Why the check exists, in a decomposition no competitor publishes. Left: coverage, what each tool saw the day it was built. Right: what it lost afterwards, the faded bar being the same tool with no refresh at all. greplost's own map falls 0.081 in import F1 across 100 commits, code-review-graph 0.079, Graphify 0.018. Every tool is exposed to that column; only one reports it.
The numbers, including the losses
The harness ships in the repository, runs in continuous integration, and writes a results file that is regenerated rather than typed. Competitors run at pinned versions following their own READMEs, and adapters convert their output to a common edge schema, so every tool is scored by the same code against the same compiler truth.
Figure 7. Staying current is cheap when no model is involved. Over the same walk on hono, greplost's hooks cost 0.282 minutes of wall clock and $0: 12.6% of Graphify's 2.241 minutes. Every tool ran its no-model path, so the $0 is what was measured, not a claim that their documented first pass is free.
Figure 8. From a fresh copy to a usable map on anyq: 0.283 s for greplost against 1.207 s and 2.159 s, plotted against call-edge F1. Fastest to start and most accurate at once is what the determinism constraint buys.
On hono, greplost measures import precision and recall of 1.000 (571 true positives, 0 false positives, 0 false negatives), export precision 1.000 and recall 0.999, call-edge precision 1.000 at high confidence with recall 0.730, and an exact import-cycle set match. Five hono files count as unparsable, meaning tree-sitter's parse root is an error node: 0.23.2 is the newest TypeScript grammar that exists, and hono's anonymous generic call signatures hit open upstream issue #335, filed 11 June 2025 and still open as of September 2026. That limitation is inherited, not fixed.
📌 Honest scope. greplost is 0.0.1 pre-release, dogfooded on its own repository and benchmarked on tier S and tier M corpora (anyq 148 files, gin, hono 248 files). There is no 10,000-file run, so targets written for 10k files are scored at the tier measured and say so. The 100-commit walk is synthetic: one resolvable import appended per commit, no deletions, renames or new files, the easy direction for an incremental updater, where the specification asks for 500 real commits. Understand-Anything is n/a on every measured row, so nothing is claimed against it. The diff-signal metric is a loss. The agent navigation benchmark has a harness, a task suite and a scoring path but no result file: it costs model credits and has not been run, so those rows read
not runrather than zero and no agent saving is claimed here. The human study has no participants and no data. "Validated" here means a continuous integration job stands behind it: typecheck, the test suite,verifyon this repository's own map, a README-versus-results check, and the tier-S structural, map-quality, 100-commit freshness and tier-S performance gates.
Why this is new, and why it's significant
What's novel. greplost ships a mechanical staleness check: verify performs a byte comparison against an in-memory rebuild and exits 1 on drift. None of Graphify 0.9.53, Understand-Anything 2.9.0 or code-review-graph 2.3.8 ships an equivalent: their artifacts are refreshed, never checked. The claim is deliberately that narrow. Not that greplost is more accurate, though on this corpus it is. Not that its output is uniquely reproducible, because Graphify's rebuild is byte-identical too. Not that competitors decay under their own hooks, because over 100 commits they do not. A refresh is an action and a check is a proof, and only one of the four produces the proof.
Why it's significant to the field. The Reproducible Builds project spent a decade establishing that bit-for-bit rebuild equality turns an artifact from something you trust into something you can check, and that argument has been made almost entirely about compiled binaries. Documentation was left out, not because the argument fails for it but because it was written by hand and there was nothing to rebuild. Once a map is derived rather than authored, the property becomes available and the same guarantee follows: a build gate that catches drift, instead of a convention that asks people to remember. That guarantee has also just acquired somewhere to matter. The Claude Code hooks and plugin interface gives a tool a documented way to inject context at SessionStart and nudge before a Grep (status as of September 2026), so an always-current map has an execution surface rather than being a file someone hopes an agent reads. There, a map that cannot be proven current is a liability, because an agent acts on it without hesitating.
Standards and ecosystem alignment. This work implements or aligns with:
- Reproducible Builds (definition, status as of September 2026): the determinism contract is that definition applied to a documentation artifact, measured as 0 bytes differing across two builds of one commit.
- tree-sitter, through
web-tree-sitter0.27.0 withtree-sitter-typescript0.23.2 (grammar ABI 14) andtree-sitter-go0.25.0 (ABI 15) vendored, so no grammar is downloaded or compiled at install time. - Claude Code plugins and hooks (reference, status as of September 2026):
SessionStart,PreToolUse(Glob,Grep),PostToolUse(Edit,Write,MultiEdit) andStop, all four advisory; none can block a tool call or change a permission decision. - Mermaid in GitHub-flavored Markdown (GitHub docs): diagrams render on GitHub with no image pipeline, keeping the map reviewable in a pull request.
- The TypeScript compiler API (5.9.3) and the Go toolchain as ground truth, so no score is computed against greplost's own output.
There is no upstream contribution yet. The one upstream issue this project depends on, tree-sitter-typescript #335, was filed by someone else and is cited as an inherited limitation, not a contribution.
See it run
bun add -g greplost # or: npm i -g greplost
cd your-repo
greplost init # builds .greplost/, installs git hooks, writes config.json
git add .greplost && git commit -m "greplost: add the map"
greplost impact packages/core/src/build.ts --depth 2
packages/core/src/build.ts blast radius 29, showing depth <= 2 # ← the answer to "what breaks"
DEPTH FILE
1 packages/core/src/index.ts
2 packages/cli/src/commands/query.ts
2 packages/sync/src/incremental.ts
2 packages/workspace/src/cross.ts
# the whole continuous integration contract
- run: bun add -g greplost
- run: greplost verify --diff
greplost: map is out of date (9 changed, 0 missing, 0 extra) # ← exit 1, unified diff below
changed INDEX.md
changed graph/imports.jsonl
changed manifest.json
🔬 Go deeper. The check is one file:
packages/sync/src/verify.tsrebuilds the structure layer in memory and compares it path by path against what is committed, at commit0c62e52. Everything else exists so that comparison can mean something.
What's next
The next number to produce is the one deliberately missing: the agent navigation benchmark, measuring tokens, tool calls, accuracy and wall clock on the same tasks with and without the map. The harness is written; running it costs model credits, and until it runs those rows say not run. After that, the tier L corpus and the diff-signal loss, which is a rendering problem rather than a design one. The larger arc is the one every agentic tool in this portfolio is on: what an agent needs is rarely more intelligence, it is a smaller and more trustworthy set of facts. A map that is proven current is a fact. Everything else is a search.
Appendix / references
- Repository: github.com/sns45/greplost
- Design and full results:
docs/greplost-tech-spec.md(3.1 and 10.0 for the head-to-head targets, 5.3 for the determinism contract, 7.3 for the verify gate); every measured number with its caveats is inbench/RESULTS.md. - Prior art measured: Graphify v0.9.53 · Understand-Anything v2.9.0 · code-review-graph v2.3.8
- Corpora: anyq · gin · hono, each pinned in
bench/corpus.json - Standards referenced: Reproducible Builds · tree-sitter · Claude Code hooks · Mermaid in GitHub-flavored Markdown
- Related in this series: tickettok · dear-claude · better-call-claude
- Discussion / feedback: github.com/sns45/greplost/issues