feat: index extra source roots beside the workspace, for the code you write against

The workspace index cannot answer questions about an installed framework or
SDK, and in plugin development that is where most of the reading goes: 64 of
80 tool calls in one real session went to the harness checkout under
/usr/local/lib/node_modules to look up the API being implemented.

A root is entered whatever it is called, and its dependencies are not: a
path ending in node_modules/... is the normal way to name a package tree,
while a node_modules below the root is somebody else's code. A result
carries the label that says which tree it came from and an absolute
sourcePath, the only form a follow-up read can resolve.

The tool description now also claims the move this replaces - searching
before reading files to find out what a module contains.
This commit is contained in:
NotExpectedYet committed 2026-09-27 07:16:21 +01:00
1 parent bccd112ce5
commit de4d020dcb
8 files changed
+873 -22

No files matched your search

+93 -1
View File
@@ -168,7 +168,99 @@ machine.
The tool description steers the model on when *not* to use it: prefer `grep` when
you already know the symbol, `read` when you need the whole file, and do not
follow up with a `read` of a range you were just handed.
follow up with a `read` of a range you were just handed. It also claims the one
move it exists to replace: **reaching for it before reading files to find out
what a module contains**, because that is a search — and one search that warms a
bounded slice of the index costs less than the first round of reads it avoids.
## Extra source roots: indexing the code you write *against*
A workspace index answers questions about the code you are writing. It cannot
answer questions about the library that code is written against — the framework,
the SDK, the installed platform — because none of that is under the workspace.
Measured on one real plugin-development session, **64 of 80 tool calls** went to
the installed harness checkout under `/usr/local/lib/node_modules` to look up the
contract being implemented: *"Read workspace slot contract"*, *"Locate
DirectoryListing definition"*, *"Read UiWorkspace service type"*. That is the only
authoritative copy of the API, and every one of those reads went into the
conversation the expensive way.
```yaml
# your profile's cordis.patch.yml
- id: dsh-recall
name: dsh-recall
config:
sourceRoots:
- /usr/local/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai
# or, with a chosen label and a workspace-relative path:
# - vendor=../packages/node_modules/@vendor
```
Registered through the settings form it is a comma-separated list, as every
list-valued setting is.
An entry is a path, optionally prefixed with `name=`:
- an **absolute** path is taken as given — what a deployment's installed tree needs;
- a **relative** path resolves against the workspace, which is what makes a
checked-in configuration portable;
- the **name** defaults to the directory's basename and becomes the first segment
of every result path from that root, so a result says which tree it came from.
### The root is entered; its dependencies are not
A root is usually *a directory of packages*, which is the one shape the workspace
walk handles worst: `node_modules` and friends are skipped at every depth, so the
interesting files are exactly the ones it refuses to enter. So the rule is
asymmetric, and the asymmetry is the feature:
- **the root itself is entered whatever it is called** — a path ending in
`node_modules/...` is the normal way to name a package-manager tree;
- **below the root** the usual skip list applies, so a `node_modules` inside one
of those packages is not indexed. A dependency is somebody else's code, and it
is the same code the root already holds.
Measured against the real harness packages (`@deepseek-ai`): **2,927 files** into
11,926 chunks, a **113 ms** walk, and **zero** indexed paths containing
`node_modules`. A query — *"how does a plugin register a settings section the
client renders"* — returned its top six hits inside `dsh-client-ui-settings*`, the
correct module, at 11 KB of excerpts against 373 KB across the 7 files they came
from.
### How a result from a root is addressed
The paths a search returns are what the caller hands to `read`, which resolves
them against the workspace. A file under an extra root is not under the workspace,
so no prefixing makes `@dsh/dsh-agent/lib/index.js` readable. Each such result
therefore carries **both**: the labelled path that says where it came from, and an
absolute `sourcePath`:
```
--- 3. @dsh/dsh-client-ui-settings/lib/types/client/contract/slots.d.ts:128-169 similarity 0.47
at /usr/local/lib/node_modules/@deepseek-ai/.../slots.d.ts
```
Reading that absolute path may still need a scope grant from the caller's own
policy — a facet guard, say. This plugin cannot grant one and does not pretend to.
An unusable entry — a typo, a path that is a file rather than a directory, two
roots claiming one name, or a name that collides with the active focus — is
**reported in the result** and skipped. One bad entry must not cost the other
roots, and it must not look like the feature doing nothing.
### Warming it before you need it
An extra root makes the index bigger, and a cold index is the one call that is
slow. `index` runs as a background job by default for exactly that reason — and
it is what makes the first search of a session cheap:
```
recall action=index
```
The job is owned by the session that started it, so its completion reaches that
conversation and its cancellation follows the session.
## Focus: letting a conversation see less