@dikolab/kbdb 0.1.5 → 0.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 (193) hide show
  1. package/README.md +30 -9
  2. package/dist/cli.cjs +181 -50
  3. package/dist/cli.cjs.map +3 -3
  4. package/dist/cli.mjs +181 -50
  5. package/dist/cli.mjs.map +3 -3
  6. package/dist/kbdb-worker.cjs +283 -29
  7. package/dist/kbdb-worker.cjs.map +4 -4
  8. package/dist/mod.cjs +13 -2
  9. package/dist/mod.cjs.map +2 -2
  10. package/dist/mod.mjs +13 -2
  11. package/dist/mod.mjs.map +2 -2
  12. package/dist/src/shared/cli/functions/format-output.function.d.ts +7 -7
  13. package/dist/src/shared/cli/functions/run-search.function.d.ts +25 -16
  14. package/dist/src/shared/cli/index.d.ts +1 -1
  15. package/dist/src/shared/cli/typings/cli-options.interface.d.ts +2 -0
  16. package/dist/src/shared/cli/typings/search-display.model.d.ts +25 -7
  17. package/dist/src/shared/mcp/functions/handle-recall.function.d.ts +29 -0
  18. package/dist/src/shared/mcp/typings/mcp-client.interface.d.ts +30 -11
  19. package/dist/src/shared/version/constants/version.constant.d.ts +1 -1
  20. package/dist/src/shared/wasm-codec/functions/decode-recall-result.function.d.ts +28 -0
  21. package/dist/src/shared/wasm-codec/functions/decode-scored-results.function.d.ts +14 -7
  22. package/dist/src/shared/wasm-codec/functions/encode-recall-params.function.d.ts +21 -0
  23. package/dist/src/shared/wasm-codec/functions/encode-search-params.function.d.ts +1 -1
  24. package/dist/src/shared/wasm-codec/index.d.ts +5 -2
  25. package/dist/src/shared/worker-client/classes/worker-client.class.d.ts +13 -3
  26. package/dist/src/shared/worker-client/index.d.ts +2 -1
  27. package/dist/src/shared/worker-client/typings/recall-result.model.d.ts +94 -0
  28. package/dist/src/shared/worker-client/typings/search-params.model.d.ts +2 -0
  29. package/dist/src/shared/worker-client/typings/search-result.model.d.ts +23 -7
  30. package/dist/src/shared/worker-daemon/classes/worker-daemon.class.d.ts +1 -0
  31. package/dist/wasm/fs-database/kbdb_fs_database.d.ts +18 -0
  32. package/dist/wasm/fs-database/kbdb_fs_database.js +30 -0
  33. package/dist/wasm/fs-database/kbdb_fs_database_bg.wasm +0 -0
  34. package/dist/wasm/fs-database/kbdb_fs_database_bg.wasm.d.ts +2 -0
  35. package/dist/wasm/kb-worker/kbdb_worker.d.ts +38 -0
  36. package/dist/wasm/kb-worker/kbdb_worker.js +56 -0
  37. package/dist/wasm/kb-worker/kbdb_worker_bg.wasm +0 -0
  38. package/dist/wasm/kb-worker/kbdb_worker_bg.wasm.d.ts +3 -0
  39. package/dist/worker.mjs +283 -29
  40. package/dist/worker.mjs.map +4 -4
  41. package/docs/details/README.md +26 -0
  42. package/docs/details/cli.md +608 -0
  43. package/docs/details/library-api.md +406 -0
  44. package/docs/details/mcp-server.md +430 -0
  45. package/docs/details/search-and-ranking.md +457 -0
  46. package/docs/details/storage.md +529 -0
  47. package/docs/goals/agents.md +751 -0
  48. package/docs/goals/architecture.svg +198 -0
  49. package/docs/goals/cli.md +2308 -0
  50. package/docs/goals/content-composer.md +609 -0
  51. package/docs/goals/content-parser.md +279 -0
  52. package/docs/goals/database.md +1452 -0
  53. package/docs/goals/document.md +368 -0
  54. package/docs/goals/mcp.md +1467 -0
  55. package/docs/goals/overview.md +124 -0
  56. package/docs/goals/query-parser.md +271 -0
  57. package/docs/goals/query-result.md +693 -0
  58. package/docs/goals/skills.md +738 -0
  59. package/docs/goals/worker-client.md +944 -0
  60. package/docs/goals/worker-daemon.md +1447 -0
  61. package/docs/modules/overview.md +298 -0
  62. package/docs/modules/rust/fs-database/functions/document.md +48 -0
  63. package/docs/modules/rust/fs-database/functions/integrity.md +68 -0
  64. package/docs/modules/rust/fs-database/functions/io.md +87 -0
  65. package/docs/modules/rust/fs-database/functions/module.md +22 -0
  66. package/docs/modules/rust/fs-database/functions/query-exec.md +44 -0
  67. package/docs/modules/rust/fs-database/functions/section.md +59 -0
  68. package/docs/modules/rust/fs-database/indexes/btree.md +35 -0
  69. package/docs/modules/rust/fs-database/indexes/corpus.md +61 -0
  70. package/docs/modules/rust/fs-database/indexes/inverted.md +60 -0
  71. package/docs/modules/rust/fs-database/indexes/module.md +64 -0
  72. package/docs/modules/rust/fs-database/indexes/positional.md +39 -0
  73. package/docs/modules/rust/fs-database/module.md +149 -0
  74. package/docs/modules/rust/fs-database/types/catalog.md +53 -0
  75. package/docs/modules/rust/fs-database/types/module.md +12 -0
  76. package/docs/modules/rust/fs-database/types/section.md +101 -0
  77. package/docs/modules/rust/kb-worker/functions/compose.md +57 -0
  78. package/docs/modules/rust/kb-worker/functions/module.md +20 -0
  79. package/docs/modules/rust/kb-worker/functions/retrieve.md +194 -0
  80. package/docs/modules/rust/kb-worker/functions/search.md +173 -0
  81. package/docs/modules/rust/kb-worker/functions/write.md +172 -0
  82. package/docs/modules/rust/kb-worker/module.md +171 -0
  83. package/docs/modules/rust/kb-worker/types/cache.md +90 -0
  84. package/docs/modules/rust/kb-worker/types/context.md +26 -0
  85. package/docs/modules/rust/kb-worker/types/module.md +16 -0
  86. package/docs/modules/rust/kb-worker/types/status.md +29 -0
  87. package/docs/modules/rust/kb-worker/types/token.md +50 -0
  88. package/docs/modules/rust/overview.md +495 -0
  89. package/docs/modules/rust/query-parser/functions/extract.md +56 -0
  90. package/docs/modules/rust/query-parser/functions/module.md +16 -0
  91. package/docs/modules/rust/query-parser/functions/parse.md +33 -0
  92. package/docs/modules/rust/query-parser/functions/pipeline.md +103 -0
  93. package/docs/modules/rust/query-parser/functions/rank.md +85 -0
  94. package/docs/modules/rust/query-parser/module.md +136 -0
  95. package/docs/modules/rust/query-parser/types/cache.md +39 -0
  96. package/docs/modules/rust/query-parser/types/keyphrase.md +60 -0
  97. package/docs/modules/rust/query-parser/types/module.md +17 -0
  98. package/docs/modules/rust/query-parser/types/token.md +42 -0
  99. package/docs/modules/rust/shared/content-composer/functions.md +114 -0
  100. package/docs/modules/rust/shared/content-composer/module.md +147 -0
  101. package/docs/modules/rust/shared/content-composer/types.md +93 -0
  102. package/docs/modules/rust/shared/content-parser/functions.md +72 -0
  103. package/docs/modules/rust/shared/content-parser/module.md +99 -0
  104. package/docs/modules/rust/shared/content-parser/types.md +71 -0
  105. package/docs/modules/rust/shared/corpus/module.md +24 -0
  106. package/docs/modules/rust/shared/corpus/types.md +141 -0
  107. package/docs/modules/rust/shared/document/module.md +25 -0
  108. package/docs/modules/rust/shared/document/types.md +154 -0
  109. package/docs/modules/rust/shared/encode/module.md +22 -0
  110. package/docs/modules/rust/shared/encode/traits.md +304 -0
  111. package/docs/modules/rust/shared/error/module.md +28 -0
  112. package/docs/modules/rust/shared/error/types.md +302 -0
  113. package/docs/modules/rust/shared/kbid/module.md +24 -0
  114. package/docs/modules/rust/shared/kbid/types.md +147 -0
  115. package/docs/modules/rust/shared/memory/functions.md +209 -0
  116. package/docs/modules/rust/shared/memory/module.md +24 -0
  117. package/docs/modules/rust/shared/module.md +168 -0
  118. package/docs/modules/rust/shared/query/module.md +27 -0
  119. package/docs/modules/rust/shared/query/types.md +236 -0
  120. package/docs/modules/rust/shared/section/module.md +25 -0
  121. package/docs/modules/rust/shared/section/types.md +294 -0
  122. package/docs/modules/rust/shared/term/module.md +25 -0
  123. package/docs/modules/rust/shared/term/types.md +139 -0
  124. package/docs/modules/typescript/cli.md +129 -0
  125. package/docs/modules/typescript/kbdb-worker.md +135 -0
  126. package/docs/modules/typescript/overview.md +378 -0
  127. package/docs/modules/typescript/shared/cli/constants.md +23 -0
  128. package/docs/modules/typescript/shared/cli/functions.md +630 -0
  129. package/docs/modules/typescript/shared/cli/module.md +125 -0
  130. package/docs/modules/typescript/shared/cli/typings.md +172 -0
  131. package/docs/modules/typescript/shared/hash/functions.md +20 -0
  132. package/docs/modules/typescript/shared/hash/module.md +21 -0
  133. package/docs/modules/typescript/shared/mcp/classes.md +179 -0
  134. package/docs/modules/typescript/shared/mcp/functions.md +386 -0
  135. package/docs/modules/typescript/shared/mcp/module.md +160 -0
  136. package/docs/modules/typescript/shared/mcp/typings.md +522 -0
  137. package/docs/modules/typescript/shared/module.md +94 -0
  138. package/docs/modules/typescript/shared/platform/functions.md +42 -0
  139. package/docs/modules/typescript/shared/platform/module.md +25 -0
  140. package/docs/modules/typescript/shared/runtime/functions.md +26 -0
  141. package/docs/modules/typescript/shared/runtime/module.md +27 -0
  142. package/docs/modules/typescript/shared/runtime/typings.md +13 -0
  143. package/docs/modules/typescript/shared/version/module.md +27 -0
  144. package/docs/modules/typescript/shared/wasm-codec/functions.md +391 -0
  145. package/docs/modules/typescript/shared/wasm-codec/module.md +98 -0
  146. package/docs/modules/typescript/shared/worker-client/classes.md +264 -0
  147. package/docs/modules/typescript/shared/worker-client/functions.md +175 -0
  148. package/docs/modules/typescript/shared/worker-client/module.md +84 -0
  149. package/docs/modules/typescript/shared/worker-client/typings.md +508 -0
  150. package/docs/modules/typescript/shared/worker-daemon/classes.md +239 -0
  151. package/docs/modules/typescript/shared/worker-daemon/constants.md +37 -0
  152. package/docs/modules/typescript/shared/worker-daemon/functions.md +221 -0
  153. package/docs/modules/typescript/shared/worker-daemon/module.md +112 -0
  154. package/docs/modules/typescript/shared/worker-daemon/typings.md +126 -0
  155. package/docs/overview.md +188 -0
  156. package/docs/release-notes/0.1.0.md +189 -0
  157. package/docs/release-notes/0.1.1.md +46 -0
  158. package/docs/release-notes/0.1.2.md +38 -0
  159. package/docs/release-notes/0.1.3.md +42 -0
  160. package/docs/release-notes/0.2.0.md +147 -0
  161. package/docs/release-notes/README.md +9 -0
  162. package/package.json +8 -2
  163. package/src/shared/cli/functions/dispatch-command.function.ts +3 -3
  164. package/src/shared/cli/functions/format-output.function.ts +41 -34
  165. package/src/shared/cli/functions/parse-args.function.ts +7 -0
  166. package/src/shared/cli/functions/run-search.function.ts +53 -27
  167. package/src/shared/cli/index.ts +4 -1
  168. package/src/shared/cli/typings/cli-options.interface.ts +2 -0
  169. package/src/shared/cli/typings/search-display.model.ts +26 -7
  170. package/src/shared/mcp/functions/create-tool-definitions.function.ts +64 -4
  171. package/src/shared/mcp/functions/handle-recall.function.ts +66 -0
  172. package/src/shared/mcp/functions/handle-tool-call.function.ts +11 -1
  173. package/src/shared/mcp/typings/mcp-client.interface.ts +33 -15
  174. package/src/shared/version/constants/version.constant.ts +1 -1
  175. package/src/shared/wasm-codec/functions/decode-recall-result.function.ts +267 -0
  176. package/src/shared/wasm-codec/functions/decode-scored-results.function.ts +81 -27
  177. package/src/shared/wasm-codec/functions/encode-recall-params.function.ts +68 -0
  178. package/src/shared/wasm-codec/functions/encode-search-params.function.ts +4 -1
  179. package/src/shared/wasm-codec/index.ts +11 -3
  180. package/src/shared/worker-client/classes/worker-client.class.ts +24 -4
  181. package/src/shared/worker-client/index.ts +11 -1
  182. package/src/shared/worker-client/typings/recall-result.model.ts +98 -0
  183. package/src/shared/worker-client/typings/search-params.model.ts +2 -0
  184. package/src/shared/worker-client/typings/search-result.model.ts +24 -7
  185. package/src/shared/worker-daemon/classes/worker-daemon.class.ts +25 -4
  186. package/src/wasm/fs-database/kbdb_fs_database.d.ts +18 -0
  187. package/src/wasm/fs-database/kbdb_fs_database.js +30 -0
  188. package/src/wasm/fs-database/kbdb_fs_database_bg.wasm +0 -0
  189. package/src/wasm/fs-database/kbdb_fs_database_bg.wasm.d.ts +2 -0
  190. package/src/wasm/kb-worker/kbdb_worker.d.ts +38 -0
  191. package/src/wasm/kb-worker/kbdb_worker.js +56 -0
  192. package/src/wasm/kb-worker/kbdb_worker_bg.wasm +0 -0
  193. package/src/wasm/kb-worker/kbdb_worker_bg.wasm.d.ts +3 -0
