Previously buildInstructions emitted one line per collection with name,
doc count, and description, which can run to ~1.5 KB for a dozen
collections and is injected into every session's system prompt at
MCP initialize. Most agents never query qmd in a given session, so the
catalogue lines are a recurring token cost for static info that the
existing 'status' tool already exposes on demand.
Now emit a single comma-joined names line plus a hint that 'status'
returns the rest. listContexts() lookup is dropped since the per-
collection descriptions are no longer rendered.
Refs #647
The query tool description tells agents to compute fromLine = line - 20
for context around a hit. For hits in lines 1 through 20 that yields a
negative fromLine, which propagated unchanged through:
MCP get handler -> store.getDocumentBody -> Array.prototype.slice
A negative slice start offsets from the end of the array rather than
clamping to the beginning, so a top-of-file hit on a long document
returned an empty string and on a short document returned content from
the wrong region (e.g. lines 11-30 of a 30-line file in response to a
request for the head of the document). The lineNumbers branch was the
same shape: addLineNumbers(text, -19) emitted "-19:", "-18:" prefixes.
Same buggy slice lived in the CLI getDocument path independently.
Fix in three layers, plus the docstring:
- src/mcp/server.ts: clamp parsedFromLine to >= 1 after parsing input
args and the :line suffix, before it reaches getDocumentBody and
addLineNumbers. Also tighten the query tool's recommendation to
`fromLine = max(1, line - 20)` so following the docstring literally
produces a valid value.
- src/cli/qmd.ts: same clamp on the CLI getDocument fromLine after
the colon-suffix parse.
- src/store.ts: defensive Math.max(0, ...) on the slice start in
getDocumentBody so SDK callers and any future entry points are
protected without relying on every caller remembering to clamp.
- test/store.test.ts: regression test on getDocumentBody with
fromLine = -19 returns the head of the document, not the tail.
- test/cli.test.ts: regression test on `qmd get --from -19` matches
the no-flag baseline (head of document).
The MCP `query` tool, HTTP `/query` endpoint, and CLI `qmd query`
all returned chunk-local line numbers in their snippet output, so
the line could not be passed back to `qmd_get` as `fromLine`
without an out-of-band lookup. Pass the full document body plus
`bestChunkPos` to `extractSnippet` instead of the chunk text alone
so it can compute absolute line offsets while still scoping the
keyword scan to the reranker-chosen chunk window (preserves #149).
Also restores documented behavior of `qmd query --full`, which was
emitting the best chunk (~3.6KB max) instead of the full document.
extractSnippet now also falls back to a full-body scan when given a
chunkPos but the chunk window contains no positive matches. The
upstream chunk selector leaves bestIdx=0 as its initialization
default whenever scoring fails to find a winner (e.g. queryTerms
filtered to empty by the length>2 guard, or semantic-only matches
with no lex overlap), so an unconditional chunk-scoped scan would
land on chunk 0 instead of where the actual match lives.
- src/mcp/server.ts: SearchResultItem gains `line: number`; both MCP
and HTTP `/query` handlers populate it
- src/cli/qmd.ts: OutputRow.body now sources from r.body
- src/store.ts: extractSnippet falls back to full-body scan when
chunk-scoped pass finds no positive match
- test/mcp.test.ts: new fixture asserts absolute line 301 for a
marker placed past the first chunk boundary
- test/store.test.ts: regression test for the bestScore<=0 fallback
src/cli/qmd.ts has the same module-scope enableProductionMode() call that
src/mcp/server.ts had — and the same test-isolation leak. test/cli.test.ts
imports buildEditorUri and termLink from this module, which executes the
top-level enableProductionMode() as a side effect of import, flipping the
global _productionMode flag for every later test file in the Bun process.
This is the actual driver of the Store Creation > createStore throws
without explicit path in test mode failure — test/cli.test.ts runs
alphabetically before test/store.test.ts, so the flag is already true by
the time store.test.ts checks it.
Mirror the fix applied to src/mcp/server.ts in the previous commit: move
enableProductionMode() from module scope into the if (isMain) guard so
the flag is only flipped when qmd is actually invoked as the CLI
entrypoint, not when the module is imported for its exports.
The top-level enableProductionMode() call added in #537 fixed a real issue
(MCP server resolving the wrong database path at startup) but introduced
test-isolation breakage as a side effect: merely importing src/mcp/server.ts
flipped the global _productionMode flag, which then broke unrelated tests
that depend on the default (development) database path resolution.
This shows up concretely as "Store Creation > createStore throws without
explicit path in test mode" failing on Bun (ubuntu-latest) in CI, because
test/mcp.test.ts imports startMcpHttpServer from this module.
Move the enableProductionMode() call from module scope into the two server
entry points (startMcpServer and startMcpHttpServer). The fix originally
intended by #537 — ensuring production mode is active before getDefaultDbPath
runs — is preserved because both call sites still flip the flag before
createStore / getDefaultDbPath. Importing the module for its exports no longer
mutates global state.
Verified locally against current main: CI failure on Bun (ubuntu-latest)
reproduces on unmodified upstream main (14+ consecutive failed runs since
#537 merged on 2026-04-09) and is resolved by this change.
Refs: #537
The narrow cross-runtime Database interface in src/db.ts defines the
subset of better-sqlite3 / bun:sqlite methods used throughout QMD.
Commit fee576b ("fix: migrate legacy lowercase paths on reindex")
introduced a db.transaction(...) call in src/store.ts but did not
extend the interface, breaking `tsc -p tsconfig.build.json`:
src/store.ts(2142,22): error TS2339: Property 'transaction' does
not exist on type 'Database'.
Both underlying engines expose transaction(fn), so this just makes
the type reflect reality.
On Windows, `HOME` is not a standard environment variable — the
equivalent is `USERPROFILE`. When MCP clients (e.g. Claude Code)
spawn the QMD server as a subprocess, they pass `USERPROFILE` but
not `HOME`. This causes QMD to fall back to `/tmp`, opening an
empty database instead of the user's actual index.
Fix: check `USERPROFILE` before falling back to `/tmp`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a proxy or firewall intercepts HuggingFace downloads, the cached
file is an HTML page instead of a GGUF model. Previously this surfaced
as an opaque "Invalid GGUF magic" error from node-llama-cpp.
Now we validate the GGUF magic bytes right after download, detect HTML
pages specifically, delete the bad file, and show an actionable error
message with workarounds (HF_ENDPOINT mirror, manual download path).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>