@dikolab/kbdb 0.5.0 → 0.6.1

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 (224) hide show
  1. package/CLA.md +68 -0
  2. package/LICENSE +676 -12
  3. package/LICENSING.md +58 -0
  4. package/README.md +50 -25
  5. package/README.md.bak +322 -0
  6. package/dist/README.md +322 -0
  7. package/dist/{chunk-BJXEVALU.mjs → chunk-QLJ33C74.mjs} +44 -44
  8. package/dist/chunk-QLJ33C74.mjs.map +7 -0
  9. package/dist/cli.cjs +550 -232
  10. package/dist/cli.cjs.map +4 -4
  11. package/dist/cli.d.ts +0 -8
  12. package/dist/cli.mjs +483 -154
  13. package/dist/cli.mjs.map +4 -4
  14. package/dist/kbdb-worker.cjs +36 -5
  15. package/dist/kbdb-worker.cjs.map +3 -3
  16. package/dist/mod.cjs +89 -30
  17. package/dist/mod.cjs.map +4 -4
  18. package/dist/mod.mjs +88 -30
  19. package/dist/mod.mjs.map +4 -4
  20. package/dist/src/cli.d.ts +0 -8
  21. package/dist/src/shared/cli/constants/defaults.constant.d.ts +1 -1
  22. package/dist/src/shared/cli/constants/recfile-command-field-map.constant.d.ts +8 -0
  23. package/dist/src/shared/cli/constants/recfile-field-maps.constant.d.ts +21 -0
  24. package/dist/src/shared/cli/functions/compute-directory-level.function.d.ts +14 -0
  25. package/dist/src/shared/cli/functions/dispatch-command.function.d.ts +2 -28
  26. package/dist/src/shared/cli/functions/format-command-output.function.d.ts +14 -0
  27. package/dist/src/shared/cli/functions/format-db-not-found.function.d.ts +20 -0
  28. package/dist/src/shared/cli/functions/format-output.function.d.ts +1 -0
  29. package/dist/src/shared/cli/functions/format-recfile-output.function.d.ts +11 -0
  30. package/dist/src/shared/cli/functions/render-recfile-value.function.d.ts +11 -0
  31. package/dist/src/shared/cli/functions/resolve-db-dir.function.d.ts +24 -0
  32. package/dist/src/shared/cli/functions/resolve-recfile-key.function.d.ts +9 -0
  33. package/dist/src/shared/cli/functions/run-db-path.function.d.ts +10 -0
  34. package/dist/src/shared/cli/functions/run-export.function.d.ts +8 -4
  35. package/dist/src/shared/cli/functions/run-import.function.d.ts +4 -0
  36. package/dist/src/shared/cli/functions/run-learn.function.d.ts +12 -0
  37. package/dist/src/shared/cli/index.d.ts +4 -1
  38. package/dist/src/shared/cli/typings/cli-options.interface.d.ts +9 -2
  39. package/dist/src/shared/cli/typings/db-resolution.interface.d.ts +9 -0
  40. package/dist/src/shared/cli/typings/recfile-field-map.type.d.ts +10 -0
  41. package/dist/src/shared/version/constants/version.constant.d.ts +1 -1
  42. package/dist/src/shared/wasm-codec/functions/decode-option-u8.function.d.ts +9 -0
  43. package/dist/src/shared/wasm-codec/functions/decode-section-records.function.d.ts +4 -0
  44. package/dist/src/shared/wasm-codec/functions/encode-add-section-params.function.d.ts +2 -45
  45. package/dist/src/shared/wasm-codec/functions/encode-update-section-params.function.d.ts +10 -1
  46. package/dist/src/shared/wasm-codec/index.d.ts +1 -1
  47. package/dist/src/shared/wasm-codec/typings/add-section-input.model.d.ts +6 -0
  48. package/dist/src/shared/wasm-codec/typings/section-record-trailing.model.d.ts +5 -0
  49. package/dist/src/shared/wasm-codec/typings/section-record.model.d.ts +5 -0
  50. package/dist/src/shared/worker-client/functions/build-deno-permissions.function.d.ts +11 -0
  51. package/dist/src/shared/worker-client/functions/read-crash-reason.function.d.ts +9 -0
  52. package/dist/src/shared/worker-client/functions/resolve-spawn-script.function.d.ts +6 -0
  53. package/dist/src/shared/worker-client/functions/spawn-daemon.function.d.ts +1 -1
  54. package/dist/src/shared/worker-client/typings/section.model.d.ts +5 -0
  55. package/dist/src/shared/worker-client/typings/worker-client-options.interface.d.ts +0 -6
  56. package/dist/src/shared/worker-daemon/functions/count-files.function.d.ts +12 -0
  57. package/dist/src/shared/worker-daemon/functions/handle-import.function.d.ts +2 -0
  58. package/dist/src/shared/worker-daemon/functions/handle-read-ops.function.d.ts +8 -0
  59. package/dist/src/shared/worker-daemon/functions/validate-daemon-path.function.d.ts +15 -0
  60. package/dist/src/worker.d.ts +0 -7
  61. package/dist/wasm/default-embedding/kbdb_default_embedding_bg.wasm +0 -0
  62. package/dist/wasm/default-embedding/package.json +1 -1
  63. package/dist/wasm/fs-database/kbdb_fs_database_bg.wasm +0 -0
  64. package/dist/wasm/fs-database/package.json +1 -1
  65. package/dist/wasm/fs-migration/package.json +1 -1
  66. package/dist/wasm/kb-worker/kbdb_worker_bg.wasm +0 -0
  67. package/dist/wasm/kb-worker/package.json +1 -1
  68. package/dist/wasm/query-parser/kbdb_query_parser_bg.wasm +0 -0
  69. package/dist/wasm/query-parser/package.json +1 -1
  70. package/dist/worker.d.ts +0 -7
  71. package/dist/worker.mjs +37 -6
  72. package/dist/worker.mjs.map +3 -3
  73. package/package.json +11 -10
  74. package/dist/chunk-BJXEVALU.mjs.map +0 -7
  75. package/dist/src/shared/cli/functions/resolve-raw-db-path.function.d.ts +0 -10
  76. package/docs/details/README.md +0 -30
  77. package/docs/details/agent-tooling.md +0 -132
  78. package/docs/details/cli.md +0 -777
  79. package/docs/details/knowledge-base.md +0 -180
  80. package/docs/details/library-api.md +0 -495
  81. package/docs/details/mcp-server.md +0 -482
  82. package/docs/details/search-and-ranking.md +0 -575
  83. package/docs/details/storage.md +0 -531
  84. package/docs/goals/agents.md +0 -751
  85. package/docs/goals/architecture.svg +0 -198
  86. package/docs/goals/auto-capture.md +0 -378
  87. package/docs/goals/benchmarking.md +0 -296
  88. package/docs/goals/cli.md +0 -2654
  89. package/docs/goals/concurrency.md +0 -259
  90. package/docs/goals/content-composer.md +0 -609
  91. package/docs/goals/content-parser.md +0 -279
  92. package/docs/goals/database.md +0 -1679
  93. package/docs/goals/document.md +0 -368
  94. package/docs/goals/hybrid-search.md +0 -465
  95. package/docs/goals/mcp.md +0 -1541
  96. package/docs/goals/overview.md +0 -124
  97. package/docs/goals/query-parser.md +0 -373
  98. package/docs/goals/query-result.md +0 -860
  99. package/docs/goals/semantic-dedup.md +0 -233
  100. package/docs/goals/skills.md +0 -810
  101. package/docs/goals/sync.md +0 -217
  102. package/docs/goals/worker-client.md +0 -1005
  103. package/docs/goals/worker-daemon.md +0 -1547
  104. package/docs/modules/overview.md +0 -319
  105. package/docs/modules/rust/embedding/module.md +0 -91
  106. package/docs/modules/rust/fs-database/functions/document.md +0 -48
  107. package/docs/modules/rust/fs-database/functions/integrity.md +0 -79
  108. package/docs/modules/rust/fs-database/functions/io.md +0 -87
  109. package/docs/modules/rust/fs-database/functions/module.md +0 -22
  110. package/docs/modules/rust/fs-database/functions/query-exec.md +0 -44
  111. package/docs/modules/rust/fs-database/functions/section.md +0 -76
  112. package/docs/modules/rust/fs-database/indexes/btree.md +0 -35
  113. package/docs/modules/rust/fs-database/indexes/corpus.md +0 -61
  114. package/docs/modules/rust/fs-database/indexes/inverted.md +0 -60
  115. package/docs/modules/rust/fs-database/indexes/module.md +0 -64
  116. package/docs/modules/rust/fs-database/indexes/positional.md +0 -39
  117. package/docs/modules/rust/fs-database/indexes/vectors.md +0 -110
  118. package/docs/modules/rust/fs-database/module.md +0 -150
  119. package/docs/modules/rust/fs-database/types/catalog.md +0 -53
  120. package/docs/modules/rust/fs-database/types/module.md +0 -12
  121. package/docs/modules/rust/fs-database/types/section.md +0 -127
  122. package/docs/modules/rust/kb-worker/functions/compose.md +0 -57
  123. package/docs/modules/rust/kb-worker/functions/module.md +0 -20
  124. package/docs/modules/rust/kb-worker/functions/retrieve.md +0 -194
  125. package/docs/modules/rust/kb-worker/functions/search.md +0 -173
  126. package/docs/modules/rust/kb-worker/functions/write.md +0 -172
  127. package/docs/modules/rust/kb-worker/module.md +0 -171
  128. package/docs/modules/rust/kb-worker/types/cache.md +0 -90
  129. package/docs/modules/rust/kb-worker/types/context.md +0 -26
  130. package/docs/modules/rust/kb-worker/types/module.md +0 -16
  131. package/docs/modules/rust/kb-worker/types/status.md +0 -29
  132. package/docs/modules/rust/kb-worker/types/token.md +0 -50
  133. package/docs/modules/rust/overview.md +0 -531
  134. package/docs/modules/rust/query-parser/functions/extract.md +0 -56
  135. package/docs/modules/rust/query-parser/functions/module.md +0 -18
  136. package/docs/modules/rust/query-parser/functions/parse.md +0 -33
  137. package/docs/modules/rust/query-parser/functions/pipeline.md +0 -9
  138. package/docs/modules/rust/query-parser/functions/rank.md +0 -85
  139. package/docs/modules/rust/query-parser/module.md +0 -131
  140. package/docs/modules/rust/query-parser/types/cache.md +0 -39
  141. package/docs/modules/rust/query-parser/types/keyphrase.md +0 -71
  142. package/docs/modules/rust/query-parser/types/module.md +0 -18
  143. package/docs/modules/rust/query-parser/types/token.md +0 -46
  144. package/docs/modules/rust/shared/content-composer/functions.md +0 -114
  145. package/docs/modules/rust/shared/content-composer/module.md +0 -147
  146. package/docs/modules/rust/shared/content-composer/types.md +0 -93
  147. package/docs/modules/rust/shared/content-parser/functions.md +0 -72
  148. package/docs/modules/rust/shared/content-parser/module.md +0 -99
  149. package/docs/modules/rust/shared/content-parser/types.md +0 -71
  150. package/docs/modules/rust/shared/corpus/module.md +0 -24
  151. package/docs/modules/rust/shared/corpus/types.md +0 -141
  152. package/docs/modules/rust/shared/document/module.md +0 -25
  153. package/docs/modules/rust/shared/document/types.md +0 -154
  154. package/docs/modules/rust/shared/encode/module.md +0 -22
  155. package/docs/modules/rust/shared/encode/traits.md +0 -304
  156. package/docs/modules/rust/shared/error/module.md +0 -28
  157. package/docs/modules/rust/shared/error/types.md +0 -302
  158. package/docs/modules/rust/shared/kbid/module.md +0 -24
  159. package/docs/modules/rust/shared/kbid/types.md +0 -147
  160. package/docs/modules/rust/shared/memory/functions.md +0 -209
  161. package/docs/modules/rust/shared/memory/module.md +0 -24
  162. package/docs/modules/rust/shared/module.md +0 -196
  163. package/docs/modules/rust/shared/pipeline/functions.md +0 -141
  164. package/docs/modules/rust/shared/pipeline/module.md +0 -62
  165. package/docs/modules/rust/shared/pipeline/types.md +0 -43
  166. package/docs/modules/rust/shared/query/module.md +0 -27
  167. package/docs/modules/rust/shared/query/types.md +0 -236
  168. package/docs/modules/rust/shared/section/module.md +0 -25
  169. package/docs/modules/rust/shared/section/types.md +0 -294
  170. package/docs/modules/rust/shared/term/module.md +0 -25
  171. package/docs/modules/rust/shared/term/types.md +0 -139
  172. package/docs/modules/typescript/cli.md +0 -131
  173. package/docs/modules/typescript/kbdb-worker.md +0 -150
  174. package/docs/modules/typescript/overview.md +0 -400
  175. package/docs/modules/typescript/shared/auto-capture/module.md +0 -73
  176. package/docs/modules/typescript/shared/cli/constants.md +0 -23
  177. package/docs/modules/typescript/shared/cli/functions.md +0 -809
  178. package/docs/modules/typescript/shared/cli/module.md +0 -138
  179. package/docs/modules/typescript/shared/cli/typings.md +0 -197
  180. package/docs/modules/typescript/shared/embedding/functions.md +0 -200
  181. package/docs/modules/typescript/shared/embedding/module.md +0 -77
  182. package/docs/modules/typescript/shared/embedding/typings.md +0 -124
  183. package/docs/modules/typescript/shared/hash/functions.md +0 -20
  184. package/docs/modules/typescript/shared/hash/module.md +0 -21
  185. package/docs/modules/typescript/shared/log/constants.md +0 -82
  186. package/docs/modules/typescript/shared/log/functions.md +0 -131
  187. package/docs/modules/typescript/shared/log/module.md +0 -153
  188. package/docs/modules/typescript/shared/log/typings.md +0 -82
  189. package/docs/modules/typescript/shared/mcp/classes.md +0 -179
  190. package/docs/modules/typescript/shared/mcp/functions.md +0 -386
  191. package/docs/modules/typescript/shared/mcp/module.md +0 -160
  192. package/docs/modules/typescript/shared/mcp/typings.md +0 -529
  193. package/docs/modules/typescript/shared/module.md +0 -110
  194. package/docs/modules/typescript/shared/platform/functions.md +0 -42
  195. package/docs/modules/typescript/shared/platform/module.md +0 -25
  196. package/docs/modules/typescript/shared/runtime/functions.md +0 -26
  197. package/docs/modules/typescript/shared/runtime/module.md +0 -27
  198. package/docs/modules/typescript/shared/runtime/typings.md +0 -13
  199. package/docs/modules/typescript/shared/version/module.md +0 -27
  200. package/docs/modules/typescript/shared/wasm-codec/functions.md +0 -391
  201. package/docs/modules/typescript/shared/wasm-codec/module.md +0 -98
  202. package/docs/modules/typescript/shared/worker-client/classes.md +0 -264
  203. package/docs/modules/typescript/shared/worker-client/functions.md +0 -175
  204. package/docs/modules/typescript/shared/worker-client/module.md +0 -92
  205. package/docs/modules/typescript/shared/worker-client/typings.md +0 -511
  206. package/docs/modules/typescript/shared/worker-daemon/classes.md +0 -239
  207. package/docs/modules/typescript/shared/worker-daemon/constants.md +0 -37
  208. package/docs/modules/typescript/shared/worker-daemon/functions.md +0 -234
  209. package/docs/modules/typescript/shared/worker-daemon/module.md +0 -118
  210. package/docs/modules/typescript/shared/worker-daemon/typings.md +0 -132
  211. package/docs/overview.md +0 -191
  212. package/docs/release-notes/0.1.0.md +0 -189
  213. package/docs/release-notes/0.1.1.md +0 -46
  214. package/docs/release-notes/0.1.2.md +0 -38
  215. package/docs/release-notes/0.1.3.md +0 -42
  216. package/docs/release-notes/0.2.0.md +0 -147
  217. package/docs/release-notes/0.3.0.md +0 -89
  218. package/docs/release-notes/0.3.1.md +0 -121
  219. package/docs/release-notes/0.3.2.md +0 -64
  220. package/docs/release-notes/0.4.0.md +0 -80
  221. package/docs/release-notes/0.4.1.md +0 -149
  222. package/docs/release-notes/0.4.3.md +0 -60
  223. package/docs/release-notes/0.5.0.md +0 -41
  224. package/docs/release-notes/README.md +0 -16
