fix(context): refresh get_docs when packages change on disk - #117
Conversation
`context add` writes to ~/.context/packages from a separate process, but a running `serve` reads that directory once at startup. A long-lived stdio server therefore kept serving the package list it saw when it launched, and `get_docs` reported a package as missing when it was already installed. The only way out was to reconnect the client. The mechanism to avoid that already existed and was wired to a single trigger. refreshGetDocsTool rebuilds the tool's `library` enum and calls sendToolListChanged, which is the MCP notification telling a client to re-fetch the tool list; it was called only from the download_package handler, so packages that arrived any other way were invisible. This adds a second trigger rather than a second mechanism. - watch.ts: a small debounced directory watcher. Debounced because one install is several filesystem events (a temp file, then a rename), which would otherwise rebuild the schema three or four times. The watcher is unref'd so it never keeps the process alive on its own, and a callback that throws cannot tear it down. - serve: watches the data directory, reloads the store and refreshes the tool. Skipped when --libs is set, because that flag pins the session to a fixed library set on purpose and picking up new packages would defeat it. - refreshGetDocsTool is now public, since the trigger lives outside the class. - loadPackages now syncs rather than only adding. It is called repeatedly now, so a package removed from disk has to leave the store too, which the add-only version could not express. HTTP transport needed no change: it builds a fresh ContextServer per session over the same store, so a session started after an install already sees it. Only the long-lived stdio server needed the live notification. Tests cover the watcher directly: a single change fires once, a burst collapses to one call, stopping prevents further calls, a throwing callback does not kill the watcher, and a missing directory is a no-op. Verified discriminating by removing the debounce, which fails three of the five. Local `pnpm test` shows 40 pre-existing failures in this environment, identical before and after this change: better-sqlite3 11.10.0 does not build against Node v26, so every sqlite-backed test errors on the missing bindings. The five new tests pass (181 to 186 passing). pnpm lint and pnpm build are clean.
🦋 Changeset detectedLatest commit: f093bce The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Two corrections, one of which was actively costing time. **The per-class OCCT reference manual IS indexed**, as `occt-refman`, 37,008 sections. This copy said it was not, which sent an agent to the bundled headers or to WebFetch as a first resort rather than a last one. Those routes remain, scoped to the case that needs them: a class genuinely absent after an OCCT bump, before the package is rebuilt. **`context query` is now a lookup step in its own right.** `get_docs` takes its `library` from an enum fixed when the MCP server connected, and the server rebuilds that list only for packages arriving through its own download_package tool. Anything installed by `context add` is therefore invisible to `get_docs` for the rest of the session while `context query` sees it immediately. The failure reads as "package missing", which an agent takes at face value and escalates on, so the policy now says plainly that it means "not in the list I was handed". Fixed upstream in neuledge/context#117. Also records that the `ecosystem` package is indexed, so this standard and every shared policy are themselves queryable, and what a version in the cache means: a package is pinned to a release and does not follow its repo. Copied verbatim from SecondMouseAU/ecosystem okf/policies/context-first.md.
Two corrections, one of which was actively costing time. **The per-class OCCT reference manual IS indexed**, as `occt-refman`, 37,008 sections. This copy said it was not, which sent an agent to the bundled headers or to WebFetch as a first resort rather than a last one. Those routes remain, scoped to the case that needs them: a class genuinely absent after an OCCT bump, before the package is rebuilt. **`context query` is now a lookup step in its own right.** `get_docs` takes its `library` from an enum fixed when the MCP server connected, and the server rebuilds that list only for packages arriving through its own download_package tool. Anything installed by `context add` is therefore invisible to `get_docs` for the rest of the session while `context query` sees it immediately. The failure reads as "package missing", which an agent takes at face value and escalates on, so the policy now says plainly that it means "not in the list I was handed". Fixed upstream in neuledge/context#117. Also records that the `ecosystem` package is indexed, so this standard and every shared policy are themselves queryable, and what a version in the cache means: a package is pinned to a release and does not follow its repo. Copied verbatim from SecondMouseAU/ecosystem okf/policies/context-first.md.
Two corrections, one of which was actively costing time. **The per-class OCCT reference manual IS indexed**, as `occt-refman`, 37,008 sections. This copy said it was not, which sent an agent to the bundled headers or to WebFetch as a first resort rather than a last one. Those routes remain, scoped to the case that needs them: a class genuinely absent after an OCCT bump, before the package is rebuilt. **`context query` is now a lookup step in its own right.** `get_docs` takes its `library` from an enum fixed when the MCP server connected, and the server rebuilds that list only for packages arriving through its own download_package tool. Anything installed by `context add` is therefore invisible to `get_docs` for the rest of the session while `context query` sees it immediately. The failure reads as "package missing", which an agent takes at face value and escalates on, so the policy now says plainly that it means "not in the list I was handed". Fixed upstream in neuledge/context#117. Also records that the `ecosystem` package is indexed, so this standard and every shared policy are themselves queryable, and what a version in the cache means: a package is pinned to a release and does not follow its repo. Copied verbatim from SecondMouseAU/ecosystem okf/policies/context-first.md.
Two corrections, one of which was actively costing time. **The per-class OCCT reference manual IS indexed**, as `occt-refman`, 37,008 sections. This copy said it was not, which sent an agent to the bundled headers or to WebFetch as a first resort rather than a last one. Those routes remain, scoped to the case that needs them: a class genuinely absent after an OCCT bump, before the package is rebuilt. **`context query` is now a lookup step in its own right.** `get_docs` takes its `library` from an enum fixed when the MCP server connected, and the server rebuilds that list only for packages arriving through its own download_package tool. Anything installed by `context add` is therefore invisible to `get_docs` for the rest of the session while `context query` sees it immediately. The failure reads as "package missing", which an agent takes at face value and escalates on, so the policy now says plainly that it means "not in the list I was handed". Fixed upstream in neuledge/context#117. Also records that the `ecosystem` package is indexed, so this standard and every shared policy are themselves queryable, and what a version in the cache means: a package is pinned to a release and does not follow its repo. Copied verbatim from SecondMouseAU/ecosystem okf/policies/context-first.md.
Two corrections, one of which was actively costing time. **The per-class OCCT reference manual IS indexed**, as `occt-refman`, 37,008 sections. This copy said it was not, which sent an agent to the bundled headers or to WebFetch as a first resort rather than a last one. Those routes remain, scoped to the case that needs them: a class genuinely absent after an OCCT bump, before the package is rebuilt. **`context query` is now a lookup step in its own right.** `get_docs` takes its `library` from an enum fixed when the MCP server connected, and the server rebuilds that list only for packages arriving through its own download_package tool. Anything installed by `context add` is therefore invisible to `get_docs` for the rest of the session while `context query` sees it immediately. The failure reads as "package missing", which an agent takes at face value and escalates on, so the policy now says plainly that it means "not in the list I was handed". Fixed upstream in neuledge/context#117. Also records that the `ecosystem` package is indexed, so this standard and every shared policy are themselves queryable, and what a version in the cache means: a package is pinned to a release and does not follow its repo. Copied verbatim from SecondMouseAU/ecosystem okf/policies/context-first.md.
Two corrections, one of which was actively costing time. **The per-class OCCT reference manual IS indexed**, as `occt-refman`, 37,008 sections. This copy said it was not, which sent an agent to the bundled headers or to WebFetch as a first resort rather than a last one. Those routes remain, scoped to the case that needs them: a class genuinely absent after an OCCT bump, before the package is rebuilt. **`context query` is now a lookup step in its own right.** `get_docs` takes its `library` from an enum fixed when the MCP server connected, and the server rebuilds that list only for packages arriving through its own download_package tool. Anything installed by `context add` is therefore invisible to `get_docs` for the rest of the session while `context query` sees it immediately. The failure reads as "package missing", which an agent takes at face value and escalates on, so the policy now says plainly that it means "not in the list I was handed". Fixed upstream in neuledge/context#117. Also records that the `ecosystem` package is indexed, so this standard and every shared policy are themselves queryable, and what a version in the cache means: a package is pinned to a release and does not follow its repo. Copied verbatim from SecondMouseAU/ecosystem okf/policies/context-first.md.
Two corrections, one of which was actively costing time. **The per-class OCCT reference manual IS indexed**, as `occt-refman`, 37,008 sections. This copy said it was not, which sent an agent to the bundled headers or to WebFetch as a first resort rather than a last one. Those routes remain, scoped to the case that needs them: a class genuinely absent after an OCCT bump, before the package is rebuilt. **`context query` is now a lookup step in its own right.** `get_docs` takes its `library` from an enum fixed when the MCP server connected, and the server rebuilds that list only for packages arriving through its own download_package tool. Anything installed by `context add` is therefore invisible to `get_docs` for the rest of the session while `context query` sees it immediately. The failure reads as "package missing", which an agent takes at face value and escalates on, so the policy now says plainly that it means "not in the list I was handed". Fixed upstream in neuledge/context#117. Also records that the `ecosystem` package is indexed, so this standard and every shared policy are themselves queryable, and what a version in the cache means: a package is pinned to a release and does not follow its repo. Copied verbatim from SecondMouseAU/ecosystem okf/policies/context-first.md.
Two corrections, one of which was actively costing time. **The per-class OCCT reference manual IS indexed**, as `occt-refman`, 37,008 sections. This copy said it was not, which sent an agent to the bundled headers or to WebFetch as a first resort rather than a last one. Those routes remain, scoped to the case that needs them: a class genuinely absent after an OCCT bump, before the package is rebuilt. **`context query` is now a lookup step in its own right.** `get_docs` takes its `library` from an enum fixed when the MCP server connected, and the server rebuilds that list only for packages arriving through its own download_package tool. Anything installed by `context add` is therefore invisible to `get_docs` for the rest of the session while `context query` sees it immediately. The failure reads as "package missing", which an agent takes at face value and escalates on, so the policy now says plainly that it means "not in the list I was handed". Fixed upstream in neuledge/context#117. Also records that the `ecosystem` package is indexed, so this standard and every shared policy are themselves queryable, and what a version in the cache means: a package is pinned to a release and does not follow its repo. Copied verbatim from SecondMouseAU/ecosystem okf/policies/context-first.md.
Two corrections, one of which was actively costing time. **The per-class OCCT reference manual IS indexed**, as `occt-refman`, 37,008 sections. This copy said it was not, which sent an agent to the bundled headers or to WebFetch as a first resort rather than a last one. Those routes remain, scoped to the case that needs them: a class genuinely absent after an OCCT bump, before the package is rebuilt. **`context query` is now a lookup step in its own right.** `get_docs` takes its `library` from an enum fixed when the MCP server connected, and the server rebuilds that list only for packages arriving through its own download_package tool. Anything installed by `context add` is therefore invisible to `get_docs` for the rest of the session while `context query` sees it immediately. The failure reads as "package missing", which an agent takes at face value and escalates on, so the policy now says plainly that it means "not in the list I was handed". Fixed upstream in neuledge/context#117. Also records that the `ecosystem` package is indexed, so this standard and every shared policy are themselves queryable, and what a version in the cache means: a package is pinned to a release and does not follow its repo. Copied verbatim from SecondMouseAU/ecosystem okf/policies/context-first.md.
Two corrections, one of which was actively costing time. **The per-class OCCT reference manual IS indexed**, as `occt-refman`, 37,008 sections. This copy said it was not, which sent an agent to the bundled headers or to WebFetch as a first resort rather than a last one. Those routes remain, scoped to the case that needs them: a class genuinely absent after an OCCT bump, before the package is rebuilt. **`context query` is now a lookup step in its own right.** `get_docs` takes its `library` from an enum fixed when the MCP server connected, and the server rebuilds that list only for packages arriving through its own download_package tool. Anything installed by `context add` is therefore invisible to `get_docs` for the rest of the session while `context query` sees it immediately. The failure reads as "package missing", which an agent takes at face value and escalates on, so the policy now says plainly that it means "not in the list I was handed". Fixed upstream in neuledge/context#117. Also records that the `ecosystem` package is indexed, so this standard and every shared policy are themselves queryable, and what a version in the cache means: a package is pinned to a release and does not follow its repo. Copied verbatim from SecondMouseAU/ecosystem okf/policies/context-first.md.
Two corrections, one of which was actively costing time. **The per-class OCCT reference manual IS indexed**, as `occt-refman`, 37,008 sections. This copy said it was not, which sent an agent to the bundled headers or to WebFetch as a first resort rather than a last one. Those routes remain, scoped to the case that needs them: a class genuinely absent after an OCCT bump, before the package is rebuilt. **`context query` is now a lookup step in its own right.** `get_docs` takes its `library` from an enum fixed when the MCP server connected, and the server rebuilds that list only for packages arriving through its own download_package tool. Anything installed by `context add` is therefore invisible to `get_docs` for the rest of the session while `context query` sees it immediately. The failure reads as "package missing", which an agent takes at face value and escalates on, so the policy now says plainly that it means "not in the list I was handed". Fixed upstream in neuledge/context#117. Also records that the `ecosystem` package is indexed, so this standard and every shared policy are themselves queryable, and what a version in the cache means: a package is pinned to a release and does not follow its repo. Copied verbatim from SecondMouseAU/ecosystem okf/policies/context-first.md.
Two corrections, one of which was actively costing time. **The per-class OCCT reference manual IS indexed**, as `occt-refman`, 37,008 sections. This copy said it was not, which sent an agent to the bundled headers or to WebFetch as a first resort rather than a last one. Those routes remain, scoped to the case that needs them: a class genuinely absent after an OCCT bump, before the package is rebuilt. **`context query` is now a lookup step in its own right.** `get_docs` takes its `library` from an enum fixed when the MCP server connected, and the server rebuilds that list only for packages arriving through its own download_package tool. Anything installed by `context add` is therefore invisible to `get_docs` for the rest of the session while `context query` sees it immediately. The failure reads as "package missing", which an agent takes at face value and escalates on, so the policy now says plainly that it means "not in the list I was handed". Fixed upstream in neuledge/context#117. Also records that the `ecosystem` package is indexed, so this standard and every shared policy are themselves queryable, and what a version in the cache means: a package is pinned to a release and does not follow its repo. Copied verbatim from SecondMouseAU/ecosystem okf/policies/context-first.md.
Two corrections, one of which was actively costing time. **The per-class OCCT reference manual IS indexed**, as `occt-refman`, 37,008 sections. This copy said it was not, which sent an agent to the bundled headers or to WebFetch as a first resort rather than a last one. Those routes remain, scoped to the case that needs them: a class genuinely absent after an OCCT bump, before the package is rebuilt. **`context query` is now a lookup step in its own right.** `get_docs` takes its `library` from an enum fixed when the MCP server connected, and the server rebuilds that list only for packages arriving through its own download_package tool. Anything installed by `context add` is therefore invisible to `get_docs` for the rest of the session while `context query` sees it immediately. The failure reads as "package missing", which an agent takes at face value and escalates on, so the policy now says plainly that it means "not in the list I was handed". Fixed upstream in neuledge/context#117. Also records that the `ecosystem` package is indexed, so this standard and every shared policy are themselves queryable, and what a version in the cache means: a package is pinned to a release and does not follow its repo. Copied verbatim from SecondMouseAU/ecosystem okf/policies/context-first.md.
Two corrections, one of which was actively costing time. The per-class OCCT reference manual IS indexed, as occt-refman, 37,008 sections. This copy said it was not, which sent an agent to the bundled headers or to WebFetch as a first resort rather than a last one. And context query is now a lookup step in its own right: get_docs takes its library from an enum fixed when the MCP server connected, so anything installed by context add is invisible to it for the rest of the session while the CLI sees it immediately. Fixed upstream in neuledge/context#117. Copied verbatim from SecondMouseAU/ecosystem okf/policies/context-first.md.
|
Thanks for this — the mechanism is right and the debounce, the 1. A package that's merely unreadable gets evicted as if deleted.
That would be a narrow race, except if (existsSync(outputPath)) unlinkSync(outputPath); // removes the installed package
const db = openDatabase(outputPath); // rebuilds in placeSo Worse, a half-built DB passes Fix: evict only on real absence. for (const pkg of store.list()) {
if (onDisk.has(packageKey(pkg))) continue;
if (existsSync(pkg.path)) continue; // unreadable != gone
store.remove(packageKey(pkg));
}2. In-flight downloads get advertised. Both download paths write if (!file.endsWith(".db") || file.startsWith(".")) continue;3. Two more worth doing: On the tests — I mutation-tested them. Removing the Happy to push these myself if you'd rather not — say the word and I'll do it, keeping you as author. Otherwise take your time; nothing here is far off. Generated by Claude Code |
context addwrites to~/.context/packagesfrom a separate process, but a runningservereads that directory once at startup. A long-lived stdio server therefore keeps serving the package list it saw when it launched, soget_docsreports a package as missing when it is already installed, and the only way out is to reconnect the client.I hit this adding a locally-built docs package while a Claude Code session was open:
context querysaw it instantly,get_docscould not see it at all.The mechanism already existed
refreshGetDocsToolrebuilds the tool'slibraryenum and callssendToolListChanged, the MCP notification that tells a client to re-fetch the tool list. It had exactly one caller, thedownload_packagehandler, so a package arriving any other way was invisible.This adds a second trigger, not a second mechanism.
The change
watch.ts, a small debounced directory watcher. Debounced because one install is several filesystem events (a temp file, then a rename into place), which would otherwise rebuild the schema three or four times for one logical change. The watcher isunref'd so it never holds the process open by itself, and a callback that throws cannot tear it down.servewatches the data directory, reloads the store, refreshes the tool. Skipped when--libsis set, since that flag pins the session to a fixed library set on purpose and picking up new packages would defeat it.refreshGetDocsToolis now public, because the trigger lives outside the class. That is the only public surface change; happy to bump the changeset tominorif you would rather treat it as one.loadPackagesnow syncs rather than only adding. It is called repeatedly now, so a package removed from disk has to leave the store too, which the add-only version could not express.HTTP needed no change. It builds a fresh
ContextServerper session over the same store, so a session started after an install already sees it. Only the long-lived stdio server needed the live notification.Tests
Five, against the watcher directly: a single change fires once, a burst collapses to one call, stopping prevents further calls, a throwing callback does not kill the watcher, and a missing directory is a no-op.
Verified discriminating rather than merely passing: removing the debounce fails three of the five.
The watcher went into its own module partly so it could be tested at all.
cli.tsbuilds acommanderprogram at module scope, so importing it from a test would run the CLI.About the local test run
pnpm lintandpnpm buildare clean.pnpm testreports 40 failures in my environment, identical before and after this change (I ran the baseline on a stash to be sure). They are allbetter-sqlite3@11.10.0failing to build against Node v26, so every sqlite-backed test errors on missing bindings:My five new tests pass, taking the suite from 181 to 186 passing. I could not verify the sqlite-backed suites locally, so those are worth a look on your CI rather than taking my word for it.
Housekeeping
Followed the
CLAUDE.mdconventions: searched open and recently closed PRs and/.plans/first (nothing related), and added a changeset. No plan file to delete, since there was none to claim.