@@ -0,0 +1,1452 @@
1
+ # Database -- Filesystem Storage Engine
2
+
3
+ *A portable, directory-based database managed by
4
+ fs-database.wasm -- no server, no cloud, just a folder
5
+ on disk.*
6
+
7
+ ## Goal
8
+
9
+ The knowledge base is stored in a `.kbdb` directory on
10
+ the local filesystem. This directory is the entire
11
+ database. There is no cloud dependency.
12
+
13
+ `fs-database.wasm` is the sole owner of all read and
14
+ write operations on this directory. It runs inside the
15
+ worker daemon (see [Worker Daemon](worker-daemon.md)),
16
+ which loads it alongside the other WASM modules.
17
+ `src/cli.ts` does not read or write database files
18
+ directly -- it uses the worker client module to send
19
+ requests to the daemon over IPC, and the daemon
20
+ delegates storage operations to `fs-database.wasm`.
21
+
22
+ ## WASM Module: fs-database.wasm
23
+
24
+ `fs-database.wasm` manages the `.kbdb` directory
25
+ structure, file formats, indexes, and data integrity.
26
+ It is a standalone module -- it does not import
27
+ `query-parser.wasm` or any other WASM module. Text processing
28
+ (tokenization, query parsing, ranking) is handled by
29
+ `query-parser.wasm` separately; `kb-worker.wasm` orchestrates
30
+ both modules.
31
+
32
+ ### Dependency Position
33
+
34
+ ```
35
+ query-parser.wasm fs-database.wasm
36
+ (standalone) (this module -- standalone)
37
+ ↑ ↑
38
+ └────────┬───────────┘
39
+ |
40
+ kb-worker.wasm
41
+ (imports both, orchestrates)
42
+ ```
43
+
44
+ ### Host Imports
45
+
46
+ `fs-database.wasm` cannot access the filesystem
47
+ directly. The TypeScript host (inside the daemon)
48
+ provides file I/O functions as WASM imports:
49
+
50
+ | Import | Description |
51
+ |--------------|------------------------------------|
52
+ | `memory` | Shared `WebAssembly.Memory` |
53
+ | `read_file` | Read file contents by path |
54
+ | `write_file` | Write data to a file path |
55
+ | `remove_file`| Delete a file by path |
56
+ | `list_dir` | List entries in a directory |
57
+ | `mkdir` | Create a directory |
58
+ | `file_exists`| Check whether a path exists |
59
+
60
+ All paths are relative to the context directory
61
+ (`${target_dir}/.kbdb`). The host resolves them to
62
+ absolute paths and enforces that no operation escapes
63
+ the database directory.
64
+
65
+ ## Storage Directory
66
+
67
+ `fs-database.wasm` accepts a `target_dir` parameter --
68
+ the absolute path to the directory that will contain the
69
+ `.kbdb` database. The worker client module resolves this
70
+ path before connecting to the daemon (see
71
+ [Worker Client](worker-client.md)):
72
+
73
+ 1. If a path is specified by the caller (e.g. via a CLI
74
+ flag `--db <path>`), use that path.
75
+ 2. Otherwise, use the current working directory (CWD).
76
+
77
+ The database directory is always `${target_dir}/.kbdb`.
78
+ `fs-database.wasm` never resolves CWD itself -- it
79
+ always receives an absolute path from the host.
80
+
81
+ If `.kbdb` does not exist at the resolved path, the
82
+ worker client throws. The caller is responsible for
83
+ database initialization before creating a worker
84
+ client -- e.g. the CLI prompts the user to create it.
85
+
86
+ The directory is portable. It can be copied, moved, or
87
+ version-controlled. Its contents are self-contained -- no
88
+ external state or configuration is required.
89
+
90
+ The `target_dir` also serves as the identity key for daemon
91
+ discovery. Each unique `target_dir` gets its own independent
92
+ cache daemon. Daemon PID files are stored in
93
+ the OS temporary directory, not inside `.kbdb`. Multiple
94
+ databases on the same machine run independent daemons with
95
+ independent caches. See
96
+ [Worker Daemon](worker-daemon.md) for daemon details.
97
+
98
+ ## Directory Layout
99
+
100
+ The `.kbdb` directory is structured like a PostgreSQL
101
+ directory-format dump: a human-readable catalog at the
102
+ root, one subdirectory per data table (sections,
103
+ documents), and a separate directory for binary indexes.
104
+ Metadata files use TOML for human inspectability.
105
+ Indexes are binary but derived and rebuildable.
106
+
107
+ ```
108
+ .kbdb/
109
+ ├── catalog.toml # database table of contents:
110
+ │ # section count, document
111
+ │ # count, timestamps, integrity
112
+ ├── sections/
113
+ │ ├── ab/
114
+ │ │ └── cdef1234...sec # section file (TOML header
115
+ │ │ # + raw content body)
116
+ │ └── ...
117
+ ├── documents/
118
+ │ └── {docid}.toml # document manifest: title +
119
+ │ # ordered section list
120
+ ├── indexes/
121
+ │ ├── meta.toml # index metadata: last rebuild
122
+ │ │ # timestamp, term count, stats
123
+ │ ├── terms.idx # inverted index (binary,
124
+ │ │ # B+ tree)
125
+ │ ├── positions.idx # positional data (binary)
126
+ │ ├── corpus.dat # corpus-wide statistics
127
+ │ │ # (binary)
128
+ │ └── references.idx # bidirectional reference
129
+ │ # graph (binary)
130
+ ├── worker.toml # cache daemon config
131
+ │ # (see Worker)
132
+ └── .gitignore # excludes indexes/ from
133
+ # version control
134
+ ```
135
+
136
+ ### Catalog (`catalog.toml`)
137
+
138
+ The catalog is the database's table of contents --
139
+ analogous to `toc.dat` in a PostgreSQL directory dump.
140
+ It is the first file `fs-database.wasm` reads when
141
+ opening a database. It records:
142
+
143
+ - Database format version.
144
+ - Total section count and document count.
145
+ - Creation and last-modified timestamps.
146
+ - Integrity checksums.
147
+
148
+ The catalog is updated on every write operation.
149
+
150
+ Example:
151
+
152
+ ```toml
153
+ [database]
154
+ version = 1
155
+ created_at = "2026-06-15T10:00:00Z"
156
+ last_modified = "2026-06-15T12:00:00Z"
157
+
158
+ [counts]
159
+ sections = 42
160
+ documents = 5
161
+
162
+ [integrity]
163
+ checksum = "sha256:abcdef..."
164
+ ```
165
+
166
+ ### Sections (`sections/`)
167
+
168
+ One file per section, stored in hash-sharded
169
+ subdirectories (first two characters of the kb-id) to
170
+ avoid large flat directories.
171
+
172
+ Every section's kb-id is derived from a SHA-256 hash
173
+ of a **typed header plus preprocessed content**,
174
+ following the same content-addressing pattern used by
175
+ Git objects (`blob <size>\0<content>`). The hash input
176
+ is:
177
+
178
+ ```
179
+ section <mime-type> <byte-size>\0<preprocessed-content>
180
+ ```
181
+
182
+ - `<mime-type>` -- the section's MIME type string
183
+ (e.g. `text/markdown`, `text/typescript`,
184
+ `image/png`).
185
+ - `<byte-size>` -- the byte length of the preprocessed
186
+ content, expressed as a decimal ASCII string (like
187
+ Git).
188
+ - `\0` -- a literal null byte separator.
189
+ - `<preprocessed-content>` -- the content after the
190
+ preprocessing pipeline (see **Content Preprocessing**
191
+ below).
192
+
193
+ The SHA-256 digest is truncated to the first 16 bytes
194
+ (128 bits) and base32-encoded (no padding, lowercase)
195
+ to produce a 26-character alphanumeric identifier that
196
+ is deterministic, collision-resistant, and URL-safe.
197
+
198
+ Because the MIME type is part of the hash input,
199
+ identical content stored as different types (e.g.
200
+ `text/markdown` vs `text/typescript`) produces
201
+ different kb-ids.
202
+
203
+ Each section file uses a TOML frontmatter header
204
+ followed by a `---` separator and the content body.
205
+ The header is human-readable; the content after the
206
+ separator is stored verbatim with no escaping.
207
+
208
+ The `type` field identifies the content kind:
209
+
210
+ - **Text sections** -- `type` is `"text/markdown"`.
211
+ The content body after `---` is raw text (markdown)
212
+ stored verbatim.
213
+ - **Code sections** -- `type` is a `text/*` MIME type
214
+ (e.g. `"text/typescript"`, `"text/html"`,
215
+ `"text/css"`) or `"application/json"`. The content
216
+ body after `---` is the raw source text stored
217
+ verbatim. Code sections are indexed -- the
218
+ tokenization pipeline processes their content,
219
+ including identifier splitting for camelCase and
220
+ snake_case terms.
221
+ - **Image sections** -- `type` is an `image/*` MIME
222
+ type (e.g. `"image/png"`, `"image/jpeg"`,
223
+ `"image/webp"`). The content body after `---` is the
224
+ image file's binary data encoded as **base64**. The
225
+ kb-id is the SHA-256 hash of the base64-encoded
226
+ string (not the raw binary), so identical base64
227
+ input always produces the same kb-id.
228
+
229
+ All section types use proper MIME type strings. The
230
+ bare string `"text"` is no longer used; text sections
231
+ use `"text/markdown"` as their canonical type.
232
+
233
+ Image section binary content is not indexed directly.
234
+ However, image sections can be searchable via their
235
+ `title` and `description` attributes (see below).
236
+ They are also retrievable by kb-id or through
237
+ document grouping.
238
+
239
+ Several metadata and pre-computed fields are stored
240
+ in the TOML header:
241
+
242
+ - **`title`** -- A short descriptive title for the
243
+ section. **Required** for image sections (`image/*`)
244
+ since images have no indexable text content and need
245
+ a human-readable identifier. Optional for text
246
+ (`text/markdown`) and code (`text/*`,
247
+ `application/json`) sections. Not included in the
248
+ kbid hash -- changing the title does not change the
249
+ section's identity. When present, the title is
250
+ tokenized and indexed with the **heading** field
251
+ weight (2.0x) for search ranking. See
252
+ [Query Result](query-result.md) for field weight
253
+ details.
254
+ - **`description`** -- An optional longer description
255
+ of the section's content or purpose. Always optional
256
+ for all section types. Not included in the kbid hash.
257
+ When present, the description is tokenized and
258
+ indexed with the **body** field weight (1.0x) for
259
+ search ranking.
260
+ - **`token_count`** -- Total number of tokens after
261
+ tokenization (u32). Sum of all per-field token
262
+ counts. Used for BM25F length normalization and
263
+ zone scoring. Computed by `query-parser.wasm` during
264
+ indexing.
265
+ - **`heading_token_count`** -- Number of tokens from
266
+ the `title` attribute (u32). Used for per-field
267
+ BM25 length normalization.
268
+ - **`body_token_count`** -- Number of tokens from the
269
+ body text content and `description` attribute (u32).
270
+ Used for per-field BM25 length normalization.
271
+ - **`code_token_count`** -- Number of tokens from
272
+ code content (u32). Non-zero only for code sections.
273
+ Used for per-field BM25 length normalization.
274
+
275
+ Token count fields are computed by `query-parser.wasm`
276
+ during tokenization and passed to `fs-database.wasm`
277
+ for storage. They are pre-computed once at insertion
278
+ time and reused on every search -- trading a small
279
+ write-time cost for significant query-time speed.
280
+
281
+ The `add_section` operation rejects requests with
282
+ `image/*` type that do not include a `title`. Text
283
+ and code sections may omit both fields.
284
+
285
+ ### Embedding Sections in Text Content
286
+
287
+ Text sections can contain **markers** -- escape
288
+ sequences that embed or link to other sections and
289
+ documents using the `$(type: value)` syntax. Six
290
+ marker types are supported: four for embedding
291
+ content inline (`$(image:)`, `$(code:)`, `$(text:)`,
292
+ `$(document:)`) and two for cross-references
293
+ (`$(link:)`, `$(doc-link:)`).
294
+
295
+ Markers are stored verbatim in the content body --
296
+ they are not expanded or resolved at storage time.
297
+ They are structural references, not searchable text;
298
+ the tokenization pipeline strips them before
299
+ indexing.
300
+
301
+ See [Document](document.md) for the full content
302
+ format specification, all marker types, syntax
303
+ rules, rendering behaviour, and the MIME-to-language
304
+ mapping table for code embeddings. See
305
+ [Content Parser](content-parser.md) for how markers
306
+ are extracted from content as structured data.
307
+
308
+ #### Section File Examples
309
+
310
+ **Text section** (`sections/ab/cdef1234...sec`):
311
+
312
+ ```
313
+ kbid = "abcdef1234567890abcdef1234"
314
+ docids = ["doc-5678"]
315
+ type = "text/markdown"
316
+ title = "Architecture Overview"
317
+ description = "High-level overview of the system"
318
+ size = 1234
319
+ token_count = 187
320
+ heading_token_count = 2
321
+ body_token_count = 187
322
+ code_token_count = 0
323
+ created_at = "2026-06-15T10:00:00Z"
324
+ checksum = "sha256:abcdef..."
325
+ ---
326
+ Section body text here.
327
+
328
+ $(image: 7fa93b2e4c8d1076a93b2e4c8d)
329
+
330
+ $(code: c4e8a91d3f7b20564e8a91d3f7)
331
+
332
+ This content continues after the embedded sections.
333
+ No escaping or encoding is applied.
334
+ ```
335
+
336
+ **Code section** (`sections/c4/e8a91d...sec`):
337
+
338
+ ```
339
+ kbid = "c4e8a91d3f7b20564e8a91d3f7"
340
+ docids = ["doc-5678"]
341
+ type = "text/typescript"
342
+ title = "greet function"
343
+ size = 256
344
+ token_count = 22
345
+ heading_token_count = 2
346
+ body_token_count = 0
347
+ code_token_count = 22
348
+ created_at = "2026-06-15T10:15:00Z"
349
+ checksum = "sha256:1a2b3c..."
350
+ ---
351
+ export function greet(name: string): string {
352
+ return `Hello, ${name}!`;
353
+ }
354
+ ```
355
+
356
+ **Image section** (`sections/7f/a93b2e...sec`):
357
+
358
+ ```
359
+ kbid = "7fa93b2e4c8d1076a93b2e4c8d"
360
+ docids = ["doc-5678"]
361
+ type = "image/png"
362
+ title = "Architecture diagram"
363
+ description = "Five-layer stack diagram"
364
+ size = 48576
365
+ token_count = 5
366
+ heading_token_count = 2
367
+ body_token_count = 3
368
+ code_token_count = 0
369
+ created_at = "2026-06-15T10:30:00Z"
370
+ checksum = "sha256:fedcba..."
371
+ ---
372
+ iVBORw0KGgoAAAANSUhEUgAA...base64 data...
373
+ ```
374
+
375
+ The image section's `token_count` is 5 (from title +
376
+ description tokens only). Binary content itself
377
+ produces zero tokens.
378
+
379
+ #### Content Preprocessing (kb-id computation)
380
+
381
+ Before computing the kb-id, text and code section
382
+ content passes through a preprocessing pipeline. The
383
+ pipeline normalizes content so that semantically
384
+ identical inputs -- differing only in BOM, line
385
+ endings, Unicode representation, or surrounding
386
+ whitespace -- produce the same kb-id.
387
+
388
+ The pipeline steps, applied in order:
389
+
390
+ 1. **BOM removal** -- Strip the UTF-8 BOM (U+FEFF) if
391
+ present at the start of the content. The BOM is an
392
+ encoding artifact, not meaningful content.
393
+ 2. **Line ending normalization** -- Convert all `\r\n`
394
+ (Windows) and bare `\r` (old Mac) sequences to `\n`
395
+ (Unix). Ensures content created on different
396
+ platforms produces the same kb-id.
397
+ 3. **Unicode NFC normalization** -- Compose decomposed
398
+ Unicode sequences into their canonical composed form
399
+ (NFC). For example, `e` + combining acute accent
400
+ becomes `é` (U+00E9). This is the same
401
+ normalization used in the tokenization pipeline (see
402
+ [Query Parser](query-parser.md)).
403
+ 4. **Whitespace trimming** -- Remove all leading and
404
+ trailing whitespace characters (spaces, tabs,
405
+ newlines, carriage returns, and other Unicode
406
+ whitespace and control characters).
407
+
408
+ This pipeline applies to **text and code sections
409
+ only**. Image sections use the base64-encoded string
410
+ as-is -- no preprocessing is applied.
411
+
412
+ The preprocessed content is what is stored on disk.
413
+ The pipeline runs once at ingest time; subsequent
414
+ reads return the already-preprocessed content. The
415
+ `<byte-size>` in the hash header is computed from the
416
+ content **after** preprocessing.
417
+
418
+ Example hash input construction:
419
+
420
+ ```
421
+ Given:
422
+ type = "text/markdown"
423
+ content (after preprocessing) = "Hello, world!"
424
+ (13 bytes)
425
+
426
+ Hash input (41 bytes):
427
+ section text/markdown 13\0Hello, world!
428
+
429
+ ├─ header: "section text/markdown 13"
430
+ ├─ null byte: \0
431
+ └─ preprocessed content: "Hello, world!"
432
+
433
+ kbid = base32(sha256(hash_input)[..16])
434
+ → 26-character lowercase alphanumeric string
435
+ ```
436
+
437
+ Sections are **content-addressable** -- identical
438
+ preprocessed content stored with the same type always
439
+ produces the same kbid and is stored once (automatic
440
+ deduplication). Because the MIME type is part of the
441
+ hash input, the same raw content stored as different
442
+ types (e.g. `text/markdown` vs `text/typescript`)
443
+ produces different kbids and different section files.
444
+ When identical content is added with the same type:
445
+
446
+ - If a different docid is provided, the existing
447
+ section's `docids` array is updated to include the
448
+ new docid -- no new section file is created
449
+ (`attached: true`).
450
+ - If no docid is provided, the section is skipped
451
+ (`skipped: true`).
452
+ - If the provided docid is already in the `docids`
453
+ array, the operation is idempotent
454
+ (`skipped: false`, `attached: false`).
455
+
456
+ A section can belong to **zero or more** documents.
457
+ The `docids` field is an array of document IDs. The
458
+ relationship between sections and documents is
459
+ **many-to-many**: a section may appear in multiple
460
+ documents, and a document contains multiple sections.
461
+
462
+ Updating a section's content creates a new file with
463
+ a new kbid; the old file is eligible for garbage
464
+ collection.
465
+
466
+ ### Documents (`documents/`)
467
+
468
+ One TOML file per document. A document groups sections
469
+ under a title with a defined order. A section may
470
+ appear in multiple documents (many-to-many). The
471
+ document manifest contains:
472
+
473
+ - Document ID (`docid`).
474
+ - Title.
475
+ - Creation timestamp.
476
+ - An ordered list of section references (kb-ids).
477
+
478
+ Example (`documents/{docid}.toml`):
479
+
480
+ ```toml
481
+ [document]
482
+ docid = "doc-5678"
483
+ title = "Getting Started Guide"
484
+ created_at = "2026-06-15T10:00:00Z"
485
+
486
+ [[sections]]
487
+ kbid = "abcdef1234567890abcdef1234"
488
+ position = 0
489
+
490
+ [[sections]]
491
+ kbid = "fedcba4321098765fedcba4321"
492
+ position = 1
493
+ ```
494
+
495
+ Human-readable -- can be inspected and diffed in git.
496
+
497
+ ### Indexes (`indexes/`)
498
+
499
+ Binary format, not human-readable. Optimized for fast
500
+ term lookup using B+ tree structures. Rebuilt from
501
+ sections and documents on demand. Can be excluded from
502
+ version control via `.gitignore`.
503
+
504
+ Three index files support the search and ranking
505
+ pipeline:
506
+
507
+ - **Inverted index** (`terms.idx`) -- B+ tree mapping
508
+ normalized terms to posting lists. Each posting
509
+ entry contains a kbid and **per-field term
510
+ frequencies**: `heading_tf` (from title), `body_tf`
511
+ (from body text + description), and `code_tf` (from
512
+ code content). Storing per-field TFs separately
513
+ (rather than a single aggregated weight) allows
514
+ BM25F field weights to be tuned at query time
515
+ without rebuilding indexes. See
516
+ [Query Result](query-result.md) for how these
517
+ values feed into the BM25F scoring formula.
518
+
519
+ - **Positional index** (`positions.idx`) -- B+ tree
520
+ mapping `(term, kbid)` pairs to position arrays
521
+ (zero-based token offsets). Supports phrase queries,
522
+ proximity scoring, and zone scoring.
523
+
524
+ - **Corpus statistics** (`corpus.dat`) -- Corpus-wide
525
+ aggregates: `total_sections` (u32),
526
+ `total_terms` (u32), `avg_section_length` (f32),
527
+ `avg_heading_length` (f32), `avg_body_length` (f32),
528
+ `avg_code_length` (f32), and per-term document
529
+ frequencies. Per-field average lengths enable
530
+ accurate per-field BM25 normalization -- a 3-word
531
+ title is not penalized against a 500-word average
532
+ body length.
533
+
534
+ - **Reference graph** (`references.idx`) --
535
+ Bidirectional index mapping content marker
536
+ references between sections and documents. Each
537
+ entry records:
538
+
539
+ | Field | Type | Description |
540
+ |------------------|--------|---------------------|
541
+ | `source_kbid` | string | Section containing the marker |
542
+ | `target_id` | string | Referenced kbid or docid |
543
+ | `marker_type` | enum | Image, Code, Text, Document, Link, DocLink |
544
+ | `context_snippet` | string | ~150 chars of surrounding text where the marker appears |
545
+ | `direction` | enum | `forward` or `back` |
546
+
547
+ For every content marker `$(type: value)` in a
548
+ section's content, the index stores two entries:
549
+ a **forward** entry (`source → target`) and a
550
+ **back** entry (`target ← source`). This enables
551
+ the `recall` tool to look up both "what this
552
+ section references" and "what references this
553
+ section" in O(1) without re-parsing content.
554
+
555
+ **Build process:** When a section is added or
556
+ updated, `fs-database.wasm` calls
557
+ `extract_content_markers()` on its content,
558
+ extracts a ~150-char context snippet around each
559
+ marker, and writes forward and back-reference
560
+ entries.
561
+
562
+ **Maintenance:** When a section is removed, all
563
+ its forward-reference entries and all
564
+ back-reference entries pointing to it are
565
+ deleted.
566
+
567
+ **Rebuild:** `rebuild` regenerates
568
+ `references.idx` from section content alongside
569
+ the other index files. The reference graph is a
570
+ derived artifact -- the content markers in
571
+ section bodies are the source of truth.
572
+
573
+ The `meta.toml` file inside `indexes/` provides
574
+ human-readable metadata about the indexes: last rebuild
575
+ timestamp, total term count, and corpus statistics
576
+ summary. This file is informational only -- the binary
577
+ index files are the source of truth for search.
578
+
579
+ ### Storage Design
580
+
581
+ The directory layout is optimized for **read speed**:
582
+
583
+ - **O(1) section lookup** -- Hash-sharded directories
584
+ allow direct file access by kb-id without scanning.
585
+ - **O(log n) term lookup** -- B+ tree indexes support
586
+ efficient range and equality queries on terms.
587
+ - **Catalog-first discovery** -- `catalog.toml` provides
588
+ section and document counts without scanning
589
+ directories.
590
+ - **Separated metadata and content** -- TOML manifests
591
+ (small, frequently read) are separate from section
592
+ content files (large, read on demand).
593
+
594
+ Write operations may be slower but are designed to be
595
+ safe:
596
+
597
+ - **Atomic writes** -- New files are written to a
598
+ temporary path and renamed into place, ensuring no
599
+ partial writes on crash.
600
+ - **Catalog update last** -- The catalog is updated
601
+ after all data files are written, so a crash mid-write
602
+ leaves the catalog consistent with the last complete
603
+ state.
604
+ - **Index rebuilds** -- Index files can be rebuilt
605
+ entirely from sections and document manifests. Index
606
+ corruption is recoverable.
607
+
608
+ ## What the Database Stores
609
+
610
+ The database holds three categories of data:
611
+
612
+ 1. **Sections** -- Individual content units stored as
613
+ files in `sections/`. Each identified by its content
614
+ hash (kb-id). The section file contains a TOML
615
+ header with metadata (including pre-computed token
616
+ counts for ranking) and the content body (raw text
617
+ for text and code sections, base64 for image
618
+ sections). Identical content is deduplicated
619
+ automatically.
620
+
621
+ 2. **Documents** -- Groupings of sections stored as
622
+ TOML manifests in `documents/`. Each document has a
623
+ title and an ordered list of section kb-ids. A
624
+ document does not contain content directly -- it
625
+ references sections.
626
+
627
+ 3. **Indexes** -- Binary files in `indexes/` mapping
628
+ extracted terms to sections (kb-ids) with per-field
629
+ term frequencies, positional data, corpus-wide
630
+ statistics (including per-field averages), and a
631
+ bidirectional reference graph of content marker
632
+ relationships. Supports term lookup, phrase
633
+ queries, BM25F ranking with tunable field weights,
634
+ and O(1) forward/back-reference lookup for the
635
+ `recall` tool. Derived data -- rebuildable from
636
+ sections and documents.
637
+
638
+ ## Operations
639
+
640
+ `fs-database.wasm` exposes the following operations.
641
+ These are called by `kb-worker.wasm` inside the daemon,
642
+ not by the worker client or TypeScript callers
643
+ directly.
644
+
645
+ ### Section Operations
646
+
647
+ 1. **Read sections** -- Given a list of kb-ids, return
648
+ the section content and metadata for each. Reads
649
+ from `sections/{prefix}/{kbid}.sec`. Returns the
650
+ TOML header fields and the raw content body.
651
+
652
+ 2. **Add section** -- Given a type, section content,
653
+ and optional title and description, create the
654
+ section file. For text and code sections, runs the
655
+ content through the preprocessing pipeline (BOM
656
+ removal, line ending normalization, Unicode NFC,
657
+ whitespace trimming). Computes the kb-id using the
658
+ typed header format
659
+ (`section <mime-type> <byte-size>\0<content>`).
660
+
661
+ During indexing, `kb-worker.wasm` calls
662
+ `query-parser.wasm` to tokenize the content per
663
+ field (title → heading, body text + description →
664
+ body, code content → code) and compute per-field
665
+ term frequencies and token counts. These
666
+ pre-computed values are passed to
667
+ `fs-database.wasm`, which writes the TOML header
668
+ (kbid, docids, type, title, description, size,
669
+ token_count, heading_token_count, body_token_count,
670
+ code_token_count, timestamp, checksum), the
671
+ separator, and the content body (preprocessed text
672
+ for text and code sections, base64 for image
673
+ sections). Updates `catalog.toml` counts. Updates
674
+ the inverted index with per-field term frequencies,
675
+ the positional index with term positions, and
676
+ corpus statistics (including per-field average
677
+ lengths).
678
+
679
+ Image sections (`image/*` type) have their binary
680
+ content stored but not indexed. However, their
681
+ `title` (required) and `description` (optional)
682
+ attributes are tokenized and indexed, making image
683
+ sections searchable via their metadata.
684
+
685
+ **Validation:** requests with `image/*` type that
686
+ omit `title` are rejected with an error. Text and
687
+ code sections may omit `title`.
688
+
689
+ 3. **Update section** -- Given a kb-id and new content,
690
+ replace the section's content. Since kb-ids are
691
+ content-addressed, this creates a new section file
692
+ with a new kb-id (including fresh token counts),
693
+ updates all document references from the old kb-id
694
+ to the new one, and marks the old section file for
695
+ garbage collection. Triggers index update: removes
696
+ old per-field term frequencies and positions,
697
+ re-tokenizes the new content per field, and stores
698
+ updated per-field TFs, positions, and token counts.
699
+ Updates corpus statistics.
700
+
701
+ 4. **Remove section** -- Given a kb-id, delete the
702
+ section file. Removes the kb-id from any document
703
+ that references it. Updates `catalog.toml` counts.
704
+ Triggers index update to remove the section's terms.
705
+
706
+ ### Document Operations
707
+
708
+ 5. **Group sections into document** -- Given a list of
709
+ kb-ids, a document title, and a docid, create a
710
+ document manifest in `documents/{docid}.toml`. The
711
+ manifest records the title, timestamp, and ordered
712
+ section list. Adds the docid to each referenced
713
+ section's `docids` array. Updates `catalog.toml`.
714
+
715
+ 6. **Remove document grouping** -- Given a docid,
716
+ delete the document manifest. The sections
717
+ themselves are not deleted -- only the grouping is
718
+ removed. Removes the docid from each section's
719
+ `docids` array. Updates `catalog.toml`.
720
+
721
+ ### Index and Storage Management
722
+
723
+ 7. **Manage indexes** -- Maintain the inverted index,
724
+ positional index, and corpus statistics in
725
+ `indexes/`. `fs-database.wasm` receives
726
+ pre-computed per-field term frequencies, term
727
+ positions, and token counts from the caller
728
+ (produced by `query-parser.wasm` via
729
+ `kb-worker.wasm` orchestration -- see
730
+ [Query Parser](query-parser.md)) and stores them
731
+ in B+ tree structures optimized for read speed.
732
+
733
+ The inverted index stores per-field term
734
+ frequencies (`heading_tf`, `body_tf`, `code_tf`)
735
+ per posting entry rather than a single aggregated
736
+ weight, enabling query-time field weight tuning.
737
+ Corpus statistics include per-field average lengths
738
+ (`avg_heading_length`, `avg_body_length`,
739
+ `avg_code_length`) for per-field BM25
740
+ normalization. See [Query Result](query-result.md)
741
+ for how these pre-computed values feed into the
742
+ ranking pipeline.
743
+
744
+ Index updates are triggered by section add, update,
745
+ and remove operations. For text and code sections,
746
+ body content is tokenized and indexed. For all
747
+ section types with `title` and/or `description`
748
+ attributes, those attributes are tokenized and
749
+ indexed with their respective field weights. Full
750
+ index rebuilds can be triggered explicitly and
751
+ reconstruct all index files, token counts, and
752
+ corpus statistics from the section and document
753
+ data on disk.
754
+
755
+ ### Search
756
+
757
+ 8. **Execute query plan** -- Given a pre-parsed query
758
+ plan (produced by `query-parser.wasm` via
759
+ `kb-worker.wasm` orchestration), execute the plan
760
+ against the indexes and return raw matches: kb-ids
761
+ with per-field term frequencies (`heading_tf`,
762
+ `body_tf`, `code_tf`), positional data, token
763
+ counts, and per-field token counts.
764
+ `fs-database.wasm` does not parse queries or rank
765
+ results -- it performs index lookups only. Ranking
766
+ is handled by `query-parser.wasm` after
767
+ `kb-worker.wasm` retrieves the raw results. See
768
+ [Query Result](query-result.md) for the full
769
+ ranking pipeline and score computation.
770
+
771
+ ### Retrieval
772
+
773
+ 9. **Retrieve document** -- Given a docid, return the
774
+ document manifest and all its sections in position
775
+ order, including section content, type, title, and
776
+ description.
777
+
778
+ 10. **Status** -- Return database metadata: target
779
+ directory, document count, section count, index
780
+ size, last-modified timestamp, and cache
781
+ statistics.
782
+
783
+ ### Lifecycle
784
+
785
+ 11. **Initialization** -- Given a target directory
786
+ path, initialize a new `.kbdb` database. Creates
787
+ the directory structure, empty catalog, and empty
788
+ index files. Returns the context ID and initial
789
+ counts.
790
+
791
+ ### Integrity and Maintenance
792
+
793
+ 12. **Integrity check** -- Verify section file
794
+ checksums match their content. Detect orphaned
795
+ sections (not referenced by any document),
796
+ orphaned documents (referencing missing sections),
797
+ broken embedding references -- markers whose
798
+ target does not exist or has the wrong type -- and
799
+ circular reference chains in `$(text:)` and
800
+ `$(document:)` markers.
801
+ 13. **Garbage collection** -- Remove section files not
802
+ referenced by any document manifest.
803
+ 14. **Index rebuild** -- Reconstruct all index files
804
+ from sections and documents. Re-tokenizes all text
805
+ and code sections per field, recomputes per-field
806
+ term frequencies and token counts (updating
807
+ section headers), and rebuilds corpus statistics
808
+ including per-field average lengths. Recovers
809
+ from index corruption.
810
+
811
+ ## Relationship to query-parser.wasm
812
+
813
+ Tokenization, keyword extraction, query parsing, and
814
+ ranking are provided by `query-parser.wasm` -- a separate
815
+ standalone module. See [Query Parser](query-parser.md) for the
816
+ full specification of the tokenization pipeline, query
817
+ parsing stages, statistical keyword extraction (RAKE,
818
+ TF-IDF), and ranking functions (BM25F, proximity
819
+ scoring, zone scoring).
820
+
821
+ `fs-database.wasm` does **not** import or call
822
+ `query-parser.wasm` directly. Instead, `kb-worker.wasm`
823
+ orchestrates both modules:
824
+
825
+ - **Indexing** -- When a section is added or updated,
826
+ `kb-worker.wasm` calls `query-parser.wasm` to
827
+ tokenize content per field (title → heading, body
828
+ text + description → body, code content → code)
829
+ and compute per-field term frequencies and token
830
+ counts. It then passes the pre-computed per-field
831
+ TFs, positions, and token counts to
832
+ `fs-database.wasm` for storage in the inverted
833
+ index and section header. Image sections (`image/*`
834
+ type) have their binary content stored without
835
+ indexing, but their `title` and `description`
836
+ attributes are tokenized and indexed.
837
+ - **Searching** -- When a query arrives,
838
+ `kb-worker.wasm` calls `query-parser.wasm` to parse
839
+ the query into a retrieval plan, passes the plan to
840
+ `fs-database.wasm` for index lookups (returning
841
+ per-field TFs, positions, and token counts), then
842
+ calls `query-parser.wasm`'s ranking functions to
843
+ score and order the raw results. See
844
+ [Query Result](query-result.md) for the full
845
+ ranking pipeline.
846
+
847
+ ### Result Granularity
848
+
849
+ Three result modes are available. See
850
+ [Query Result](query-result.md) for ranking
851
+ algorithms, score computation, and result mode
852
+ details.
853
+
854
+ - **Sections** -- Individual matching sections,
855
+ identified by kb-id.
856
+ - **Whole documents** -- Full documents containing
857
+ matches, reassembled from the ordered sections in the
858
+ document manifest.
859
+ - **Statistics** -- Result counts and ranking summaries
860
+ without content bodies.
861
+
862
+ ### Result Structure
863
+
864
+ Each result includes the section's kb-id, the parent
865
+ document ID (docid), a relevance score, a content
866
+ snippet showing the matching context, and the matched
867
+ terms. Results are ordered by descending score.
868
+
869
+ ## Worker Architecture and Caching
870
+
871
+ See [Worker Daemon](worker-daemon.md) for the full
872
+ specification of the cache daemon, worker
873
+ configuration, cache invalidation, and daemon
874
+ lifecycle.
875
+
876
+ ## API Contracts
877
+
878
+ The worker client module communicates with the worker
879
+ daemon over IPC using JSON-RPC 2.0 (see
880
+ [Worker Daemon](worker-daemon.md) for the IPC protocol
881
+ and [Worker Client](worker-client.md) for the client
882
+ specification). Inside the daemon,
883
+ `kb-worker.wasm` calls `fs-database.wasm` for database
884
+ operations. All data at the IPC boundary uses JSON
885
+ serialization.
886
+
887
+ The daemon may use alternative formats (shared
888
+ `WebAssembly.Memory` buffers, pointer/length pairs)
889
+ internally between `kb-worker.wasm` and
890
+ `fs-database.wasm` for performance. The module-internal
891
+ format is an implementation detail that does not affect
892
+ the worker client's IPC API.
893
+
894
+ ### TypeScript Layer Role
895
+
896
+ `src/cli.ts` is a thin relay that delegates all
897
+ database work to the worker client module.
898
+ For example, the CLI's responsibilities are limited to:
899
+
900
+ 1. **Receive** -- Accept the request from CLI arguments.
901
+ 2. **Create worker client** -- Call
902
+ `createWorkerClient({ targetDir })`, which handles
903
+ daemon discovery, spawning, and IPC connection (see
904
+ [Worker Client](worker-client.md)).
905
+ 3. **Call** -- Invoke the appropriate worker client
906
+ method (e.g. `client.search()`,
907
+ `client.addSection()`).
908
+ 4. **Wait** -- Block until the worker client returns
909
+ the typed response.
910
+ 5. **Relay or reformat** -- Return the response based
911
+ on the requested output format:
912
+ - If the requested format is JSON (the default), or
913
+ no format is specified, relay the response directly
914
+ to stdout -- no transformation needed.
915
+ - If the requested format is plain text (CLI only,
916
+ via `--format` flag), reformat the response before
917
+ outputting.
918
+ 6. **Disconnect** -- Call `client.disconnect()`.
919
+
920
+ The TypeScript layer does not parse, enrich, filter, or
921
+ transform the worker client's response when relaying as
922
+ JSON. The response is the final output. The TypeScript
923
+ layer only performs reformatting when an alternative
924
+ output format is explicitly requested via CLI flags.
925
+
926
+ ### Operations
927
+
928
+ These are the operations that `fs-database.wasm`
929
+ exposes to `kb-worker.wasm` inside the daemon. The
930
+ worker client does not call them directly -- it uses
931
+ the low-level IPC protocol documented in
932
+ [Worker Daemon](worker-daemon.md) (query/token
933
+ pattern). `kb-worker.wasm` orchestrates the WASM
934
+ calls and returns results through the daemon's IPC
935
+ layer.
936
+
937
+ **Initialization:**
938
+
939
+ ```json
940
+ // Request
941
+ {
942
+ "target_dir": "<absolute path>"
943
+ }
944
+
945
+ // Response
946
+ {
947
+ "ctx": "<context-id>",
948
+ "document_count": 0,
949
+ "section_count": 0,
950
+ "index_state": "ready"
951
+ }
952
+ ```
953
+
954
+ **Search:**
955
+
956
+ ```json
957
+ // Request
958
+ {
959
+ "query": "<keyword/phrase query>",
960
+ "mode": "sections | documents | stats",
961
+ "limit": 20,
962
+ "offset": 0
963
+ }
964
+ ```
965
+
966
+ Response per mode:
967
+
968
+ - **sections**:
969
+ ```json
970
+ {
971
+ "results": [
972
+ {
973
+ "kbid": "<26-char hash>",
974
+ "docids": ["<doc id>"],
975
+ "type": "text/markdown",
976
+ "heading": "Section Heading",
977
+ "score": 8.74,
978
+ "snippet": "...matching context...",
979
+ "matched_terms": ["term1", "term2"]
980
+ }
981
+ ],
982
+ "total": 42,
983
+ "query_time_ms": 12,
984
+ "query": "original query"
985
+ }
986
+ ```
987
+
988
+ - **documents**:
989
+ ```json
990
+ {
991
+ "results": [
992
+ {
993
+ "docid": "<doc id>",
994
+ "title": "Document Title",
995
+ "score": 12.3,
996
+ "matching_sections": [
997
+ {
998
+ "kbid": "<26-char hash>",
999
+ "type": "text/markdown",
1000
+ "heading": "Section Heading",
1001
+ "score": 8.74,
1002
+ "snippet": "...matching context..."
1003
+ }
1004
+ ]
1005
+ }
1006
+ ],
1007
+ "total": 5,
1008
+ "query_time_ms": 15,
1009
+ "query": "original query"
1010
+ }
1011
+ ```
1012
+
1013
+ - **stats**:
1014
+ ```json
1015
+ {
1016
+ "total_matches": 42,
1017
+ "query_time_ms": 8,
1018
+ "top_terms": [
1019
+ {
1020
+ "term": "keyword",
1021
+ "frequency": 15,
1022
+ "sections_matched": 7
1023
+ }
1024
+ ],
1025
+ "query": "original query"
1026
+ }
1027
+ ```
1028
+
1029
+ **Read sections:**
1030
+
1031
+ ```json
1032
+ // Request
1033
+ {
1034
+ "kbids": ["<26-char hash>", "<26-char hash>"]
1035
+ }
1036
+
1037
+ // Response
1038
+ {
1039
+ "sections": [
1040
+ {
1041
+ "kbid": "<26-char hash>",
1042
+ "docids": ["<doc id>"],
1043
+ "type": "text/markdown",
1044
+ "title": "Architecture Overview",
1045
+ "description": "High-level overview",
1046
+ "content": "Section body text..."
1047
+ }
1048
+ ]
1049
+ }
1050
+ ```
1051
+
1052
+ **Add section (text):**
1053
+
1054
+ ```json
1055
+ // Request
1056
+ {
1057
+ "content": "Section body text...",
1058
+ "type": "text/markdown",
1059
+ "title": "Architecture Overview",
1060
+ "description": "High-level overview",
1061
+ "docid": "<doc id>"
1062
+ }
1063
+
1064
+ // Response
1065
+ {
1066
+ "kbid": "<26-char hash>",
1067
+ "type": "text/markdown",
1068
+ "title": "Architecture Overview",
1069
+ "description": "High-level overview",
1070
+ "indexed_terms_count": 42,
1071
+ "elapsed_ms": 15,
1072
+ "attached": false
1073
+ }
1074
+ ```
1075
+
1076
+ **Add section (image):**
1077
+
1078
+ ```json
1079
+ // Request
1080
+ {
1081
+ "content": "iVBORw0KGgoAAAANSUhEUgAA...",
1082
+ "type": "image/png",
1083
+ "title": "Architecture diagram",
1084
+ "description": "Five-layer stack diagram",
1085
+ "docid": "<doc id>"
1086
+ }
1087
+
1088
+ // Response
1089
+ {
1090
+ "kbid": "<26-char hash>",
1091
+ "type": "image/png",
1092
+ "title": "Architecture diagram",
1093
+ "description": "Five-layer stack diagram",
1094
+ "indexed_terms_count": 0,
1095
+ "elapsed_ms": 8,
1096
+ "attached": false
1097
+ }
1098
+ ```
1099
+
1100
+ The `content` field for image sections is the
1101
+ base64-encoded binary image data. The binary content
1102
+ itself is not indexed, but the `title` (required) and
1103
+ `description` (optional) attributes are tokenized and
1104
+ indexed -- so `indexed_terms_count` reflects the terms
1105
+ extracted from those attributes. The `title` field is
1106
+ **required** for image sections -- requests that omit
1107
+ it are rejected with an error.
1108
+
1109
+ **Add section (code):**
1110
+
1111
+ ```json
1112
+ // Request
1113
+ {
1114
+ "content": "export function greet(name: string)...",
1115
+ "type": "text/typescript",
1116
+ "title": "greet function",
1117
+ "docid": "<doc id>"
1118
+ }
1119
+
1120
+ // Response
1121
+ {
1122
+ "kbid": "<26-char hash>",
1123
+ "type": "text/typescript",
1124
+ "title": "greet function",
1125
+ "indexed_terms_count": 12,
1126
+ "elapsed_ms": 10,
1127
+ "attached": false
1128
+ }
1129
+ ```
1130
+
1131
+ Code sections (`text/*` and `application/json`) are
1132
+ stored as raw text and indexed through the
1133
+ tokenization pipeline. The identifier splitting stage
1134
+ handles camelCase and snake_case decomposition of
1135
+ code tokens.
1136
+
1137
+ **Update section:**
1138
+
1139
+ ```json
1140
+ // Request
1141
+ {
1142
+ "kbid": "<26-char hash>",
1143
+ "content": "New section body text..."
1144
+ }
1145
+
1146
+ // Response
1147
+ {
1148
+ "old_kbid": "<26-char hash>",
1149
+ "new_kbid": "<26-char hash>",
1150
+ "reindexed_terms_count": 38,
1151
+ "elapsed_ms": 12
1152
+ }
1153
+ ```
1154
+
1155
+ **Remove section:**
1156
+
1157
+ ```json
1158
+ // Request
1159
+ {
1160
+ "kbid": "<26-char hash>"
1161
+ }
1162
+
1163
+ // Response
1164
+ {
1165
+ "removed": true,
1166
+ "elapsed_ms": 5
1167
+ }
1168
+ ```
1169
+
1170
+ **Group sections (create document):**
1171
+
1172
+ ```json
1173
+ // Request
1174
+ {
1175
+ "docid": "<doc id>",
1176
+ "title": "Document Title",
1177
+ "kbids": ["<26-char hash>", "<26-char hash>"]
1178
+ }
1179
+
1180
+ // Response
1181
+ {
1182
+ "docid": "<doc id>",
1183
+ "section_count": 2,
1184
+ "elapsed_ms": 8
1185
+ }
1186
+ ```
1187
+
1188
+ **Remove document grouping:**
1189
+
1190
+ ```json
1191
+ // Request
1192
+ {
1193
+ "docid": "<doc id>"
1194
+ }
1195
+
1196
+ // Response
1197
+ {
1198
+ "removed": true,
1199
+ "sections_ungrouped": 2,
1200
+ "elapsed_ms": 5
1201
+ }
1202
+ ```
1203
+
1204
+ **Retrieve document:**
1205
+
1206
+ ```json
1207
+ // Request
1208
+ {
1209
+ "docid": "<doc id>"
1210
+ }
1211
+
1212
+ // Response
1213
+ {
1214
+ "docid": "<doc id>",
1215
+ "title": "Document Title",
1216
+ "created_at": "2026-06-15T10:00:00Z",
1217
+ "sections": [
1218
+ {
1219
+ "kbid": "<26-char hash>",
1220
+ "docids": ["<doc id>"],
1221
+ "type": "text/markdown",
1222
+ "title": "Architecture Overview",
1223
+ "description": "High-level overview",
1224
+ "content": "Section body text...",
1225
+ "position": 0
1226
+ }
1227
+ ]
1228
+ }
1229
+ ```
1230
+
1231
+ **Status:**
1232
+
1233
+ ```json
1234
+ // Request (no parameters)
1235
+ {}
1236
+
1237
+ // Response
1238
+ {
1239
+ "target_dir": "/absolute/path",
1240
+ "document_count": 5,
1241
+ "section_count": 42,
1242
+ "index_size_bytes": 2048576,
1243
+ "last_modified": "2026-06-15T12:00:00Z",
1244
+ "cache_stats": {
1245
+ "items_cached": 150,
1246
+ "hit_rate": 0.87
1247
+ }
1248
+ }
1249
+ ```
1250
+
1251
+ **Integrity check:**
1252
+
1253
+ ```json
1254
+ // Request (no parameters)
1255
+ {}
1256
+
1257
+ // Response
1258
+ {
1259
+ "ok": false,
1260
+ "errors": [
1261
+ {
1262
+ "type": "checksum_mismatch",
1263
+ "entity": "a1b2c3d4e5f6g7h8j9k0l1m2n3",
1264
+ "detail": "File hash does not match filename"
1265
+ },
1266
+ {
1267
+ "type": "orphan_section",
1268
+ "entity": "x9y8z7w6v5u4t3s2r1q0p9o8n7",
1269
+ "detail": "Section not referenced by any document"
1270
+ },
1271
+ {
1272
+ "type": "broken_reference",
1273
+ "entity": "a1b2c3d4e5f6g7h8j9k0l1m2n3",
1274
+ "detail": "$(image: missing0id) references non-existent section"
1275
+ }
1276
+ ],
1277
+ "sections_checked": 42,
1278
+ "documents_checked": 5,
1279
+ "references_checked": 87,
1280
+ "elapsed_ms": 150
1281
+ }
1282
+ ```
1283
+
1284
+ The integrity check verifies:
1285
+
1286
+ - **checksum_mismatch** -- section file content hash
1287
+ does not match the filename (kbid). Indicates
1288
+ corruption or manual editing.
1289
+ - **orphan_section** -- section file exists but is not
1290
+ referenced by any document manifest. Eligible for
1291
+ garbage collection.
1292
+ - **orphan_document** -- document manifest references
1293
+ one or more section kbids that do not exist on disk.
1294
+ - **broken_reference** -- a marker (`$(image:)`,
1295
+ `$(code:)`, `$(text:)`, `$(document:)`, `$(link:)`,
1296
+ or `$(doc-link:)`) references an entity that does
1297
+ not exist.
1298
+ - **type_mismatch** -- a marker references an entity
1299
+ of the wrong type (e.g. `$(image:)` pointing to a
1300
+ text section, or `$(code:)` pointing to an image
1301
+ section).
1302
+ - **circular_reference** -- a `$(text:)` or
1303
+ `$(document:)` marker chain loops back to an entity
1304
+ already in the expansion path.
1305
+
1306
+ When `ok` is `true`, the `errors` array is empty.
1307
+
1308
+ **Garbage collection:**
1309
+
1310
+ ```json
1311
+ // Request (no parameters)
1312
+ {}
1313
+
1314
+ // Response
1315
+ {
1316
+ "removed_sections": 3,
1317
+ "removed_bytes": 15360,
1318
+ "elapsed_ms": 25
1319
+ }
1320
+ ```
1321
+
1322
+ Garbage collection removes section files not listed
1323
+ in any document manifest. A section is eligible for
1324
+ removal when its `docids` array is empty -- it exists
1325
+ on disk but no document references it. GC does not
1326
+ remove documents, indexes, or the catalog. After
1327
+ removal, all caches are invalidated to prevent stale
1328
+ reads.
1329
+
1330
+ **Index rebuild:**
1331
+
1332
+ ```json
1333
+ // Request (no parameters)
1334
+ {}
1335
+
1336
+ // Response
1337
+ {
1338
+ "sections_reindexed": 39,
1339
+ "terms_indexed": 1500,
1340
+ "elapsed_ms": 500
1341
+ }
1342
+ ```
1343
+
1344
+ Index rebuild reconstructs all index files
1345
+ (`indexes/` directory) from section files and
1346
+ document manifests on disk. Text and code sections
1347
+ are re-tokenized per field through the
1348
+ [Query Parser](query-parser.md) pipeline and their
1349
+ per-field term frequencies re-indexed. Image section
1350
+ binary content is skipped, but their `title` and
1351
+ `description` attributes are re-tokenized and
1352
+ re-indexed. All token counts and per-field corpus
1353
+ averages are recomputed. Content markers in all
1354
+ text sections are re-parsed to rebuild
1355
+ `references.idx` (forward and back-reference
1356
+ entries). Equivalent to deleting `indexes/` and
1357
+ re-adding every section. After rebuild, all caches
1358
+ are invalidated.
1359
+
1360
+ ### Error Responses
1361
+
1362
+ Errors are returned as JSON-RPC 2.0 error objects:
1363
+
1364
+ ```json
1365
+ {
1366
+ "error": "Description of the error",
1367
+ "code": "ERROR_CODE"
1368
+ }
1369
+ ```
1370
+
1371
+ The worker client throws these as typed errors. The
1372
+ caller (CLI or MCP) handles them -- e.g. the CLI maps
1373
+ them to stderr output.
1374
+
1375
+ ## Tech and Algorithms Behind
1376
+
1377
+ This section lists the specific algorithms and data
1378
+ structures used, organized by module.
1379
+
1380
+ ### fs-database.wasm -- Storage
1381
+
1382
+ | Approach | Role |
1383
+ |-------------------------------------|-----------------------------------------|
1384
+ | Content-Addressable Store (SHA-256, typed header) | Section files named by hash of `section <mime-type> <byte-size>\0<content>` -- type-aware deduplication, integrity |
1385
+ | Content Preprocessing Pipeline | BOM removal, line ending normalization, Unicode NFC, whitespace trimming -- applied before hashing for text and code sections |
1386
+ | Hash-Sharded Directories | 2-char prefix subdirectories for O(1) section lookup |
1387
+ | B+ Tree Indexes | Ordered key lookups in binary index files for fast term search |
1388
+ | Inverted Index | Maps terms to kb-ids and positions for search |
1389
+ | Atomic Writes (temp + rename) | Crash-safe file writes |
1390
+ | TOML Manifests | Human-readable metadata for catalog, documents, and index metadata |
1391
+
1392
+ ### query-parser.wasm -- Processing
1393
+
1394
+ See [Query Parser](query-parser.md) for full details.
1395
+
1396
+ | Algorithm / Technique | Role |
1397
+ |-------------------------------------|-----------------------------------------|
1398
+ | RAKE | Keyphrase extraction |
1399
+ | TF-IDF | Term weighting |
1400
+ | Snowball Stemmer (Porter2) | Word root form reduction |
1401
+ | Unicode Segmentation | Word boundary detection |
1402
+ | N-gram Extraction | Bigrams/trigrams for phrase matching |
1403
+ | BM25F | Primary ranking function |
1404
+ | Proximity / Zone Scoring | Supplementary ranking signals |
1405
+
1406
+ ### Section Identification
1407
+
1408
+ | Approach | Details |
1409
+ |-----------------------------|------------------------------------------|
1410
+ | Git-Style Typed Header | Hash input is `section <mime-type> <byte-size>\0<preprocessed-content>` -- MIME type is part of the hash, so same content as different types produces different kb-ids |
1411
+ | SHA-256 + Truncation + Base32 | SHA-256 of the typed header, truncated to 128 bits (16 bytes), base32 lowercase (no padding) -- 26 alphanumeric characters |
1412
+ | Content Preprocessing | Text and code: BOM removal, `\r\n`/`\r` to `\n`, Unicode NFC, whitespace trimming. Images: base64 string used as-is |
1413
+
1414
+ ## Design Constraints
1415
+
1416
+ - `fs-database.wasm` is the sole owner of `.kbdb` I/O.
1417
+ The worker client module delegates all storage
1418
+ operations to the worker daemon over IPC, and the
1419
+ daemon delegates to `fs-database.wasm` via
1420
+ `kb-worker.wasm`. `src/cli.ts` does not read or
1421
+ write database files directly.
1422
+ - Sections are content-addressable and identified by
1423
+ their content hash. `fs-database.wasm` never modifies
1424
+ a section file in place -- updates produce new files
1425
+ with new kb-ids.
1426
+ - Indexes are derived data. They can be rebuilt entirely
1427
+ from sections and document manifests. Loss of index
1428
+ files is recoverable.
1429
+ - The `.gitignore` at the database root excludes binary
1430
+ indexes from version control by default. Sections,
1431
+ documents, and the catalog are the version-controlled
1432
+ source of truth.
1433
+ - The database should remain human-inspectable and
1434
+ version-controllable where practical. Metadata files
1435
+ use TOML; section files use TOML headers with raw
1436
+ content bodies.
1437
+ - No external database process or server dependency.
1438
+ - The database must support concurrent read access from
1439
+ multiple CLI invocations via the shared daemon without
1440
+ corruption.
1441
+ - Garbage collection of unreferenced sections is
1442
+ `fs-database.wasm`'s responsibility. Sections not
1443
+ listed in any document manifest may be removed.
1444
+ - The cache daemon is a short-lived background process,
1445
+ not a persistent server. See
1446
+ [Worker Daemon](worker-daemon.md) for daemon lifecycle
1447
+ and configuration.
1448
+ - All IPC API request and response data uses JSON
1449
+ serialization (JSON-RPC 2.0). Inter-module
1450
+ communication (`kb-worker.wasm` to both
1451
+ `fs-database.wasm` and `query-parser.wasm`) uses shared
1452
+ `WebAssembly.Memory` buffers.