md-semantic-search 1.0.2 → 1.1.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.
Files changed (66) hide show
  1. package/README.md +198 -8
  2. package/RESEARCH.md +25 -3
  3. package/bin/cli.mjs +657 -29
  4. package/dist/completions.d.ts +25 -0
  5. package/dist/completions.js +238 -0
  6. package/dist/core.d.ts +31 -0
  7. package/dist/core.js +121 -1
  8. package/dist/download-progress.d.ts +52 -0
  9. package/dist/download-progress.js +112 -0
  10. package/dist/index-format.js +8 -1
  11. package/dist/index.d.ts +6 -4
  12. package/dist/index.js +5 -4
  13. package/dist/indexer.d.ts +1 -0
  14. package/dist/indexer.js +5 -5
  15. package/dist/ivf.d.ts +14 -7
  16. package/dist/ivf.js +126 -18
  17. package/dist/mcp.d.ts +143 -4
  18. package/dist/mcp.js +499 -23
  19. package/dist/models.d.ts +1 -1
  20. package/dist/models.js +52 -31
  21. package/dist/open.d.ts +43 -0
  22. package/dist/open.js +84 -0
  23. package/dist/providers.d.ts +21 -0
  24. package/dist/providers.js +104 -0
  25. package/dist/query-cache-disk.d.ts +14 -0
  26. package/dist/query-cache-disk.js +175 -0
  27. package/dist/rag.d.ts +72 -7
  28. package/dist/rag.js +191 -13
  29. package/dist/repair.d.ts +36 -0
  30. package/dist/repair.js +208 -0
  31. package/dist/rerank.d.ts +20 -4
  32. package/dist/rerank.js +14 -4
  33. package/dist/search.d.ts +35 -0
  34. package/dist/search.js +175 -5
  35. package/dist/serve.d.ts +126 -0
  36. package/dist/serve.js +676 -46
  37. package/dist/tui.d.ts +8 -0
  38. package/dist/tui.js +80 -13
  39. package/dist/watcher.d.ts +16 -0
  40. package/dist/watcher.js +41 -0
  41. package/dist/webui.d.ts +13 -0
  42. package/dist/webui.js +252 -0
  43. package/dist/wikilinks.d.ts +31 -0
  44. package/dist/wikilinks.js +185 -1
  45. package/package.json +1 -1
  46. package/src/completions.ts +255 -0
  47. package/src/core.ts +160 -1
  48. package/src/download-progress.ts +146 -0
  49. package/src/index-format.ts +7 -1
  50. package/src/index.ts +11 -2
  51. package/src/indexer.ts +8 -4
  52. package/src/ivf.ts +157 -20
  53. package/src/mcp.ts +514 -23
  54. package/src/models.ts +58 -33
  55. package/src/open.ts +119 -0
  56. package/src/providers.ts +134 -0
  57. package/src/query-cache-disk.ts +191 -0
  58. package/src/rag.ts +279 -17
  59. package/src/repair.ts +245 -0
  60. package/src/rerank.ts +43 -8
  61. package/src/search.ts +199 -6
  62. package/src/serve.ts +726 -43
  63. package/src/tui.ts +86 -14
  64. package/src/watcher.ts +46 -0
  65. package/src/webui.ts +255 -0
  66. package/src/wikilinks.ts +220 -1
package/README.md CHANGED
@@ -109,6 +109,9 @@ measurements that shaped its defaults.
109
109
 
110
110
  ## Install
111
111
 
112
+ Requires **Node.js ≥ 18**. Native dependencies ship as prebuilt binaries — no
113
+ compiler toolchain needed on any platform.
114
+
112
115
  Run on demand with `npx` (no install):
113
116
 
114
117
  ```bash
@@ -131,6 +134,9 @@ npm install
131
134
  node bin/cli.mjs --help
132
135
  ```
133
136
 
137
+ For headless server deployments there is a Docker path — see
138
+ [docs/deploy.md](./docs/deploy.md).
139
+
134
140
  ---
