knowledge-rag 3.9.0 → 4.2.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 (2) hide show
  1. package/README.md +286 -8
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -28,17 +28,66 @@ pip install knowledge-rag → restart Claude Code → search_knowledge("your que
28
28
 
29
29
  **12 MCP Tools** | **Hybrid Search + Reranking** | **20 File Formats** | **Optional NVIDIA GPU** | **100% Local**
30
30
 
31
- [What's New](#whats-new-in-v390) | [Supported Formats](#supported-formats) | [Installation](#installation) | [Configuration](#configuration) | [API Reference](#api-reference) | [Architecture](#architecture)
31
+ [What's New](#whats-new-in-v420) | [Supported Formats](#supported-formats) | [Installation](#installation) | [Configuration](#configuration) | [API Reference](#api-reference) | [Architecture](#architecture)
32
32
 
33
33
  </div>
34
34
 
35
35
  ---
36
36
 
37
- ## What's New in v3.9.0
37
+ ## Star History
38
+
39
+ <div align="center">
40
+
41
+ <a href="https://www.star-history.com/?repos=lyonzin%2Fknowledge-rag&type=date&legend=top-left">
42
+ <picture>
43
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=lyonzin/knowledge-rag&type=date&theme=dark&legend=top-left" />
44
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=lyonzin/knowledge-rag&type=date&legend=top-left" />
45
+ <img alt="Star History Chart" src="https://api.star-history.com/chart?repos=lyonzin/knowledge-rag&type=date&legend=top-left" />
46
+ </picture>
47
+ </a>
48
+
49
+ </div>
50
+
51
+ ---
52
+
53
+ ## What's New in v4.2.0
54
+
55
+ ### Search Performance & Output Quality (v4.2.0)
56
+
57
+ **128× faster BM25 search** — replaced `rank-bm25` full-corpus scan with a custom **inverted-index** implementation. Only documents containing query terms are scored, using `numpy.argpartition` for O(n) top-k selection. Adjacent chunk fetching now uses a single batched ChromaDB call instead of N round-trips, and an O(1) reverse lookup (`_source_to_docid`) eliminates linear scans.
58
+
59
+ **Smarter output** — two new parameters on `search_knowledge`:
60
+ - **`snippet_mode`** (default: `true`) — truncates content to ~500 characters at natural break points, reducing token consumption by ~72%. Adds `content_length` field with original size; use `get_document()` for full content.
61
+ - **`min_score`** — filters results below a normalized relevance threshold (0.0–1.0). Eliminates low-quality noise from results. Response includes `filtered_by_score` count for transparency.
62
+
63
+ Both parameters are fully backwards-compatible (existing callers see no change in behavior).
64
+
65
+ ### Enterprise Concurrent Access — SSE/HTTP Transport (v4.0.0)
66
+
67
+ The server now supports **SSE** and **streamable-http** transport modes. Instead of spawning a separate process per client (stdio), a single server process serves all clients with shared resources — 1 embedding model, 1 ChromaDB, 1 query cache.
68
+
69
+ ```yaml
70
+ # config.yaml
71
+ server:
72
+ transport: "sse" # "stdio" | "sse" | "streamable-http"
73
+ host: "127.0.0.1"
74
+ port: 8179
75
+ ```
76
+
77
+ Or via CLI: `knowledge-rag --transport sse`
78
+
79
+ **Optional enterprise features** (all disabled by default):
80
+ - **Rate limiting**: Sliding-window counter, configurable RPM and burst
81
+ - **Prometheus metrics**: `/metrics` endpoint on separate port
82
+ - **Bearer auth**: Token validation for SSE/HTTP connections
83
+
84
+ All 12 MCP tools are instrumented with `@rate_limited` and `@instrument` decorators — zero overhead when features are disabled. Default transport remains **stdio** for full backwards compatibility.
85
+
86
+ > **Migration**: Existing users need zero changes. SSE mode is opt-in via `server.transport: "sse"` in config.yaml. See [Configuration](#configuration) for details.
38
87
 
39
88
  ### Quality Gate — 7-Pillar PR Validation
40
89
 
41
- knowledge-rag is now used daily by 70+ enterprise teams. Every PR (including dependabot bumps and one-line fixes) is now evaluated against **35+ automated checks** spread across 7 pillars before any human review:
90
+ Every PR (including dependabot bumps and one-line fixes) is now evaluated against **35+ automated checks** spread across 7 pillars before any human review:
42
91
 
43
92
  | Pillar | What it enforces | Tools |
44
93
  |---|---|---|
@@ -86,6 +135,7 @@ All methods produce the same MCP server. See [Installation](#installation) for f
86
135
 
87
136
  ### Recent Highlights
88
137
 
138
+ - **v4.0.0** — **Enterprise concurrent access**: SSE/HTTP transport (1 server → N clients), thread-safe shared state, optional rate limiting + Prometheus metrics, ChromaDB WAL mode, `--transport` CLI
89
139
  - **v3.9.0** — **Quality Gate** activated: 35+ automated PR checks across 7 pillars (Security, Stability, Memory Leak, Versatility, Scalability, Versioning, Quality) + nightly resilience suite (chaos, soak, determinism, mutation)
90
140
  - **v3.8.1** — Critical hotfix: loud-fail embeddings (no more silent zero-vector corruption); Windows CI flake erradicated (HF_HUB_OFFLINE + shell:bash + atexit wrapper)
91
141
  - **v3.8.0** — Lazy-load embeddings, opt-in single-instance guard, version sync across PyPI/NPM/Docker
@@ -174,7 +224,7 @@ flowchart TB
174
224
  direction LR
175
225
  ROUTER["Keyword Router<br/>(word boundaries)"]
176
226
  SEMANTIC["Semantic Search<br/>(ChromaDB)"]
177
- BM25["BM25 Keyword<br/>(rank-bm25 + expansion)"]
227
+ BM25["BM25 Keyword<br/>(inverted-index + expansion)"]
178
228
  RRF["Reciprocal Rank<br/>Fusion (RRF)"]
179
229
  RERANK["Cross-Encoder<br/>Reranker"]
180
230
 
@@ -238,15 +288,23 @@ flowchart TB
238
288
  subgraph HYBRID["Hybrid Search"]
239
289
  direction LR
240
290
  SEMANTIC["Semantic Search<br/>(ChromaDB embeddings)<br/>Conceptual similarity"]
241
- BM25["BM25 Search<br/>(expanded query)<br/>Exact term matching"]
291
+ BM25["BM25 Inverted-Index<br/>(posting lists + numpy top-k)<br/>Exact term matching"]
242
292
  end
243
293
 
244
294
  subgraph FUSION["Result Fusion + Reranking"]
245
295
  RRF["Reciprocal Rank Fusion<br/>score = alpha * 1/(k+rank_sem)<br/>+ (1-alpha) * 1/(k+rank_bm25)"]
246
296
  RERANK["Cross-Encoder Reranker<br/>Re-scores top 3x candidates<br/>query+doc pair scoring"]
247
297
  SORT["Sort by Reranker Score<br/>Normalize to 0-1"]
298
+ ADJ["Adjacent Chunk Expansion<br/>(batch fetch ±1 chunk)"]
248
299
 
249
- RRF --> RERANK --> SORT
300
+ RRF --> RERANK --> SORT --> ADJ
301
+ end
302
+
303
+ subgraph OUTPUT["Output Processing"]
304
+ MINSCORE["min_score Filter<br/>(discard below threshold)"]
305
+ SNIPPET["snippet_mode Truncation<br/>(~500 chars at natural break)"]
306
+
307
+ MINSCORE --> SNIPPET
250
308
  end
251
309
 
252
310
  CATEGORY --> HYBRID
@@ -254,7 +312,8 @@ flowchart TB
254
312
  SEMANTIC --> RRF
255
313
  BM25 --> RRF
256
314
 
257
- SORT --> RESULTS["Results<br/>search_method: hybrid|semantic|keyword<br/>score + reranker_score + raw_rrf_score"]
315
+ ADJ --> MINSCORE
316
+ SNIPPET --> RESULTS["Results<br/>search_method: hybrid|semantic|keyword<br/>score + filtered_by_score + content_length"]
258
317
  ```
259
318
 
260
319
  ### Document Ingestion Flow
@@ -331,6 +390,7 @@ flowchart LR
331
390
 
332
391
  - Python 3.11+
333
392
  - Claude Code CLI
393
+ - *…or any other MCP client (Claude Desktop, Cursor, VS Code, Antigravity, opencode, Windsurf) — see [Use with other MCP clients](#use-with-other-mcp-clients)*
334
394
  - ~200MB disk for model cache (auto-downloaded on first run)
335
395
  - *Optional:* NVIDIA GPU + CUDA for accelerated embeddings (`pip install knowledge-rag[gpu]` + `models.embedding.gpu: true` in config)
336
396
 
@@ -446,6 +506,94 @@ Add to `~/.claude.json`:
446
506
  > Replace `YOUR_USER` with your username, or use the full path from `echo $HOME`.
447
507
  </details>
448
508
 
509
+ #### Option F: SSE Server Mode (multi-agent)
510
+
511
+ For multi-agent setups where multiple clients query the same knowledge base simultaneously:
512
+
513
+ ```bash
514
+ pip install knowledge-rag[server] # Adds uvicorn for SSE/HTTP
515
+ knowledge-rag --transport sse # Starts on http://127.0.0.1:8179
516
+ ```
517
+
518
+ Then configure each MCP client to connect via SSE:
519
+
520
+ ```json
521
+ {
522
+ "mcpServers": {
523
+ "knowledge-rag": {
524
+ "type": "sse",
525
+ "url": "http://127.0.0.1:8179/sse"
526
+ }
527
+ }
528
+ }
529
+ ```
530
+
531
+ One server process serves all agents — shared embedding model, shared cache, shared ChromaDB. See [Configuration > Server](#server) for rate limiting, metrics, and auth options.
532
+
533
+ ### Use with other MCP clients
534
+
535
+ `knowledge-rag` supports both **stdio** (default, 1:1) and **SSE** (1:N) transport modes. In stdio mode, it works with any MCP-compatible client, not only Claude Code. The launch command is the same everywhere (the `python -m mcp_server.server` from whichever install method you picked); only the **config file location** and **JSON shape** differ per client.
536
+
537
+ #### Clients using the standard `mcpServers` format
538
+
539
+ For **Claude Desktop, Cursor, Antigravity, and Windsurf**, use the same block — only the file location changes:
540
+
541
+ ```json
542
+ {
543
+ "mcpServers": {
544
+ "knowledge-rag": {
545
+ "command": "/home/YOUR_USER/knowledge-rag/venv/bin/python",
546
+ "args": ["-m", "mcp_server.server"]
547
+ }
548
+ }
549
+ }
550
+ ```
551
+
552
+ > **Windows**: set `command` to the full path of `venv\Scripts\python.exe`.
553
+
554
+ | Client | Config file | Notes |
555
+ |---|---|---|
556
+ | **Claude Code** | use `claude mcp add …` (see install methods above) | The CLI writes `~/.claude.json` for you — manual edits to it aren't reliably picked up. |
557
+ | **Claude Desktop** | macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` · Windows: `%APPDATA%\Claude\claude_desktop_config.json` | Easiest: **Settings → Developer → Edit Config** opens the correct file (avoids the Windows Store/MSIX path quirk). |
558
+ | **Cursor** | `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (per project) | — |
559
+ | **Antigravity** | macOS/Linux: `~/.gemini/antigravity/mcp_config.json` · Windows: `%USERPROFILE%\.gemini\antigravity\mcp_config.json` | Open via Agent panel → **"…" → Manage MCP Servers → View raw config**. |
560
+ | **Windsurf** | `~/.codeium/windsurf/mcp_config.json` (global only) | Easiest: Cascade panel → MCP → **View raw config**. |
561
+
562
+ #### VS Code — uses a `servers` key
563
+
564
+ VS Code (Copilot MCP) nests servers under **`servers`**, not `mcpServers`. Put this in `.vscode/mcp.json` (workspace) or the file opened by the **MCP: Open User Configuration** command:
565
+
566
+ ```json
567
+ {
568
+ "servers": {
569
+ "knowledge-rag": {
570
+ "type": "stdio",
571
+ "command": "/home/YOUR_USER/knowledge-rag/venv/bin/python",
572
+ "args": ["-m", "mcp_server.server"]
573
+ }
574
+ }
575
+ }
576
+ ```
577
+
578
+ #### opencode — uses an `mcp` key
579
+
580
+ opencode nests servers under **`mcp`**, takes `command` as a single **array**, and uses `environment` instead of `env`. Put this in `opencode.json` (project root) or `~/.config/opencode/opencode.json` (global):
581
+
582
+ ```jsonc
583
+ {
584
+ "$schema": "https://opencode.ai/config.json",
585
+ "mcp": {
586
+ "knowledge-rag": {
587
+ "type": "local",
588
+ "command": ["/home/YOUR_USER/knowledge-rag/venv/bin/python", "-m", "mcp_server.server"],
589
+ "enabled": true
590
+ }
591
+ }
592
+ }
593
+ ```
594
+
595
+ > **Any other MCP client**: point it at the same command + args (`…/venv/bin/python -m mcp_server.server`). If it speaks stdio MCP, knowledge-rag works — only the config file's location and key naming differ. Check your client's docs for the exact path.
596
+
449
597
  ### Verify
450
598
 
451
599
  ```bash
@@ -554,6 +702,8 @@ Hybrid search combining semantic search + BM25 keyword search with cross-encoder
554
702
  | `max_results` | int | 5 | Maximum results to return (1-20) |
555
703
  | `category` | string | null | Filter by category |
556
704
  | `hybrid_alpha` | float | 0.3 | Balance: 0.0 = keyword only, 1.0 = semantic only |
705
+ | `min_score` | float | 0.0 | Minimum relevance score (0.0-1.0) to include a result. Use 0.2-0.4 to cut noise |
706
+ | `snippet_mode` | bool | true | Truncate content to ~500 chars at natural break points. Adds `content_length` field |
557
707
 
558
708
  **Returns:**
559
709
 
@@ -563,6 +713,7 @@ Hybrid search combining semantic search + BM25 keyword search with cross-encoder
563
713
  "query": "mimikatz credential dump",
564
714
  "hybrid_alpha": 0.5,
565
715
  "result_count": 3,
716
+ "filtered_by_score": 2,
566
717
  "cache_hit_rate": "0.0%",
567
718
  "results": [
568
719
  {
@@ -660,7 +811,6 @@ Get statistics about the knowledge base index.
660
811
  "stats": {
661
812
  "total_documents": 75,
662
813
  "total_chunks": 9256,
663
- "unique_content_hashes": 9100,
664
814
  "categories": {"security": 52, "development": 8},
665
815
  "supported_formats": [".md", ".txt", ".pdf", ".py", ".json", ".docx", ".xlsx", ".pptx", ".csv", ".ipynb"],
666
816
  "embedding_model": "BAAI/bge-small-en-v1.5",
@@ -842,6 +992,21 @@ query_expansions:
842
992
  privesc:
843
993
  - privilege escalation
844
994
  - privesc
995
+
996
+ # Server — enterprise features (new in v4.0.0)
997
+ server:
998
+ transport: "stdio" # "stdio" | "sse" | "streamable-http"
999
+ host: "127.0.0.1" # Bind address (SSE/HTTP only)
1000
+ port: 8179 # Bind port (SSE/HTTP only)
1001
+ auth:
1002
+ bearer_token: "" # Set a secret to enable auth (SSE/HTTP only)
1003
+ rate_limit:
1004
+ enabled: false
1005
+ requests_per_minute: 60
1006
+ burst: 10
1007
+ metrics:
1008
+ enabled: false
1009
+ port: 9179 # Separate port for Prometheus scraping
845
1010
  ```
846
1011
 
847
1012
  > See `config.example.yaml` for the fully documented template with explanations for every field.
@@ -861,6 +1026,22 @@ Pre-built configurations for common use cases:
861
1026
 
862
1027
  ### Configuration Reference
863
1028
 
1029
+ #### Server
1030
+
1031
+ | Field | Default | Description |
1032
+ |-------|---------|-------------|
1033
+ | `server.transport` | `"stdio"` | Transport protocol: `"stdio"`, `"sse"`, or `"streamable-http"` |
1034
+ | `server.host` | `"127.0.0.1"` | Bind address for SSE/HTTP mode |
1035
+ | `server.port` | `8179` | Bind port for SSE/HTTP mode |
1036
+ | `server.auth.bearer_token` | `""` (disabled) | Bearer token for SSE/HTTP auth. Empty = no auth |
1037
+ | `server.rate_limit.enabled` | `false` | Enable per-client rate limiting |
1038
+ | `server.rate_limit.requests_per_minute` | `60` | Max requests per minute |
1039
+ | `server.rate_limit.burst` | `10` | Burst allowance above steady rate |
1040
+ | `server.metrics.enabled` | `false` | Enable Prometheus `/metrics` endpoint |
1041
+ | `server.metrics.port` | `9179` | Port for metrics scraping |
1042
+
1043
+ In stdio mode (default), server settings are ignored. SSE/HTTP mode auto-enables the single-instance lock.
1044
+
864
1045
  #### Paths
865
1046
 
866
1047
  | Field | Default | Description |
@@ -957,6 +1138,22 @@ query_expansions:
957
1138
 
958
1139
  Set `query_expansions: {}` for no expansion.
959
1140
 
1141
+ `query_expansions` is directional: only the key on the left triggers the terms on the right. If you need mutual expansion without duplicating entries, use `query_expansion_groups`.
1142
+
1143
+ ```yaml
1144
+ query_expansion_groups:
1145
+ - ["triple barrier", "tb", "trip_barr"]
1146
+ - ["profit factor", "pf"]
1147
+ ```
1148
+
1149
+ Each group is interpreted symmetrically, so every term expands to the rest of the group. The final internal expansion table is built by merging both sources:
1150
+
1151
+ 1. `query_expansions` entries are loaded as-is.
1152
+ 2. `query_expansion_groups` adds reciprocal links for every term in each group.
1153
+ 3. Overlaps are merged by union with duplicate terms removed.
1154
+
1155
+ This keeps backward compatibility while allowing concise synonym groups.
1156
+
960
1157
  ### Hybrid Search Tuning
961
1158
 
962
1159
  | hybrid_alpha | Behavior | Best For |
@@ -1098,10 +1295,59 @@ export KNOWLEDGE_RAG_SINGLE_INSTANCE=1
1098
1295
 
1099
1296
  A second instance exits immediately with code 75. Default is OFF (multi-client friendly). Full guide: [docs/single-instance.md](docs/single-instance.md). Sample MCP config: [examples/mcp-config-single-instance.json](examples/mcp-config-single-instance.json).
1100
1297
 
1298
+ ### SSE server won't start
1299
+
1300
+ ```bash
1301
+ # Check if port 8179 is already in use
1302
+ # Windows:
1303
+ netstat -aon | findstr :8179
1304
+ # Linux/macOS:
1305
+ lsof -i :8179
1306
+ ```
1307
+
1308
+ If `uvicorn` is not found, install the server extras: `pip install knowledge-rag[server]`
1309
+
1310
+ ### Can't connect to SSE server
1311
+
1312
+ Verify the server is running and the URL is correct:
1313
+
1314
+ ```bash
1315
+ curl http://127.0.0.1:8179/sse
1316
+ ```
1317
+
1318
+ Common issues:
1319
+ - Wrong URL: must end with `/sse` (not just the port)
1320
+ - Firewall blocking the port
1321
+ - Server started with a different host/port than configured in the MCP client
1322
+
1101
1323
  ---
1102
1324
 
1103
1325
  ## Changelog
1104
1326
 
1327
+ ### v4.0.0 (2026-06-09) — Enterprise Concurrent Access
1328
+
1329
+ - **NEW**: SSE and streamable-http transport modes — 1 server serves N clients (`server.transport: "sse"` in config.yaml or `--transport sse` CLI).
1330
+ - **NEW**: Thread-safe shared state for concurrent queries — QueryCache locking, BM25 build lock, orchestrator double-checked locking.
1331
+ - **NEW**: ChromaDB WAL mode enabled automatically in SSE/HTTP mode for concurrent read performance.
1332
+ - **NEW**: Optional rate limiting — sliding-window counter, configurable RPM and burst, disabled by default.
1333
+ - **NEW**: Optional Prometheus metrics endpoint — tool call counts, latency histograms, separate port, disabled by default.
1334
+ - **NEW**: All 12 MCP tools instrumented with `@rate_limited` and `@instrument` decorators (zero-cost when disabled).
1335
+ - **NEW**: `--transport` CLI override for Docker/systemd deployments.
1336
+ - **NEW**: `pip install knowledge-rag[server]` optional dependency for SSE/HTTP (uvicorn).
1337
+ - **CHANGED**: SSE/HTTP mode auto-enables single-instance lock (port collision prevention).
1338
+ - **CHANGED**: `mcp` dependency bumped to `>=1.6.0` (SSE/streamable-http support).
1339
+ - **MIGRATION**: Default transport remains `stdio` — existing users need zero changes. See config.example.yaml for SSE setup.
1340
+
1341
+ ### v3.9.1 (2026-06-08)
1342
+
1343
+ - **FIX**: Expand `~` in `config.yaml` path values (`documents_dir`, `data_dir`, `models_cache_dir`) via `expanduser()` on all platforms (#86).
1344
+ - **FIX**: Warn when `documents_dir` resolves to a non-existent path instead of silently indexing zero files.
1345
+ - **FIX**: File watcher now uses accumulate-mode debounce — bulk file copies no longer starve the reindex trigger.
1346
+ - **FIX**: Concurrent `index_all()` calls are serialized via `_index_lock` to prevent ChromaDB SQLite corruption.
1347
+ - **FIX**: `collection.add()` is batched (500 chunks/call) to cap memory usage during large reindex operations.
1348
+ - **NEW**: `KNOWLEDGE_RAG_WATCHER_DISABLED=1` env var to disable the file watcher for troubleshooting.
1349
+ - **NEW**: Progress logging every 10% for reindex operations with >100 documents.
1350
+
1105
1351
  ### v3.9.0 (2026-05-10) — Quality Gate
1106
1352
 
1107
1353
  **Major governance + CI hardening release. No runtime behavior change in `mcp_server/`. Public API surface unchanged from v3.8.1.**
@@ -1145,6 +1391,38 @@ A second instance exits immediately with code 75. Default is OFF (multi-client f
1145
1391
 
1146
1392
  ### Unreleased
1147
1393
 
1394
+ ### v4.2.0 (2026-06-17) — Search Performance & Output Quality
1395
+
1396
+ - **PERF**: Custom inverted-index BM25 replaces `rank-bm25` full-corpus scan — 128× faster keyword search on 50K+ chunk corpora. Only documents containing query terms are scored via posting lists.
1397
+ - **PERF**: `numpy.argpartition` for O(n) top-k selection instead of O(n log n) sort.
1398
+ - **PERF**: Batched adjacent chunk fetch — single ChromaDB `collection.get()` call replaces N round-trips per result.
1399
+ - **PERF**: O(1) reverse lookup via `_source_to_docid` dict eliminates linear scans of `_indexed_docs` in `search_similar`, `update_document`, `remove_document`, and `_expand_with_adjacent_chunks`.
1400
+ - **NEW**: `snippet_mode` parameter on `search_knowledge` (default: `true`) — truncates content to ~500 chars at natural break points with `content_length` field. Reduces token consumption by ~72%.
1401
+ - **NEW**: `min_score` parameter on `search_knowledge` (default: `0.0`) — filters results below a normalized relevance threshold. Response includes `filtered_by_score` count.
1402
+ - **NEW**: `filtered_by_score` field in search response JSON for transparency.
1403
+ - **DEPS**: `numpy` added as direct dependency (was transitive via fastembed); `rank-bm25` import removed from server.py.
1404
+ - **TEST**: 6 new tests for `min_score` filtering and `snippet_mode` truncation.
1405
+ - **TEST**: Updated backwards-compat baseline to include new `search_knowledge` parameters.
1406
+
1407
+ ### v4.1.2 (2026-06-17)
1408
+
1409
+ - **FIX**: `_save_metadata` dict snapshot prevents concurrent modification crash during file watcher events.
1410
+ - **STYLE**: ruff format applied to server.py.
1411
+
1412
+ ### v4.1.1 (2026-06-17)
1413
+
1414
+ - **FIX**: All `_indexed_docs` iterations now use `list()` snapshot, preventing `dictionary changed size during iteration` crash when FileWatcher modifies the index concurrently with MCP tool calls (affects `search_knowledge`, `search_similar`, `update_document`, `remove_document`, `evaluate_retrieval`, `list_categories`, `list_documents`)
1415
+
1416
+ ### v4.1.0 (2026-06-17)
1417
+
1418
+ - **Added:** `query_expansion_groups` config for symmetric synonym expansion (#92)
1419
+ - **Improved:** `expand_query()` now returns deterministic expansion order (set → ordered list with dedup)
1420
+
1421
+ ### v4.0.1 (2026-06-16)
1422
+
1423
+ - **FIX**: Orphan cleanup now runs before indexing loop, preventing chunk loss when files are moved (#90).
1424
+ - **FIX**: Chunk deduplication is now per-document instead of global, preventing cross-document chunk deletion (#91).
1425
+ - **FIX**: Added `on_moved` handler to `DocumentWatcher` for proper file move detection.
1148
1426
  - **FIX**: Startup preflight probes ChromaDB in a child process and moves crashing persistent indexes to `data/backups/auto-repair-*` before MCP initialization.
1149
1427
  - **FIX**: Reranker load failures now fall back to RRF ordering instead of failing `search_knowledge` on offline machines.
1150
1428
  - **FIX**: Virtualenv project-root detection now handles Python symlinks that resolve to the system interpreter.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knowledge-rag",
3
- "version": "3.9.0",
3
+ "version": "4.2.0",
4
4
  "description": "Local RAG System for Claude Code — Hybrid search + Cross-encoder Reranking + 12 MCP Tools + 20 Format Parsers. Zero external servers.",
5
5
  "bin": {
6
6
  "knowledge-rag": "./bin/cli.js"