2026-09-20
Pack the repo, not the prompt
Real repositories do not fit in a context window. Everyone who has pasted half a monorepo into a chat already knows this. The usual responses are worse than the problem: dump more files, raise the window, or hope the model guesses the right path. I got tired of that, so I wrote a local indexer that walks the tree once and only packs what the task needs.
The window is not the repo
A 128k window is a billing feature. It is not a map of the code. Even when the window is huge, stuffing it full of tests, snapshots, generated fixtures, and three copies of the same helper does not make the model more oriented. It makes the relevant function sit in the middle of noise. Agents fail in boring ways here. They edit the test that mentioned the symbol instead of the implementation. They miss the import graph. They hallucinate a file that would have been obvious if the outline had been in the prompt instead of a random grep.
Hand-picking files does not scale past a few thousand lines. You do it for an afternoon and then you start skipping the boring packages, which is exactly where the bug is. I wanted something I could point at a tree I do not already hold in my head.
Index once
grasp walks the repo, honors `.gitignore`, skips binaries, and builds an index: symbols, imports, an import graph, chunked source, a BM25 text index. It writes `.grasp/index.json`. That is the whole trick. Run `grasp index` after significant changes. Nothing watches the tree live, on purpose. A silent reindex in the middle of a refactor is a good way to search yesterday's names.
I pointed it at `facebook/react` while building it: 7,226 files, 88,854 chunks, indexed in about fifteen seconds on a laptop. That number is a calibration, not a benchmark claim for your machine. It is enough to know this is not a toy that only works on the blog's own repo.
Zero runtime dependencies. Node's own `fs`, `path`, and `crypto`. You can `npm i -g @zent7x/grasp` or `npx @zent7x/grasp index` from inside the repository you want it to understand. CLI for your shell, or an MCP server over stdio when an editor or an agent needs the same context on demand.
Ask, then pack
`grasp ask` ranks chunks against a task and prints `path:Lstart-Lend [symbol] score`. The score is BM25, boosted when a query term matches a symbol name, boosted again when a top hit is graph-adjacent to another top hit. That last part matters. A function is rarely the whole story. The caller two files over is usually the reason you asked.
`grasp pack` is the thing I actually paste. Same ranking, but the output is a Markdown bundle of real source, packed greedily up to a token budget. `--budget 8000 --out context.md` is a typical invocation. Drop `--out` and it prints. This is what `grasp_pack` returns over MCP. It is not a summary. Summaries are how you lose the line that had the bug. It is the source, truncated by relevance instead of by hope.
`grasp outline` is the other door. A compact, directory-grouped symbol tree. Every function, class, and const it found, with line ranges, no bodies. When I do not know what to ask yet, I read the outline first. Asking a vague question of a 7,000-file tree is how you get a vague pack.
Noise is the default
Test, fixture, snapshot, and example files are demoted unless you pass `--tests`. Otherwise the pack fills with the files that mention the words most often, which in a healthy repo is the test suite. The implementation then ranks under the noise, and the agent 'helps' by rewriting an assertion. I would rather miss a test name than miss `src/`. You can turn the demotion off when the task is the tests. The default is the opposite of dump-everything, because dump-everything is how we got here.
There is a TUI if you want to poke the index with your hands: `grasp tui`. Type a query, open a chunk, pack the top results, quit. Color on a TTY, `NO_COLOR=1` when you do not want it. I use the CLI more than the TUI. The TUI is for the days when I do not yet know the name of the thing I am hunting.
Local, on purpose
grasp runs on your machine. The index is a file in the repo's working tree, not a cloud. That is the same reason tally is one HTML file and cogrep does not phone home. I do not want a third party holding a map of a private codebase so an agent can be slightly more helpful. If the job is 'give the model the right source', the right source can be selected offline.
This pairs with routing.run more than it looks. One tool decides what text leaves your disk. The other refuses to keep that text once it is in flight. I do not want a world where we carefully pack a prompt and then a router writes it down. Pack the repo. Forget the prompt. The rest is models, and models are a commodity.
If you want the commands, they are in the README. If you want the position, it is this. Do not throw the tree at the window. Build a map, ask a question, pack only what the question needs. Then go write the patch. The agent can come along. It does not get to eat the whole company to do it.
Reply by email: zentex@warm.run