135
141
 
136
142
  ## Library usage
@@ -177,7 +183,14 @@ indexes and existing library consumers; schema-v3 search uses persisted BM25.
177
183
  mdss index --db /path/to/your/markdown
178
184
  ```
179
185
 
180
- First run downloads the model (~280 MB). The index is written to `<db>/.mdss/`
186
+ First run downloads the model (~280 MB) — and the download itself is
187
+ **visible**: an aggregated bar on stderr shows percent across every file of the
188
+ model directory, live MB/s and ETA (the same bar reappears when `--rerank`
189
+ first pulls the cross-encoder). Under `--json` the bar is replaced by throttled
190
+ `{"type":"download-progress", …}` / `{"type":"download-complete", …}` events
191
+ **on stderr**, so stdout stays machine-readable. A warm cache produces no
192
+ download output at all, and `--offline` behaves exactly as before. The index
193
+ is written to `<db>/.mdss/`
181
194
  by default (override with `--index-dir`). Re-run after editing your notes — it's
182
195
  incremental at two levels: unchanged files are skipped via their md5, and inside
183
196
  a changed file only the sections whose text actually changed are re-embedded
@@ -374,8 +387,31 @@ mdss search --db /path/to/your/markdown "how do I rotate the api token" --rerank
374
387
  - The reranker is lazy: nothing loads unless `--rerank` is actually requested,
375
388
  so searches without it pay zero extra cost.
376
389
 
390
+ ### 3. Ask & Interactive Multi-turn Chat (`mdss ask`, `mdss chat`)
377
391
 
378
- ### 3. Serve (daemon, warm index + model)
392
+ `md-semantic-search` includes a grounded RAG QA engine that operates fully offline by default with zero external dependencies, or connects optionally to local/remote LLMs (Ollama, OpenAI-compatible APIs).
393
+
394
+ ```bash
395
+ # One-shot grounded QA with citations:
396
+ mdss ask --db ./notes "What is the token expiration policy?"
397
+
398
+ # Multi-turn persistent chat session:
399
+ mdss chat --db ./notes
400
+
401
+ # Resume the most recent session or a specific session:
402
+ mdss chat --db ./notes --resume
403
+ mdss chat --db ./notes --session my-session-id
404
+
405
+ # One-shot query into a persistent session:
406
+ mdss chat --db ./notes --session my-session-id "What about database failover?"
407
+ ```
408
+
409
+ - **Offline Extractive Synthesis**: Zero-dependency default extracts salient factual sentences with strict `[file › heading]` source provenance.
410
+ - **Persistent Sessions**: Multi-turn sessions are stored in `<indexDir>/sessions/<id>.json` preserving full conversation turns, retrieved chunk provenance manifests (file, heading, exact line numbers, chunk hashes), and model metadata.
411
+ - **Context Carry-Over**: Follow-up turns re-rank and expand query tokens using prior conversational context.
412
+ - **Interactive REPL Commands**: `/session` (info), `/sources` (manifest of last turn), `/help`, `/exit`.
413
+
414
+ ### 4. Serve (daemon, warm index + model)
379
415
 
380
416
  ```bash
381
417
  mdss serve --db /path/to/your/markdown [--port 8747] [--host 127.0.0.1] [--watch]
