Commit Graph

46 Commits

Author SHA1 Message Date
Tobi Lutke
c162ed1319
fix: disable libggml-metal residency sets on darwin
The libggml-metal static device destructor asserts on a non-empty
residency set during libc `exit()` → `__cxa_finalize_ranges`
(ggml-org/llama.cpp#17869). The residency set's 180 s keep_alive timer
hasn't expired by exit, so `GGML_ASSERT([rsets->data count] == 0)`
fails and `ggml_abort` dumps a multi-kB backtrace to stderr after the
user-visible output. Every llama-using CLI command (`query`,
`vsearch`, `embed`) was affected, plus the `bun test` runner.

No JS-side dispose path can prevent it: the static destructor runs
after every JS-reachable cleanup, and Node's `reallyExit` calls libc
`exit()` not `_exit()` (verified in node/src/api/environment.cc),
so it does NOT skip C++ static destructors as we'd assumed.

The actual fix is to disable residency sets via
`GGML_METAL_NO_RESIDENCY=1` before the native binding loads. For
QMD's short-lived CLI workflow there's no measurable cost
(benchmarked: identical wall time with and without on M3 Pro).

Three propagation points are needed:
- `bin/qmd` exports the env var before spawning node/bun. This
  covers all production CLI invocations.
- `src/test-preload.ts` mirrors the launcher for `bun test` runs.
  Bun does NOT sync `process.env` mutations to libc `setenv()`
  (verified empirically — Node does, via uv_os_setenv), so on Bun we
  reach for `bun:ffi` to call `setenv()` directly. vitest forks
  per-test-file so its parent never loads the binding.
- `qmd doctor` reports the mitigation state via the new
  `isDarwinMetalMitigationActive()` predicate so users can verify it
  in their environment.

Opt back in with `QMD_METAL_KEEP_RESIDENCY=1` (long-lived qmd
processes, MCP daemon hot reload, upstream fix triage). The old
`QMD_DISABLE_DARWIN_QUERY_JSON_SAFE_EXIT` is removed — its per-command
bypass mechanism didn't actually work on Node (it called
`process.reallyExit` which goes through libc exit) and is fully
replaced by the launcher env var.

Removed the old broken `installDarwinExitGuard()` mechanism from
LlamaCpp; kept the function name as a no-op shim for back-compat.
2026-05-28 13:40:14 -07:00
Tobi Lutke
3de3162e1a
feat(cli): ./-prefix $PWD-relative --full-path; add --format <kind>
--full-path now ./-prefixes any path that resolves under $PWD, both for
search/query results and for get/multi-get headers. This makes the
output unambiguously a filesystem path — a bare 'notes/foo.md' could be
misread as a collection-relative qmd:// fragment, but './notes/foo.md'
cannot. Absolute realpaths (when the file is outside $PWD) are
unchanged. Extracted as renderFullPath() and reused across the three
call sites so the policy stays consistent.

New --format <kind> flag selects output format for search/query and
multi-get (cli|json|csv|md|xml|files). The legacy boolean aliases
(--json/--csv/--md/--xml/--files) still work for back-compat but are
removed from --help; the skill is updated to use --format.

ANSI colors and OSC 8 hyperlinks are already gated on process.stdout
.isTTY, so piped/agentic invocations get clean plain-text output with
no escape sequences. Verified via od -c on a piped 'qmd search' run.
2026-05-28 11:35:21 -07:00
Tobi Lutke
436420e927
feat(search,query): --full-path swaps qmd:// for on-disk paths
`qmd://` URIs remain the default identifier in search and query output
(across all formats: cli, --json, --md, --csv, --xml, --files). The
default CLI view now consistently prints the full qmd:// URI as the
visible label so it can be piped straight into `qmd get`, and --md
output gains a **file:** line for the same reason.

--full-path (already on get/multi-get) now also applies to search and
query: the per-result label becomes the file's on-disk path — relative
to $PWD when the file is in a subfolder of the current directory,
absolute realpath otherwise — and the per-result #docid is dropped
because the path is the identifier. Falls back to qmd:// when the file
is no longer resolvable on disk.

Also locks in @@ -line,count @@ header arithmetic with a regression test
that mirrors the user-reported 77-line / '1 before, 72 after' scenario.
2026-05-28 11:18:03 -07:00
Tobi Lutke
41bc3a27d8
feat(get,multi-get): line-numbered + docid output, line ranges, --full-path
Redesign the get/multi-get retrieval surface so callers can cite what
they retrieved and request follow-up slices without piping through sed:

- Output is line-numbered by default; opt out with --no-line-numbers.
- Header always identifies the document by qmd:// path + #docid. The
  MCP get/multi_get tools default lineNumbers=true to match.
- qmd get and the MCP get tool accept a :from:count suffix on a path
  or docid (e.g. '#abc123:120:40' reads 40 lines from line 120).
  Explicit --from/-l flags still override the suffix.
