@tobilu/qmd 1.1.5 → 2.0.0

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 CHANGED
@@ -2,6 +2,46 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.0.0] - 2026-03-10
6
+
7
+ QMD 2.0 declares a stable library API. The SDK is now the primary interface —
8
+ the MCP server is a clean consumer of it, and the source is organized into
9
+ `src/cli/` and `src/mcp/`. Also: Node 25 support and a runtime-aware bin wrapper
10
+ for bun installs.
11
+
12
+ ### Changes
13
+
14
+ - Stable SDK API with `QMDStore` interface — search, retrieval, collection/context
15
+ management, indexing, lifecycle
16
+ - Unified `search()`: pass `query` for auto-expansion or `queries` for
17
+ pre-expanded lex/vec/hyde — replaces the old query/search/structuredSearch split
18
+ - New `getDocumentBody()`, `getDefaultCollectionNames()`, `Maintenance` class
19
+ - MCP server rewritten as a clean SDK consumer — zero internal store access
20
+ - CLI and MCP organized into `src/cli/` and `src/mcp/` subdirectories
21
+ - Runtime-aware `bin/qmd` wrapper detects bun vs node to avoid ABI mismatches.
22
+ Closes #319
23
+ - `better-sqlite3` bumped to ^12.4.5 for Node 25 support. Closes #257
24
+ - Utility exports: `extractSnippet`, `addLineNumbers`, `DEFAULT_MULTI_GET_MAX_BYTES`
25
+
26
+ ### Fixes
27
+
28
+ - Remove unused `import { resolve }` in store.ts that shadowed local export
29
+
30
+ ## [1.1.6] - 2026-03-09
31
+
32
+ QMD can now be used as a library. `import { createStore } from '@tobilu/qmd'`
33
+ gives you the full search and indexing API — hybrid query, BM25, structured
34
+ search, collection/context management — without shelling out to the CLI.
35
+
36
+ ### Changes
37
+
38
+ - **SDK / library mode**: `createStore({ dbPath, config })` returns a
39
+ `QMDStore` with `query()`, `search()`, `structuredSearch()`, `get()`,
40
+ `multiGet()`, and collection/context management methods. Supports inline
41
+ config (no files needed) or a YAML config path.
42
+ - **Package exports**: `package.json` now declares `main`, `types`, and
43
+ `exports` so bundlers and TypeScript resolve `@tobilu/qmd` correctly.
44
+
5
45
  ## [1.1.5] - 2026-03-07
6
46
 
7
47
  Ambiguous queries like "performance" now produce dramatically better results
package/README.md CHANGED
@@ -74,12 +74,10 @@ qmd get "docs/api-reference.md" --full
74
74
  Although the tool works perfectly fine when you just tell your agent to use it on the command line, it also exposes an MCP (Model Context Protocol) server for tighter integration.
75
75
 
76
76
  **Tools exposed:**
77
- - `qmd_search` - Fast BM25 keyword search (supports collection filter)
78
- - `qmd_vector_search` - Semantic vector search (supports collection filter)
79
- - `qmd_deep_search` - Deep search with query expansion and reranking (supports collection filter)
80
- - `qmd_get` - Retrieve document by path or docid (with fuzzy matching suggestions)
81
- - `qmd_multi_get` - Retrieve multiple documents by glob pattern, list, or docids
82
- - `qmd_status` - Index health and collection info
77
+ - `query` Search with typed sub-queries (`lex`/`vec`/`hyde`), combined via RRF + reranking
78
+ - `get` Retrieve a document by path or docid (with fuzzy matching suggestions)
79
+ - `multi_get` Batch retrieve by glob pattern, comma-separated list, or docids
80
+ - `status` Index health and collection info
83
81
 
84
82
  **Claude Desktop configuration** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
85
83
 
@@ -137,6 +135,242 @@ LLM models stay loaded in VRAM across requests. Embedding/reranking contexts are
137
135
 
138
136
  Point any MCP client at `http://localhost:8181/mcp` to connect.
139
137
 