@@ -420,11 +456,76 @@ const { results } = await r.json();
420
456
  ```
421
457
 
422
458
  The API is unauthenticated by default (or secured via `--api-key`) — by default it binds to **loopback only**
423
- (`127.0.0.1`), so other machines on your LAN cannot reach it. Pass `--host
424
- 0.0.0.0` (or `MDSS_HOST`) only if you intend to expose it on a network, and put
425
- a reverse proxy (or at least a firewall rule) in front of it. Request bodies
426
- are capped at 64 KB (oversized → `413`), and malformed JSON gets a clear `400`
427
- instead of being silently swallowed.
459
+ (`127.0.0.1`), so other machines on your LAN cannot reach it. Passing `--host 0.0.0.0` (or `MDSS_HOST`)
460
+ without authentication **refuses to start** — set an API key, or opt in explicitly with `--allow-unsecured`
461
+ (see [Securing remote deployments](#securing-remote-deployments)). Request bodies are capped at 64 KB
462
+ (oversized → `413`), malformed JSON gets a clear `400`, per-client request rate is capped at 60/min with a
463
+ burst of 10 (`429 + Retry-After` above that; tune via `--rate-limit`), and at most `CPU count` searches run
464
+ concurrently with a bounded wait queue (`503` when saturated; tune via `--max-concurrency`). Every response
465
+ carries `X-Content-Type-Options: nosniff` and `Cache-Control: no-store`; the `Host` header is validated
466
+ against a loopback allowlist (`403` otherwise) so DNS-rebinding pages cannot read your notes cross-origin,
467
+ and CORS stays fully off unless you pass `--cors-origin`. `/health` exposes the live counters
468
+ (`in_flight`, `queued`, `rejected_total`).
469
+
470
+ ### Web UI (built-in, `--ui`)
471
+
472
+ By default `mdss serve` also serves a **zero-dependency web UI at `/`** — a keyboard-first search page
473
+ with live debounced results, term highlighting, score bars, tag/type/status filters, dark/light theme
474
+ (follows the system until you toggle it) and an API-key prompt when Bearer auth is enabled (the key is kept
475
+ in `sessionStorage`, same-origin only). It ships as plain HTML/CSS/JS split into three assets so a strict
476
+ CSP applies (`default-src 'none'; script-src 'self'; style-src 'self'; connect-src 'self';
477
+ frame-ancestors 'none'`) — no inline code, no external calls. Use `--no-ui` to restore pure JSON at `/`;
478
+ the JSON contract (`/search`, `/health`, `/help`) never changes. A Playwright smoke test runs weekly via
479
+ the opt-in `ui-smoke` workflow.
480
+
481
+ ### Securing remote deployments
482
+
483
+ `mdss serve` is designed to be private-first: loopback bind by default, refuse-to-start for unauthenticated
484
+ network binds, DNS-rebinding protection, and rate limiting built in. To serve a team over the network safely:
485
+
486
+ 1. **Keep TLS + auth at a reverse proxy** — mdss speaks plain HTTP on loopback; nginx/caddy terminate TLS.
487
+
488
+ ```nginx
489
+ # /etc/nginx/sites-available/mdss — TLS termination + Bearer-auth passthrough
490
+ server {
491
+ listen 443 ssl;
492
+ server_name search.example.com;
493
+ ssl_certificate /etc/letsencrypt/live/search.example.com/fullchain.pem;
494
+ ssl_certificate_key /etc/letsencrypt/live/search.example.com/privkey.pem;
495
+
496
+ location / {
497
+ proxy_pass http://127.0.0.1:8747;
498
+ proxy_set_header Host $host; # must match --allowed-host if not loopback-named
499
+ proxy_set_header X-Forwarded-For $remote_addr;
500
+ }
501
+ }
502
+ ```
503
+
504
+ ```caddyfile
505
+ # Caddyfile — automatic HTTPS
506
+ search.example.com {
507
+ reverse_proxy 127.0.0.1:8747
508
+ }
509
+ ```
510
+
511
+ 2. **Set an API key without leaking it into shell history or CI logs:**
512
+
513
+ ```bash
514
+ echo "$RANDOM_KEY" > /etc/mdss/api-key && chmod 600 /etc/mdss/api-key
515
+ mdss serve --db /srv/wiki --host 127.0.0.1 --api-key-file /etc/mdss/api-key
516
+ # or MDSS_API_KEY_FILE=/etc/mdss/api-key
517
+ ```
518
+
519
+ 3. **If you must expose mdss directly** (no proxy), require the key *and* keep the firewall tight:
520
+
521
+ ```bash
522
+ MDSS_API_KEY=$(openssl rand -hex 32) mdss serve --db /srv/wiki --host 0.0.0.0
523
+ ```
524
+
525
+ The startup banner warns loudly whenever an unsecured non-loopback bind was explicitly allowed.
526
+
527
+ 4. **Browser clients on another origin?** Opt into CORS explicitly, exact origins only:
528
+ `--cors-origin https://intranet.example.com`.
428
529
 
