@wei840222/qmd 2026.8.23

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.
Files changed (94) hide show
  1. package/CHANGELOG.md +1373 -0
  2. package/LICENSE +45 -0
  3. package/README.md +1439 -0
  4. package/THIRD_PARTY_NOTICES.md +31 -0
  5. package/bin/qmd +192 -0
  6. package/dist/ast.d.ts +65 -0
  7. package/dist/ast.js +334 -0
  8. package/dist/bench/bench.d.ts +35 -0
  9. package/dist/bench/bench.js +338 -0
  10. package/dist/bench/cjk-baseline.d.ts +36 -0
  11. package/dist/bench/cjk-baseline.js +111 -0
  12. package/dist/bench/fixture.d.ts +2 -0
  13. package/dist/bench/fixture.js +84 -0
  14. package/dist/bench/score.d.ts +38 -0
  15. package/dist/bench/score.js +107 -0
  16. package/dist/bench/types.d.ts +110 -0
  17. package/dist/bench/types.js +8 -0
  18. package/dist/cli/build-info.json +4 -0
  19. package/dist/cli/embed-lock.d.ts +24 -0
  20. package/dist/cli/embed-lock.js +94 -0
  21. package/dist/cli/embedding-owner.d.ts +10 -0
  22. package/dist/cli/embedding-owner.js +20 -0
  23. package/dist/cli/formatter.d.ts +120 -0
  24. package/dist/cli/formatter.js +355 -0
  25. package/dist/cli/mcp-pid.d.ts +25 -0
  26. package/dist/cli/mcp-pid.js +86 -0
  27. package/dist/cli/qmd.d.ts +72 -0
  28. package/dist/cli/qmd.js +4806 -0
  29. package/dist/cli/version.d.ts +42 -0
  30. package/dist/cli/version.js +80 -0
  31. package/dist/collections.d.ts +200 -0
  32. package/dist/collections.js +433 -0
  33. package/dist/db.d.ts +65 -0
  34. package/dist/db.js +143 -0
  35. package/dist/diagnostics.d.ts +62 -0
  36. package/dist/diagnostics.js +260 -0
  37. package/dist/embedding/config.d.ts +52 -0
  38. package/dist/embedding/config.js +229 -0
  39. package/dist/embedding/identity.d.ts +58 -0
  40. package/dist/embedding/identity.js +321 -0
  41. package/dist/embedding/local-identity.d.ts +1 -0
  42. package/dist/embedding/local-identity.js +15 -0
  43. package/dist/embedding/local.d.ts +34 -0
  44. package/dist/embedding/local.js +290 -0
  45. package/dist/embedding/openai.d.ts +79 -0
  46. package/dist/embedding/openai.js +477 -0
  47. package/dist/embedding/owner.d.ts +13 -0
  48. package/dist/embedding/owner.js +36 -0
  49. package/dist/embedding/provider.d.ts +68 -0
  50. package/dist/embedding/provider.js +16 -0
  51. package/dist/embedding/remote-chunking.d.ts +22 -0
  52. package/dist/embedding/remote-chunking.js +83 -0
  53. package/dist/embedding/remote-embedding.d.ts +15 -0
  54. package/dist/embedding/remote-embedding.js +77 -0
  55. package/dist/hybrid-llm.d.ts +18 -0
  56. package/dist/hybrid-llm.js +53 -0
  57. package/dist/index.d.ts +244 -0
  58. package/dist/index.js +418 -0
  59. package/dist/llm.d.ts +566 -0
  60. package/dist/llm.js +1847 -0
  61. package/dist/maintenance.d.ts +33 -0
  62. package/dist/maintenance.js +52 -0
  63. package/dist/mcp/origin-guard.d.ts +67 -0
  64. package/dist/mcp/origin-guard.js +137 -0
  65. package/dist/mcp/server.d.ts +116 -0
  66. package/dist/mcp/server.js +919 -0
  67. package/dist/paths.d.ts +1 -0
  68. package/dist/paths.js +4 -0
  69. package/dist/remote-llm.d.ts +52 -0
  70. package/dist/remote-llm.js +464 -0
  71. package/dist/search/cjk-analyzer.d.ts +33 -0
  72. package/dist/search/cjk-analyzer.js +158 -0
  73. package/dist/search/cjk-index.d.ts +104 -0
  74. package/dist/search/cjk-index.js +1031 -0
  75. package/dist/search/jieba-loader.d.ts +23 -0
  76. package/dist/search/jieba-loader.js +79 -0
  77. package/dist/search/query-expansion.d.ts +23 -0
  78. package/dist/search/query-expansion.js +43 -0
  79. package/dist/search/zh-dict.txt +624013 -0
  80. package/dist/store.d.ts +1218 -0
  81. package/dist/store.js +6076 -0
  82. package/dist/trust.d.ts +152 -0
  83. package/dist/trust.js +249 -0
  84. package/package.json +139 -0
  85. package/scripts/build.mjs +83 -0
  86. package/scripts/check-package-grammars.mjs +29 -0
  87. package/scripts/package-smoke.mjs +205 -0
  88. package/scripts/sync-zh-dict.mjs +187 -0
  89. package/scripts/test-all.mjs +45 -0
  90. package/skills/qmd/SKILL.md +324 -0
  91. package/skills/qmd/references/mcp-setup.md +119 -0
  92. package/skills/release/SKILL.md +141 -0
  93. package/skills/release/scripts/install-hooks.sh +38 -0
  94. package/skills/release/scripts/release-context.sh +129 -0