- qmd multi-get now includes #docid in every output format (--md,
  --json, --csv, --xml, --files, default CLI), matching qmd search.
- New --full-path flag swaps the qmd:// + docid header for the
  document's on-disk path (handy for piping into Read/Edit/editors);
  falls back to the canonical header when the file no longer exists.
2026-05-28 10:55:55 -07:00
Tobi Lütke
7a5d8f5574
Make bin/qmd launcher a shebang polyglot to support both Windows cmd/ps1 native wrappers and sh-invoked smoke tests
Result: {"status":"keep","test_status":0}
2026-05-22 20:08:49 +00:00
Tobi Lütke
a8a314b802
Stabilize doctor CLI tests 2026-05-19 23:34:06 +00:00
Tobi Lütke
b5f156c313
Improve qmd diagnostics and embed resilience 2026-05-19 21:39:48 +00:00
Tobi Lutke
105c577b3b
docs: improve qmd skill guidance 2026-05-19 15:22:14 -04:00
Tobi Lutke
d9348f43a0
feat: add local init and doctor diagnostics 2026-05-19 14:27:33 -04:00
Tobi Lutke
5cda3cf54c
Improve qmd doctor diagnostics 2026-05-19 12:48:16 -04:00
Tobi Lütke
ac6b154f0c
feat: add qmd doctor vector diagnostics 2026-05-18 01:52:05 +00:00
Tobi Lütke
ad8a371be2
Fix QMD CI test runtime assumptions 2026-05-16 23:52:53 +00:00
Tobi Lütke
1f757379e2
Fix GPU status guidance and benchmark warnings 2026-05-16 23:45:58 +00:00
Tobi Lütke
c18c74a134
Serve QMD skill instructions from CLI 2026-05-16 22:43:33 +00:00
Tobi Lütke
e4505607f9
Merge remote-tracking branch 'origin/main' into workoff/t_0d576ae5-dev-review
# Conflicts:
#	CHANGELOG.md
2026-05-16 17:26:27 +00:00
Tobias Lütke
bad20f5565
Merge pull request #644 from Ginja/fix/snippet-absolute-line-numbers
fix: return absolute line numbers from qmd_query
2026-05-16 13:18:35 -04:00
Tobi Lütke
dd5d82d523
fix: keep llama GPU fallback noise off JSON stdout 2026-05-16 17:18:06 +00:00
Tobias Lütke
d0bcdf0cfb
Merge pull request #635 from erlebach/fix/ls-absolute-path-collections
fix(ls): handle collections whose names are absolute paths
2026-05-16 13:13:15 -04:00
Tobi Lütke
2dc8634ac7
fix(ls): preserve qmd:/// collection aliases 2026-05-16 17:12:38 +00:00
Riley Shott
aa1818e181
fix: clamp negative fromLine in get to avoid silent tail content
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).
2026-05-13 23:59:33 -07:00
Riley Shott
1f522cffe2
fix: return absolute line numbers from qmd_query
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
2026-05-13 23:59:32 -07:00
Tobi Lütke
e36ab96567
fix: allow HTTP query rerank control 2026-05-09 19:03:17 +00:00
Tobi Lütke
669e234d1e
test: index MCP HTTP fixture before query 2026-05-09 18:56:06 +00:00
Tobi Lütke
b32ee4e660
test: make CI fixture invocations portable 2026-05-09 18:45:56 +00:00
Tobi Lütke
ddc969a5f4
fix embed model and qmd home resolution 2026-05-09 18:17:10 +00:00
Tobi Lütke
b775592230
fix mcp --index store selection 2026-05-09 18:16:02 +00:00
Tobi Lütke
3653f6015c
Fix MCP stdio native log pollution 2026-05-09 18:14:15 +00:00
Tobias Lütke
e8de7cab02 fix(cli): make status device probe opt-in 2026-04-21 21:45:52 -04:00
Tobias Lütke
525b9970cd
Merge pull request #546 from junmo-kim/fix/handelize-preserve-case
fix: preserve original case in handelize()
2026-04-10 20:48:24 -04:00
Kim Junmo
9fb9de4fd2 fix: preserve original case in handelize()
The blanket .toLowerCase() in handelize() drops filename casing,
which breaks path resolution on case-sensitive filesystems (Linux).
Files like README.md, CHANGELOG.md, and SKILL.md become unreachable
when the index stores them as readme.md, changelog.md, skill.md.

Since FTS5 already performs case-insensitive matching via the
unicode61 tokenizer, lowercasing the stored path provides no search
benefit — it only corrupts the metadata used to locate files on disk.