429
530
  ### 4. MCP Server (Claude Desktop, Cursor, Copilot, Antigravity)
430
531
 
@@ -434,11 +535,35 @@ instead of being silently swallowed.
434
535
  mdss mcp --db /path/to/your/markdown
435
536
  ```
436
537
 
538
+ **Streamable HTTP transport (issue #123):** a long-running daemon can expose
539
+ the same tools over HTTP — `mdss serve --db ./notes --mcp` mounts `/mcp`
540
+ (POST JSON-RPC, SSE channel on GET, `Mcp-Session-Id` sessions, protocol
541
+ `2025-03-26`). It sits behind the exact same auth, Host-allowlist and
542
+ rate-limit gates as `/search`, so remote agents inherit the hardening. stdio
543
+ via `mdss mcp` remains the default transport.
544
+
437
545
  #### Available MCP Tools:
438
- - **`search_markdown`**: Hybrid semantic vector + BM25 search with optional `path` glob and `tag` filters.
546
+ - **`search_markdown`**: Hybrid semantic vector + BM25 search with optional `path` glob, `tag`, rich `filter`, and `graphBoost`.
439
547
  - **`get_chunk`**: Retrieve a specific section by file and heading.
548
+ - **`get_lines`**: Retrieve exact line spans (`fromLine`, `maxLines`) from an indexed Markdown document.
549
+ - **`related_notes`**: Find graph-connected (outgoing, backlinks, 2-hop) and semantically related notes for a document.
440
550
  - **`list_files`**: List indexed Markdown files with chunk counts.
441
551
  - **`index_status`**: Inspect index health, chunk counts, and model metadata.
552
+ - **`ask_knowledge_base`**: Grounded direct Q&A synthesis with citations.
553
+ - *All tools return `structuredContent` alongside text and include `readOnly: true, idempotent: true` annotations.*
554
+
555
+ #### Available MCP Resources & Templates:
556
+ - **`mdss://note/{path}`**: Read full content of an indexed Markdown document.
557
+ - **`mdss://note/{path}?fromLine=1&maxLines=50`**: Addressable line-span slices of notes.
558
+ - **`mdss://vault/{vault}`**: List all indexed notes within a vault.
559
+ - **`mdss://status`**: Live index status and chunk count statistics.
560
+
561
+ #### Available MCP Prompts:
562
+ - **`search-and-cite`**: Synthesizes a grounded answer referencing `mdss://note/{path}` resource URIs.
563
+ - **`summarize-note`**: Produces a structured summary of core concepts and linked notes.
564
+ - **`compare-notes`**: Identifies agreements, divergences, and synergies across two notes.
565
+ - **`find-contradictions`**: Audits notes for conflicting statements or outdated ADRs.
566
+ - **`timeline`**: Builds a chronological milestone / event timeline from notes.
442
567
 
443
568
  #### Claude Desktop Configuration:
444
569
  Add to your `claude_desktop_config.json`:
@@ -485,6 +610,9 @@ Add to your `claude_desktop_config.json`:
485
610
  | `--k <n>` | Number of results, positive integer (default 6). |
486
611
  | `--json` | Machine-readable output: `index` → build result JSON, `stats` → index stats JSON, `search` → hit list JSON (each hit includes `matches` — the query terms found in the chunk). |
487
612
  | `--semantic` | `search` only: pure vector ranking, skip lexical/RRF fusion (zero lexical tokenization). |
