Skip to content

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 — --root if 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 a root still 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

Index coverage on one unrelated TypeScript repository of 247 files. 120 are indexed as TypeScript or JavaScript; 127 are not indexed at all. Every response prints both counts, scoped to the path searched. 'Not found' from these tools means 'not found in TypeScript and JavaScript'. The unindexed count is printed so that a gap never reads as a confident absence — which is the whole of the difference between a limit and a bug.
'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()), a require, 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.js name — not a path, because examples/ and vendor/ hold real source — and stopped walking what the repository's .gitignore ignores. 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_symbol for fail refuses 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-checkout still walks upwards through three find_callers hops. 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.