ko-qmd 2.8.3-ko.3 → 2.8.3-ko.5

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,174 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.8.3-ko.5] - 2026-09-24
6
+
7
+ Rerank gets an input cap. Truncating each document to 128 tokens keeps KB
8
+ goldset quality (Hit@5 0.97 / Recall@5 0.77) while cutting cold rerank p90 from
9
+ 3.6s to 0.9s on an M3 Max. The cap can be set daemon-wide
10
+ (`QMD_RERANK_MAX_DOC_TOKENS`) or per request (`rerankMaxDocTokens` on REST
11
+ `/query` and SDK `search()`). The README is now a Korean guide to ko-qmd itself,
12
+ and the English reference lives in `docs/REFERENCE.md`.
13
+
14
+ ### Changes
15
+
16
+ - README.md is rewritten in Korean around ko-qmd itself: what differs from
17
+ upstream, install, quick start, MCP/REST/SDK, environment variables and the
18
+ maintenance guide that used to live in `README.ko.md` (now removed). The
19
+ upstream-derived English reference (full CLI, MCP tool parameters, SDK API,
20
+ score fusion) moved to `docs/REFERENCE.md`, with install and import names
21
+ switched to `ko-qmd`.
22
+ - REST `POST /query` and SDK `search()` take `rerankMaxDocTokens`, a
23
+ per-request rerank doc token cap. For that request it overrides the daemon's
24
+ `QMD_RERANK_MAX_DOC_TOKENS`, and it never raises the context budget. A caller
25
+ like the KB seam can then turn a capped rerank on for one machine without
26
+ changing the daemon's env. The rerank cache key follows the effective cap
27
+ (request, else env). Invalid values (non-integer, ≤ 0, strings) are ignored.
28
+ - `QMD_RERANK_MAX_DOC_TOKENS` caps the tokens of each document sent to the
29
+ reranker, below the context budget. Unset keeps today's behaviour. On the KB
30
+ goldset (40 queries, candidateLimit 15), a cap of 128 keeps quality at
31
+ Hit@5 0.97 / Recall@5 0.77 against 0.95 / 0.76 uncapped. It cuts cold p90 from
32
+ 3623ms to 897ms on an M3 Max and from 4720ms to 2192ms on another Apple Silicon
33
+ Mac. The cap is part of the rerank cache key, so capped and uncapped scores
34
+ never mix. Listed in `qmd doctor`.
35
+ - `scripts/dogfood.sh` warms the embedding model with one vec query after
36
+ every daemon restart (deploy and `--restore`). A fresh daemon's first vec
37
+ query took 15.5s, so the first KB seam search after a restart hit its 20s
38
+ limit and fell back to rg. A failed warm-up is a warning, not a failed restart.
39
+ - `scripts/dogfood.sh` waits for the *restarted* daemon. `launchctl kickstart -k`
40
+ returns while the old process is still shutting down and answering `/health`,
41
+ so the restart looked done ~9s before the new process listened. A `/health`
42
+ whose `uptime` predates the kickstart is now treated as the old process.
43
+
44
+ ## [2.8.3-ko.4] - 2026-09-24
45
+
46
+ Korean documents are now chunked at their real token budget, and Hangul
47
+ spellings of technical loanwords (`서치`, `웹 코어`) reach notes written in Latin
48
+ script. This release also syncs tobi/qmd up to `04e4dbd` — metadata filtering,
49
+ the oxlint fence and the REST JSON body guard — and adds `qmd grep`, `path`/
50
+ `since`/`until` scoping on MCP and REST, and a daemon that keeps the text index
51
+ fresh on its own. Existing indexes need one `qmd embed` run to pick up the new
52
+ chunking, and SDK callers that passed `filter` for path/time scoping must rename
53
+ it to `scope`.
54
+
55
+ ### Breaking
56
+
57
+ - SDK: ko-qmd's path/time narrowing option is now `scope` (was `filter`) on
58
+ `search()`, `searchLex()` and `searchVector()`, and the trailing `scope`
59
+ parameter of `searchFTS`/`searchVec`. `filter` is upstream's metadata filter.
60
+ CLI `--path/--since/--until` and REST/MCP `path/since/until` are unchanged,
61
+ and a request may pass both — a result has to satisfy each.
62
+
63
+ ### Upstream sync
64
+
65
+ - Merged tobi/qmd `main` up to `04e4dbd` (24 commits past v2.8.3): oxlint
66
+ fence (`bun run lint` is oxlint; the tsc check is `bun run test:types`),
67
+ metadata filtering, separator-aware FTS terms (quoted `"PIO-1384"` now
68
+ matches), REST JSON body guard, Windows index names, unreadable-gguf
69
+ diagnostics and the store-local embedding tokenizer. ko-qmd behaviour is
70
+ kept: the Hangul query branch runs ahead of the separator split, a metadata
71
+ filter gets the same anti-starvation scan as `--path`, and bm25 on the
72
+ ko-vault bench is identical per query.
73
+
74
+ ### Added
75
+
76
+ - Added Oxlint lint fence.
77
+ - Document metadata and metadata filtering. Markdown documents can opt into typed metadata through a namespaced frontmatter block (`qmd.metadata` with strings, numbers, booleans, or flat homogeneous arrays), and every search surface — CLI `search`/`vsearch`/`query` via `--filter <json>`, the SDK's `filter` option on `search()`/`searchLex()`/`searchVector()`, the MCP `query` tool, and HTTP `POST /query` and `/search` — accepts one shared recursive filter AST discriminated by `operator`: `and`/`or`/`not` logical groups, `eq`/`ne`/`gt`/`gte`/`lt`/`lte` comparisons, `in`/`nin`/`all` membership, and `exists` presence. Every returned result satisfies the filter (applied before RRF fusion and reranking); like collection filtering, highly selective filters remain best-effort for top-K completeness. Frontmatter stays ordinary searchable content — no chunking, embedding, snippet, or line-number changes — and documents without `qmd.metadata` behave exactly as before. JSON/SDK/MCP/HTTP results now include each document's indexed metadata, and `qmd status` reports how many documents still need metadata extraction (a normal `qmd update` backfills existing indexes).
78
+
79
+ ### Fixed
80
+
81
+ - Embedding generation and legacy fingerprint adoption now tokenize documents
82
+ with the store-selected embedding model instead of the global default. This
83
+ keeps chunk boundaries aligned with the model that creates and verifies the
84
+ stored vectors without initializing an unrelated provider.
85
+
86
+ ### Changes
87
+
88
+ - Plain Hangul lex terms that spell a common technical loanword (`서치`, `웹`,
89
+ `코어`, `마이그레이션`, about 80 entries in a built-in table) also match the
90
+ Latin spelling, so `하이브리드 서치` finds a note that only says
91
+ `hybrid-search` and `레몬 웹 코어` reaches `lemon-web-core`. The lookup runs
92
+ on particle-stripped stems too (`서치를`), single-syllable entries (`웹`, `훅`)
93
+ are bridged when they stand alone (not `웹을`), and terms with no entry produce the same FTS5 query as before.
94
+ Query-side only; no re-index. On the ko-vault bench the eleven new loanword
95
+ queries go from bm25_r5 0.0000 to 1.0000 while the original 52 hold at
96
+ 0.9519 per query.
97
+ - REST `POST /query` and the MCP `query` tool attach `vec_score` (cosine
98
+ similarity) and `fts_score` (normalized BM25) to each result on the
99
+ `rerank:false` path, where `score` is the 1/rank fusion position and cannot
100
+ be thresholded. Each is the max over the sub-queries that returned the hit
101
+ and is omitted, never 0, when that backend did not return it. The daemon
102
+ query log (`QMD_QUERY_LOG`) records the same two fields per result; the row
103
+ schema stays `v: 1` and `rerank:true` responses are unchanged.
104
+ - Korean documents are chunked at their real token budget instead of the
105
+ English-tuned 3.0 chars/token: `estimateCharsPerToken` blends CJK and other
106
+ scripts as a harmonic mean (measured ko 1.64 / en 6.08 chars per token on
107
+ Qwen3-Embedding-0.6B-Q8_0, 2026-09-21), and indexing plus both search-path
108
+ best-chunk selections size chunks with it. A 20KB Korean document now
109
+ first-passes into ~900-token chunks instead of being split ~855 and Korean
110
+ rerank chunks shrink from ~3600 to ~1440 chars. Non-CJK text keeps the ratio
111
+ each call site already used — 3.0 when indexing, 4.0 on the search paths — so
112
+ English chunk boundaries do not move, and the ko-vault bench holds
113
+ (bm25_r5=0.9519).
114
+ - Re-embedding a document whose chunk count shrank now prunes its stale
115
+ higher-`seq` vectors before inserting, so one `qmd embed` run converges
116
+ instead of waiting for a second run. The embedding fingerprint gains a
117
+ `chunk_chars_estimator` term, so `qmd status` reports re-embedding as needed
118
+ for existing indexes; run `qmd embed` once to pick up the new chunking.
119
+
120
+ - `search`, `vsearch` and `query` take `--path`, `--since` and `--until`, so a
121
+ search can be narrowed to part of a collection or to what changed recently.
122
+ `--path` matches globs against `collection/path` and is repeatable, with a
123
+ `!` prefix to exclude; `--since` / `--until` take a span (`7d`) or a date.
124
+ The filter narrows the corpus rather than the result list — excluded
125
+ documents are gone before ranking, on the vector path as well as BM25, so a
126
+ narrow filter returns its own best matches instead of whatever survived the
127
+ global top-K. A span that does not parse is an error, and a filter that
128
+ leaves nothing to search says so instead of looking like "no match".
129
+
130
+ - `qmd query --explain` now lists every expansion sub-query that placed the
131
+ document, with that sub-query's own rank, contribution, backend score and the
132
+ text it ran. It previously showed the top three contributions collapsed onto
133
+ one line without the query text, which could not distinguish a document that
134
+ ranked first in one sub-query and nowhere else from one that ranked middling
135
+ everywhere — two situations that call for different fixes.
136
+
137
+ - `qmd grep <pattern>` matches document bodies exactly, by string or regular
138
+ expression, with no ranking and no LLM: every matching line, grouped by file,
139
+ with line numbers that `qmd get <file>:<n>:<count>` accepts. It is the escape
140
+ hatch for a query that finds nothing because tokenization never produced the
141
+ term the document contains — the failure this fork keeps hitting with Korean
142
+ text — and for finding an identifier or error string verbatim. It searches
143
+ exactly what the index holds and honours collection scoping, `--path`,
144
+ `--since` and `--until`. Smart case: an uppercase letter in the pattern makes
145
+ it case-sensitive, and Hangul has no case so Korean patterns are unaffected.
146
+ `-F` searches for a literal string. An MCP `grep` tool exposes the same thing
147
+ to agents, which is the reason it exists: a client whose only tools are
148
+ `query`, `get` and `multi_get` has no other way to match a string exactly.
149
+
150
+ - The MCP `query` tool and the REST `/query` endpoint take the same `path`,
151
+ `since` and `until` parameters, so an agent can scope a search to a folder or
152
+ to recent changes without shelling out. A span that does not parse comes back
153
+ as an error rather than an unfiltered search, and an empty result under a
154
+ filter reports how many documents the filter left — an agent reading a bare
155
+ "No results" would otherwise conclude the corpus has nothing on the topic.
156
+
157
+ - The HTTP and stdio MCP daemon keeps the text index current on its own. Before
158
+ each search it checks the collections that search covers — at most once every
159
+ 30 seconds each — and re-indexes the ones whose files changed, so a note
160
+ written while the daemon runs is findable on the next query without `qmd
161
+ update`. It updates keyword search only; vectors still wait for `qmd embed`.
162
+ It does not run a collection's `update:` hook, does not clear the LLM cache
163
+ the way `qmd update` does, and does not touch a collection whose folder has
164
+ vanished or emptied, since reindexing would deactivate every document in it.
165
+
166
+ - The daemon's query log now covers the MCP `query` tool, not just the REST
167
+ endpoints. A search an agent runs through MCP over HTTP lands in the same
168
+ `queries-YYYY-MM.jsonl` with `via: "mcp"`, the same result path spelling and
169
+ the same `X-QMD-*` header handling, so a dogfooding session's real traffic is
170
+ in one file. A plain `query` argument is recorded as one search of type
171
+ `auto`. stdio MCP calls stay unlogged — they carry no request to annotate.
172
+
5
173
  ## [2.8.3-ko.3] - 2026-09-18
6
174
 
7
175
  The reranker now decides which document ranks first. The blend's retrieval-position