@wei840222/qmd 2026.8.23 → 2026.8.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +69 -21
- package/LICENSE +0 -23
- package/README.md +10 -30
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/bin/qmd +16 -116
- package/dist/ast.js +1 -1
- package/dist/cli/build-info.json +2 -2
- package/dist/cli/qmd.d.ts +1 -1
- package/dist/cli/qmd.js +4 -7
- package/dist/db.d.ts +14 -40
- package/dist/db.js +21 -74
- package/dist/search/zh-dict.txt +13 -7
- package/dist/store.js +1 -1
- package/package.json +10 -12
- package/scripts/check-package-grammars.mjs +1 -1
- package/scripts/package-smoke.mjs +3 -17
- package/scripts/test-all.mjs +0 -1
- package/skills/qmd/SKILL.md +18 -10
- package/skills/qmd/references/mcp-setup.md +4 -20
- package/skills/qmd/references/query-syntax.md +158 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,32 +2,68 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [2026.8.23-1] - 2026-08-23
|
|
6
|
+
|
|
5
7
|
### Added
|
|
6
8
|
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- Added CJK-aware lexical retrieval with independent character,
|
|
13
|
-
bigram FTS5 channels, versioned rank fusion and
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
|
|
9
|
+
- **Pure Node.js + pnpm Standardized Toolchain**: Completely standardized the project on
|
|
10
|
+
Node.js (>=22) and pnpm (`pnpm@11.15.1`). Removed all Bun-specific runtime abstractions,
|
|
11
|
+
lockfile heuristics, and dual-runtime test preloads.
|
|
12
|
+
- **Oxlint & Code Quality Fences**: Added Oxlint static analysis rules (`oxlint`) and anti-slop
|
|
13
|
+
guardrails across the codebase.
|
|
14
|
+
- **CJK 3-Way Lexical Retrieval**: Added CJK-aware lexical retrieval with independent character,
|
|
15
|
+
Jieba word, and bigram FTS5 shadow channels, versioned reciprocal rank fusion (RRF), and
|
|
16
|
+
explain traces.
|
|
17
|
+
- **Bundled Traditional Chinese Jieba Dictionary**: Added a bundled Traditional Chinese dictionary
|
|
18
|
+
deterministically combining `@node-rs/jieba` (`2.0.2`), APCLab's Taiwan dictionary, and
|
|
19
|
+
`sysprog21/zhtw-mcp` technical terms. Pinned sources and hashes are recorded in
|
|
20
|
+
`src/search/zh-dict.sources.json` and `THIRD_PARTY_NOTICES.md`.
|
|
21
|
+
- **Custom User Dictionary Support**: Added `user_dict` configuration supporting custom
|
|
22
|
+
user dictionaries with automatic analyzer fingerprint updates.
|
|
23
|
+
- **Typed Remote OpenAI-Compatible Embedding Provider**: Added typed `OpenAIEmbeddingProvider`,
|
|
24
|
+
provider-qualified vector identities, resumable chunk persistence, build leases, remote
|
|
25
|
+
preflight, chunk byte budgeting, and safe OpenAI error handling.
|
|
26
|
+
- **Remote LLM Query Expansion & Chat Reranking**: Supported OpenAI-compatible
|
|
27
|
+
`/v1/chat/completions` API endpoints for remote query expansion and reranking with XML-structured
|
|
28
|
+
prompts and current timestamp injection.
|
|
29
|
+
- **Vector Table Collection Column**: Added `collection` column to vector indexes with automatic
|
|
30
|
+
schema migration and collection-scoped filtering.
|
|
31
|
+
- **Single-Item Batch Embedding Fallback**: Added single-item fallback to gracefully handle and
|
|
32
|
+
recover from oversized batch chunks during embedding and vector repair.
|
|
33
|
+
- **Agent Skill Query Syntax Reference**: Added `skills/qmd/references/query-syntax.md` detailing
|
|
34
|
+
formal EBNF grammar, search operators, and structured MCP JSON payloads, linked directly from
|
|
35
|
+
`skills/qmd/SKILL.md`.
|
|
36
|
+
- **Automated Jules PR Code Reviews**: Integrated Google Jules PR review GitHub Actions workflow
|
|
37
|
+
(`.github/workflows/jules-pr-review.yml`) with automatic `git diff` fallback for large diffs (>20k lines).
|
|
38
|
+
- **Trivy Vulnerability Scanner & Fast CI Checks**: Integrated Trivy filesystem and dependency security
|
|
39
|
+
scanner, TypeScript type checking (`pnpm run test:types`), and Tree-sitter WASM grammars smoke
|
|
40
|
+
(`pnpm run smoke:package-grammars`) in `.github/workflows/ci.yml`.
|
|
24
41
|
|
|
25
42
|
### Changed
|
|
26
43
|
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
44
|
+
- **Runtime Standardization & Bun Deprecation**: Standardized database layer on `better-sqlite3`
|
|
45
|
+
and `sqlite-vec`. Streamlined `bin/qmd` launcher to a pure Node.js / `tsx` trampoline.
|
|
46
|
+
Replaced all `bun` / `bunx` scripts with `node`, `pnpm`, `tsx`, and `npx`.
|
|
47
|
+
- **Unified `models` Configuration Block**: Replaced disjointed embedding and expand configurations
|
|
48
|
+
with a unified `models:` configuration block supporting `embed`, `generate`, and `rerank`
|
|
49
|
+
endpoints/models.
|
|
50
|
+
- **Generic MCP Client Documentation**: Generalized MCP server configuration instructions across
|
|
51
|
+
`README.md` and `skills/qmd/references/mcp-setup.md` to support all MCP-compatible clients
|
|
52
|
+
(Cursor, Claude Desktop, Zed, OpenClaw, etc.).
|
|
53
|
+
- **Standardized Multi-Holder MIT License**: Updated `LICENSE` to the standard multi-holder copyright
|
|
54
|
+
formatting (Tobi Lutke and Wan, Jiun Wei) for full compatibility with automated license scanners.
|
|
55
|
+
- **Test Suite Semver Suffix Support**: Updated CLI `--version` test in `test/esm-ambiguous-module.test.ts`
|
|
56
|
+
to support Semver prerelease/build suffixes (e.g. `2026.8.23-1`).
|
|
57
|
+
- **Remote LLM Prompt Architecture & Hardening**: Remote query expansion and chat-completions
|
|
58
|
+
reranking now use consistent XML prompt structures, escape untrusted prompt data, preserve
|
|
59
|
+
cross-language technical terms, and discard low-confidence chat rerank scores below `0.1`.
|
|
60
|
+
- **Direct Remote Authorization**: Streamlined remote embedding and execution by removing
|
|
61
|
+
legacy interactive remote consent prompts while enforcing strict identity fingerprinting.
|
|
62
|
+
- **Database Performance Optimizations**: Skipped redundant database write transactions when
|
|
63
|
+
configuration state remains unchanged.
|
|
64
|
+
- **Dependency Upgrades**: Upgraded `@node-rs/jieba` to `2.0.2` and updated package scope to
|
|
65
|
+
`@wei840222/qmd`.
|
|
66
|
+
|
|
31
67
|
- Remote query expansion and chat-completions reranking now use a consistent
|
|
32
68
|
Gemini-style XML prompt structure in both system and user messages, separating
|
|
33
69
|
role, instructions, constraints, output format, context, task, and final
|
|
@@ -102,6 +138,18 @@
|
|
|
102
138
|
reopen, keep CLI update/include settings synchronized, and use atomic durable
|
|
103
139
|
YAML replacement.
|
|
104
140
|
|
|
141
|
+
### Removed
|
|
142
|
+
|
|
143
|
+
- Removed Nix flake configurations (`flake.nix`, `flake.lock`) and related CI jobs.
|
|
144
|
+
- Removed `bun.lock`, `src/test-preload.ts`, and `test/launcher-detection.test.sh`.
|
|
145
|
+
- Removed legacy interactive remote embedding consent workflows in favor of direct configuration-based authorization.
|
|
146
|
+
|
|
147
|
+
### Security
|
|
148
|
+
|
|
149
|
+
- Added Trivy filesystem and dependency security scanning in CI workflows.
|
|
150
|
+
- Bound MCP HTTP server explicitly to `127.0.0.1`.
|
|
151
|
+
- Sanitized remote LLM error handling to prevent leaking credentials or headers.
|
|
152
|
+
|
|
105
153
|
### Compatibility
|
|
106
154
|
|
|
107
155
|
- `vectors_vec` still supports one embedding dimension at a time. Switching
|
package/LICENSE
CHANGED
|
@@ -1,29 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2024-2026 Tobi Lutke
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
MIT License
|
|
26
|
-
|
|
27
4
|
Copyright (c) 2026 Wan, Jiun Wei
|
|
28
5
|
|
|
29
6
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
package/README.md
CHANGED
|
@@ -28,14 +28,13 @@ You can read more about QMD's progress in the [CHANGELOG](CHANGELOG.md).
|
|
|
28
28
|
## Quick Start (Local Embedding Default)
|
|
29
29
|
|
|
30
30
|
```sh
|
|
31
|
-
# Install globally
|
|
31
|
+
# Install globally
|
|
32
32
|
npm install -g @wei840222/qmd
|
|
33
33
|
# or
|
|
34
|
-
|
|
34
|
+
pnpm add -g @wei840222/qmd
|
|
35
35
|
|
|
36
36
|
# Or run directly
|
|
37
37
|
npx @wei840222/qmd ...
|
|
38
|
-
bunx @wei840222/qmd ...
|
|
39
38
|
|
|
40
39
|
# Create collections for your notes, docs, and meeting transcripts
|
|
41
40
|
qmd collection add ~/notes --name notes
|
|
@@ -96,27 +95,9 @@ Although the tool works perfectly fine when you just tell your agent to use it o
|
|
|
96
95
|
- `multi_get` — Batch retrieve by glob pattern, comma-separated list, or docids
|
|
97
96
|
- `status` — Index health and collection info
|
|
98
97
|
|
|
99
|
-
|
|
98
|
+
#### Stdio Transport (Default)
|
|
100
99
|
|
|
101
|
-
|
|
102
|
-
{
|
|
103
|
-
"mcpServers": {
|
|
104
|
-
"qmd": {
|
|
105
|
-
"command": "qmd",
|
|
106
|
-
"args": ["mcp"]
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
**Claude Code** — Install the plugin (recommended):
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
claude plugin marketplace add tobi/qmd
|
|
116
|
-
claude plugin install qmd@qmd
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
Or configure MCP manually in `~/.claude/settings.json`:
|
|
100
|
+
Add QMD to your MCP client configuration (e.g. Cursor, Claude Desktop, Zed, or other MCP-compatible clients):
|
|
120
101
|
|
|
121
102
|
```json
|
|
122
103
|
{
|
|
@@ -211,7 +192,7 @@ results seem unscoped. The HTTP `/query` and `/search` endpoints return
|
|
|
211
192
|
|
|
212
193
|
### SDK / Library Usage
|
|
213
194
|
|
|
214
|
-
Use QMD as a library in your own Node.js
|
|
195
|
+
Use QMD as a library in your own Node.js applications.
|
|
215
196
|
|
|
216
197
|
#### Installation
|
|
217
198
|
|
|
@@ -552,7 +533,6 @@ The `query` command uses **Reciprocal Rank Fusion (RRF)** with position-aware bl
|
|
|
552
533
|
### System Requirements
|
|
553
534
|
|
|
554
535
|
- **Node.js** >= 22
|
|
555
|
-
- **Bun** >= 1.0.0
|
|
556
536
|
- **macOS**: Homebrew SQLite (for extension support)
|
|
557
537
|
```sh
|
|
558
538
|
brew install sqlite
|
|
@@ -729,16 +709,16 @@ Review the resulting pin and dictionary diff before committing an update.
|
|
|
729
709
|
```sh
|
|
730
710
|
npm install -g @wei840222/qmd
|
|
731
711
|
# or
|
|
732
|
-
|
|
712
|
+
pnpm add -g @wei840222/qmd
|
|
733
713
|
```
|
|
734
714
|
|
|
735
715
|
### Development
|
|
736
716
|
|
|
737
717
|
```sh
|
|
738
|
-
git clone https://github.com/
|
|
718
|
+
git clone https://github.com/wei840222/qmd
|
|
739
719
|
cd qmd
|
|
740
|
-
|
|
741
|
-
|
|
720
|
+
pnpm install
|
|
721
|
+
npm link
|
|
742
722
|
```
|
|
743
723
|
|
|
744
724
|
## Usage
|
|
@@ -811,7 +791,7 @@ opt in. Run `qmd status` to verify which grammars are available.
|
|
|
811
791
|
|
|
812
792
|
> **Note:** Tree-sitter grammars are optional dependencies. If they are not
|
|
813
793
|
> installed, `--chunk-strategy auto` falls back to regex-only chunking
|
|
814
|
-
> automatically.
|
|
794
|
+
> automatically.
|
|
815
795
|
|
|
816
796
|
### Context Management
|
|
817
797
|
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
The bundled Traditional Chinese Jieba dictionary combines the following MIT-licensed sources:
|
|
6
6
|
|
|
7
|
-
- `@node-rs/jieba` 2.0.
|
|
7
|
+
- `@node-rs/jieba` 2.0.2 (`dict.txt`), Copyright (c) LongYinan.
|
|
8
8
|
- `APCLab/jieba-tw` (`jieba/dict.txt`) at commit `2bd4c0913cdadc865879b21a00c78e28ef1ba2c5`, Copyright (c) 2013 Sun Junyi.
|
|
9
|
-
- `sysprog21/zhtw-mcp` (`assets/ruleset.json`) at commit `
|
|
9
|
+
- `sysprog21/zhtw-mcp` (`assets/ruleset.json`) at commit `40e669a769bfcafc5f21f9ca6e2f5152b54c8831`, Copyright (c) 2026 National Cheng Kung University, Taiwan.
|
|
10
10
|
|
|
11
11
|
The source pins and content fingerprints are recorded in `src/search/zh-dict.sources.json` in the qmd source distribution. qmd has no runtime source dependency on these upstream repositories.
|
|
12
12
|
|
package/bin/qmd
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// 2>/dev/null;
|
|
2
|
+
// 2>/dev/null; exec node "$0" "$@"
|
|
3
3
|
// Cross-platform launcher for qmd.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
// on Windows generated shims that tried to route through `/bin/sh` — a path
|
|
7
|
-
// that doesn't exist on Windows, so `qmd` failed immediately after a global
|
|
8
|
-
// install. Rewriting the launcher in Node.js lets npm generate native
|
|
5
|
+
// Rewriting the launcher in Node.js lets npm/pnpm generate native
|
|
9
6
|
// cmd/ps1/sh shims that invoke `node` directly on every platform.
|
|
10
7
|
|
|
11
|
-
import { spawn
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
12
9
|
import { existsSync, realpathSync } from "node:fs";
|
|
13
|
-
import { dirname, resolve
|
|
10
|
+
import { dirname, resolve } from "node:path";
|
|
14
11
|
import { fileURLToPath } from "node:url";
|
|
15
12
|
|
|
16
|
-
// Resolve symlinks so global installs (npm link / npm install -g) can find
|
|
13
|
+
// Resolve symlinks so global installs (npm link / npm install -g / pnpm add -g) can find
|
|
17
14
|
// the actual package directory instead of the global bin directory.
|
|
18
15
|
const self = realpathSync(fileURLToPath(import.meta.url));
|
|
19
16
|
const pkgDir = resolve(dirname(self), "..");
|
|
@@ -22,7 +19,7 @@ const tsEntry = resolve(pkgDir, "src/cli/qmd.ts");
|
|
|
22
19
|
|
|
23
20
|
// MCP stdio reserves stdout exclusively for JSON-RPC frames. node-llama-cpp
|
|
24
21
|
// / llama.cpp / ggml can write native logs directly to stdout before JS-level
|
|
25
|
-
// log handlers are attached, so seed the native quiet env before Node
|
|
22
|
+
// log handlers are attached, so seed the native quiet env before Node imports
|
|
26
23
|
// the CLI and its LLM modules. Preserve explicit user values when provided.
|
|
27
24
|
if (process.argv[2] === "mcp") {
|
|
28
25
|
process.env.LLAMA_LOG_LEVEL = process.env.LLAMA_LOG_LEVEL || "error";
|
|
@@ -36,145 +33,49 @@ if (process.argv[2] === "mcp") {
|
|
|
36
33
|
// residency set is empty (ggml-org/llama.cpp#22593); the keep_alive hasn't
|
|
37
34
|
// expired by exit, so the assertion fails and ggml_abort dumps a multi-kB
|
|
38
35
|
// stack trace to stderr even when the user-visible results were already
|
|
39
|
-
// emitted correctly.
|
|
40
|
-
// destructor runs in __cxa_finalize_ranges, after every JS-reachable cleanup.
|
|
36
|
+
// emitted correctly.
|
|
41
37
|
//
|
|
42
38
|
// For QMD's short-lived CLI workflow, residency sets provide no observable
|
|
43
|
-
// performance benefit
|
|
44
|
-
// measured: identical wall time with and without on M3 Pro), so disable them
|
|
45
|
-
// by default on darwin. The env var must be set BEFORE the native llama.cpp
|
|
46
|
-
// binding loads, which is why it lives here in the launcher rather than in
|
|
47
|
-
// the JS entry point. Opt back in with QMD_METAL_KEEP_RESIDENCY=1 if you
|
|
48
|
-
// run long-lived qmd processes (the MCP daemon may benefit on hot reload)
|
|
49
|
-
// or are triaging an upstream Metal teardown fix.
|
|
39
|
+
// performance benefit, so disable them by default on darwin.
|
|
50
40
|
if (process.platform === "darwin" && process.env.QMD_METAL_KEEP_RESIDENCY !== "1") {
|
|
51
41
|
process.env.GGML_METAL_NO_RESIDENCY = process.env.GGML_METAL_NO_RESIDENCY || "1";
|
|
52
42
|
}
|
|
53
43
|
|
|
54
|
-
function hasBun() {
|
|
55
|
-
try {
|
|
56
|
-
const res = spawnSync("bun", ["--version"], { stdio: "ignore", shell: process.platform === "win32" });
|
|
57
|
-
return res.status === 0;
|
|
58
|
-
} catch {
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
44
|
// In published packages, bin/qmd must run dist/. In a git checkout, however,
|
|
64
45
|
// dist/ is often ignored and can be stale after git reset or branch switches.
|
|
65
46
|
// Prefer source mode only for checkouts so ./bin/qmd reflects the checked-out
|
|
66
47
|
// source without changing packaged/runtime behavior.
|
|
67
|
-
//
|
|
68
|
-
// Critical: source-mode detection must NOT trigger when a package manager
|
|
69
|
-
// installed us. `pnpm install -g .` (and `npm install -g .`) copy the entire
|
|
70
|
-
// working tree — including .git/, bun.lock, package-lock.json, src/, and even
|
|
71
|
-
// node_modules/ — into <prefix>/node_modules/@tobilu/qmd/, so .git and a
|
|
72
|
-
// lockfile being present is not a reliable "this is a working tree" signal.
|
|
73
|
-
// What IS reliable: a package-manager install always lands the package
|
|
74
|
-
// directory inside a `node_modules/` segment; a bare working-tree checkout
|
|
75
|
-
// (with `bun link` or a direct path invocation) does not. Gate source mode
|
|
76
|
-
// on that. Allow QMD_SOURCE_MODE=1 / =0 as an explicit override for the
|
|
77
|
-
// rare case where the heuristic disagrees with the user.
|
|
78
48
|
const sourceOverride = process.env.QMD_SOURCE_MODE;
|
|
79
49
|
const looksInstalled = pkgDir.split("/").includes("node_modules");
|
|
80
50
|
const sourceAllowed = sourceOverride === "1"
|
|
81
51
|
|| (sourceOverride !== "0" && !looksInstalled);
|
|
82
52
|
|
|
83
53
|
let useSourceMode = false;
|
|
84
|
-
let
|
|
85
|
-
let sourceArgs = [];
|
|
54
|
+
let runnerArgs = [];
|
|
86
55
|
|
|
87
56
|
if (sourceAllowed && existsSync(resolve(pkgDir, ".git")) && existsSync(tsEntry)) {
|
|
88
|
-
// Lockfile-driven runner selection — mirror the dist-mode logic below so
|
|
89
|
-
// source mode picks the same runtime the user's deps were installed for.
|
|
90
|
-
// package-lock.json wins over bun.lock when both are present: pnpm/npm
|
|
91
|
-
// installs ship the Node-ABI native modules (better-sqlite3, sqlite-vec),
|
|
92
|
-
// and running Bun against them produces ABI mismatches. This also fixes
|
|
93
|
-
// pnpm-global installs, which copy the whole working tree — including .git
|
|
94
|
-
// and bun.lock — into the install dir and used to route through Bun even
|
|
95
|
-
// when the user installed via npm/pnpm.
|
|
96
|
-
const hasNpmLock = existsSync(resolve(pkgDir, "package-lock.json"));
|
|
97
|
-
const hasBunLock = existsSync(resolve(pkgDir, "bun.lock")) || existsSync(resolve(pkgDir, "bun.lockb"));
|
|
98
57
|
const tsxEntry = resolve(pkgDir, "node_modules/tsx/dist/cli.mjs");
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (hasNpmLock && tsxAvailable) {
|
|
102
|
-
useSourceMode = true;
|
|
103
|
-
sourceRunner = "node";
|
|
104
|
-
sourceArgs = [tsxEntry, tsEntry, ...process.argv.slice(2)];
|
|
105
|
-
} else if (hasBunLock && hasBun()) {
|
|
58
|
+
if (existsSync(tsxEntry)) {
|
|
106
59
|
useSourceMode = true;
|
|
107
|
-
|
|
108
|
-
sourceArgs = [tsEntry, ...process.argv.slice(2)];
|
|
109
|
-
} else if (tsxAvailable) {
|
|
110
|
-
useSourceMode = true;
|
|
111
|
-
sourceRunner = "node";
|
|
112
|
-
sourceArgs = [tsxEntry, tsEntry, ...process.argv.slice(2)];
|
|
60
|
+
runnerArgs = [tsxEntry, tsEntry, ...process.argv.slice(2)];
|
|
113
61
|
}
|
|
114
62
|
}
|
|
115
63
|
|
|
116
64
|
if (!useSourceMode && !existsSync(jsEntry)) {
|
|
117
65
|
console.error(`qmd is not built: missing ${jsEntry}`);
|
|
118
|
-
console.error("Run:
|
|
66
|
+
console.error("Run: pnpm install && pnpm run build");
|
|
119
67
|
console.error("Or: npm install && npm run build");
|
|
120
68
|
console.error("After building, run: qmd doctor");
|
|
121
69
|
process.exit(1);
|
|
122
70
|
}
|
|
123
71
|
|
|
124
|
-
// Detect the package manager that installed dependencies by checking lockfiles.
|
|
125
|
-
// $BUN_INSTALL is intentionally NOT checked — it only indicates that bun exists
|
|
126
|
-
// on the system, not that it was used to install this package (see #361).
|
|
127
|
-
//
|
|
128
|
-
// package-lock.json takes priority: if it exists, npm installed the native
|
|
129
|
-
// modules for Node. The repo ships bun.lock, so without this check, source
|
|
130
|
-
// builds that use npm would be incorrectly routed to bun, causing ABI
|
|
131
|
-
// mismatches with better-sqlite3 / sqlite-vec (see #381).
|
|
132
|
-
//
|
|
133
|
-
// Package-manager installs keep their lockfile at the install root, above
|
|
134
|
-
// node_modules/ — never inside the package directory itself. A bun global
|
|
135
|
-
// install writes $BUN_INSTALL/install/global/bun.lock while the package
|
|
136
|
-
// lands in .../install/global/node_modules/@tobilu/qmd/, so the package-dir
|
|
137
|
-
// checks below can't match and qmd would run under whatever node is first
|
|
138
|
-
// on PATH. The native modules were built by the installing runtime; after
|
|
139
|
-
// the next Node major upgrade they fail to load (ERR_DLOPEN_FAILED,
|
|
140
|
-
// NODE_MODULE_VERSION mismatch). Apply the same npm-priority rule at the
|
|
141
|
-
// install root when the package directory itself has no lockfile.
|
|
142
|
-
const pathParts = pkgDir.split(sep);
|
|
143
|
-
const nodeModulesIndex = pathParts.lastIndexOf("node_modules");
|
|
144
|
-
const installRoot = nodeModulesIndex === -1 ? null : pathParts.slice(0, nodeModulesIndex).join(sep);
|
|
145
|
-
const hasRootNpmLock = installRoot !== null && existsSync(resolve(installRoot, "package-lock.json"));
|
|
146
|
-
const hasRootBunLock = installRoot !== null
|
|
147
|
-
&& (existsSync(resolve(installRoot, "bun.lock")) || existsSync(resolve(installRoot, "bun.lockb")));
|
|
148
|
-
|
|
149
|
-
let runnerName = "node";
|
|
150
|
-
if (existsSync(resolve(pkgDir, "package-lock.json"))) {
|
|
151
|
-
runnerName = "node";
|
|
152
|
-
} else if (existsSync(resolve(pkgDir, "bun.lock")) || existsSync(resolve(pkgDir, "bun.lockb"))) {
|
|
153
|
-
runnerName = "bun";
|
|
154
|
-
} else if (hasRootNpmLock) {
|
|
155
|
-
runnerName = "node";
|
|
156
|
-
} else if (hasRootBunLock) {
|
|
157
|
-
runnerName = "bun";
|
|
158
|
-
} else {
|
|
159
|
-
runnerName = "node";
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const selected = useSourceMode ? sourceRunner : (runnerName === "node" ? "node" : "bun");
|
|
163
72
|
// Pin Node to the binary that launched this trampoline. Native addons
|
|
164
|
-
// (better-sqlite3) are compiled for that install's ABI
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
// If the trampoline itself is running under bun (`bun bin/qmd`), keep
|
|
168
|
-
// PATH `node` so we don't load Node-ABI addons into bun.
|
|
169
|
-
const runner = selected === "node" && typeof process.versions.bun !== "string"
|
|
170
|
-
? process.execPath
|
|
171
|
-
: selected;
|
|
172
|
-
const args = useSourceMode ? sourceArgs : [jsEntry, ...process.argv.slice(2)];
|
|
173
|
-
const needsShell = (runner === "bun") && process.platform === "win32";
|
|
73
|
+
// (better-sqlite3) are compiled for that install's ABI.
|
|
74
|
+
const runner = process.execPath;
|
|
75
|
+
const args = useSourceMode ? runnerArgs : [jsEntry, ...process.argv.slice(2)];
|
|
174
76
|
|
|
175
77
|
const child = spawn(runner, args, {
|
|
176
78
|
stdio: "inherit",
|
|
177
|
-
shell: needsShell,
|
|
178
79
|
});
|
|
179
80
|
|
|
180
81
|
child.on("exit", (code, signal) => {
|
|
@@ -186,7 +87,6 @@ child.on("exit", (code, signal) => {
|
|
|
186
87
|
});
|
|
187
88
|
|
|
188
89
|
child.on("error", (err) => {
|
|
189
|
-
|
|
190
|
-
console.error(`qmd: failed to launch ${name}: ${err.message}`);
|
|
90
|
+
console.error(`qmd: failed to launch node: ${err.message}`);
|
|
191
91
|
process.exit(1);
|
|
192
92
|
});
|
package/dist/ast.js
CHANGED
|
@@ -58,7 +58,7 @@ export function formatGrammarLoadError(language, err) {
|
|
|
58
58
|
const grammar = GRAMMAR_MAP[language];
|
|
59
59
|
const detail = err instanceof Error ? err.message : String(err);
|
|
60
60
|
return `${grammar.pkg}/${grammar.wasm} failed to load (${detail}); falling back to regex chunking. ` +
|
|
61
|
-
`Repair a broken global install with:
|
|
61
|
+
`Repair a broken global install with: pnpm add ${grammar.pkg}@${grammar.version}`;
|
|
62
62
|
}
|
|
63
63
|
// =============================================================================
|
|
64
64
|
// Per-Language Query Definitions
|
package/dist/cli/build-info.json
CHANGED
package/dist/cli/qmd.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ type FinishSuccessfulCliCommandOptions = {
|
|
|
37
37
|
* order, and the process exits cleanly. The `GGML_METAL_NO_RESIDENCY=1` env
|
|
38
38
|
* var that `bin/qmd` exports is a defense-in-depth safety net for paths
|
|
39
39
|
* that still call `process.exit()` after loading the native binding
|
|
40
|
-
* (signal handlers, error paths,
|
|
40
|
+
* (signal handlers, error paths, test runners).
|
|
41
41
|
*
|
|
42
42
|
* If the caller passes an explicit `exit` for testability, we honor it —
|
|
43
43
|
* the lifecycle tests verify the legacy flush → cleanup → exit ordering.
|
package/dist/cli/qmd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { openDatabase, loadSqliteVec } from "../db.js";
|
|
3
3
|
import fastGlob from "fast-glob";
|
|
4
4
|
import { spawn as nodeSpawn } from "child_process";
|
|
5
5
|
import { isQmdMcpPid, mcpDaemonStateFiles } from "./mcp-pid.js";
|
|
@@ -270,7 +270,7 @@ async function flushWritable(stream) {
|
|
|
270
270
|
* order, and the process exits cleanly. The `GGML_METAL_NO_RESIDENCY=1` env
|
|
271
271
|
* var that `bin/qmd` exports is a defense-in-depth safety net for paths
|
|
272
272
|
* that still call `process.exit()` after loading the native binding
|
|
273
|
-
* (signal handlers, error paths,
|
|
273
|
+
* (signal handlers, error paths, test runners).
|
|
274
274
|
*
|
|
275
275
|
* If the caller passes an explicit `exit` for testability, we honor it —
|
|
276
276
|
* the lifecycle tests verify the legacy flush → cleanup → exit ordering.
|
|
@@ -3933,7 +3933,7 @@ async function showDoctor() {
|
|
|
3933
3933
|
const nextSteps = [];
|
|
3934
3934
|
console.log(`${c.bold}QMD Doctor${c.reset}\n`);
|
|
3935
3935
|
console.log(`Index: ${getDbPath()}`);
|
|
3936
|
-
console.log(`Runtime:
|
|
3936
|
+
console.log(`Runtime: better-sqlite3`);
|
|
3937
3937
|
try {
|
|
3938
3938
|
const row = db.prepare(`SELECT sqlite_version() AS version`).get();
|
|
3939
3939
|
doctorCheck("SQLite runtime", true, row.version);
|
|
@@ -4632,11 +4632,8 @@ if (isMain) {
|
|
|
4632
4632
|
const selfPath = fileURLToPath(import.meta.url);
|
|
4633
4633
|
const indexArgs = cli.values.index ? ["--index", String(cli.values.index)] : [];
|
|
4634
4634
|
const hostArgs = host ? ["--host", host] : [];
|
|
4635
|
-
const isBunRuntime = typeof process.versions.bun === "string";
|
|
4636
4635
|
const spawnArgs = selfPath.endsWith(".ts")
|
|
4637
|
-
?
|
|
4638
|
-
? [selfPath, ...indexArgs, "mcp", "--http", "--port", String(port), ...hostArgs]
|
|
4639
|
-
: ["--import", pathJoin(dirname(selfPath), "..", "..", "node_modules", "tsx", "dist", "esm", "index.mjs"), selfPath, ...indexArgs, "mcp", "--http", "--port", String(port), ...hostArgs]
|
|
4636
|
+
? ["--import", pathJoin(dirname(selfPath), "..", "..", "node_modules", "tsx", "dist", "esm", "index.mjs"), selfPath, ...indexArgs, "mcp", "--http", "--port", String(port), ...hostArgs]
|
|
4640
4637
|
: [selfPath, ...indexArgs, "mcp", "--http", "--port", String(port), ...hostArgs];
|
|
4641
4638
|
const child = nodeSpawn(process.execPath, spawnArgs, {
|
|
4642
4639
|
stdio: ["ignore", logFd, logFd],
|
package/dist/db.d.ts
CHANGED
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* db.ts -
|
|
2
|
+
* db.ts - SQLite database connection and extension management
|
|
3
3
|
*
|
|
4
|
-
* Provides
|
|
5
|
-
* and
|
|
6
|
-
* difference is the import path.
|
|
7
|
-
*
|
|
8
|
-
* On macOS, Apple's system SQLite is compiled with SQLITE_OMIT_LOAD_EXTENSION,
|
|
9
|
-
* which prevents loading native extensions like sqlite-vec. When running under
|
|
10
|
-
* Bun we call Database.setCustomSQLite() to swap in Homebrew's full-featured
|
|
11
|
-
* SQLite build before creating any database instances.
|
|
4
|
+
* Provides Database export and connection management using better-sqlite3
|
|
5
|
+
* and sqlite-vec.
|
|
12
6
|
*/
|
|
13
|
-
|
|
7
|
+
import BetterSqlite3 from "better-sqlite3";
|
|
14
8
|
export type SQLiteValue = string | number | bigint | Buffer | Uint8Array | Float32Array | null;
|
|
15
9
|
export type SQLiteParams = readonly SQLiteValue[];
|
|
16
10
|
/**
|
|
17
|
-
* Open a SQLite database
|
|
11
|
+
* Open a SQLite database using better-sqlite3.
|
|
18
12
|
*
|
|
19
|
-
* `
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* `query` racing a long `embed`, or a first-open schema migration racing any
|
|
24
|
-
* routine command) queue at batch boundaries instead of failing on contact.
|
|
13
|
+
* `better-sqlite3` defaults `busy_timeout` to 0, so concurrent writers throw
|
|
14
|
+
* `SQLITE_BUSY` instead of waiting. WAL improves read-while-write concurrency
|
|
15
|
+
* but does not serialise writers. Setting the timeout at connection open makes
|
|
16
|
+
* parallel processes queue at batch boundaries instead of failing on contact.
|
|
25
17
|
*
|
|
26
18
|
* WAL is enabled here too (with a bounded retry) so connection-level pragmas
|
|
27
19
|
* live in one place and the cold-database journal migration survives concurrent
|
|
@@ -29,37 +21,19 @@ export type SQLiteParams = readonly SQLiteValue[];
|
|
|
29
21
|
*
|
|
30
22
|
* Default 120_000 ms outlasts the worst-case batch commit on a multi-GB
|
|
31
23
|
* index. Override with `QMD_SQLITE_BUSY_TIMEOUT` (value in milliseconds; `0`
|
|
32
|
-
* restores the upstream fail-fast behaviour).
|
|
33
|
-
* https://bun.sh/docs/api/sqlite#busy-timeout.
|
|
24
|
+
* restores the upstream fail-fast behaviour).
|
|
34
25
|
*/
|
|
35
26
|
export declare function openDatabase(path: string): Database;
|
|
36
27
|
/** Open an existing database without changing journal mode, schema, or user data. */
|
|
37
28
|
export declare function openReadOnlyDatabase(path: string): Database;
|
|
38
29
|
/**
|
|
39
|
-
*
|
|
30
|
+
* Database and Statement types used throughout QMD.
|
|
40
31
|
*/
|
|
41
|
-
export
|
|
42
|
-
|
|
43
|
-
prepare(sql: string): Statement;
|
|
44
|
-
loadExtension(path: string): void;
|
|
45
|
-
transaction<T extends (...args: SQLiteValue[]) => unknown>(fn: T): T & {
|
|
46
|
-
immediate: T;
|
|
47
|
-
};
|
|
48
|
-
close(): void;
|
|
49
|
-
}
|
|
50
|
-
export interface Statement {
|
|
51
|
-
run(...params: SQLiteValue[]): {
|
|
52
|
-
changes: number;
|
|
53
|
-
lastInsertRowid: number | bigint;
|
|
54
|
-
};
|
|
55
|
-
get<T = unknown>(...params: SQLiteValue[]): T | undefined;
|
|
56
|
-
all<T = unknown>(...params: SQLiteValue[]): T[];
|
|
57
|
-
iterate<T = unknown>(...params: SQLiteValue[]): IterableIterator<T>;
|
|
58
|
-
}
|
|
32
|
+
export type Database = BetterSqlite3.Database;
|
|
33
|
+
export type Statement<T extends SQLiteParams = SQLiteParams> = BetterSqlite3.Statement<T>;
|
|
59
34
|
/**
|
|
60
35
|
* Load the sqlite-vec extension into a database.
|
|
61
36
|
*
|
|
62
|
-
* Throws with
|
|
63
|
-
* unavailable.
|
|
37
|
+
* Throws with fix instructions when the extension is unavailable.
|
|
64
38
|
*/
|
|
65
39
|
export declare function loadSqliteVec(db: Database): void;
|
package/dist/db.js
CHANGED
|
@@ -1,57 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* db.ts -
|
|
2
|
+
* db.ts - SQLite database connection and extension management
|
|
3
3
|
*
|
|
4
|
-
* Provides
|
|
5
|
-
* and
|
|
6
|
-
* difference is the import path.
|
|
7
|
-
*
|
|
8
|
-
* On macOS, Apple's system SQLite is compiled with SQLITE_OMIT_LOAD_EXTENSION,
|
|
9
|
-
* which prevents loading native extensions like sqlite-vec. When running under
|
|
10
|
-
* Bun we call Database.setCustomSQLite() to swap in Homebrew's full-featured
|
|
11
|
-
* SQLite build before creating any database instances.
|
|
4
|
+
* Provides Database export and connection management using better-sqlite3
|
|
5
|
+
* and sqlite-vec.
|
|
12
6
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
let _sqliteVecLoad;
|
|
16
|
-
if (isBun) {
|
|
17
|
-
// Dynamic string prevents tsc from resolving bun:sqlite on Node.js builds
|
|
18
|
-
const bunSqlite = "bun:" + "sqlite";
|
|
19
|
-
const BunDatabase = (await import(/* @vite-ignore */ bunSqlite)).Database;
|
|
20
|
-
// See: https://bun.com/docs/runtime/sqlite#setcustomsqlite
|
|
21
|
-
if (process.platform === "darwin") {
|
|
22
|
-
const homebrewPaths = [
|
|
23
|
-
"/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib", // Apple Silicon
|
|
24
|
-
"/usr/local/opt/sqlite/lib/libsqlite3.dylib", // Intel
|
|
25
|
-
];
|
|
26
|
-
for (const p of homebrewPaths) {
|
|
27
|
-
try {
|
|
28
|
-
BunDatabase.setCustomSQLite(p);
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
catch { }
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
_Database = BunDatabase;
|
|
35
|
-
// setCustomSQLite may have silently failed — test that extensions actually work.
|
|
36
|
-
try {
|
|
37
|
-
const { getLoadablePath } = await import("sqlite-vec");
|
|
38
|
-
const vecPath = getLoadablePath();
|
|
39
|
-
const testDb = new BunDatabase(":memory:");
|
|
40
|
-
testDb.loadExtension(vecPath);
|
|
41
|
-
testDb.close();
|
|
42
|
-
_sqliteVecLoad = (db) => db.loadExtension(vecPath);
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
// Vector search won't work, but BM25 and other operations are unaffected.
|
|
46
|
-
_sqliteVecLoad = null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
// Dual-runtime: better-sqlite3 matches Database at runtime; published types do not share an interface with bun:sqlite.
|
|
51
|
-
_Database = (await import("better-sqlite3")).default;
|
|
52
|
-
const sqliteVec = await import("sqlite-vec");
|
|
53
|
-
_sqliteVecLoad = (db) => sqliteVec.load(db);
|
|
54
|
-
}
|
|
7
|
+
import BetterSqlite3 from "better-sqlite3";
|
|
8
|
+
import * as sqliteVec from "sqlite-vec";
|
|
55
9
|
function isBusyError(err) {
|
|
56
10
|
if (typeof err !== "object" || err === null)
|
|
57
11
|
return false;
|
|
@@ -86,14 +40,12 @@ function enableWal(db, budgetMs) {
|
|
|
86
40
|
}
|
|
87
41
|
}
|
|
88
42
|
/**
|
|
89
|
-
* Open a SQLite database
|
|
43
|
+
* Open a SQLite database using better-sqlite3.
|
|
90
44
|
*
|
|
91
|
-
* `
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* `query` racing a long `embed`, or a first-open schema migration racing any
|
|
96
|
-
* routine command) queue at batch boundaries instead of failing on contact.
|
|
45
|
+
* `better-sqlite3` defaults `busy_timeout` to 0, so concurrent writers throw
|
|
46
|
+
* `SQLITE_BUSY` instead of waiting. WAL improves read-while-write concurrency
|
|
47
|
+
* but does not serialise writers. Setting the timeout at connection open makes
|
|
48
|
+
* parallel processes queue at batch boundaries instead of failing on contact.
|
|
97
49
|
*
|
|
98
50
|
* WAL is enabled here too (with a bounded retry) so connection-level pragmas
|
|
99
51
|
* live in one place and the cold-database journal migration survives concurrent
|
|
@@ -101,11 +53,10 @@ function enableWal(db, budgetMs) {
|
|
|
101
53
|
*
|
|
102
54
|
* Default 120_000 ms outlasts the worst-case batch commit on a multi-GB
|
|
103
55
|
* index. Override with `QMD_SQLITE_BUSY_TIMEOUT` (value in milliseconds; `0`
|
|
104
|
-
* restores the upstream fail-fast behaviour).
|
|
105
|
-
* https://bun.sh/docs/api/sqlite#busy-timeout.
|
|
56
|
+
* restores the upstream fail-fast behaviour).
|
|
106
57
|
*/
|
|
107
58
|
export function openDatabase(path) {
|
|
108
|
-
const db = new
|
|
59
|
+
const db = new BetterSqlite3(path);
|
|
109
60
|
const raw = process.env.QMD_SQLITE_BUSY_TIMEOUT;
|
|
110
61
|
const parsed = raw !== undefined && raw !== "" ? Number(raw) : Number.NaN;
|
|
111
62
|
const busyTimeoutMs = Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 120_000;
|
|
@@ -115,10 +66,8 @@ export function openDatabase(path) {
|
|
|
115
66
|
}
|
|
116
67
|
/** Open an existing database without changing journal mode, schema, or user data. */
|
|
117
68
|
export function openReadOnlyDatabase(path) {
|
|
118
|
-
const options =
|
|
119
|
-
|
|
120
|
-
: { readonly: true, fileMustExist: true };
|
|
121
|
-
const db = new _Database(path, options);
|
|
69
|
+
const options = { readonly: true, fileMustExist: true };
|
|
70
|
+
const db = new BetterSqlite3(path, options);
|
|
122
71
|
const raw = process.env.QMD_SQLITE_BUSY_TIMEOUT;
|
|
123
72
|
const parsed = raw !== undefined && raw !== "" ? Number(raw) : Number.NaN;
|
|
124
73
|
const busyTimeoutMs = Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 120_000;
|
|
@@ -128,16 +77,14 @@ export function openReadOnlyDatabase(path) {
|
|
|
128
77
|
/**
|
|
129
78
|
* Load the sqlite-vec extension into a database.
|
|
130
79
|
*
|
|
131
|
-
* Throws with
|
|
132
|
-
* unavailable.
|
|
80
|
+
* Throws with fix instructions when the extension is unavailable.
|
|
133
81
|
*/
|
|
134
82
|
export function loadSqliteVec(db) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
throw new Error(`sqlite-vec extension is unavailable. ${
|
|
83
|
+
try {
|
|
84
|
+
sqliteVec.load(db);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
88
|
+
throw new Error(`sqlite-vec extension is unavailable. Ensure the sqlite-vec native module is installed correctly: ${message}`);
|
|
141
89
|
}
|
|
142
|
-
_sqliteVecLoad(db);
|
|
143
90
|
}
|
package/dist/search/zh-dict.txt
CHANGED
|
@@ -46617,6 +46617,7 @@ c++ 3 nz
|
|
|
46617
46617
|
來歷不明 5 Vi
|
|
46618
46618
|
來歸 4 Vi
|
|
46619
46619
|
來源 1909 N
|
|
46620
|
+
來源位置 1000000 nz
|
|
46620
46621
|
來源國 18 N
|
|
46621
46622
|
來源地 3 N
|
|
46622
46623
|
來源場 4 N
|
|
@@ -53444,7 +53445,7 @@ c++ 3 nz
|
|
|
53444
53445
|
停養 5 Vt
|
|
53445
53446
|
停養區 1 N
|
|
53446
53447
|
停餐 18 N
|
|
53447
|
-
停駐
|
|
53448
|
+
停駐 1000000 nz
|
|
53448
53449
|
停駕 6 Vi
|
|
53449
53450
|
停駛 271 Vi
|
|
53450
53451
|
停驗 2 Vt
|
|
@@ -151253,6 +151254,7 @@ c++ 3 nz
|
|
|
151253
151254
|
多鹽 1 N
|
|
151254
151255
|
多麗 1 N
|
|
151255
151256
|
多麼 338 ADV
|
|
151257
|
+
多點傳送 1000000 nz
|
|
151256
151258
|
多黨 19 N
|
|
151257
151259
|
多黨制 1 N
|
|
151258
151260
|
夛 198 zg
|
|
@@ -181443,6 +181445,7 @@ c++ 3 nz
|
|
|
181443
181445
|
封前朝 2 nr
|
|
181444
181446
|
封包 1000000 nz
|
|
181445
181447
|
封包交換 1000000 nz
|
|
181448
|
+
封包遺失 1000000 nz
|
|
181446
181449
|
封华歆 2 nr
|
|
181447
181450
|
封博望 3 nr
|
|
181448
181451
|
封博陵 2 nr
|
|
@@ -223286,6 +223289,7 @@ c++ 3 nz
|
|
|
223286
223289
|
快反 22 v
|
|
223287
223290
|
快取 1000000 nz
|
|
223288
223291
|
快取記憶體 1000000 nz
|
|
223292
|
+
快取項目 1000000 nz
|
|
223289
223293
|
快咬 3 v
|
|
223290
223294
|
快嘴 11 N
|
|
223291
223295
|
快嘴利舌 3 i
|
|
@@ -327168,7 +327172,7 @@ c++ 3 nz
|
|
|
327168
327172
|
比容 7 n
|
|
327169
327173
|
比对 88 d
|
|
327170
327174
|
比对法 3 n
|
|
327171
|
-
比對
|
|
327175
|
+
比對 1000000 nz
|
|
327172
327176
|
比對市 1 N
|
|
327173
327177
|
比對戰 1 N
|
|
327174
327178
|
比小孔 2 nr
|
|
@@ -345178,7 +345182,7 @@ c++ 3 nz
|
|
|
345178
345182
|
流离颠疐 3 i
|
|
345179
345183
|
流离颠顿 3 i
|
|
345180
345184
|
流移失所 3 i
|
|
345181
|
-
流程
|
|
345185
|
+
流程 1000000 nz
|
|
345182
345186
|
流程再造 1000000 nz
|
|
345183
345187
|
流程化 4 n
|
|
345184
345188
|
流程图 18 n
|
|
@@ -386962,6 +386966,7 @@ c++ 3 nz
|
|
|
386962
386966
|
生命觀 2 N
|
|
386963
386967
|
生命诚可贵 3 i
|
|
386964
386968
|
生命财产 3 l
|
|
386969
|
+
生命週期 1000000 nz
|
|
386965
386970
|
生命體 10 N
|
|
386966
386971
|
生員 3 N
|
|
386967
386972
|
生唐 2 t
|
|
@@ -402286,7 +402291,7 @@ c++ 3 nz
|
|
|
402286
402291
|
相究 1 Vt
|
|
402287
402292
|
相空间 3 n
|
|
402288
402293
|
相竞 16 v
|
|
402289
|
-
相符
|
|
402294
|
+
相符 1000000 nz
|
|
402290
402295
|
相符合 3 nr
|
|
402291
402296
|
相等 18 Vi
|
|
402292
402297
|
相等于 3 l
|
|
@@ -448252,6 +448257,7 @@ c++ 3 nz
|
|
|
448252
448257
|
群憤 1 Vi
|
|
448253
448258
|
群戲 1 N
|
|
448254
448259
|
群批 1 N
|
|
448260
|
+
群播 1000000 nz
|
|
448255
448261
|
群擋球 1 N
|
|
448256
448262
|
群攀 1 N
|
|
448257
448263
|
群攻 1 Vt
|
|
@@ -456405,7 +456411,7 @@ c++ 3 nz
|
|
|
456405
456411
|
背日 3 n
|
|
456406
456412
|
背日性 3 n
|
|
456407
456413
|
背旮旯儿 3 z
|
|
456408
|
-
背景
|
|
456414
|
+
背景 1000000 nz
|
|
456409
456415
|
背景值 15 N
|
|
456410
456416
|
背景光 1 N
|
|
456411
456417
|
背景噪声 10 n
|
|
@@ -501378,7 +501384,7 @@ c++ 3 nz
|
|
|
501378
501384
|
註消 1 N
|
|
501379
501385
|
註生娘娘 44 N
|
|
501380
501386
|
註腳 10 N
|
|
501381
|
-
註解
|
|
501387
|
+
註解 1000000 nz
|
|
501382
501388
|
註記 184 N
|
|
501383
501389
|
註語 1 N
|
|
501384
501390
|
註載 1 N
|
|
@@ -542725,7 +542731,7 @@ c++ 3 nz
|
|
|
542725
542731
|
遮空蔽日 3 i
|
|
542726
542732
|
遮簷 2 N
|
|
542727
542733
|
遮簾 1 N
|
|
542728
|
-
遮罩
|
|
542734
|
+
遮罩 1000000 nz
|
|
542729
542735
|
遮羞 14 Vi
|
|
542730
542736
|
遮羞布 37 N
|
|
542731
542737
|
遮羞費 18 N
|
package/dist/store.js
CHANGED
|
@@ -3018,7 +3018,7 @@ export function countOrphanedVectors(db) {
|
|
|
3018
3018
|
* Returns the number of orphaned embedding chunks deleted.
|
|
3019
3019
|
*/
|
|
3020
3020
|
export function cleanupOrphanedVectors(db) {
|
|
3021
|
-
// sqlite-vec may not be loaded
|
|
3021
|
+
// sqlite-vec may not be loaded if extension failed to initialize.
|
|
3022
3022
|
// The vectors_vec virtual table can appear in sqlite_master from a prior
|
|
3023
3023
|
// session, but querying it without the vec0 module loaded will crash (#380).
|
|
3024
3024
|
if (!isSqliteVecAvailable()) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wei840222/qmd",
|
|
3
|
-
"version": "2026.8.
|
|
3
|
+
"version": "2026.8.24",
|
|
4
4
|
"packageManager": "pnpm@11.15.1",
|
|
5
5
|
"description": "Query Markup Documents - On-device hybrid search for markdown files with BM25, vector search, and LLM reranking",
|
|
6
6
|
"type": "module",
|
|
@@ -34,17 +34,15 @@
|
|
|
34
34
|
"lint": "oxlint",
|
|
35
35
|
"test": "node scripts/test-all.mjs",
|
|
36
36
|
"test:types": "node ./node_modules/typescript/bin/tsc -p tsconfig.build.json --noEmit",
|
|
37
|
-
"test:
|
|
38
|
-
"test:
|
|
39
|
-
"test:
|
|
40
|
-
"test:models:bun": "bun scripts/test-runtime.mjs bun --models-only",
|
|
41
|
-
"test:unit": "CI=true node ./node_modules/vitest/vitest.mjs run --reporter=verbose --testTimeout 60000 test/ && CI=true bun test --timeout 60000 --preload ./src/test-preload.ts test/",
|
|
37
|
+
"test:models": "node scripts/test-runtime.mjs",
|
|
38
|
+
"test:models:only": "node scripts/test-runtime.mjs --models-only",
|
|
39
|
+
"test:unit": "CI=true node ./node_modules/vitest/vitest.mjs run --reporter=verbose --testTimeout 60000 test/",
|
|
42
40
|
"test:quality": "node ./node_modules/vitest/vitest.mjs run --reporter=verbose test/eval-cjk.test.ts",
|
|
43
41
|
"test:package": "node scripts/package-smoke.mjs",
|
|
44
42
|
"smoke:package-grammars": "node scripts/check-package-grammars.mjs",
|
|
45
43
|
"dict:sync": "node scripts/sync-zh-dict.mjs",
|
|
46
|
-
"measure:cjk": "
|
|
47
|
-
"inspector": "
|
|
44
|
+
"measure:cjk": "tsx scripts/measure-cjk.ts",
|
|
45
|
+
"inspector": "npx @modelcontextprotocol/inspector tsx src/cli/qmd.ts mcp",
|
|
48
46
|
"release": "./scripts/release.sh",
|
|
49
47
|
"qmd": "tsx src/cli/qmd.ts",
|
|
50
48
|
"index": "tsx src/cli/qmd.ts index",
|
|
@@ -52,7 +50,7 @@
|
|
|
52
50
|
"search": "tsx src/cli/qmd.ts search",
|
|
53
51
|
"vsearch": "tsx src/cli/qmd.ts vsearch",
|
|
54
52
|
"rerank": "tsx src/cli/qmd.ts rerank",
|
|
55
|
-
"bench": "
|
|
53
|
+
"bench": "tsx src/cli/qmd.ts bench"
|
|
56
54
|
},
|
|
57
55
|
"publishConfig": {
|
|
58
56
|
"access": "public"
|
|
@@ -67,7 +65,7 @@
|
|
|
67
65
|
},
|
|
68
66
|
"dependencies": {
|
|
69
67
|
"@modelcontextprotocol/server": "2.0.0",
|
|
70
|
-
"@node-rs/jieba": "2.0.
|
|
68
|
+
"@node-rs/jieba": "2.0.2",
|
|
71
69
|
"better-sqlite3": "^13.0.3",
|
|
72
70
|
"fast-glob": "3.3.3",
|
|
73
71
|
"node-llama-cpp": "3.20.0",
|
|
@@ -112,7 +110,7 @@
|
|
|
112
110
|
"vite": "7.3.5"
|
|
113
111
|
},
|
|
114
112
|
"peerDependencies": {
|
|
115
|
-
"typescript": "^5.9.3"
|
|
113
|
+
"typescript": "^5.9.3 || ^6.0.0-0"
|
|
116
114
|
},
|
|
117
115
|
"engines": {
|
|
118
116
|
"node": ">=22.0.0"
|
|
@@ -134,6 +132,6 @@
|
|
|
134
132
|
"local-ai",
|
|
135
133
|
"llm"
|
|
136
134
|
],
|
|
137
|
-
"author": "
|
|
135
|
+
"author": "Wan, Jiun Wei <wei840222@gmail.com>",
|
|
138
136
|
"license": "MIT"
|
|
139
137
|
}
|
|
@@ -24,6 +24,6 @@ for (const grammar of grammars) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
if (!ok) {
|
|
27
|
-
console.error("\nAST grammar package smoke check failed. Run `
|
|
27
|
+
console.error("\nAST grammar package smoke check failed. Run `pnpm install` locally or repair a broken global install with the matching `pnpm add tree-sitter-...@<version>` command shown by `qmd status`.");
|
|
28
28
|
process.exit(1);
|
|
29
29
|
}
|
|
@@ -99,12 +99,6 @@ assertPath("THIRD_PARTY_NOTICES.md", "third-party notices");
|
|
|
99
99
|
run("compiled CLI under Node", process.execPath, ["dist/cli/qmd.js", "--help"], { quiet: true });
|
|
100
100
|
run("package wrapper", "sh", ["bin/qmd", "--help"], { quiet: true });
|
|
101
101
|
|
|
102
|
-
if (process.env.QMD_SKIP_BUN_SMOKE === "1") {
|
|
103
|
-
console.log("==> compiled CLI under Bun (skipped by QMD_SKIP_BUN_SMOKE=1)");
|
|
104
|
-
} else {
|
|
105
|
-
run("compiled CLI under Bun", "bun", ["dist/cli/qmd.js", "--help"], { quiet: true });
|
|
106
|
-
}
|
|
107
|
-
|
|
108
102
|
const packageSmokeRoot = process.env.QMD_PACKAGE_SMOKE_TMPDIR || join(root, ".tmp");
|
|
109
103
|
mkdirSync(packageSmokeRoot, { recursive: true });
|
|
110
104
|
const packageSmokeDir = mkdtempSync(join(packageSmokeRoot, "qmd-package-smoke-"));
|
|
@@ -129,9 +123,9 @@ try {
|
|
|
129
123
|
`${JSON.stringify({ private: true, type: "module" }, null, 2)}\n`,
|
|
130
124
|
);
|
|
131
125
|
run(
|
|
132
|
-
"install packed tarball with
|
|
133
|
-
"
|
|
134
|
-
["
|
|
126
|
+
"install packed tarball with npm",
|
|
127
|
+
"npm",
|
|
128
|
+
["install", "--ignore-scripts", "--no-package-lock", join(packageSmokeDir, tarballName)],
|
|
135
129
|
{ cwd: consumerDir, quiet: true },
|
|
136
130
|
);
|
|
137
131
|
|
|
@@ -179,14 +173,6 @@ try {
|
|
|
179
173
|
["--input-type=module", "--eval", jiebaSmoke],
|
|
180
174
|
{ cwd: consumerDir, env: smokeEnv, quiet: true },
|
|
181
175
|
);
|
|
182
|
-
if (process.env.QMD_SKIP_BUN_SMOKE !== "1") {
|
|
183
|
-
run(
|
|
184
|
-
"packed jieba capability under Bun",
|
|
185
|
-
"bun",
|
|
186
|
-
["--eval", jiebaSmoke],
|
|
187
|
-
{ cwd: consumerDir, env: smokeEnv, quiet: true },
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
176
|
} finally {
|
|
191
177
|
rmSync(packageSmokeDir, { recursive: true, force: true });
|
|
192
178
|
}
|
package/scripts/test-all.mjs
CHANGED
|
@@ -41,5 +41,4 @@ function run(label, command, args, options = {}) {
|
|
|
41
41
|
|
|
42
42
|
run("TypeScript build typecheck", process.execPath, [join(root, "node_modules", "typescript", "bin", "tsc"), "-p", "tsconfig.build.json", "--noEmit"]);
|
|
43
43
|
run("Vitest suite under Node", process.execPath, [join(root, "node_modules", "vitest", "vitest.mjs"), "run", "--reporter=verbose", "--testTimeout", "60000", "test/"], { env: { CI: "true" } });
|
|
44
|
-
run("Bun test suite", "bun", ["test", "--timeout", "60000", "--preload", "./src/test-preload.ts", "test/"], { env: { CI: "true" } });
|
|
45
44
|
run("Package smoke", process.execPath, ["scripts/package-smoke.mjs"]);
|
package/skills/qmd/SKILL.md
CHANGED
|
@@ -4,8 +4,8 @@ description: Search local markdown knowledge bases, notes, docs, and wikis with
|
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Requires qmd CLI or MCP server. Install via `npm install -g @wei840222/qmd`.
|
|
6
6
|
metadata:
|
|
7
|
-
author:
|
|
8
|
-
version:
|
|
7
|
+
author: wei840222
|
|
8
|
+
version: 2026.8.23-1
|
|
9
9
|
allowed-tools: Bash(qmd:*), mcp__qmd__*
|
|
10
10
|
---
|
|
11
11
|
|
|
@@ -209,8 +209,14 @@ When using the MCP server, prefer structured searches:
|
|
|
209
209
|
{
|
|
210
210
|
"searches": [
|
|
211
211
|
{ "type": "lex", "query": "cockpit OKR Goodhart" },
|
|
212
|
-
{
|
|
213
|
-
|
|
212
|
+
{
|
|
213
|
+
"type": "vec",
|
|
214
|
+
"query": "data informed not metric driven product judgment"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"type": "hyde",
|
|
218
|
+
"query": "A concept note explains that metrics are useful as instruments, but leaders should not let OKRs or dashboards replace judgment."
|
|
219
|
+
}
|
|
214
220
|
],
|
|
215
221
|
"intent": "Find the concept note about using metrics as instruments without becoming metric-driven.",
|
|
216
222
|
"collections": ["concepts"],
|
|
@@ -245,6 +251,8 @@ qmd query $'intent: Find the customer proximity concept, not generic customer de
|
|
|
245
251
|
qmd search "six-week cadence WhatsApp merchant relationships Shawn Ryan" -c sources -n 10
|
|
246
252
|
```
|
|
247
253
|
|
|
254
|
+
For the complete EBNF grammar, search operators, and JSON payload specifications, see [Query Syntax Reference](references/query-syntax.md).
|
|
255
|
+
|
|
248
256
|
## Setup and maintenance
|
|
249
257
|
|
|
250
258
|
Only mutate indexes when the user asked for setup or maintenance. Searching and
|
|
@@ -264,17 +272,17 @@ Configure models and custom endpoints in `~/.config/qmd/index.yml` under the `mo
|
|
|
264
272
|
```yaml
|
|
265
273
|
models:
|
|
266
274
|
embed: hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf
|
|
267
|
-
embed_api_url: https://api.example.com/v1
|
|
268
|
-
embed_api_model: text-embedding-3-small
|
|
269
|
-
embed_dimension: 1536
|
|
275
|
+
embed_api_url: https://api.example.com/v1 # Both embed_api_url and embed_api_model enable remote embeddings
|
|
276
|
+
embed_api_model: text-embedding-3-small # or text-embedding-3-large
|
|
277
|
+
embed_dimension: 1536 # Optional: expected vector dimension; validates local output
|
|
270
278
|
|
|
271
279
|
# Optional: Remote LLM Query Expansion (aliases: generate_url, generate_base_url, generate_api_url)
|
|
272
|
-
generate_api_url: https://api.example.com/v1
|
|
273
|
-
generate_api_model: qwen3-7b-instruct
|
|
280
|
+
generate_api_url: https://api.example.com/v1 # Base URL (appends /chat/completions) or full endpoint
|
|
281
|
+
generate_api_model: qwen3-7b-instruct # or your-model-name
|
|
274
282
|
|
|
275
283
|
# Optional: Remote Reranking (supports rerank_url / rerank_base_url / rerank_api_url)
|
|
276
284
|
rerank_api_url: https://api.example.com/v1/chat/completions # Supports both /v1/rerank and /v1/chat/completions LLM endpoints
|
|
277
|
-
rerank_api_model: bge-reranker-v2-m3
|
|
285
|
+
rerank_api_model: bge-reranker-v2-m3 # or gpt-4o-mini / qwen3-7b-instruct
|
|
278
286
|
|
|
279
287
|
# Optional: Custom User Dictionary for CJK segmentation
|
|
280
288
|
dictionary: ~/.config/qmd/dictionary.txt
|
|
@@ -10,30 +10,14 @@ qmd embed
|
|
|
10
10
|
|
|
11
11
|
## Configure MCP Client
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
```json
|
|
15
|
-
{
|
|
16
|
-
"mcpServers": {
|
|
17
|
-
"qmd": { "command": "qmd", "args": ["mcp"] }
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
```
|
|
13
|
+
Add QMD to your MCP client configuration (e.g. Cursor, Claude Desktop, Zed, OpenClaw, or other MCP-compatible clients):
|
|
21
14
|
|
|
22
|
-
**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
23
15
|
```json
|
|
24
16
|
{
|
|
25
17
|
"mcpServers": {
|
|
26
|
-
"qmd": {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
**OpenClaw** (`~/.openclaw/openclaw.json`):
|
|
32
|
-
```json
|
|
33
|
-
{
|
|
34
|
-
"mcp": {
|
|
35
|
-
"servers": {
|
|
36
|
-
"qmd": { "command": "qmd", "args": ["mcp"] }
|
|
18
|
+
"qmd": {
|
|
19
|
+
"command": "qmd",
|
|
20
|
+
"args": ["mcp"]
|
|
37
21
|
}
|
|
38
22
|
}
|
|
39
23
|
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# QMD Query Syntax Reference
|
|
2
|
+
|
|
3
|
+
QMD queries are structured documents composed of typed sub-queries. Each line specifies a search type and query text. The hybrid retrieval engine combines results via Reciprocal Rank Fusion (RRF) and reranks them.
|
|
4
|
+
|
|
5
|
+
## Grammar
|
|
6
|
+
|
|
7
|
+
```ebnf
|
|
8
|
+
query = policy_query | query_document ;
|
|
9
|
+
policy_query = [ policy_prefix ] text ;
|
|
10
|
+
policy_prefix = "lex:" | "expand:" ;
|
|
11
|
+
query_document = [ intent_line ] { typed_line } ;
|
|
12
|
+
intent_line = "intent:" text newline ;
|
|
13
|
+
typed_line = type ":" text newline ;
|
|
14
|
+
type = "lex" | "vec" | "hyde" ;
|
|
15
|
+
text = quoted_phrase | plain_text ;
|
|
16
|
+
quoted_phrase = '"' { character } '"' ;
|
|
17
|
+
plain_text = { character } ;
|
|
18
|
+
newline = "\n" ;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Query Types
|
|
22
|
+
|
|
23
|
+
| Type | Method | Best For | Description |
|
|
24
|
+
|------|--------|----------|-------------|
|
|
25
|
+
| `lex` | BM25 (FTS5) | Exact terms, identifiers, code, titles | Keyword search with prefix, phrase, and negation support |
|
|
26
|
+
| `vec` | Vector | Natural language concepts | Semantic similarity search using local/remote embeddings |
|
|
27
|
+
| `hyde` | Vector | Complex conceptual questions | Hypothetical Document Embedding (generate expected passage) |
|
|
28
|
+
|
|
29
|
+
## Default Policy & Expansion Behavior
|
|
30
|
+
|
|
31
|
+
A query is either a single policy query or a multi-line query document:
|
|
32
|
+
- **`auto` (Default)**: CJK queries and strong lexical matches automatically bypass model expansion. Other plain queries expand into `lex`, `vec`, and `hyde` variants.
|
|
33
|
+
- **`expand:` / `--expand` (`force`)**: Explicitly forces expansion even if bypass heuristics apply.
|
|
34
|
+
- **`lex:` (`skip`)**: Explicitly disables expansion and performs direct BM25 search.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Automatic policy:
|
|
38
|
+
qmd query "how does authentication work"
|
|
39
|
+
|
|
40
|
+
# Force expansion:
|
|
41
|
+
qmd query "expand: how does authentication work"
|
|
42
|
+
# or: qmd query --expand "資料庫同步"
|
|
43
|
+
|
|
44
|
+
# Explicitly skip expansion:
|
|
45
|
+
qmd query "lex: authentication"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Lexical Search Syntax (`lex:`)
|
|
49
|
+
|
|
50
|
+
Lex queries support powerful search operators:
|
|
51
|
+
|
|
52
|
+
| Syntax | Meaning | Example | Notes |
|
|
53
|
+
|--------|---------|---------|-------|
|
|
54
|
+
| `word` | Prefix match | `perf` | Matches "performance", "perform", etc. |
|
|
55
|
+
| `"phrase"` | Exact phrase match | `"rate limiter"` | Terms must appear consecutively in order |
|
|
56
|
+
| `-word` | Exclude term | `-sports` | Documents containing this word are excluded |
|
|
57
|
+
| `-"phrase"` | Exclude phrase | `-"test data"` | Documents containing this phrase are excluded |
|
|
58
|
+
|
|
59
|
+
### Examples
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
lex: CAP theorem consistency
|
|
63
|
+
lex: "machine learning" -"deep learning"
|
|
64
|
+
lex: auth -oauth -saml
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Vector Search Syntax (`vec:`)
|
|
68
|
+
|
|
69
|
+
Natural language questions or descriptive phrases:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
vec: how does the rate limiter handle burst traffic
|
|
73
|
+
vec: what is the tradeoff between consistency and availability
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Hypothetical Document Embeddings (`hyde:`)
|
|
77
|
+
|
|
78
|
+
A 50–100 word hypothetical answer passage representing what the target document likely says:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
hyde: The rate limiter uses a sliding window counter algorithm with a 60-second window. When a client exceeds 100 requests per minute, subsequent requests return 429 Too Many Requests.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Multi-Line Structured Queries
|
|
85
|
+
|
|
86
|
+
Combine multiple sub-query types for optimal retrieval. The first sub-query receives **2x weight** during Reciprocal Rank Fusion:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
lex: rate limiter algorithm
|
|
90
|
+
vec: how does rate limiting work in the API
|
|
91
|
+
hyde: The API implements rate limiting using a token bucket algorithm...
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Disambiguating with `intent:`
|
|
95
|
+
|
|
96
|
+
An optional `intent:` line provides background context to disambiguate ambiguous queries. It steers query expansion, reranking, and snippet selection without generating search vectors itself:
|
|
97
|
+
|
|
98
|
+
- At most one `intent:` line per query document.
|
|
99
|
+
- Must be combined with at least one `lex:`, `vec:`, or `hyde:` line.
|
|
100
|
+
- Can also be passed via the `--intent` CLI flag or MCP `intent` parameter.
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
intent: web page load times and Core Web Vitals
|
|
104
|
+
lex: performance
|
|
105
|
+
vec: how to improve performance
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Collection Scoping
|
|
109
|
+
|
|
110
|
+
Scope search to specific collections using `-c` (CLI) or `collections` (MCP/SDK):
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# CLI:
|
|
114
|
+
qmd query -c docs "how does auth work"
|
|
115
|
+
qmd query -c docs -c notes $'lex: auth\nvec: authentication flow'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## MCP Tool Call Payloads
|
|
119
|
+
|
|
120
|
+
When calling the `qmd` MCP server's `query` tool, provide a structured `searches` array:
|
|
121
|
+
|
|
122
|
+
### Standard Multi-Modal Query
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"searches": [
|
|
127
|
+
{ "type": "lex", "query": "CAP theorem" },
|
|
128
|
+
{ "type": "vec", "query": "consistency vs availability tradeoffs in distributed storage" }
|
|
129
|
+
],
|
|
130
|
+
"collections": ["docs"],
|
|
131
|
+
"limit": 10
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Query with Disambiguating Intent
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"searches": [
|
|
140
|
+
{ "type": "lex", "query": "performance metrics" },
|
|
141
|
+
{ "type": "vec", "query": "page load and network latency optimization" }
|
|
142
|
+
],
|
|
143
|
+
"intent": "Front-end web performance and Core Web Vitals optimization",
|
|
144
|
+
"collections": ["frontend", "notes"],
|
|
145
|
+
"limit": 5
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Plain Query with Policy Control & Explain Trace
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"query": "CAP theorem consistency",
|
|
154
|
+
"expansion": "auto",
|
|
155
|
+
"explain": true,
|
|
156
|
+
"collections": ["docs"]
|
|
157
|
+
}
|
|
158
|
+
```
|