scope
Limits
Five runs, one proof of concept. Not a product.
Run 4's brief was evaluation, not features, so what it found it wrote down rather than patched. Run 5 then closed the coverage gaps that list exposed — the ignore file, symlinks, bundled files, call attribution, an optional root — and left everything that needs a decision rather than a fix. This page is what is left.
non-goals
What it was built not to become
- no editor integration
- No VS Code extension, no LSP server, no language-server protocol adapter of any kind.
- no remote service
- stdio transport only. No HTTP, no auth, no multi-tenancy, no deployment. Run 5 gave the server a default root —
--rootif one was passed, else the git repository above the directory it was launched in — so a call need not name one, and it prints which it chose and why on stderr. A call that names arootstill wins, and there is still no configuration file. - no language ambition
- TypeScript and JavaScript. Python and Go grammars already ship with the WASM package, so a second language is a small addition rather than a project — and it is still not done.
- no semantic search
- No embeddings, no vector store, no ranking model.
- no network at runtime
- This is a local filesystem tool.
No write tools was lifted for run 3: retrieval alone does not close the loop, because the model still has to write the code back and rewriting a whole file to change one function is where the token cost returns.
No persistent cache was partly lifted at the same time — an index keyed on path plus mtime plus size, so a server start re-parses only what changed. Still no daemon and no separate process: the index is a file the server reads at startup and rewrites when it refreshes.
Both are recorded in PROJECT.md struck through rather than deleted, because the numbers there were measured under them.
node --import tsx src/server/main.tsdaiveloper 0.1.0 listening on stdio
That line goes to stderr. stdout belongs to the JSON-RPC transport, and a diagnostic written to it breaks the session.
what it cannot see
Four things a response will not tell you unless you read the count
- 'not found' means 'not found in TypeScript and JavaScript'
- On an unfamiliar repository, responses read
120 files scanned, 127 unindexed. Over half the tree — Markdown, JSON, CSS — is invisible to these tools, and the count is printed precisely so a gap never reads as a confident absence. - four call shapes come back unattributed
- Run 5 resolves each call site through the calling file's own imports, so sites arrive under the declaration they actually call. Four shapes defeat that and are reported rather than guessed: a call through a property, a namespace import (
ns.thing()), arequire, and a path alias whose suffix matches more than one file. Those land under?; sites resolving into a package are counted and excluded. An empty result is still not proof that a symbol is unused. - generated files are skipped, and the skip is counted
- Run 4 found a 556-line file whose first line was a 15,020-character minified bundle, indexing as dozens of one-character declarations. Run 5 stopped parsing and searching those on a line over 2,000 characters or a
.min.jsname — not a path, becauseexamples/andvendor/hold real source — and stopped walking what the repository's.gitignoreignores. All of it lands on the response:45 files scanned, 3 unindexed, 2 paths not walked, with the generated files named. - an ambiguous name always costs a round trip
read_symbolforfailrefuses with 2 declarations named 'fail'; pass file, or qualify as Class.method. A fixture with one deliberate decoy under-represents how often this happens; in a real repository it is the normal case.
cut, and still cut
Carried forward rather than quietly dropped
- callee-direction traversal
call-chain-checkoutstill walks upwards through threefind_callershops. One call in the other direction would answer it.- a combined locate-and-read call
- The most likely remaining token win, named in run 3 and not taken. It is the whole of
error-to-status-mapping's +26.1% against the oracle. - file watching
- Cut because the staleness it would fix does not exist: every indexed tool call refreshes before it reads, and a no-op refresh over 45 files costs 0.59 ms. It becomes worth doing on a tree large enough for the stat sweep to dominate. Neither run 4 nor run 5 re-measured it, so 0.59 ms is still run 3's number on run 3's tree.
- Python
- Optional from the start, still not done.
In a sandbox where ~/.cache cannot be created, the server prints one line to stderr and falls back to the OS temp directory. That is correct behaviour — and in a container that resets /tmp, it means every server start is cold.
The repository also shipped for three runs with an empty README at its root, present since the initial commit and never filled in. Run 4 found it. Nobody outside the project could have installed the thing.