@@ -0,0 +1,324 @@
1
+ ---
2
+ name: qmd
3
+ description: Search local markdown knowledge bases, notes, docs, and wikis with QMD. Use when users ask to find notes, retrieve documents, inspect a wiki, answer from indexed markdown, or set up QMD access.
4
+ license: MIT
5
+ compatibility: Requires qmd CLI or MCP server. Install via `npm install -g @wei840222/qmd`.
6
+ metadata:
7
+ author: tobi
8
+ version: "2.6.3"
9
+ allowed-tools: Bash(qmd:*), mcp__qmd__*
10
+ ---
11
+
12
+ # QMD - Query Markdown Documents
13
+
14
+ ## How search works
15
+
16
+ QMD searches local markdown collections: notes, docs, wikis, transcripts, and
17
+ project knowledge bases. Use it before web search when the answer may already be
18
+ in indexed local files.
19
+
20
+ The workflow is always:
21
+
22
+ 1. Search for candidate documents.
23
+ 2. Retrieve the full source with `qmd get` or `qmd multi-get`.
24
+ 3. Answer from retrieved text, citing paths or docids.
25
+
26
+ Do not answer from snippets alone when the user needs facts, decisions, quotes,
27
+ or nuance. Snippets are only leads.
28
+
29
+ Typical loop:
30
+
31
+ ```bash
32
+ qmd search "merchant reality support interviews" -n 5
33
+ # leads: #abc123 concepts/customer-proximity.md; #def432 sources/merchant-call.md
34
+ qmd multi-get "#abc123,#def432" --format md
35
+ ```
36
+
37
+ **Default to structured `qmd query` with `intent:`, `lex:`, `vec:`, and `hyde:`
38
+ fields that you write yourself.** You are a better query expander than the
39
+ built-in model: you know the user's actual goal, the domain vocabulary, and the
40
+ nearby-but-wrong concepts to avoid. Do not just paste the user's words into
41
+ `qmd query "..."` and hope the expansion model guesses right — supply the
42
+ `intent:` and craft the lexical and semantic terms deliberately (see
43
+ [Pick the right search mode](#pick-the-right-search-mode)).
44
+
45
+ When reporting what you retrieved, a compact note is enough; do not paste whole
46
+ files unless needed:
47
+
48
+ ```text
49
+ Retrieved:
50
+ - #abc123 concepts/customer-proximity.md
51
+ - #def432 sources/merchant-call.md
52
+ ```
53
+
54
+ ## Pick the right search mode
55
+
56
+ Use **BM25 lexical search** when you know exact words, titles, names, code
57
+ symbols, or rare phrases:
58
+
59
+ ```bash
60
+ qmd search "cockpit OKR Goodhart" -n 10
61
+ qmd search '"AI Before Headcount"' -c concepts -n 5
62
+ ```
63
+
64
+ Use **`qmd query` with structured fields** when the user describes an idea
65
+ indirectly, uses different wording than the source, or needs conceptual recall.
66
+ **This is the default mode — write the fields yourself rather than leaning on
67
+ query expansion.** Combine exact anchors with semantic recall:
68
+
69
+ ```bash
70
+ qmd query $'intent: Find the concept note about metrics as instruments without letting OKRs replace judgment.\nlex: cockpit instruments OKR Goodhart metrics judgment\nvec: data informed not metric driven product judgment\nhyde: A concept note says metrics are useful like cockpit instruments, but leaders should remain data-informed rather than metric-driven because OKRs and dashboards can Goodhart product judgment.'
71
+ ```
72
+
73
+ Structured query fields (you author each one — do not delegate this to the
74
+ expansion model):
75
+
76
+ - `intent:` states what you are trying to find **and what to avoid**. Always
77
+ supply this. It steers ranking away from nearby-but-wrong concepts.
78
+ - `lex:` exact terms, aliases, titles, code symbols, and rare words you expect
79
+ in the source. This is your own keyword expansion.
80
+ - `vec:` paraphrases the idea in natural language, in source-like wording.
81
+ - `hyde:` describes the document or answer that would satisfy the request.
82
+
83
+ You do not need all four every time, but you should almost always write at least
84
+ `intent:` plus one of `lex:`/`vec:`. A bare `qmd query "the user's sentence"`
85
+ throws away the context only you have and relies on the built-in expander to
86
+ reconstruct it — prefer the structured form.
87
+
88
+ If you genuinely have nothing to expand (a single rare token, a verbatim phrase),
89
+ that is a job for `qmd search`, not bare `qmd query`:
90
+
91
+ ```bash
92
+ qmd query --format json --explain $'intent: ...\nlex: ...\nvec: ...' # inspect ranking
93
+ ```
94
+
95
+ If `qmd query` is slow or model/GPU setup fails, fall back to `qmd search` with
96
+ better lexical terms.
97
+
98
+ ## Retrieve sources
99
+
100
+ Search results include docids like `#abc123` and `qmd://...` paths. Fetch them:
101
+
102
+ ```bash
103
+ qmd get "#abc123"
104
+ qmd get qmd://concepts/ai-before-headcount.md
105
+ qmd multi-get "#abc123,#def432" --format md
106
+ qmd multi-get 'concepts/{ai-before-headcount.md,data-informed-not-metric-driven.md}' --format md
107
+ qmd multi-get 'sources/podcast-2025-*.md' -l 80
108
+ ```
109
+
110
+ Use `multi-get` when comparing several hits or gathering context across pages.
111
+
112
+ ### Output is line-numbered and carries the docid — cite both
113
+
114
+ `get` and `multi-get` are **line-numbered by default** and always print the
115
+ document's `#docid` and `qmd://` path. So `get` output looks like:
116
+
117
+ ```text
118
+ qmd://concepts/note.md #abc123
119
+ ---
120
+
121
+ 1: # Metrics as instruments
122
+ 2:
123
+ 3: Treat dashboards like cockpit instruments...
124
+ ```
125
+
126
+ Cite the docid and exact line numbers in your answer, and use the numbers to ask
127
+ for the next slice. Pass `--no-line-numbers` only when you need raw content to
128
+ copy verbatim (e.g. reproducing a code block).
129
+
130
+ When you need to open or edit the underlying file (e.g. hand a path to `Read`,
131
+ `Edit`, or an editor), add `--full-path`. It replaces the `qmd://` URL + docid
132
+ header with the document's on-disk path, falling back to the canonical header if
133
+ the file no longer exists on disk:
134
+
135
+ ```text
136
+ $ qmd get "#abc123" --full-path
137
+ /Users/you/notes/concepts/note.md
138
+ ---
139
+
140
+ 1: # Metrics as instruments
141
+ ```
142
+
143
+ `--full-path` works the same way on `qmd search` and `qmd query`: result paths
144
+ become the file's on-disk path — `./`-prefixed relative path when the file is
145
+ inside `$PWD`, absolute realpath otherwise — and the per-result `#docid` is
146
+ dropped because the path is the identifier. The leading `./` is intentional so
147
+ the output is unambiguously a filesystem path and cannot be mistaken for a bare
148
+ collection-relative string. Default search/query output still uses `qmd://`
149
+ URIs; only opt into `--full-path` when you specifically need a path you can hand
150
+ to a non-QMD tool.
151
+
152
+ ### Read line ranges with the `:from:count` suffix — never pipe through `sed`/`head`/`tail`
153
+
154
+ `qmd get` slices files itself. Use the suffix or flags; do **not** shell out to
155
+ `sed -n`, `head`, `tail`, or `awk` to pull a line range. Piping defeats docid
156
+ resolution, virtual-path lookups, line numbering, and the header, and it is
157
+ slower and more error-prone.
158
+
159
+ The most compact form is a `:from:count` suffix right on the path or docid —
160
+ prefer it:
161
+
162
+ ```bash
163
+ qmd get "#abc123:120:40" # 40 lines starting at line 120
164
+ qmd get qmd://concepts/note.md:200:60 # lines 200–259
165
+ qmd get "#abc123:120" # from line 120 to end of file
166
+ qmd get "#abc123" --from 120 -l 40 # equivalent, using flags
167
+ ```
168
+
169
+ Suffix and flags:
170
+
171
+ - `<path>:<from>:<count>` — start at line `<from>`, read `<count>` lines. **Best
172
+ for reading around a search hit.**
173
+ - `<path>:<from>` — start at `<from>`, read to end of file.
174
+ - `--from <line>` / `-l <lines>` — flag equivalents. Explicit flags override the
175
+ suffix, so `... :5:2 -l 1` reads 1 line.
176
+ - `--no-line-numbers` — drop the `N:` prefixes (line numbers are on by default).
177
+
178
+ Wrong: `qmd get "#abc123" | sed -n '120,160p'`
179
+ Right: `qmd get "#abc123:120:40"`
180
+
181
+ Search results include a `:line` anchor on each hit — feed it straight into
182
+ `qmd get path:line:<n>` to read a window around the match (line numbers in the
183
+ output will start at `line`).
184
+
185
+ ## Discover what is indexed
186
+
187
+ Run `qmd status` or `qmd ls` to quickly discover available collections, indexed file counts, and model readiness (all run in sub-seconds):
188
+
189
+ ```bash
190
+ qmd status # check collection overview, document counts, and model health
191
+ qmd collection list # list registered collection paths
192
+ qmd ls # list collection virtual paths
193
+ ```
194
+
195
+ Always use collection filters (`-c <name>`) when searching specific topics to prevent cross-corpus drift and ensure fast, accurate results:
196
+
197
+ ```bash
198
+ qmd search "headcount autonomous agents" -c concepts -n 10
199
+ qmd query "merchant support product reality" -c concepts -c sources -n 10
200
+ ```
201
+
202
+ > **Performance Tip for Agents:** Collection filters (`-c`) are natively indexed in both lexical and vector virtual tables. Scoping by collection guarantees millisecond search latency and avoids ranking dilution from unrelated collections. Omit `-c` only when searching across the entire workspace.
203
+
204
+ ## MCP Tool: `query`
205
+
206
+ When using the MCP server, prefer structured searches:
207
+
208
+ ```json
209
+ {
210
+ "searches": [
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." }
214
+ ],
215
+ "intent": "Find the concept note about using metrics as instruments without becoming metric-driven.",
216
+ "collections": ["concepts"],
217
+ "limit": 10
218
+ }
219
+ ```
220
+
221
+ Query types:
222
+
223
+ - `lex` — BM25 keyword search. Best for exact terms, names, titles, and code.
224
+ - `vec` — vector semantic search. Best for natural-language concepts.
225
+ - `hyde` — vector search using a hypothetical answer/document passage.
226
+
227
+ ## Query craft
228
+
229
+ Good QMD searches mix three things:
230
+
231
+ 1. **Title/alias anchors:** exact page titles, named entities, phrases.
232
+ 2. **Semantic paraphrase:** how a human would describe the idea.
233
+ 3. **Negative space:** enough intent to avoid nearby-but-wrong concepts.
234
+
235
+ Examples:
236
+
237
+ ```bash
238
+ # Exact-ish title lookup
239
+ qmd search '"arm the rebels" merchants tools big companies' -c concepts
240
+
241
+ # Semantic concept lookup
242
+ qmd query $'intent: Find the customer proximity concept, not generic customer delight.\nlex: support pseudonymous merchant customer interviews\nvec: founder stays close to merchant reality through support and product use'
243
+
244
+ # Source lookup
245
+ qmd search "six-week cadence WhatsApp merchant relationships Shawn Ryan" -c sources -n 10
246
+ ```
247
+
248
+ ## Setup and maintenance
249
+
250
+ Only mutate indexes when the user asked for setup or maintenance. Searching and
251
+ retrieving are safe; collection/index mutation is not a casual first step.
252
+
253
+ ```bash
254
+ npm install -g @wei840222/qmd
255
+ qmd collection add ~/notes --name notes
256
+ qmd update
257
+ qmd embed
258
+ ```
259
+
260
+ ### Models & Remote Endpoint Configuration
261
+
262
+ Configure models and custom endpoints in `~/.config/qmd/index.yml` under the `models:` section:
263
+
264
+ ```yaml
265
+ models:
266
+ 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
270
+
271
+ # 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
274
+
275
+ # Optional: Remote Reranking (supports rerank_url / rerank_base_url / rerank_api_url)
276
+ 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
278
+
279
+ # Optional: Custom User Dictionary for CJK segmentation
280
+ dictionary: ~/.config/qmd/dictionary.txt
281
+ ```
282
+
283
+ > **Smart URL Resolution & Reranker Endpoints:** Remote LLM URLs support `_url`, `_base_url`, and `_api_url` aliases. Given a Base URL (e.g. `https://api.example.com/v1`), QMD automatically appends `/chat/completions` or `/rerank`. Explicit endpoint URLs are preserved as-is. For reranking, QMD supports both dedicated Cross-Encoder endpoints (`/v1/rerank`) and general LLM endpoints (`/v1/chat/completions`) with strict JSON sanitization and prompt-tail Recency Enforcement.
284
+
285
+ > **CLI & MCP Integration:** Remote LLM query expansion and LLM Chat Reranking are automatically wired into CLI (`qmd query`) and MCP. Query expansion automatically maintains language & script consistency matching the user query (e.g. Traditional Chinese queries produce Traditional Chinese `lex`, `vec`, and `hyde` variations).
286
+ >
287
+ > **Batch Embedding & Vector Indexing:** Embedding requests for both indexing and query expansion automatically use high-throughput Batch APIs with graceful per-item fallback. Vector search leverages native collection pre-filtering for sub-second query latency across large datasets.
288
+
289
+ Health and diagnostics:
290
+
291
+ ```bash
292
+ qmd doctor
293
+ qmd status
294
+ qmd pull
295
+ ```
296
+
297
+ `qmd doctor` checks config, model cache, device/GPU setup, vector fingerprints, CJK channel readiness, and common environment overrides. If a model-backed command fails, run it before changing configuration.
298
+
299
+ ## MCP setup
300
+
301
+ See `references/mcp-setup.md` for Claude Code, Claude Desktop, OpenClaw, and HTTP
302
+ server configuration.
303
+
304
+ ## Pitfalls
305
+
306
+ - **Do not stop at snippets.** Fetch documents before making claims.
307
+ - **Do not slice files with `sed`/`head`/`tail`.** Use the `path:from:count`
308
+ suffix (e.g. `qmd get "#abc123:120:40"`) or `--from`/`-l`. Output is already
309
+ line-numbered; piping breaks docid resolution, the header, and virtual paths.
310
+ - **Do not lean on query expansion.** Write `intent:`/`lex:`/`vec:`/`hyde:`
311
+ yourself. A bare `qmd query "user sentence"` discards the context only you
312
+ have. You expand the query; the model just ranks.
313
+ - **Do not overuse semantic search.** If you know exact titles or terms, BM25 is
314
+ faster and often better.
315
+ - **Do not mutate indexes casually.** `qmd collection add`, `qmd update`, and
316
+ `qmd embed` change local state and can be expensive.
317
+ - **Model-backed commands can be environment-sensitive.** If `qmd query`,
318
+ `qmd vsearch`, or reranking fails because local models/GPU are unavailable,
319
+ use `qmd search` and stronger lexical/structured terms.
320
+ - **Ambiguous user wording needs intent.** Add `intent:` rather than hoping query
321
+ expansion guesses the right domain.
322
+ - **Collection names matter.** Search `concepts` for synthesized wiki pages,
323
+ `sources` for transcripts/raw source pages, and docs collections for code or
324
+ project documentation.
@@ -0,0 +1,119 @@
1
+ # QMD MCP Server Setup
2
+
3
+ ## Install
4
+
5
+ ```bash
6
+ npm install -g @wei840222/qmd
7
+ qmd collection add ~/path/to/markdown --name myknowledge
8
+ qmd embed
9
+ ```
10
+
11
+ ## Configure MCP Client
12
+
13
+ **Claude Code** (`~/.claude/settings.json`):
14
+ ```json
15
+ {
16
+ "mcpServers": {
17
+ "qmd": { "command": "qmd", "args": ["mcp"] }
18
+ }
19
+ }
20
+ ```
21
+
22
+ **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
23
+ ```json
24
+ {
25
+ "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"] }
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ ## Protocol
43
+
44
+ QMD speaks MCP **2026-07-28** (SDK 2.x) and dual-speaks 2025-era stdio clients.
45
+
46
+ - **stdio** (`qmd mcp`): hosts still launch this as a subprocess. Opening
47
+ `initialize` (2025) or a `_meta`-enveloped request / `server/discover`
48
+ (2026) pins the connection's era.
49
+ - **HTTP** (`qmd mcp --http`): sessionless Streamable HTTP. No
50
+ `Mcp-Session-Id`, no handshake. Each POST is independent. 2026 clients
51
+ MUST send `MCP-Protocol-Version`, `Mcp-Method`, and (for `tools/call`)
52
+ `Mcp-Name`. Version/caps travel in `_meta`. Call `server/discover` to
53
+ learn supported versions and capabilities. `tools/list` is cacheable
54
+ (`ttlMs` / `cacheScope`) and returns tools in a stable order.
55
+
56
+ ## HTTP Mode
57
+
58
+ ```bash
59
+ qmd mcp --http # Port 8181
60
+ qmd mcp --http --daemon # Background
61
+ qmd mcp stop # Stop daemon
62
+ ```
63
+
64
+ `POST /mcp` is the MCP endpoint (JSON). `GET /health` is a liveness check.
65
+ There is no session GET stream and no idle-session TTL.
66
+
67
+ ## Tools
68
+
69
+ ### query
70
+
71
+ Search with pre-expanded queries.
72
+
73
+ ```json
74
+ {
75
+ "searches": [
76
+ { "type": "lex", "query": "keyword phrases" },
77
+ { "type": "vec", "query": "natural language question" },
78
+ { "type": "hyde", "query": "hypothetical answer passage..." }
79
+ ],
80
+ "limit": 10,
81
+ "collection": "optional",
82
+ "minScore": 0.0
83
+ }
84
+ ```
85
+
86
+ | Type | Method | Input |
87
+ |------|--------|-------|
88
+ | `lex` | BM25 | Keywords (2-5 terms) |
89
+ | `vec` | Vector | Question |
90
+ | `hyde` | Vector | Answer passage (50-100 words) |
91
+
92
+ ### get
93
+
94
+ Retrieve document by path or `#docid`.
95
+
96
+ | Param | Type | Description |
97
+ |-------|------|-------------|
98
+ | `path` | string | File path or `#docid` |
99
+ | `full` | bool? | Return full content |
100
+ | `lineNumbers` | bool? | Add line numbers |
101
+
102
+ ### multi_get
103
+
104
+ Retrieve multiple documents.
105
+
106
+ | Param | Type | Description |
107
+ |-------|------|-------------|
108
+ | `pattern` | string | Glob or comma-separated list |
109
+ | `maxBytes` | number? | Skip large files (default 64KB) |
110
+
111
+ ### status
112
+
113
+ Index health and collections. No params.
114
+
115
+ ## Troubleshooting
116
+
117
+ - **Not starting**: `which qmd`, `qmd mcp` manually
118
+ - **No results**: `qmd collection list`, `qmd embed`
119
+ - **Slow first search**: Normal, models loading (~3GB)
@@ -0,0 +1,141 @@
1
+ ---
2
+ name: release
3
+ description: Manage releases for this project. Validates changelog, installs git hooks, and cuts releases. Use when user says "/release", "release 1.0.5", "cut a release", or asks about the release process. NOT auto-invoked by the model.
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ # Release
8
+
9
+ Cut a release, validate the changelog, and ensure git hooks are installed.
10
+
11
+ ## Usage
12
+
13
+ `/release 1.0.5` or `/release patch` (bumps patch from current version).
14
+
15
+ ## Process
16
+
17
+ When the user triggers `/release <version>`:
18
+
19
+ 1. **Gather context** — run `skills/release/scripts/release-context.sh <version>`.
20
+ This silently installs git hooks and prints everything needed: version info,
21
+ working directory status, commits since last release, files changed, current
22
+ `[Unreleased]` content, and the previous release entry for style reference.
23
+
24
+ 2. **Commit outstanding work** — if the context shows staged, modified, or
25
+ untracked files that belong in this release, commit them first. Use the
26
+ /commit skill or make well-formed commits directly.
27
+
28
+ 3. **Write the changelog** — if `[Unreleased]` is empty, write it now using
29
+ the commits and file changes from the context output. Follow the changelog
30
+ standard below. Re-run the context script after committing if needed.
31
+
32
+ 4. **Check dependency updates** — before cutting the release, check for
33
+ updates to `sqlite-vec` (and platform packages), `node-llama-cpp`,
34
+ and `better-sqlite3`. Run `pnpm outdated` and report any available
35
+ updates for these packages. If updates exist, bump them (pinned, no
36
+ `^` ranges) and re-run tests before proceeding.
37
+
38
+ 5. **Cut the release** — run `scripts/release.sh <version>`. This renames
39
+ `[Unreleased]` → `[X.Y.Z] - date`, inserts a fresh `[Unreleased]`,
40
+ bumps `package.json` and the plugin version in
41
+ `.claude-plugin/marketplace.json` (so installed plugins see the update),
42
+ commits, and tags.
43
+
44
+ 6. **Show the final changelog** — print the full `[Unreleased]` +
45
+ minor series rollup via `scripts/extract-changelog.sh <version>`.
46
+ Ask the user to confirm before pushing.
47
+
48
+ 7. **Push** — after explicit confirmation, run `git push origin main --tags`.
49
+
50
+ 8. **Watch CI** — after the push, start a background dispatch to watch the
51
+ publish workflow. Use `interactive_shell` in dispatch mode with:
52
+ ```
53
+ gh run watch $(gh run list --workflow=publish.yml --limit=1 --json databaseId --jq '.[0].databaseId') --exit-status
54
+ ```
55
+ The agent will be notified when CI completes and should report the result.
56
+
57
+ If any step fails, stop and explain. Never force-push or skip validation.
58
+
59
+ ## Dependency Policy
60
+
61
+ All dependencies must be pinned to exact versions (no `^` or `~` ranges).
62
+ The lockfile ensures reproducible installs. When adding or updating any
63
+ dependency, always use the exact version string (e.g. `"3.18.1"` not
64
+ `"^3.18.1"`).
65
+
66
+ ## Changelog Standard
67
+
68
+ The changelog lives in `CHANGELOG.md` and follows [Keep a Changelog](https://keepachangelog.com/) conventions.
69
+
70
+ ### Heading format
71
+
72
+ - `## [Unreleased]` — accumulates entries between releases
73
+ - `## [X.Y.Z] - YYYY-MM-DD` — released versions
74
+
75
+ ### Structure of a release entry
76
+
77
+ Each version entry has two parts:
78
+
79
+ **1. Highlights (optional, 1-4 sentences of prose)**
80
+
81
+ Immediately after the version heading, before any `###` section. The elevator
82
+ pitch — what would you tell someone in 30 seconds? Only for significant
83
+ releases; skip for small patches.
84
+
85
+ ```markdown
86
+ ## [1.1.0] - 2026-03-01
87
+
88
+ QMD now runs on both Node.js and Bun, with up to 2.7x faster reranking
89
+ through parallel contexts. GPU auto-detection replaces the unreliable
90
+ `gpu: "auto"` with explicit CUDA/Metal/Vulkan probing.
91
+ ```
92
+
93
+ **2. Detailed changelog (`### Changes` and `### Fixes`)**
94
+
95
+ ```markdown
96
+ ### Changes
97
+
98
+ - Runtime: support Node.js (>=22) alongside Bun. The `qmd` wrapper
99
+ auto-detects a suitable install via PATH. #149 (thanks @igrigorik)
100
+ - Performance: parallel embedding & reranking — up to 2.7x faster on
101
+ multi-core machines.
102
+
103
+ ### Fixes
104
+
105
+ - Prevent VRAM waste from duplicate context creation during concurrent
106
+ `embedBatch` calls. #152 (thanks @jkrems)
107
+ ```
108
+
109
+ ### Writing guidelines
110
+
111
+ - **Explain the why, not just the what.** The changelog is for users.
112
+ - **Include numbers.** "2.7x faster", "17x less memory".
113
+ - **Group by theme, not by file.** "Performance" not "Changes to llm.ts".
114
+ - **Don't list every commit.** Aggregate related changes.
115
+ - **Credit contributors:** end bullets with `#NNN (thanks @username)` for
116
+ external PRs. No need to credit the repo owner.
117
+
118
+ ### What not to include
119
+
120
+ - Internal refactors with no user-visible effect
121
+ - Dependency bumps (unless fixing a user-facing bug)
122
+ - CI/tooling changes (unless affecting the release artifact)
123
+ - Test additions (unless validating a fix worth mentioning)
124
+
125
+ ## GitHub Release Notes
126
+
127
+ Each GitHub release includes the full changelog for the **minor series** back
128
+ to x.x.0. The `scripts/extract-changelog.sh` script handles this, and the
129
+ publish workflow (`publish.yml`) calls it to populate the GitHub release.
130
+
131
+ ## Git Hooks
132
+
133
+ The pre-push hook (`scripts/pre-push`) blocks `v*` tag pushes unless:
134
+
135
+ 1. `package.json` version matches the tag
136
+ 2. `CHANGELOG.md` has a `## [X.Y.Z] - date` entry for the version
137
+ 3. CI passed on GitHub (warns in non-interactive shells, blocks in terminals)
138
+
139
+ Hooks are installed silently by the context script. They can also be installed
140
+ manually via `skills/release/scripts/install-hooks.sh` or automatically via
141
+ `bun install` (prepare script).
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Install git hooks for release validation.
5
+ # Idempotent — safe to run multiple times.
6
+
7
+ REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
8
+ if [[ -z "$REPO_ROOT" ]]; then
9
+ echo "Error: not in a git repository" >&2
10
+ exit 1
11
+ fi
12
+
13
+ HOOKS_DIR="$REPO_ROOT/.git/hooks"
14
+ SOURCE="$REPO_ROOT/scripts/pre-push"
15
+
16
+ if [[ ! -f "$SOURCE" ]]; then
17
+ echo "Error: scripts/pre-push not found at $SOURCE" >&2
18
+ exit 1
19
+ fi
20
+
21
+ # Install pre-push hook
22
+ if [[ -L "$HOOKS_DIR/pre-push" ]] && [[ "$(readlink "$HOOKS_DIR/pre-push")" == "$SOURCE" ]]; then
23
+ echo "pre-push hook: already installed (symlink)"
24
+ elif [[ -f "$HOOKS_DIR/pre-push" ]]; then
25
+ # Existing hook that isn't our symlink — back it up
26
+ BACKUP="$HOOKS_DIR/pre-push.backup.$(date +%s)"
27
+ echo "pre-push hook: backing up existing hook to $(basename "$BACKUP")"
28
+ mv "$HOOKS_DIR/pre-push" "$BACKUP"
29
+ ln -sf "$SOURCE" "$HOOKS_DIR/pre-push"
30
+ echo "pre-push hook: installed (symlink → scripts/pre-push)"
31
+ else
32
+ ln -sf "$SOURCE" "$HOOKS_DIR/pre-push"
33
+ echo "pre-push hook: installed (symlink → scripts/pre-push)"
34
+ fi
35
+
36
+ # Ensure the source is executable
37
+ chmod +x "$SOURCE"
38
+ echo "Done."