@wei840222/qmd 2026.8.23 → 2026.8.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wei840222/qmd",
3
- "version": "2026.8.23",
3
+ "version": "2026.8.28",
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:node": "bun scripts/test-runtime.mjs node",
38
- "test:bun": "bun scripts/test-runtime.mjs bun",
39
- "test:models:node": "bun scripts/test-runtime.mjs node --models-only",
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": "bun scripts/measure-cjk.ts",
47
- "inspector": "bunx @modelcontextprotocol/inspector tsx src/cli/qmd.ts mcp",
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": "bun src/cli/qmd.ts 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.1",
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": "Tobi Lutke <tobi@lutke.com>",
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 `bun install` locally or repair a broken global install with the matching `bun add tree-sitter-...@<version>` command shown by `qmd status`.");
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 Bun",
133
- "bun",
134
- ["add", "--ignore-scripts", join(packageSmokeDir, tarballName)],
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
  }
@@ -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"]);
@@ -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: tobi
8
- version: "2.6.3"
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
- { "type": "vec", "query": "data informed not metric driven product judgment" },
213
- { "type": "hyde", "query": "A concept note explains that metrics are useful as instruments, but leaders should not let OKRs or dashboards replace judgment." }
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"],
@@ -224,6 +230,8 @@ Query types:
224
230
  - `vec` — vector semantic search. Best for natural-language concepts.
225
231
  - `hyde` — vector search using a hypothetical answer/document passage.
226
232
 
233
+ When invoking `query` with a plain `query` string instead of explicit `searches`, you can set `includeHyde: false` (to omit HyDE passage generation) and `expansion: "auto" | "force" | "skip"`.
234
+
227
235
  ## Query craft
228
236
 
229
237
  Good QMD searches mix three things:
@@ -245,6 +253,8 @@ qmd query $'intent: Find the customer proximity concept, not generic customer de
245
253
  qmd search "six-week cadence WhatsApp merchant relationships Shawn Ryan" -c sources -n 10