@@ -1,860 +0,0 @@
1
- # Query Result -- Ranking and Ordering
2
-
3
- *How search results are scored, combined, ordered,
4
- and shaped into the three result modes.*
5
-
6
- This document is the authoritative specification for
7
- the ranking pipeline -- the path from raw index
8
- matches to ordered, scored results. It consolidates
9
- the algorithms, formulas, parameters, and data
10
- structures that determine result relevance. For the
11
- tokenization pipeline that produces the indexed terms,
12
- see [Query Parser](query-parser.md). For the storage
13
- engine that holds the indexes, see
14
- [Database](database.md).
15
-
16
- ## Ranking Pipeline Overview
17
-
18
- The full ranking path, from query string to final
19
- result set:
20
-
21
- ```
22
- query string
23
- -> tokenize (query-parser.wasm)
24
- -> parse query -> QueryPlan (query-parser.wasm)
25
- -> execute plan -> Vec<RawMatch> (fs-database.wasm)
26
- -> fetch CorpusStats (fs-database.wasm)
27
- -> score: BM25F (query-parser.wasm)
28
- -> score: proximity (query-parser.wasm)
29
- -> score: zone (query-parser.wasm)
30
- -> score: freshness (query-parser.wasm, optional)
31
- -> combine scores
32
- -> sort descending
33
- -> truncate to limit
34
- -> shape into result mode
35
- -> return
36
- ```
37
-
38
- `kb-worker.wasm` orchestrates the entire flow. It
39
- calls `query-parser.wasm` for tokenization, parsing,
40
- and scoring, and `fs-database.wasm` for index lookups
41
- and corpus statistics. Neither standalone module calls
42
- the other directly.
43
-
44
- ## Searchable Content Sources
45
-
46
- Not all section data contributes equally to search
47
- and ranking. Content is tokenized and pre-computed
48
- at section insertion time (not at query time) and
49
- stored in the section header and inverted index for
50
- fast retrieval. See [Database](database.md) for the
51
- storage format of token counts and per-field term
52
- frequencies.
53
-
54
- ### Text Content (`text/*` Sections)
55
-
56
- Sections with MIME type `text/*` (e.g.
57
- `text/markdown`, `text/typescript`,
58
- `text/javascript`) are the primary searchable
59
- content. Their text body is tokenized, stemmed, and
60
- indexed. During indexing, the system extracts and
61
- stores the per-field term frequencies, positions,
62
- and token counts needed for later search and
63
- ranking.
64
-
65
- Text content maps to the **body** field in BM25F
66
- (`FieldWeights.body`, default 1.0).
67
-
68
- ### Title Attribute (All Sections)
69
-
70
- Any section with a `title` attribute has its title
71
- tokenized and indexed alongside (or instead of) the
72
- body content. Title terms receive the **heading**
73
- field weight in BM25F (`FieldWeights.heading`,
74
- default 2.0) -- giving title terms **2x the ranking
75
- influence** compared to body text.
76
-
77
- For binary sections (e.g. `image/*`,
78
- `application/octet-stream`) that have no searchable
79
- text body, the `title` is **required** and serves as
80
- the primary searchable content. Without a title,
81
- binary sections are not searchable and cannot appear
82
- in ranked results.
83
-
84
- ### Description Attribute (Optional)
85
-
86
- Any section with a `description` attribute has its
87
- description tokenized and indexed. Description terms
88
- receive the **body** field weight in BM25F
89
- (`FieldWeights.body`, default 1.0) -- the same
90
- ranking weight as `text/*` section body content.
91
-
92
- Description is optional for all section types. When
93
- present, it provides additional searchable text that
94
- supplements the body content (for text sections) or
95
- the title (for binary sections).
96
-
97
- ### Binary Sections
98
-
99
- Sections with binary MIME types (e.g. `image/*`,
100
- `application/octet-stream`) contain non-textual data
101
- (base64-encoded) that cannot be tokenized or indexed
102
- directly. Their searchability relies entirely on
103
- metadata attributes:
104
-
105
- 1. **`title`** (required for searchability) -- indexed
106
- with heading weight (2.0x). A binary section
107
- without a title is stored but invisible to search.
108
- 2. **`description`** (optional) -- indexed with body
109
- weight (1.0x). Provides additional search surface
110
- for binary content.
111
-
112
- A well-titled image section can appear in search
113
- results ranked by its title relevance, even though
114
- its pixel data is not searchable.
115
-
116
- ### Code Sections
117
-
118
- Sections with code-related MIME types (e.g.
119
- `text/typescript`, `text/javascript`) have their
120
- source code tokenized with identifier splitting
121
- (camelCase, snake_case, PascalCase). Code content
122
- maps to the **code** field weight in BM25F
123
- (`FieldWeights.code`, default 1.5) -- giving code
124
- terms **1.5x the ranking influence** compared to
125
- plain body text.
126
-
127
- If a code section also has `title` and/or
128
- `description`, those are indexed with their
129
- respective weights (heading 2.0x, body 1.0x) in
130
- addition to the code body.
131
-
132
- ### Field Weight Mapping
133
-
134
- | Content source | BM25F field | Default weight |
135
- |-------------------------|-------------|----------------|
136
- | `title` attribute | `heading` | 2.0 |
137
- | `text/*` body content | `body` | 1.0 |
138
- | `description` attribute | `body` | 1.0 |
139
- | Code section body | `code` | 1.5 |
140
- | Binary content (raw) | -- | not indexed |
141
-
142
- ### Pre-Computed Data at Insertion Time
143
-
144
- When a section is added or updated, `kb-worker.wasm`
145
- calls `query-parser.wasm` to tokenize the content
146
- and compute the following data, which is then stored
147
- by `fs-database.wasm` for use at query time:
148
-
149
- - **Per-field term frequencies** -- for each term,
150
- the term frequency in each field (heading, body,
151
- code) is stored in the inverted index as separate
152
- values rather than a single aggregated weight. This
153
- lets BM25F apply field weights at query time,
154
- making them tunable without rebuilding indexes.
155
- - **Token counts** -- total and per-field token
156
- counts are stored in the section header. Used for
157
- BM25F length normalization and zone scoring.
158
- - **Term positions** -- zero-based token offsets
159
- stored in the positional index. Used for proximity
160
- and zone scoring.
161
- - **Corpus statistics updates** -- total sections,
162
- total terms, per-term document frequencies, and
163
- per-field average lengths are updated in
164
- `corpus.dat`.
165
-
166
- ## Raw Match (Pre-Ranking Data)
167
-
168
- A `RawMatch` is the data that comes out of
169
- `fs-database.wasm` after index lookups, before any
170
- scoring is applied:
171
-
172
- ```rust
173
- pub struct RawMatch {
174
- pub kbid: KbId,
175
- pub field_tfs: Vec<(Term, FieldTf)>,
176
- pub positions: Vec<(Term, Vec<u32>)>,
177
- pub token_count: u32,
178
- pub field_token_counts: FieldTokenCounts,
179
- }
180
-
181
- pub struct FieldTf {
182
- pub heading: f32,
183
- pub body: f32,
184
- pub code: f32,
185
- }
186
-
187
- pub struct FieldTokenCounts {
188
- pub heading: u32,
189
- pub body: u32,
190
- pub code: u32,
191
- }
192
- ```
193
-
194
- - `field_tfs` -- per-term, per-field term frequency
195
- values retrieved from the inverted index. Each
196
- entry contains the TF for the heading, body, and
197
- code fields separately.
198
- - `positions` -- zero-based token offsets per term,
199
- retrieved from the positional index. Fed to
200
- proximity and zone scoring.
201
- - `token_count` -- total tokens in the section,
202
- retrieved from the section header. Used for BM25F
203
- length normalization (`|d|`) and zone scoring (20%
204
- heading zone boundary).
205
- - `field_token_counts` -- per-field token counts,
206
- retrieved from the section header. Used for
207
- per-field BM25 length normalization.
208
-
209
- ## Corpus Statistics
210
-
211
- `CorpusStats` provides corpus-wide aggregates needed
212
- for IDF computation and length normalization:
213
-
214
- ```rust
215
- pub struct CorpusStats {
216
- pub total_sections: u32,
217
- pub total_terms: u32,
218
- pub avg_section_length: f32,
219
- pub avg_heading_length: f32,
220
- pub avg_body_length: f32,
221
- pub avg_code_length: f32,
222
- }
223
- ```
224
-
225
- - `total_sections` -- `N` in the IDF formula. Counts
226
- only sections with at least one indexed term
227
- (excludes binary sections with no title).
228
- - `avg_section_length` -- average total token count
229
- per indexed section. Used for overall BM25F length
230
- normalization.
231
- - `avg_heading_length` -- average heading field token
232
- count. Used for per-field BM25 normalization of
233
- title terms.
234
- - `avg_body_length` -- average body field token
235
- count. Used for per-field BM25 normalization of
236
- body and description terms.
237
- - `avg_code_length` -- average code field token
238
- count. Used for per-field BM25 normalization of
239
- code terms.
240
-
241
- Per-field averages enable accurate per-field BM25
242
- normalization -- a 3-word title is not penalized
243
- against a 500-word average body length.
244
-
245
- ## Scoring Functions
246
-
247
- ### BM25F (Primary Ranking)
248
-
249
- Field-Weighted Okapi BM25. The primary ranking
250
- function that produces the base relevance score.
251
-
252
- **Function signature:**
253
-
254
- ```rust
255
- pub fn score_bm25f(
256
- params: &BM25FParams,
257
- raw_match: &RawMatch,
258
- corpus: &CorpusStats,
259
- ) -> f32;
260
- ```
261
-
262
- **Parameters:**
263
-
264
- ```rust
265
- pub struct BM25FParams {
266
- pub k1: f32, // default 1.2
267
- pub b: f32, // default 0.75
268
- pub field_weights: FieldWeights,
269
- }
270
-
271
- pub struct FieldWeights {
272
- pub heading: f32, // default 2.0
273
- pub body: f32, // default 1.0
274
- pub code: f32, // default 1.5
275
- }
276
- ```
277
-
278
- **IDF formula:**
279
-
280
- ```
281
- idf(t) = ln((N - df(t) + 0.5) / (df(t) + 0.5) + 1)
282
- ```
283
-
284
- Where `N` = `corpus.total_sections` and `df(t)` =
285
- number of sections containing term `t`.
286
-
287
- **Per-field BM25 score:**
288
-
289
- For each field `f` (heading, body, code), the
290
- per-term score uses the field-specific TF and the
291
- field-specific average length:
292
-
293
- ```
294
- score_f(t, d) = idf(t)
295
- * (tf_f(t,d) * (k1 + 1))
296
- / (tf_f(t,d) + k1 * (1 - b + b * |d_f| / avgdl_f))
297
- ```
298
-
299
- Where:
300
- - `tf_f(t,d)` = term frequency of `t` in field `f`
301
- of section `d` (from `FieldTf`)
302
- - `|d_f|` = token count of field `f` in section `d`
303
- (from `FieldTokenCounts`)
304
- - `avgdl_f` = average token count of field `f`
305
- across the corpus (from `CorpusStats`)
306
-
307
- **Field weighting:**
308
-
309
- The final BM25F score for a term is the weighted sum
310
- of per-field scores:
311
-
312
- ```
313
- bm25f(t, d) = w_heading * score_heading(t, d)
314
- + w_body * score_body(t, d)
315
- + w_code * score_code(t, d)
316
- ```
317
-
318
- The total BM25F score for a section is the sum of
319
- `bm25f(t, d)` across all matched query terms.
320
-
321
- **Parameter explanations:**
322
-
323
- - `k1` (1.2) -- term saturation. Higher values let
324
- term frequency continue to increase the score;
325
- lower values flatten the curve faster.
326
- - `b` (0.75) -- length normalization. 0 = no length
327
- normalization; 1 = full normalization. At 0.75,
328
- longer sections are moderately penalized relative
329
- to average-length sections.
330
- - `field_weights` -- per-field multipliers. Because
331
- per-field TFs are stored separately in the inverted
332
- index, field weights can be tuned at query time
333
- without rebuilding indexes.
334
-
335
- ### Proximity Scoring (Supplementary)
336
-
337
- Boosts sections where query terms appear close
338
- together.
339
-
340
- **Function signature:**
341
-
342
- ```rust
343
- pub fn score_proximity(
344
- positions: &[(Term, Vec<u32>)],
345
- query_terms: &[Term],
346
- ) -> f32;
347
- ```
348
-
349
- **Algorithm:**
350
-
351
- 1. For each pair of query terms that both appear in
352
- the section, compute the minimum distance between
353
- any of their positions.
354
- 2. The proximity score is the inverse of the minimum
355
- span across all query terms.
356
- 3. Sections where all query terms appear within a
357
- small window get the highest boost.
358
- 4. Single-term queries receive a proximity score of 0
359
- (no boost).
360
-
361
- ### Zone Scoring (Supplementary)
362
-
363
- Structural weighting. Terms appearing in high-value
364
- positions within a section get boosted.
365
-
366
- **Function signature:**
367
-
368
- ```rust
369
- pub fn score_zone(
370
- positions: &[(Term, Vec<u32>)],
371
- section_length: u32,
372
- ) -> f32;
373
- ```
374
-
375
- **Algorithm:**
376
-
377
- - Terms in the first ~20% of a section (the "heading
378
- zone") receive higher weight than terms in the
379
- remaining body.
380
- - The heading zone boundary is computed as
381
- `0.2 * token_count`, using the pre-computed
382
- `token_count` from the section header.
383
- - The heading zone approximates headings and first
384
- paragraphs, which typically carry more topical
385
- signal.
386
- - The zone score is added to the combined score as a
387
- supplementary boost.
388
-
389
- ### Freshness Weighting (Optional)
390
-
391
- Time-decay boost based on section insertion
392
- timestamp.
393
-
394
- **Function signature:**
395
-
396
- ```rust
397
- pub fn score_freshness(
398
- created_at: u64,
399
- now: u64,
400
- half_life_days: f32,
401
- ) -> f32;
402
- ```
403
-
404
- **Algorithm:**
405
-
406
- - Exponential decay: newer sections score higher.
407
- - `half_life_days` controls how fast the boost
408
- decays. After one half-life, the freshness score
409
- is halved.
410
- - This is an **optional** signal -- it can be
411
- disabled by omitting the freshness weight from
412
- score combination.
413
-
414
- ## Score Combination
415
-
416
- The four individual scores are combined into a final
417
- ranking score:
418
-
419
- ```
420
- final_score = bm25f_score
421
- + proximity_weight * proximity_score
422
- + zone_weight * zone_score
423
- + freshness_weight * freshness_score
424
- ```
425
-
426
- BM25F is the primary signal. Proximity, zone, and
427
- freshness are supplementary signals added to the
428
- BM25F base. Their weights control how much influence
429
- each supplementary signal has relative to the primary
430
- BM25F score.
431
-
432
- Results are sorted by `final_score` in **descending
433
- order** (highest relevance first). Ties are broken
434
- by kbid lexicographic order for deterministic
435
- ordering.
436
-
437
- ## Score Normalization
438
-
439
- Raw BM25F scores vary in magnitude across queries and
440
- corpora, making them difficult to interpret in
441
- isolation. The ranking pipeline produces a
442
- **normalized confidence** score alongside the raw
443
- score:
444
-
445
- ```
446
- confidence = final_score / max_score_in_result_set
447
- ```
448
-
449
- - The top-ranked result always has `confidence = 1.0`.
450
- - A result with half the raw score of the top result
451
- has `confidence = 0.5`.
452
- - When the result set is empty, no normalization is
453
- needed.
454
-
455
- `confidence` is a relative measure within a single
456
- query's result set -- it does not compare across
457
- different queries. Its purpose is to let the caller
458
- distinguish strong matches from weak tail results
459
- without needing to understand BM25F score magnitudes.
460
-
461
- The CLI exposes `confidence` in `SearchDisplay` for
462
- human inspection. The MCP projection omits it --
463
- result ordering is the ranking signal for AI agents.
464
-
465
- ## Result Truncation and Pagination
466
-
467
- After sorting, results are windowed by `offset` and
468
- `limit`. The ranking pipeline scores and sorts the
469
- full result set, then skips the first `offset`
470
- entries and takes the next `limit` entries. Both
471
- parameters are optional: `offset` defaults to `0`,
472
- `limit` defaults to `20`.
473
-
474
- The result is a paged envelope, not a bare array:
475
-
476
- ```ts
477
- interface PagedSearchResult {
478
- items: SectionMatch[] | DocumentMatch[];
479
- total: number;
480
- offset: number;
481
- limit: number;
482
- has_more: boolean;
483
- relaxed: boolean;
484
- }
485
- ```
486
-
487
- | Field | Description |
488
- |------------|-----------------------------------|
489
- | `items` | The windowed result records |
490
- | `total` | Full match count before windowing |
491
- | `offset` | Echo of requested offset (or 0) |
492
- | `limit` | Echo of effective limit (or 20) |
493
- | `has_more` | `true` when `offset + items.length < total` |
494
- | `relaxed` | `true` when results came from relaxed-match fallback (see below) |
495
-
496
- `has_more` is a convenience boolean so the caller
497
- does not need to compute whether more pages exist.
498
- The AI agent can page forward by calling `search`
499
- again with `offset` incremented by `limit`.
500
-
501
- Stats mode uses the same envelope with
502
- `items: []` and aggregate data in a separate
503
- `stats` field alongside the paging fields.
504
-
505
- ## Result Modes
506
-
507
- Three result modes shape the ranked results
508
- differently.
509
-
510
- ### Sections Mode
511
-
512
- Returns individual `SectionMatch` records, each with
513
- its own score:
514
-
515
- ```ts
516
- interface SectionMatch {
517
- kbid: string;
518
- docids: string[];
519
- type: string;
520
- heading: string;
521
- score: number;
522
- confidence: number;
523
- snippet: string;
524
- matched_terms: string[];
525
- matched_fields: string[];
526
- created_at: string;
527
- }
528
- ```
529
-
530
- `heading` is `string | null` -- `null` when the
531
- section has no title. `confidence` is the normalized
532
- score (0.0--1.0) relative to the top result (see
533
- Score Normalization above). `matched_fields` is a
534
- subset of `["heading", "body", "code"]` indicating
535
- which fields contained term matches -- lets the
536
- caller see whether the match was in a title vs body
537
- text. `created_at` is the section's ISO 8601
538
- creation timestamp.
539
-
540
- `snippet` is always populated for text and code
541
- sections using KWIC
542
- (keyword-in-context): ~150 characters centered on
543
- the highest-scoring matched term position, with
544
- `...` ellipsis at boundaries. If the best match is
545
- in the heading (not body), the snippet is the first
546
- ~150 characters of the body content. For image
547
- sections and sections with no body content,
548
- `snippet` is an empty string.
549
-
550
- Sections are ordered by `score` descending. Each
551
- section appears at most once.
552
-
553
- ### Documents Mode
554
-
555
- Returns `DocumentMatch` records. Aggregates
556
- section-level scores into a document-level score:
557
-
558
- ```ts
559
- interface DocumentMatch {
560
- docid: string;
561
- title: string;
562
- score: number;
563
- matching_sections: Array<{
564
- kbid: string;
565
- type: string;
566
- heading: string;
567
- score: number;
568
- snippet: string;
569
- }>;
570
- }
571
- ```
572
-
573
- **Document score aggregation:** A document's score is
574
- the sum (or max, depending on config) of its matching
575
- sections' scores. Documents are ordered by their
576
- aggregated score descending. Within each document,
577
- matching sections are ordered by their individual
578
- scores descending.
579
-
580
- Sections not belonging to any document are returned
581
- as single-section documents.
582
-
583
- ### Stats Mode
584
-
585
- Returns aggregate statistics without content bodies:
586
-
587
- ```ts
588
- interface StatsSearchResult {
589
- total_matches: number;
590
- query_time_ms: number;
591
- top_terms: TermStat[];
592
- query: string;
593
- }
594
-
595
- interface TermStat {
596
- term: string;
597
- frequency: number;
598
- sections_matched: number;
599
- }
600
- ```
601
-
602
- No individual scores. `top_terms` are ordered by
603
- `frequency` descending.
604
-
605
- ## MCP Search Projection
606
-
607
- When search results are returned via the MCP
608
- `search` tool, the ranking engine produces full
609
- `SectionMatch` records but the MCP layer projects
610
- a subset of fields optimized for AI agent
611
- consumption:
612
-
613
- ```ts
614
- interface McpSearchResult {
615
- kbid: string;
616
- heading: string | null;
617
- type: string;
618
- docids: string[];
619
- snippet: string;
620
- matched_terms: string[];
621
- matched_fields: string[];
622
- created_at: string;
623
- content?: string;
624
- }
625
- ```
626
-
627
- The `score` and `confidence` fields from
628
- `SectionMatch` are intentionally omitted. Results
629
- are ordered by relevance descending; the position
630
- in the array is the ranking signal. Exposing
631
- uncalibrated BM25F scores provides no actionable
632
- information to the agent and risks confusing score
633
- magnitudes across different queries.
634
-
635
- `matched_fields` is included because it tells the
636
- agent whether the match was in a heading (strong
637
- topical signal) vs body text (weaker). `created_at`
638
- is included to let agents prefer recent results
639
- when multiple sections cover the same topic.
640
-
641
- `matched_terms` is a flat `string[]` of the query
642
- terms that matched -- no per-term frequency counts.
643
- The agent needs to know *which* terms matched, not
644
- *how often*.
645
-
646
- `heading` is `string | null`. When a section has
647
- no title, `heading` is `null` (not empty string).
648
- This lets the AI agent distinguish "no title" from
649
- "empty title."
650
-
651
- The `content` field is present only when the MCP
652
- search tool's `content` parameter is `true`. When
653
- present, it contains the full section text (or
654
- base64-encoded data for image sections).
655
-
656
- The CLI `search` command continues to expose
657
- `score` via the `SearchDisplay` type for human
658
- inspection and debugging.
659
-
660
- The MCP result uses the same `PagedSearchResult`
661
- envelope with `items`, `total`, `offset`, `limit`,
662
- `has_more`, and `relaxed`.
663
-
664
- ## Relaxed-Match Fallback
665
-
666
- When the primary query (implicit AND -- all terms
667
- required) returns **zero results**, the ranking
668
- engine automatically retries with relaxed matching:
669
-
670
- 1. **OR mode** -- any query term may match, not all
671
- required. The query plan is re-executed with OR
672
- semantics using the same tokenized terms.
673
-
674
- 2. Results from the relaxed retry are flagged:
675
- `relaxed: true` in the `PagedSearchResult`
676
- envelope. Normal (AND) results have
677
- `relaxed: false`.
678
-
679
- Relaxed mode is transparent to the caller -- no
680
- special syntax or parameters are needed. The engine
681
- tries AND first and falls back to OR only when AND
682
- produces zero matches. This converts a silent miss
683
- ("nothing found") into a soft hit ("weaker matches
684
- found") that the caller can reason about.
685
-
686
- The AI agent (or human user) can check the `relaxed`
687
- flag to know that the results are partial-term
688
- matches rather than full-term matches. Relaxed
689
- results typically have lower relevance -- the
690
- ordering and confidence scores still reflect relative
691
- quality within the relaxed set.
692
-
693
- When the primary AND query returns at least one
694
- result, no fallback is attempted. The `relaxed` flag
695
- is always `false` for non-empty AND results.
696
-
697
- ## References Resolution
698
-
699
- After ranking and truncation, the search flow
700
- resolves content marker references for the returned
701
- sections. Each result set includes a `references`
702
- array sourced from the bidirectional reference
703
- graph index (`references.idx`). Each reference
704
- entry includes:
705
-
706
- - `kbid` or `docid` of the referenced entity.
707
- - `marker_type` (Image, Code, Text, Document,
708
- Link, or DocLink).
709
- - `context_snippet` (~150 chars of the surrounding
710
- text where the marker appears).
711
- - `direction` (`forward` or `back`).
712
-
713
- Forward references come from markers in the
714
- returned sections' content. Back-references come
715
- from markers in *other* sections that point to
716
- the returned sections. Both directions are looked
717
- up from the reference graph index in O(1) rather
718
- than re-parsing content at query time. See
719
- [Database -- Reference Graph](database.md) for the
720
- index structure and [Content Parser](content-parser.md)
721
- for how markers are extracted at indexing time.
722
-
723
- ## Index Structures Supporting Ranking
724
-
725
- The ranking pipeline consumes data from three index
726
- structures maintained by `fs-database.wasm`:
727
-
728
- - **Inverted index** (`indexes/terms.idx`) -- maps
729
- normalized terms to posting lists of
730
- `(kbid, heading_tf, body_tf, code_tf)` tuples.
731
- Supports term lookup, IDF computation, and
732
- per-field term frequency retrieval.
733
- - **Positional index** (`indexes/positions.idx`) --
734
- maps `(term, kbid)` pairs to position arrays
735
- (zero-based token offsets). Supports proximity
736
- scoring, zone scoring, and phrase verification.
737
- - **Corpus statistics** (`indexes/corpus.dat`) --
738
- stores `total_sections`, `total_terms`,
739
- `avg_section_length`, `avg_heading_length`,
740
- `avg_body_length`, `avg_code_length`, and per-term
741
- document frequencies. Supports IDF and per-field
742
- length normalization.
743
-
744
- See [Database](database.md) for full index
745
- specifications.
746
-
747
- ## WASM Exports
748
-
749
- The query-parser WASM exports used for ranking:
750
-
751
- | Export | Role |
752
- |------------------------|--------------------------|
753
- | `qp_score_bm25f` | BM25F primary scoring |
754
- | `qp_score_proximity` | Proximity boost |
755
- | `qp_score_zone` | Zone/structural boost |
756
- | `qp_score_freshness` | Freshness decay boost |
757
-
758
- These are called by `kb-worker.wasm` after
759
- `fs-database.wasm` returns raw matches.
760
-
761
- ## Keyword Extraction and Term Weighting
762
-
763
- Terms are weighted during indexing -- the other side
764
- of the ranking coin:
765
-
766
- - **RAKE** (Rapid Automatic Keyword Extraction) --
767
- identifies candidate keyphrases by splitting on
768
- stop words, scores by word degree / frequency
769
- ratio.
770
- - **TF-IDF** -- per-section, per-field term frequency
771
- combined with corpus-wide inverse document
772
- frequency. Per-field TFs are stored in the inverted
773
- index as separate values per posting entry.
774
-
775
- See [Query Parser](query-parser.md) for the full
776
- tokenization pipeline and extraction algorithms.
777
-
778
- ## Content Authoring Best Practices
779
-
780
- ### How Field Weights Affect Searchability
781
-
782
- kbdb uses three weighted fields for ranking (see
783
- the [Field Weight Mapping](#field-weight-mapping)
784
- table above). Content authors can influence how
785
- well their content is found by understanding these
786
- weights:
787
-
788
- - **Title / heading text** carries **2.0x** the
789
- ranking weight of body text. A match in a heading
790
- has twice the influence on ranking as the same
791
- match in the body.
792
- - **Code blocks** carry **1.5x** weight. Code
793
- identifiers (function names, variable names, class
794
- names) are split on camelCase/snake_case boundaries
795
- and indexed as separate terms.
796
- - **Body text and descriptions** are the baseline at
797
- **1.0x**.
798
- - **Binary content** (images, attachments) is not
799
- indexed at all -- searchability depends entirely on
800
- the `title` (required) and `description` (optional)
801
- attributes.
802
-
803
- ### Writing Index-Friendly Headings
804
-
805
- The heading is the single highest-impact field for
806
- searchability. Follow these guidelines:
807
-
808
- - Use **descriptive, keyword-rich headings**. Prefer
809
- "Authentication and Login Flow" over just "Auth
810
- Flow" -- the expanded heading gives the search
811
- engine more term surface.
812
- - Include **synonyms and alternative phrasing** in
813
- headings when natural. If a section covers logging
814
- in, mention both "login" and "sign-in" if
815
- applicable.
816
- - Avoid generic headings like "Overview",
817
- "Introduction", or "Notes" that carry no
818
- discriminative value for search.
819
- - Stemming is automatic ("connections" matches
820
- "connect"), but different root words are NOT
821
- equivalent without synonym expansion --
822
- "login" does not match "sign-in" by default.
823
-
824
- ### Structuring Content for Recall
825
-
826
- Content structure affects the progressive context
827
- expansion provided by the `recall` command:
828
-
829
- - Place the most important terms in the **first 20%**
830
- of a section. The zone scoring algorithm gives
831
- extra weight to terms in this heading zone.
832
- - Use explicit cross-references (`$(link:kbid)`
833
- markers) between related sections. The reference
834
- graph enables progressive context expansion via
835
- `recall` at depth 1+.
836
- - Group related sections into documents. The `recall`
837
- command at depth 2+ returns sibling sections,
838
- giving AI agents full surrounding context.
839
- - For code sections: use meaningful identifiers.
840
- camelCase and snake_case names are automatically
841
- split and indexed. `getUserById` produces four
842
- searchable terms: "get", "user", "by", "id".
843
-
844
- ### Leveraging the Query Syntax
845
-
846
- Guidance for AI agents and human users on writing
847
- effective queries:
848
-
849
- - Use **multiple short keyword queries** rather than
850
- one long natural-language question. Lexical search
851
- matches on token overlap, not semantic meaning.
852
- - Use **exact phrases** (`"error handling"`) when term
853
- order matters.
854
- - Use **boolean operators** (`auth AND token`,
855
- `cache OR redis`) to broaden or narrow results.
856
- - Use **term exclusion** (`auth -oauth`) to filter out
857
- irrelevant matches.
858
- - After finding results with `search`, use `recall`
859
- with increasing depth to progressively expand
860
- context.