Remove .toLowerCase() and update all affected test expectations.
2026-04-09 07:59:22 +09:00
cocoon
8404cc3bb1 fix(uri): include index in custom qmd links 2026-04-07 23:26:19 +08:00
dan mackinlay
1bada2eba6 Add explicit TTY link output tests 2026-04-05 17:58:09 -04:00
dan mackinlay
06f5642252 Fix stale ls test expectation 2026-04-05 17:56:26 -04:00
dan mackinlay
636631225e Add clickable OSC8 editor links for CLI search results 2026-04-05 17:56:26 -04:00
programcaicai
809aa36172 fix: bound memory usage during embed 2026-03-13 17:39:17 +08:00
nkkko
b16d77146a feat(skill): install packaged qmd skill 2026-03-10 23:18:15 +01:00
Tobi Lutke
c68904fe08
refactor: move CLI and MCP to subdirectories, MCP consumes SDK
Move frontends into src/cli/ and src/mcp/ to separate them from the
core library. The MCP server is fully rewritten to import only from
the SDK (src/index.ts) — zero direct store.ts/collections.ts/llm.ts
access.

- src/qmd.ts → src/cli/qmd.ts
- src/formatter.ts → src/cli/formatter.ts
- src/mcp.ts → src/mcp/server.ts (rewritten to use QMDStore SDK)
- New src/maintenance.ts: Maintenance class for CLI housekeeping
- SDK gains: getDocumentBody(), getDefaultCollectionNames(),
  extractSnippet/addLineNumbers/DEFAULT_MULTI_GET_MAX_BYTES exports,
  getDefaultDbPath re-export, InternalStore type export
- package.json bin/scripts updated for new paths
- All 692 tests pass
2026-03-10 11:39:55 -04:00
Tobias Lütke
8bd93366ad
Merge pull request #228 from amsminn/fix-empty-results-format
fix(cli): prevent parser breakage on empty results across output formats
2026-03-07 14:25:16 -04:00
Tobias Lütke
a28163fb2c
Merge pull request #304 from sebkouba/feature/collection-ignore
feat: add ignore patterns for collections
2026-03-07 14:25:02 -04:00
Brian Le
49d5b4f450
fix(index): deactivate stale docs on empty collection updates 2026-03-06 16:29:52 -05:00
Sebastian Kouba
fde542cd0d feat: add ignore patterns for collections
Add an optional 'ignore' field to collection config that accepts an array
of glob patterns to exclude from indexing. This allows collections to skip
specific subdirectories without needing separate collections.

Example YAML config:
  personal:
    path: ~/personal_synced
    pattern: '**/*.md'
    ignore:
      - 'Sessions/**'
      - 'archive/**'

The ignore patterns are passed to fast-glob's ignore option alongside the
existing hardcoded excludes (node_modules, .git, etc). Already-indexed
files matching new ignore patterns are deactivated on the next update.

Changes:
- Add ignore?: string[] to Collection interface
- Pass ignore patterns through to fast-glob in indexFiles()
- Show ignore patterns in collection list/status output
- 5 new CLI integration tests covering the feature
2026-03-05 19:17:44 +01:00
CHAEWAN KIM
b024693f5d
Merge branch 'main' into fix-empty-results-format 2026-02-23 22:36:21 -08:00
Tobi Lutke
0b57711d32
refactor: replace bash wrapper with standard #!/usr/bin/env node shebang
The qmd bin was a custom bash script that discovered node via hardcoded
fallback paths (mise, asdf, nvm, homebrew). This was nonstandard and
caused ABI mismatches when installed via bun (native modules compiled
for bun but executed with node).

Now uses the standard npm bin convention: dist/qmd.js with a node
shebang, added by the build script. The isMain guard resolves symlinks
so it works when npm/bun create symlinked bin entries.

Also converts all dynamic require() calls in tests to ESM imports, and
adds container-based smoke tests (test/smoke-install.sh) that verify
install + run under both node and bun via mise in a Debian container.
2026-02-22 11:09:36 -04:00
CHAEWAN KIM
7579a2c017 fix(cli): prevent parser breakage on empty results across output formats
- --json: []
- --csv: header only
- --xml: <results></results>
- --md/--files: empty output
- default CLI keeps human-readable message
2026-02-19 15:38:42 -08:00
Tobi Lutke
edc9a87234
fix: correct test paths after moving to test/ directory
- cli.test.ts: fix qmdScript path from <root>/qmd.ts to <root>/src/qmd.ts
  (broke when tests moved from src/integration/ to test/)
- mcp.test.ts: forward Mcp-Session-Id header per MCP Streamable HTTP spec
2026-02-15 21:46:45 -04:00
Tobi Lutke
870d3aed3b
test: move all tests to flat test/ directory
No more src/models/ and src/integration/ subfolders to forget about.
All 9 test files live in test/, one command runs everything:

  npx vitest run test/
  bun test test/
2026-02-15 21:37:47 -04:00