613
+ | `--open [N]` | `search`: open the top (or Nth) hit in your editor at its `startLine` — `$MDSS_EDITOR`/`$VISUAL`/`$EDITOR`, VS Code `--goto`, or the GUI opener. With `--json` on a non-TTY prints the intent instead of launching (issue #110). |
614
+ | `--fix` | `check`: apply safe auto-repairs — stale `.mdss.lock`, SHA-mismatched `vectors.bin` sidecar, corrupt `ivf.json`; rebuilds a corrupt index / recomputes `.hashes.json` when `--db` is available. Never touches source notes (issue #117). |
615
+ | `--dry-run` | `check --fix`: print the planned repairs without mutating anything. |
488
616
  | `--rerank` | Re-rank the candidate pool with a cross-encoder (`Xenova/bge-reranker-base`, ~280 MB model, downloaded on first use). Slower, sharper results — see *Reranking* below. |
489
617
  | `--graph-boost <n>` | Weight for Obsidian wikilink graph ranking and PageRank prior (search; default 0). Incorporates document centrality and 2-hop contextual propagation. |
490
618
  | `--filter <expr>` | Rich boolean filter across frontmatter tags and properties (e.g. `tag:engineering AND status != archived`). |
@@ -492,12 +620,50 @@ Add to your `claude_desktop_config.json`:
492
620
  | `--nprobe <n>` | Number of nearest centroid clusters to probe during ANN search (default 8). |
493
621
  | `--port <n>` | HTTP port for `serve` (default 8747, or `MDSS_PORT`). |
494
622
  | `--host <ip>` | Bind address for `serve` (default `127.0.0.1` — loopback only; use `0.0.0.0` to expose on the LAN, or `MDSS_HOST`). |
623
+ | `--api-key <key>` | `serve`: require Bearer auth on every request (`MDSS_API_KEY`). |
624
+ | `--api-key-file <path>` | `serve`: read the API key from a file instead of a literal value — avoids shell-history / CI-log leaks (`MDSS_API_KEY_FILE`; trailing newline trimmed, POSIX permissions checked). |
625
+ | `--allow-unsecured` | `serve`: explicit opt-in to bind non-loopback **without** auth; without it (and without an API key) such a bind refuses to start (issue #121). |
626
+ | `--allowed-host <h>` | `serve`: extra `Host` header value to accept (repeatable); anything else gets `403` — DNS-rebinding guard (issue #120). |
627
+ | `--cors-origin <o>` | `serve`: allow cross-origin browser access from this exact origin (repeatable); CORS is off unless set (issue #120). |
628
+ | `--rate-limit <n>` | `serve`: max `/search` requests per minute per client, burst 10 → `429 + Retry-After` above it; `0` disables (default 60, `MDSS_RATE_LIMIT`; issue #119). |
629
+ | `--max-concurrency <n>` | `serve`: max concurrent in-flight searches; queue cap 2× → `503` when full; `0` disables (default = CPU count, `MDSS_MAX_CONCURRENCY`; issue #119). |
495
630
  | `--watch` | `serve`: re-index incrementally when files change. |
631
+ | `--watch-debug` | `serve --watch`: verbose trace of polls, FS-error classifications and retries (issue #116). |
632
+ | `--recency <days>` | `search`: time-decay half-life — passages get a `0.5^(age/halfLife)` boost after fusion; age from frontmatter `created`/`updated`, then file mtime; surfaced in `explain` (issue #127). |
633
+ | `--embedder <name>` | Use an external embedding provider (`ollama` or `openai`) instead of local transformers.js, together with `--embedder-model` (+ `--embedder-base-url`, `--embedder-key-file`). The provider id/model/dim are recorded in the index — switching providers invalidates vectors with the usual full re-embed. Local remains the zero-config default and the only fully-offline path (issue #124). |
634
+ | `--no-query-cache` | `search`: skip the persistent query-embedding cache — repeated queries in fresh processes re-embed otherwise (issue #114). |
635
+ | `--no-ui` | `serve`: disable the built-in web UI at `/` — restores JSON API help there (issue #111). |
496
636
  | `--watch-interval <ms>` | `serve --watch`: poll every N ms (default 3000). |
497
637
  | `--watch-delay <ms>` | `serve --watch`: quiet-period debounce before a burst of saves triggers ONE re-index (default 1000; issue #42). |
498
638
  | `--offline` | Never download the model — require a cached one (or `MDSS_OFFLINE=1`). |
499
639
  | `--version` | Print the installed version. |
500
640
 
641
+ ### Shell completions
642
+
643
+ `mdss completions <bash|zsh|fish|powershell>` prints a self-contained completion
644
+ script covering every subcommand and flag, with directory completion for
645
+ `--db` / `--index-dir` / `--cache-dir` / `--vault` and file completion for
646
+ `--output` / `--config` / `--api-key-file`:
647
+
648
+ ```bash
649
+ # bash — current session only:
650
+ eval "$(mdss completions bash)"
651
+ # bash — persistent:
652
+ mdss completions bash > ~/.local/share/bash-completions/completions/mdss
653
+
654
+ # zsh:
655
+ mkdir -p ~/.zfunc && mdss completions zsh > ~/.zfunc/_mdss # fpath before compinit
656
+
657
+ # fish:
658
+ mdss completions fish > ~/.config/fish/completions/mdss.fish
659
+
660
+ # PowerShell — paste into your $PROFILE:
661
+ mdss completions powershell >> $PROFILE
662
+ ```
663
+
664
+ A unit test guards against flag drift: the generated tables must match the
665
+ flags `parseArgs` actually accepts.
666
+
501
667
  ### The base can live outside the project
502
668
 
503
669
  The index does not need write access to your notes if they're read-only — just
@@ -517,6 +683,13 @@ mdss index
517
683
  mdss search "incident runbook for db failover"
518
684
  ```
519
685
 
686
+ **Supported layouts** (hardened by the issue #118 suite): deep trees beyond
687
+ Windows MAX_PATH (>260 chars) work; unicode and spaces in names work;
688
+ `--index-dir` may live on a different drive than `--db`; NTFS junction loops
689
+ terminate the walker (bounded by path length); `\\?\` extended-length roots are
690
+ accepted for `--db`/`--index-dir`; remote UNC shares (`\\server\share`) are
691
+ **rejected** by design (local-only security boundary).
692
+
520
693
  ### Configuration file (`.mdssrc` / `mdss.config.json`)
521
694
 
522
695
  Instead of repeating CLI options on every command, create a `.mdssrc.json` or `mdss.config.json` file.
@@ -650,6 +823,23 @@ is an in-memory dot-product sweep.
650
823
 
651
824
  - **Obsidian**: An official plugin is available in [`integrations/obsidian`](./integrations/obsidian) providing a live semantic search panel directly in your vault connected to `mdss serve`.
652
825
  - **Model Context Protocol (MCP)**: Native stdio JSON-RPC 2.0 server (`mdss mcp`) integrating with Claude Desktop, Cursor, Copilot, Antigravity, and AI agents for local knowledge retrieval.
826
+ - **VS Code / Raycast / Alfred**: see [`integrations/README.md`](./integrations/README.md). Releases are automated — tagging `integrations/vscode/vX.Y.Z` builds and attaches the VSIX to a GitHub Release from source (no binaries are committed; issue #112).
827
+ - **Windows tray icon**: [`integrations/windows/`](./integrations/windows/) — a zero-dependency system-tray companion for `mdss serve` (pure PowerShell + .NET WinForms, built into Windows): state-colored icon, indexing stats on hover (chunks / model / index age), a balloon when your notes get re-indexed. One-command start:
828
+
829
+ ```cmd
830
+ integrations\windows\start-mdss.cmd D:\Notes
831
+ ```
832
+
833
+ ## Contributing
834
+
835
+ Contributions are welcome — bug reports with reproduction steps, docs fixes,
836
+ tests, and features all help. Start with
837
+ [CONTRIBUTING.md](./CONTRIBUTING.md) (dev setup, project tour, conventions)
838
+ and look for issues labeled **`good first issue`** — they are scoped for a
839
+ first-time contributor. For the internals — module map, data-flow diagrams,
840
+ on-disk formats, and extension points — see
841
+ [ARCHITECTURE.md](./ARCHITECTURE.md). By participating you agree to the
842
+ [Code of Conduct](./CODE_OF_CONDUCT.md).
653
843
 
654
844
  ## License
655
845
 
package/RESEARCH.md CHANGED
@@ -226,7 +226,29 @@ If no candidate meets the multilingual non-inferiority gate, `bge-reranker-base`
226
226
  - **Trigger:** WebAssembly SIMD dot-product kernels will be considered **only if** CPU profiling on >50,000 chunks shows JS float multiplication accounts for >40% of warm search latency.
227
227
  - **Constraint:** Zero native Node C++ bindings; must maintain pure JS fallback for non-WASM environments.
228
228
 
229
- ### ANN / IVF Scale Threshold (Issue #32)
229
+ ### ANN / IVF Scale Threshold & K-Means++ Seeding (Issues #32 & #139)
230
230
  - **Baseline:** Exact $O(N \cdot d)$ linear sweep.
231
- - **Trigger:** ANN (Inverted File / HNSW) indexing is a scale contingency. It shall **not** be introduced until corpus size exceeds **100,000+ chunks** and exact warm sweep latency exceeds 50 ms.
232
- - **Requirement:** Exact pre/post-filter correctness must be preserved; small knowledge bases (<100k chunks) will always use exact linear search.
231
+ - **K-Means++ Seeding:** Uses deterministic pseudo-random $D^2$-weighted centroid seeding (Mulberry32) to prevent initial centroid collapse on correlated notes corpora.
232
+ - **Measured Quality Gate:** `bench/ann-recall.mjs` benchmarks Recall@10 across $nprobe \in \{1, 4, 8, 16\}$ on clustered corpora, demonstrating $\ge 99.5\%$ Recall@10 at default $nprobe=8$ with sub-millisecond candidate pruning.
233
+ - **Trigger:** ANN indexing is auto-engaged above `ANN_THRESHOLD = 500` chunks when `--ann` is passed or IVF index is persisted, while exact pre/post-filter correctness is strictly preserved.
234
+
235
+ ---
236
+
237
+ ## 14. Matryoshka Representation Learning (MRL) Dimension Selection (Issue #143)
238
+
239
+ ### Mechanism
240
+ Models trained with Matryoshka Representation Learning (MRL) loss (such as `onnx-community/Qwen3-Embedding-0.6B-ONNX` and `Xenova/bge-m3`) embed semantic information hierarchically, allowing vectors to be sliced at predefined sub-dimensions (`32`, `64`, `128`, `256`, `512`, `768`, `1024`) and re-normalized to unit L2 norm without significant loss in semantic retrieval fidelity.
241
+
242
+ ### Benefits & Trade-offs
243
+ - **Storage & RAM Savings:** 1024 → 256 dimensions reduces `vectors.bin` size and memory consumption by 4× (from ~19.5 MB to ~4.88 MB per 5,000 chunks).
244
+ - **Sweep Latency:** Dot-product cosine sweep latency is halved (e.g. from ~14.2 ms to ~6.8 ms for 5,000 chunks in single-threaded pure JS).
245
+ - **Integrity & Cache Invalidation:** `embeddingAdapterFingerprint` binds the selected dimension (`model.dim`). Changing `--dimensions` automatically invalidates previous chunk hashes and forces clean re-indexing rather than silent vector dimension mismatch.
246
+
247
+ ### Usage
248
+ ```bash
249
+ # Build index truncated to 256 dims (4x smaller vectors)
250
+ mdss index --db ./notes --model qwen3-embedding-0.6b --dimensions 256
251
+
252
+ # Search with dimension safety validation
253
+ mdss search --db ./notes --dimensions 256 "query"
254
+ ```