138
+ ### SDK / Library Usage
139
+
140
+ Use QMD as a library in your own Node.js or Bun applications.
141
+
142
+ #### Installation
143
+
144
+ ```sh
145
+ npm install @tobilu/qmd
146
+ ```
147
+
148
+ #### Quick Start
149
+
150
+ ```typescript
151
+ import { createStore } from '@tobilu/qmd'
152
+
153
+ const store = await createStore({
154
+ dbPath: './my-index.sqlite',
155
+ config: {
156
+ collections: {
157
+ docs: { path: '/path/to/docs', pattern: '**/*.md' },
158
+ },
159
+ },
160
+ })
161
+
162
+ const results = await store.search({ query: "authentication flow" })
163
+ console.log(results.map(r => `${r.title} (${Math.round(r.score * 100)}%)`))
164
+
165
+ await store.close()
166
+ ```
167
+
168
+ #### Store Creation
169
+
170
+ `createStore()` accepts three modes:
171
+
172
+ ```typescript
173
+ import { createStore } from '@tobilu/qmd'
174
+
175
+ // 1. Inline config — no files needed besides the DB
176
+ const store = await createStore({
177
+ dbPath: './index.sqlite',
178
+ config: {
179
+ collections: {
180
+ docs: { path: '/path/to/docs', pattern: '**/*.md' },
181
+ notes: { path: '/path/to/notes' },
182
+ },
183
+ },
184
+ })
185
+
186
+ // 2. YAML config file — collections defined in a file
187
+ const store2 = await createStore({
188
+ dbPath: './index.sqlite',
189
+ configPath: './qmd.yml',
190
+ })
191
+
192
+ // 3. DB-only — reopen a previously configured store
193
+ const store3 = await createStore({ dbPath: './index.sqlite' })
194
+ ```
195
+
196
+ #### Search
197
+
198
+ The unified `search()` method handles both simple queries and pre-expanded structured queries:
199
+
200
+ ```typescript
201
+ // Simple query — auto-expanded via LLM, then BM25 + vector + reranking
202
+ const results = await store.search({ query: "authentication flow" })
203
+
204
+ // With options
205
+ const results2 = await store.search({
206
+ query: "rate limiting",
207
+ intent: "API throttling and abuse prevention",
208
+ collection: "docs",
209
+ limit: 5,
210
+ minScore: 0.3,
211
+ explain: true,
212
+ })
213
+
214
+ // Pre-expanded queries — skip auto-expansion, control each sub-query
215
+ const results3 = await store.search({
216
+ queries: [
217
+ { type: 'lex', query: '"connection pool" timeout -redis' },
218
+ { type: 'vec', query: 'why do database connections time out under load' },
219
+ ],
220
+ collections: ["docs", "notes"],
221
+ })
222
+
223
+ // Skip reranking for faster results
224
+ const fast = await store.search({ query: "auth", rerank: false })
225
+ ```
226
+
227
+ For direct backend access:
228
+
229
+ ```typescript
230
+ // BM25 keyword search (fast, no LLM)
231
+ const lexResults = await store.searchLex("auth middleware", { limit: 10 })
232
+
233
+ // Vector similarity search (embedding model, no reranking)
234
+ const vecResults = await store.searchVector("how users log in", { limit: 10 })
235
+
236
+ // Manual query expansion for full control
237
+ const expanded = await store.expandQuery("auth flow", { intent: "user login" })
238
+ const results4 = await store.search({ queries: expanded })
239
+ ```
240
+
241
+ #### Retrieval
242
+
243
+ ```typescript
244
+ // Get a document by path or docid
245
+ const doc = await store.get("docs/readme.md")
246
+ const byId = await store.get("#abc123")
247
+
248
+ if (!("error" in doc)) {
249
+ console.log(doc.title, doc.displayPath, doc.context)
250
+ }
251
+
252
+ // Get document body with line range
253
+ const body = await store.getDocumentBody("docs/readme.md", {
254
+ fromLine: 50,
255
+ maxLines: 100,
256
+ })
257
+
258
+ // Batch retrieve by glob or comma-separated list
259
+ const { docs, errors } = await store.multiGet("docs/**/*.md", {
260
+ maxBytes: 20480,
261
+ })
262
+ ```
263
+
264
+ #### Collections
265
+
266
+ ```typescript
267
+ // Add a collection
268
+ await store.addCollection("myapp", {
269
+ path: "/src/myapp",
270
+ pattern: "**/*.ts",
271
+ ignore: ["node_modules/**", "*.test.ts"],
272
+ })
273
+
274
+ // List collections with document stats
275
+ const collections = await store.listCollections()
276
+ // => [{ name, pwd, glob_pattern, doc_count, active_count, last_modified, includeByDefault }]
277
+
278
+ // Get names of collections included in queries by default
279
+ const defaults = await store.getDefaultCollectionNames()
280
+
281
+ // Remove / rename
282
+ await store.removeCollection("myapp")
283
+ await store.renameCollection("old-name", "new-name")
284
+ ```
285
+
286
+ #### Context
287
+
288
+ Context adds descriptive metadata that improves search relevance and is returned alongside results:
289
+
290
+ ```typescript
291
+ // Add context for a path within a collection
292
+ await store.addContext("docs", "/api", "REST API reference documentation")
293
+
294
+ // Set global context (applies to all collections)
295
+ await store.setGlobalContext("Internal engineering documentation")
296
+
297
+ // List all contexts
298
+ const contexts = await store.listContexts()
299
+ // => [{ collection, path, context }]
300
+
301
+ // Remove context
302
+ await store.removeContext("docs", "/api")
303
+ await store.setGlobalContext(undefined) // clear global
304
+ ```
305
+
306
+ #### Indexing
307
+
308
+ ```typescript
309
+ // Re-index collections by scanning the filesystem
310
+ const result = await store.update({
311
+ collections: ["docs"], // optional — defaults to all
312
+ onProgress: ({ collection, file, current, total }) => {
313
+ console.log(`[${collection}] ${current}/${total} ${file}`)
314
+ },
315
+ })
316
+ // => { collections, indexed, updated, unchanged, removed, needsEmbedding }
317
+
318
+ // Generate vector embeddings
319
+ const embedResult = await store.embed({
320
+ force: false, // true to re-embed everything
321
+ onProgress: ({ current, total, collection }) => {
322
+ console.log(`Embedding ${current}/${total}`)
323
+ },
324
+ })
325
+ ```
326
+
327
+ #### Types
328
+
329
+ Key types exported for SDK consumers:
330
+
331
+ ```typescript
332
+ import type {
333
+ QMDStore, // The store interface
334
+ SearchOptions, // Options for search()
335
+ LexSearchOptions, // Options for searchLex()
336
+ VectorSearchOptions, // Options for searchVector()
337
+ HybridQueryResult, // Search result with score, snippet, context
338
+ SearchResult, // Result from searchLex/searchVector
339
+ ExpandedQuery, // Typed sub-query { type: 'lex'|'vec'|'hyde', query }
340
+ DocumentResult, // Document metadata + body
341
+ DocumentNotFound, // Error with similarFiles suggestions
342
+ MultiGetResult, // Batch retrieval result
343
+ UpdateProgress, // Progress callback info for update()
344
+ UpdateResult, // Aggregated update result
345
+ EmbedProgress, // Progress callback info for embed()
346
+ EmbedResult, // Embedding result
347
+ StoreOptions, // createStore() options
348
+ CollectionConfig, // Inline config shape
349
+ IndexStatus, // From getStatus()
350
+ IndexHealthInfo, // From getIndexHealth()
351
+ } from '@tobilu/qmd'
352
+ ```
353
+
354
+ Utility exports:
355
+
356
+ ```typescript
357
+ import {
358
+ extractSnippet, // Extract a relevant snippet from text
359
+ addLineNumbers, // Add line numbers to text
360
+ DEFAULT_MULTI_GET_MAX_BYTES, // Default max file size for multiGet (10KB)
361
+ Maintenance, // Database maintenance operations
362
+ } from '@tobilu/qmd'
363
+ ```
364
+
365
+ #### Lifecycle
366
+
367
+ ```typescript
368
+ // Close the store — disposes LLM models and DB connection
369
+ await store.close()
370
+ ```
371
+
372
+ The SDK requires explicit `dbPath` — no defaults are assumed. This makes it safe to embed in any application without side effects.
373
+
140
374
  ## Architecture
