Named project roots for dependency sources
The agent reads dependency source through the opensrc cache, outside the session root. Before this decision only codegraph_node file mode could reach it, by accident: an absolute path outside the session root built an index in a tree nobody asked about, served it with no label, and codegraph_explore answered the same path from the wrong project. We decided that all six tools take an optional projectRoot, that a call naming one is served from it and labeled with its name and version, that building there is allowed only under a Trusted root, and that serving an index which already exists is allowed anywhere.
The decision
- All six tools take an optional
projectRoot. It is the call's anchor: file arguments resolve inside it and never move it. - Building an index in a Named root is allowed only under a Trusted root:
$OPENSRC_HOMEor~/.opensrc, plusCODEGRAPH_PI_TRUSTED_ROOTS, plus/codegraph add <path>for the session. Serving an existing index is not gated: the act that needs consent is the one with a cost and a side effect. - One root serves one call. No call spans two roots, because the graph is per-root and edges exist only inside one index.
- A Named path inside a cache entry snaps up to that entry, so one fetched source has one complete index instead of one partial index per member crate.
- A Dependency source gets no watcher and no prewarm. It reconciles before each query, which measured about 22 ms per 1 000 files.
- A version mismatch stays visible instead of being detected. opensrc's cache can hold a version the project does not use; the label is what lets the agent see it.
Considered Options
- A name parameter resolved through opensrc. Rejected on evidence.
opensrc path <name>resolves against a registry, not against the cache:opensrc path crates:accesskitfetched 0.25.0 over the network and rewrote the cache manifest while 0.24.1 sat cached, and--cwddid not switch the registry for a Rust project (Package "accesskit" not found on npm). Its lockfile detection is npm-only, and otherwise it uses the latest published version. A name therefore cannot be resolved offline to the version a project pins, and resolving it inside a tool would put a network fetch in a search call. The agent already holds the absolute path:opensrc pathruns in bash, in the open. - Anchor inference - extend
codegraph_node's file anchor to all six tools, so any path-shaped argument moves the root. Rejected. It reverses the rule that afileused to disambiguate a symbol never moves the anchor, andcodegraph_explore's query is free text, so inference there is guesswork. - A central index cache outside the tree. Not possible.
CODEGRAPH_DIRrenames the index directory but it must stay a single path segment inside the root; a separator,.., or an absolute path is ignored. A symlink re-opens the lockfile and inode-replacement problems ADR 0002 rejected, and copying the source would make every emitted path a lie. The index lives in-tree, soopensrc removeandopensrc cleanremove it with the source - the right lifetime for an index that is disposable and rebuildable in seconds.
Consequences
- This reverses "there is no
projectPathparameter on any tool" in the extension README and the assertion intests/codegraph/tools.test.ts. Both are rewritten, not deleted: the parameter is bounded, and the bound is the point. Upstream's word stays out of the schema, because upstream's parameter is unbounded. - Results from a Named root must be labeled, since two roots both contain
src/lib.rs. The label comes fromopensrc list --json: longest path prefix wins, all names on one path are shown, and a root that matches no entry falls back to a path form. A missing label never fails a call. One module names opensrc, the wayindexAdapter.tsnames the codegraph library. - Measured cost of a build: 1.3 s and 34.6 MB for 390 files (egui 0.36.1), 2.4 s and 99 MB for 1 218 files (pi 0.84.4). Disk is 3-5x the source, which is why the Trusted-root bound exists and why no prewarm was added.
- The prompt note gains one policy line, and only when a Trusted root exists on disk. Where no call can build, the note must not advertise one.