246
254
  ```
247
255
 
256
+ For the complete EBNF grammar, search operators, and JSON payload specifications, see [Query Syntax Reference](references/query-syntax.md).
257
+
248
258
  ## Setup and maintenance
249
259
 
250
260
  Only mutate indexes when the user asked for setup or maintenance. Searching and
@@ -264,17 +274,17 @@ Configure models and custom endpoints in `~/.config/qmd/index.yml` under the `mo
264
274
  ```yaml
265
275
  models:
266
276
  embed: hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf
267
- embed_api_url: https://api.example.com/v1 # Both embed_api_url and embed_api_model enable remote embeddings
268
- embed_api_model: text-embedding-3-small # or text-embedding-3-large
269
- embed_dimension: 1536 # Optional: expected vector dimension; validates local output
277
+ embed_api_url: https://api.example.com/v1 # Both embed_api_url and embed_api_model enable remote embeddings
278
+ embed_api_model: text-embedding-3-small # or text-embedding-3-large
279
+ embed_dimension: 1536 # Optional: expected vector dimension; validates local output
270
280
 
271
281
  # Optional: Remote LLM Query Expansion (aliases: generate_url, generate_base_url, generate_api_url)
272
- generate_api_url: https://api.example.com/v1 # Base URL (appends /chat/completions) or full endpoint
273
- generate_api_model: qwen3-7b-instruct # or your-model-name
282
+ generate_api_url: https://api.example.com/v1 # Base URL (appends /chat/completions) or full endpoint
283
+ generate_api_model: qwen3-7b-instruct # or your-model-name
274
284
 
275
285
  # Optional: Remote Reranking (supports rerank_url / rerank_base_url / rerank_api_url)
276
286
  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 # or gpt-4o-mini / qwen3-7b-instruct
287
+ rerank_api_model: bge-reranker-v2-m3 # or gpt-4o-mini / qwen3-7b-instruct
278
288
 
279
289
  # Optional: Custom User Dictionary for CJK segmentation
280
290
  dictionary: ~/.config/qmd/dictionary.txt
@@ -10,30 +10,14 @@ qmd embed
10
10
 
11
11
  ## Configure MCP Client
12
12
 
13
- **Claude Code** (`~/.claude/settings.json`):
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": { "command": "qmd", "args": ["mcp"] }
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,165 @@
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
+ - **`--no-hyde`**: Disables HyDE (hypothetical document) in query expansion, generating only `lex` and `vec` variants (faster, avoids hallucinated passage drift).
34
+ - **`expand:` / `--expand` (`force`)**: Explicitly forces expansion even if bypass heuristics apply.
35
+ - **`lex:` (`skip`)**: Explicitly disables expansion and performs direct BM25 search.
36
+
37
+ ```bash
38
+ # Automatic policy:
39
+ qmd query "how does authentication work"
40
+
41
+ # Disable HyDE during expansion:
42
+ qmd query --no-hyde "how does authentication work"
43
+
44
+ # Force expansion:
45
+ qmd query "expand: how does authentication work"
46
+ # or: qmd query --expand "資料庫同步"
47
+
48
+ # Explicitly skip expansion:
49
+ qmd query "lex: authentication"
50
+ ```
51
+
52
+ ## Lexical Search Syntax (`lex:`)
53
+
54
+ Lex queries support powerful search operators:
55
+
56
+ | Syntax | Meaning | Example | Notes |
57
+ |--------|---------|---------|-------|
58
+ | `word` | Prefix match | `perf` | Matches "performance", "perform", etc. |
59
+ | `"phrase"` | Exact phrase match | `"rate limiter"` | Terms must appear consecutively in order |
60
+ | `-word` | Exclude term | `-sports` | Documents containing this word are excluded |
61
+ | `-"phrase"` | Exclude phrase | `-"test data"` | Documents containing this phrase are excluded |
62
+
63
+ ### Examples
64
+
65
+ ```
66
+ lex: CAP theorem consistency
67
+ lex: "machine learning" -"deep learning"
68
+ lex: auth -oauth -saml
69
+ ```
70
+
71
+ ## Vector Search Syntax (`vec:`)
72
+
73
+ Natural language questions or descriptive phrases:
74
+
75
+ ```
76
+ vec: how does the rate limiter handle burst traffic
77
+ vec: what is the tradeoff between consistency and availability
78
+ ```
79
+
80
+ ## Hypothetical Document Embeddings (`hyde:`)
81
+
82
+ A 50–100 word hypothetical answer passage representing what the target document likely says:
83
+
84
+ ```
85
+ 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.
86
+ ```
87
+
88
+ When relying on query expansion, HyDE generation can be excluded using `--no-hyde` (CLI) or `"includeHyde": false` (MCP/SDK).
89
+
90
+ ## Multi-Line Structured Queries
91
+
92
+ Combine multiple sub-query types for optimal retrieval. The first sub-query receives **2x weight** during Reciprocal Rank Fusion:
93
+
94
+ ```
95
+ lex: rate limiter algorithm
96
+ vec: how does rate limiting work in the API
97
+ hyde: The API implements rate limiting using a token bucket algorithm...
98
+ ```
99
+
100
+ ## Disambiguating with `intent:`
101
+
102
+ An optional `intent:` line provides background context to disambiguate ambiguous queries. It steers query expansion, reranking, and snippet selection without generating search vectors itself:
103
+
104
+ - At most one `intent:` line per query document.
105
+ - Must be combined with at least one `lex:`, `vec:`, or `hyde:` line.
106
+ - Can also be passed via the `--intent` CLI flag or MCP `intent` parameter.
107
+
108
+ ```
109
+ intent: web page load times and Core Web Vitals
110
+ lex: performance
111
+ vec: how to improve performance
112
+ ```
113
+
114
+ ## Collection Scoping
115
+
116
+ Scope search to specific collections using `-c` (CLI) or `collections` (MCP/SDK):
117
+
118
+ ```bash
119
+ # CLI:
120
+ qmd query -c docs "how does auth work"
121
+ qmd query -c docs -c notes $'lex: auth\nvec: authentication flow'
122
+ ```
123
+
124
+ ## MCP Tool Call Payloads
125
+
126
+ When calling the `qmd` MCP server's `query` tool, provide a structured `searches` array:
127
+
128
+ ### Standard Multi-Modal Query
129
+
130
+ ```json
131
+ {
132
+ "searches": [
133
+ { "type": "lex", "query": "CAP theorem" },
134
+ { "type": "vec", "query": "consistency vs availability tradeoffs in distributed storage" }
135
+ ],
136
+ "collections": ["docs"],
137
+ "limit": 10
138
+ }
139
+ ```
140
+
141
+ ### Query with Disambiguating Intent
142
+
143
+ ```json
144
+ {
145
+ "searches": [
146
+ { "type": "lex", "query": "performance metrics" },
147
+ { "type": "vec", "query": "page load and network latency optimization" }
148
+ ],
149
+ "intent": "Front-end web performance and Core Web Vitals optimization",
150
+ "collections": ["frontend", "notes"],
151
+ "limit": 5
152
+ }
153
+ ```
154
+
155
+ ### Plain Query with Policy Control & Explain Trace
156
+
157
+ ```json
158
+ {
159
+ "query": "authentication flow",
160
+ "expansion": "auto",
161
+ "includeHyde": false,
162
+ "explain": true,
163
+ "collections": ["docs"]
164
+ }
165
+ ```