141
375
 
142
376
  ```
package/bin/qmd ADDED
@@ -0,0 +1,12 @@
1
+ #!/bin/sh
2
+ # Detect the runtime used to install this package and use the matching one
3
+ # to avoid native module ABI mismatches (e.g., better-sqlite3 compiled for bun vs node)
4
+
5
+ DIR="$(cd "$(dirname "$0")/.." && pwd)"
6
+
7
+ # Check if we were installed with bun (look for bun.lock or bun-lockb)
8
+ if [ -f "$DIR/bun.lock" ] || [ -f "$DIR/bun.lockb" ] || [ -n "$BUN_INSTALL" ]; then
9
+ exec bun "$DIR/dist/cli/qmd.js" "$@"
10
+ else
11
+ exec node "$DIR/dist/cli/qmd.js" "$@"
12
+ fi
@@ -4,7 +4,7 @@
4
4
  * Provides methods to format search results and documents into various output formats:
5
5
  * JSON, CSV, XML, Markdown, files list, and CLI (colored terminal output).
6
6
  */
7
- import type { SearchResult, MultiGetResult, DocumentResult } from "./store.js";
7
+ import type { SearchResult, MultiGetResult, DocumentResult } from "../store.js";
8
8
  export type { SearchResult, MultiGetResult, DocumentResult };
9
9
  export type MultiGetFile = {
10
10
  filepath: string;
@@ -4,7 +4,7 @@
4
4
  * Provides methods to format search results and documents into various output formats:
5
5
  * JSON, CSV, XML, Markdown, files list, and CLI (colored terminal output).
6
6
  */
7
- import { extractSnippet } from "./store.js";
7
+ import { extractSnippet } from "../store.js";
8
8
  // =============================================================================
9
9
  // Helper Functions
10
10
  // =============================================================================