@dikolab/kbdb 0.5.0 → 0.6.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 (224) hide show
  1. package/CLA.md +68 -0
  2. package/LICENSE +676 -12
  3. package/LICENSING.md +58 -0
  4. package/README.md +47 -24
  5. package/README.md.bak +320 -0
  6. package/dist/README.md +320 -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 +10 -9
  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,1679 +0,0 @@
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
- Additional metadata fields support truth maintenance
282
- and provenance tracking:
283
-
284
- - **`source_key`** -- Logical identity hash (16
285
- lowercase hex characters). Derived from the source
286
- file path and heading text:
287
- `sha256("${source_path}\0${heading}")[..8]`. Used
288
- for supersession -- matching existing sections when
289
- content is re-learned. `null` for stdin input and
290
- sections without a source path. See **Logical
291
- Identity (Source Key)** below.
292
- - **`source_path`** -- Absolute path of the file that
293
- was learned. `null` for stdin input and
294
- programmatic API calls. Stored for provenance
295
- tracking and source-aware garbage collection.
296
- - **`source_mtime`** -- ISO 8601 timestamp of the
297
- source file's last modification time at learn time.
298
- `null` when no source file exists. Allows staleness
299
- detection: if the source file's mtime has changed
300
- since this timestamp, the section may be outdated.
301
- - **`updated_at`** -- ISO 8601 timestamp of the last
302
- supersession. `null` on first creation. Set when a
303
- section supersedes an existing one with the same
304
- source key.
305
- - **`tags`** -- Array of user-supplied string tags for
306
- scoping and filtering. Empty array when no tags are
307
- provided. Tags are not indexed for search -- they
308
- are metadata for query-time filtering.
309
- - **`supersedes`** -- The kbid of the section this one
310
- replaced via supersession. `null` on first creation
311
- and for non-superseding additions. Creates a
312
- supersession chain that can be traversed to find
313
- prior versions.
314
-
315
- The `add_section` operation rejects requests with
316
- `image/*` type that do not include a `title`. Text
317
- and code sections may omit both fields.
318
-
319
- ### Embedding Sections in Text Content
320
-
321
- Text sections can contain **markers** -- escape
322
- sequences that embed or link to other sections and
323
- documents using the `$(type: value)` syntax. Six
324
- marker types are supported: four for embedding
325
- content inline (`$(image:)`, `$(code:)`, `$(text:)`,
326
- `$(document:)`) and two for cross-references
327
- (`$(link:)`, `$(doc-link:)`).
328
-
329
- Markers are stored verbatim in the content body --
330
- they are not expanded or resolved at storage time.
331
- They are structural references, not searchable text;
332
- the tokenization pipeline strips them before
333
- indexing.
334
-
335
- See [Document](document.md) for the full content
336
- format specification, all marker types, syntax
337
- rules, rendering behaviour, and the MIME-to-language
338
- mapping table for code embeddings. See
339
- [Content Parser](content-parser.md) for how markers
340
- are extracted from content as structured data.
341
-
342
- #### Section File Examples
343
-
344
- **Text section** (`sections/ab/cdef1234...sec`):
345
-
346
- ```
347
- kbid = "abcdef1234567890abcdef1234"
348
- docids = ["doc-5678"]
349
- type = "text/markdown"
350
- title = "Architecture Overview"
351
- description = "High-level overview of the system"
352
- size = 1234
353
- token_count = 187
354
- heading_token_count = 2
355
- body_token_count = 187
356
- code_token_count = 0
357
- created_at = "2026-06-15T10:00:00Z"
358
- checksum = "sha256:abcdef..."
359
- source_key = "a1b2c3d4e5f67890"
360
- source_path = "/home/user/docs/architecture.md"
361
- source_mtime = "2026-06-15T09:45:00Z"
362
- updated_at = ""
363
- tags = ["architecture", "overview"]
364
- supersedes = ""
365
- ---
366
- Section body text here.
367
-
368
- $(image: 7fa93b2e4c8d1076a93b2e4c8d)
369
-
370
- $(code: c4e8a91d3f7b20564e8a91d3f7)
371
-
372
- This content continues after the embedded sections.
373
- No escaping or encoding is applied.
374
- ```
375
-
376
- **Code section** (`sections/c4/e8a91d...sec`):
377
-
378
- ```
379
- kbid = "c4e8a91d3f7b20564e8a91d3f7"
380
- docids = ["doc-5678"]
381
- type = "text/typescript"
382
- title = "greet function"
383
- size = 256
384
- token_count = 22
385
- heading_token_count = 2
386
- body_token_count = 0
387
- code_token_count = 22
388
- created_at = "2026-06-15T10:15:00Z"
389
- checksum = "sha256:1a2b3c..."
390
- source_key = "f8e7d6c5b4a39281"
391
- source_path = "/home/user/src/greet.ts"
392
- source_mtime = "2026-06-15T10:10:00Z"
393
- updated_at = ""
394
- tags = ["api", "greeting"]
395
- supersedes = ""
396
- ---
397
- export function greet(name: string): string {
398
- return `Hello, ${name}!`;
399
- }
400
- ```
401
-
402
- **Image section** (`sections/7f/a93b2e...sec`):
403
-
404
- ```
405
- kbid = "7fa93b2e4c8d1076a93b2e4c8d"
406
- docids = ["doc-5678"]
407
- type = "image/png"
408
- title = "Architecture diagram"
409
- description = "Five-layer stack diagram"
410
- size = 48576
411
- token_count = 5
412
- heading_token_count = 2
413
- body_token_count = 3
414
- code_token_count = 0
415
- created_at = "2026-06-15T10:30:00Z"
416
- checksum = "sha256:fedcba..."
417
- source_key = "1029384756abcdef"
418
- source_path = "/home/user/docs/arch-diagram.png"
419
- source_mtime = "2026-06-15T10:25:00Z"
420
- updated_at = ""
421
- tags = ["diagram", "architecture"]
422
- supersedes = ""
423
- ---
424
- iVBORw0KGgoAAAANSUhEUgAA...base64 data...
425
- ```
426
-
427
- The image section's `token_count` is 5 (from title +
428
- description tokens only). Binary content itself
429
- produces zero tokens.
430
-
431
- #### Content Preprocessing (kb-id computation)
432
-
433
- Before computing the kb-id, text and code section
434
- content passes through a preprocessing pipeline. The
435
- pipeline normalizes content so that semantically
436
- identical inputs -- differing only in BOM, line
437
- endings, Unicode representation, or surrounding
438
- whitespace -- produce the same kb-id.
439
-
440
- The pipeline steps, applied in order:
441
-
442
- 1. **BOM removal** -- Strip the UTF-8 BOM (U+FEFF) if
443
- present at the start of the content. The BOM is an
444
- encoding artifact, not meaningful content.
445
- 2. **Line ending normalization** -- Convert all `\r\n`
446
- (Windows) and bare `\r` (old Mac) sequences to `\n`
447
- (Unix). Ensures content created on different
448
- platforms produces the same kb-id.
449
- 3. **Unicode NFC normalization** -- Compose decomposed
450
- Unicode sequences into their canonical composed form
451
- (NFC). For example, `e` + combining acute accent
452
- becomes `é` (U+00E9). This is the same
453
- normalization used in the tokenization pipeline (see
454
- [Query Parser](query-parser.md)).
455
- 4. **Whitespace trimming** -- Remove all leading and
456
- trailing whitespace characters (spaces, tabs,
457
- newlines, carriage returns, and other Unicode
458
- whitespace and control characters).
459
-
460
- This pipeline applies to **text and code sections
461
- only**. Image sections use the base64-encoded string
462
- as-is -- no preprocessing is applied.
463
-
464
- The preprocessed content is what is stored on disk.
465
- The pipeline runs once at ingest time; subsequent
466
- reads return the already-preprocessed content. The
467
- `<byte-size>` in the hash header is computed from the
468
- content **after** preprocessing.
469
-
470
- Example hash input construction:
471
-
472
- ```
473
- Given:
474
- type = "text/markdown"
475
- content (after preprocessing) = "Hello, world!"
476
- (13 bytes)
477
-
478
- Hash input (41 bytes):
479
- section text/markdown 13\0Hello, world!
480
-
481
- ├─ header: "section text/markdown 13"
482
- ├─ null byte: \0
483
- └─ preprocessed content: "Hello, world!"
484
-
485
- kbid = base32(sha256(hash_input)[..16])
486
- → 26-character lowercase alphanumeric string
487
- ```
488
-
489
- Sections are **content-addressable** -- identical
490
- preprocessed content stored with the same type always
491
- produces the same kbid and is stored once (automatic
492
- deduplication). Because the MIME type is part of the
493
- hash input, the same raw content stored as different
494
- types (e.g. `text/markdown` vs `text/typescript`)
495
- produces different kbids and different section files.
496
- When identical content is added with the same type:
497
-
498
- - If a different docid is provided, the existing
499
- section's `docids` array is updated to include the
500
- new docid -- no new section file is created
501
- (`attached: true`).
502
- - If no docid is provided, the section is skipped
503
- (`skipped: true`).
504
- - If the provided docid is already in the `docids`
505
- array, the operation is idempotent
506
- (`skipped: false`, `attached: false`).
507
-
508
- A section can belong to **zero or more** documents.
509
- The `docids` field is an array of document IDs. The
510
- relationship between sections and documents is
511
- **many-to-many**: a section may appear in multiple
512
- documents, and a document contains multiple sections.
513
-
514
- Updating a section's content creates a new file with
515
- a new kbid; the old file is eligible for garbage
516
- collection.
517
-
518
- #### Logical Identity (Source Key)
519
-
520
- Content-addressing identifies *what a section
521
- currently says*. The **source key** identifies *what
522
- a section is about* -- it is a stable logical
523
- identity that persists across content edits.
524
-
525
- The source key is derived from the section's origin:
526
-
527
- ```
528
- source_key = sha256("${source_path}\0${heading}")[..8]
529
- → 16 lowercase hex characters
530
- ```
531
-
532
- - `source_path` -- the absolute path of the file that
533
- was learned (normalized: resolved symlinks, no
534
- trailing separator).
535
- - `heading` -- the section's title text (or empty
536
- string when no title is provided).
537
- - `\0` -- a null byte separator.
538
-
539
- The SHA-256 digest is truncated to 8 bytes (64 bits)
540
- and hex-encoded to produce a 16-character identifier.
541
-
542
- Source keys enable **supersession**: when content is
543
- re-learned from the same source with the same
544
- heading, the system can identify the existing section
545
- and replace it rather than creating a sibling. This
546
- prevents the brain from silently accumulating
547
- contradictions when facts are corrected.
548
-
549
- Source key is `null` for:
550
- - Stdin input (`-`) -- no source path exists.
551
- - Sections created without a source path (e.g.
552
- programmatic API calls that omit it).
553
-
554
- When `source_key` is `null`, supersession is not
555
- available and the section behaves as a pure
556
- content-addressed entry (current behavior).
557
-
558
- See **Add section** below for supersession semantics.
559
-
560
- ### Documents (`documents/`)
561
-
562
- One TOML file per document. A document groups sections
563
- under a title with a defined order. A section may
564
- appear in multiple documents (many-to-many). The
565
- document manifest contains:
566
-
567
- - Document ID (`docid`).
568
- - Title.
569
- - Creation timestamp.
570
- - An ordered list of section references (kb-ids).
571
-
572
- Example (`documents/{docid}.toml`):
573
-
574
- ```toml
575
- [document]
576
- docid = "doc-5678"
577
- title = "Getting Started Guide"
578
- created_at = "2026-06-15T10:00:00Z"
579
-
580
- [[sections]]
581
- kbid = "abcdef1234567890abcdef1234"
582
- position = 0
583
-
584
- [[sections]]
585
- kbid = "fedcba4321098765fedcba4321"
586
- position = 1
587
- ```
588
-
589
- Human-readable -- can be inspected and diffed in git.
590
-
591
- ### Indexes (`indexes/`)
592
-
593
- Binary format, not human-readable. Optimized for fast
594
- term lookup using B+ tree structures. Rebuilt from
595
- sections and documents on demand. Can be excluded from
596
- version control via `.gitignore`.
597
-
598
- Three index files support the search and ranking
599
- pipeline:
600
-
601
- - **Inverted index** (`terms.idx`) -- B+ tree mapping
602
- normalized terms to posting lists. Each posting
603
- entry contains a kbid and **per-field term
604
- frequencies**: `heading_tf` (from title), `body_tf`
605
- (from body text + description), and `code_tf` (from
606
- code content). Storing per-field TFs separately
607
- (rather than a single aggregated weight) allows
608
- BM25F field weights to be tuned at query time
609
- without rebuilding indexes. See
610
- [Query Result](query-result.md) for how these
611
- values feed into the BM25F scoring formula.
612
-
613
- - **Positional index** (`positions.idx`) -- B+ tree
614
- mapping `(term, kbid)` pairs to position arrays
615
- (zero-based token offsets). Supports phrase queries,
616
- proximity scoring, and zone scoring.
617
-
618
- - **Corpus statistics** (`corpus.dat`) -- Corpus-wide
619
- aggregates: `total_sections` (u32),
620
- `total_terms` (u32), `avg_section_length` (f32),
621
- `avg_heading_length` (f32), `avg_body_length` (f32),
622
- `avg_code_length` (f32), and per-term document
623
- frequencies. Per-field average lengths enable
624
- accurate per-field BM25 normalization -- a 3-word
625
- title is not penalized against a 500-word average
626
- body length.
627
-
628
- - **Reference graph** (`references.idx`) --
629
- Bidirectional index mapping content marker
630
- references between sections and documents. Each
631
- entry records:
632
-
633
- | Field | Type | Description |
634
- |------------------|--------|---------------------|
635
- | `source_kbid` | string | Section containing the marker |
636
- | `target_id` | string | Referenced kbid or docid |
637
- | `marker_type` | enum | Image, Code, Text, Document, Link, DocLink |
638
- | `context_snippet` | string | ~150 chars of surrounding text where the marker appears |
639
- | `direction` | enum | `forward` or `back` |
640
-
641
- For every content marker `$(type: value)` in a
642
- section's content, the index stores two entries:
643
- a **forward** entry (`source → target`) and a
644
- **back** entry (`target ← source`). This enables
645
- the `recall` tool to look up both "what this
646
- section references" and "what references this
647
- section" in O(1) without re-parsing content.
648
-
649
- **Build process:** When a section is added or
650
- updated, `fs-database.wasm` calls
651
- `extract_content_markers()` on its content,
652
- extracts a ~150-char context snippet around each
653
- marker, and writes forward and back-reference
654
- entries.
655
-
656
- **Maintenance:** When a section is removed, all
657
- its forward-reference entries and all
658
- back-reference entries pointing to it are
659
- deleted.
660
-
661
- **Rebuild:** `rebuild` regenerates
662
- `references.idx` from section content alongside
663
- the other index files. The reference graph is a
664
- derived artifact -- the content markers in
665
- section bodies are the source of truth.
666
-
667
- The `meta.toml` file inside `indexes/` provides
668
- human-readable metadata about the indexes: last rebuild
669
- timestamp, total term count, and corpus statistics
670
- summary. This file is informational only -- the binary
671
- index files are the source of truth for search.
672
-
673
- ### Storage Design
674
-
675
- The directory layout is optimized for **read speed**:
676
-
677
- - **O(1) section lookup** -- Hash-sharded directories
678
- allow direct file access by kb-id without scanning.
679
- - **O(log n) term lookup** -- B+ tree indexes support
680
- efficient range and equality queries on terms.
681
- - **Catalog-first discovery** -- `catalog.toml` provides
682
- section and document counts without scanning
683
- directories.
684
- - **Separated metadata and content** -- TOML manifests
685
- (small, frequently read) are separate from section
686
- content files (large, read on demand).
687
-
688
- Write operations may be slower but are designed to be
689
- safe:
690
-
691
- - **Atomic writes** -- New files are written to a
692
- temporary path and renamed into place, ensuring no
693
- partial writes on crash.
694
- - **Catalog update last** -- The catalog is updated
695
- after all data files are written, so a crash mid-write
696
- leaves the catalog consistent with the last complete
697
- state.
698
- - **Index rebuilds** -- Index files can be rebuilt
699
- entirely from sections and document manifests. Index
700
- corruption is recoverable.
701
-
702
- ## What the Database Stores
703
-
704
- The database holds three categories of data:
705
-
706
- 1. **Sections** -- Individual content units stored as
707
- files in `sections/`. Each identified by its content
708
- hash (kb-id). The section file contains a TOML
709
- header with metadata (including pre-computed token
710
- counts for ranking) and the content body (raw text
711
- for text and code sections, base64 for image
712
- sections). Identical content is deduplicated
713
- automatically.
714
-
715
- 2. **Documents** -- Groupings of sections stored as
716
- TOML manifests in `documents/`. Each document has a
717
- title and an ordered list of section kb-ids. A
718
- document does not contain content directly -- it
719
- references sections.
720
-
721
- 3. **Indexes** -- Binary files in `indexes/` mapping
722
- extracted terms to sections (kb-ids) with per-field
723
- term frequencies, positional data, corpus-wide
724
- statistics (including per-field averages), and a
725
- bidirectional reference graph of content marker
726
- relationships. Supports term lookup, phrase
727
- queries, BM25F ranking with tunable field weights,
728
- and O(1) forward/back-reference lookup for the
729
- `recall` tool. Derived data -- rebuildable from
730
- sections and documents.
731
-
732
- ## Operations
733
-
734
- `fs-database.wasm` exposes the following operations.
735
- These are called by `kb-worker.wasm` inside the daemon,
736
- not by the worker client or TypeScript callers
737
- directly.
738
-
739
- ### Section Operations
740
-
741
- 1. **Read sections** -- Given a list of kb-ids, return
742
- the section content and metadata for each. Reads
743
- from `sections/{prefix}/{kbid}.sec`. Returns the
744
- TOML header fields and the raw content body.
745
-
746
- 2. **Add section** -- Given a type, section content,
747
- and optional title and description, create the
748
- section file. For text and code sections, runs the
749
- content through the preprocessing pipeline (BOM
750
- removal, line ending normalization, Unicode NFC,
751
- whitespace trimming). Computes the kb-id using the
752
- typed header format
753
- (`section <mime-type> <byte-size>\0<content>`).
754
-
755
- During indexing, `kb-worker.wasm` calls
756
- `query-parser.wasm` to tokenize the content per
757
- field (title → heading, body text + description →
758
- body, code content → code) and compute per-field
759
- term frequencies and token counts. These
760
- pre-computed values are passed to
761
- `fs-database.wasm`, which writes the TOML header
762
- (kbid, docids, type, title, description, size,
763
- token_count, heading_token_count, body_token_count,
764
- code_token_count, created_at, checksum, source_key,
765
- source_path, source_mtime, updated_at, tags,
766
- supersedes), the separator, and the content body
767
- (preprocessed text for text and code sections,
768
- base64 for image sections). Updates `catalog.toml`
769
- counts. Updates the inverted index with per-field
770
- term frequencies, the positional index with term
771
- positions, and corpus statistics (including
772
- per-field average lengths).
773
-
774
- Image sections (`image/*` type) have their binary
775
- content stored but not indexed. However, their
776
- `title` (required) and `description` (optional)
777
- attributes are tokenized and indexed, making image
778
- sections searchable via their metadata.
779
-
780
- **Supersession:** When the `replace` flag is set
781
- and the new section has a non-null `source_key`,
782
- `fs-database.wasm` searches for an existing section
783
- with the same `source_key`. If found:
784
-
785
- - The new section's `supersedes` field is set to
786
- the old section's kbid.
787
- - The new section's `updated_at` is set to the
788
- current timestamp.
789
- - The old kbid is replaced with the new kbid in all
790
- document manifests that reference it.
791
- - The old section's `docids` array is emptied,
792
- making it eligible for garbage collection.
793
- - Index entries for the old section are removed and
794
- replaced with entries for the new section.
795
-
796
- When `replace` is not set, or `source_key` is
797
- `null`, the add operation behaves as before (pure
798
- content-addressed addition with deduplication).
799
-
800
- **Near-duplicate detection:** After the kbid is
801
- computed and deduplication is checked, the add
802
- operation computes a Jaccard similarity coefficient
803
- between the new section's stemmed term set and
804
- existing sections' term sets. Sections exceeding a
805
- similarity threshold of 0.85 are reported as
806
- near-duplicates in the response
807
- (`near_duplicates: [{kbid, similarity}]`). This is
808
- advisory -- the section is still stored. The check
809
- uses the inverted index for efficient comparison
810
- without reading every section's content.
811
-
812
- **Heading-less import warning:** Text and code
813
- sections added without a `title` emit a warning in
814
- the response: `heading_missing`. The heading field
815
- carries 2.0x ranking weight; omitting it means the
816
- section loses its strongest searchability signal.
817
- The section is still stored -- this is a warning,
818
- not an error.
819
-
820
- **Validation:** requests with `image/*` type that
821
- omit `title` are rejected with an error. Text and
822
- code sections may omit `title` (with the
823
- heading-less warning above).
824
-
825
- 3. **Update section** -- Given a kb-id and new content,
826
- replace the section's content. Since kb-ids are
827
- content-addressed, this creates a new section file
828
- with a new kb-id (including fresh token counts),
829
- updates all document references from the old kb-id
830
- to the new one, and marks the old section file for
831
- garbage collection. Triggers index update: removes
832
- old per-field term frequencies and positions,
833
- re-tokenizes the new content per field, and stores
834
- updated per-field TFs, positions, and token counts.
835
- Updates corpus statistics.
836
-
837
- 4. **Remove section** -- Given a kb-id, delete the
838
- section file. Removes the kb-id from any document
839
- that references it. Updates `catalog.toml` counts.
840
- Triggers index update to remove the section's terms.
841
-
842
- ### Document Operations
843
-
844
- 5. **Group sections into document** -- Given a list of
845
- kb-ids, a document title, and a docid, create a
846
- document manifest in `documents/{docid}.toml`. The
847
- manifest records the title, timestamp, and ordered
848
- section list. Adds the docid to each referenced
849
- section's `docids` array. Updates `catalog.toml`.
850
-
851
- 6. **Remove document grouping** -- Given a docid,
852
- delete the document manifest. The sections
853
- themselves are not deleted -- only the grouping is
854
- removed. Removes the docid from each section's
855
- `docids` array. Updates `catalog.toml`.
856
-
857
- ### Index and Storage Management
858
-
859
- 7. **Manage indexes** -- Maintain the inverted index,
860
- positional index, and corpus statistics in
861
- `indexes/`. `fs-database.wasm` receives
862
- pre-computed per-field term frequencies, term
863
- positions, and token counts from the caller
864
- (produced by `query-parser.wasm` via
865
- `kb-worker.wasm` orchestration -- see
866
- [Query Parser](query-parser.md)) and stores them
867
- in B+ tree structures optimized for read speed.
868
-
869
- The inverted index stores per-field term
870
- frequencies (`heading_tf`, `body_tf`, `code_tf`)
871
- per posting entry rather than a single aggregated
872
- weight, enabling query-time field weight tuning.
873
- Corpus statistics include per-field average lengths
874
- (`avg_heading_length`, `avg_body_length`,
875
- `avg_code_length`) for per-field BM25
876
- normalization. See [Query Result](query-result.md)
877
- for how these pre-computed values feed into the
878
- ranking pipeline.
879
-
880
- Index updates are triggered by section add, update,
881
- and remove operations. For text and code sections,
882
- body content is tokenized and indexed. For all
883
- section types with `title` and/or `description`
884
- attributes, those attributes are tokenized and
885
- indexed with their respective field weights. Full
886
- index rebuilds can be triggered explicitly and
887
- reconstruct all index files, token counts, and
888
- corpus statistics from the section and document
889
- data on disk.
890
-
891
- ### Search
892
-
893
- 8. **Execute query plan** -- Given a pre-parsed query
894
- plan (produced by `query-parser.wasm` via
895
- `kb-worker.wasm` orchestration), execute the plan
896
- against the indexes and return raw matches: kb-ids
897
- with per-field term frequencies (`heading_tf`,
898
- `body_tf`, `code_tf`), positional data, token
899
- counts, and per-field token counts.
900
- `fs-database.wasm` does not parse queries or rank
901
- results -- it performs index lookups only. Ranking
902
- is handled by `query-parser.wasm` after
903
- `kb-worker.wasm` retrieves the raw results. See
904
- [Query Result](query-result.md) for the full
905
- ranking pipeline and score computation.
906
-
907
- ### Retrieval
908
-
909
- 9. **Retrieve document** -- Given a docid, return the
910
- document manifest and all its sections in position
911
- order, including section content, type, title, and
912
- description.
913
-
914
- 10. **Status** -- Return database metadata: target
915
- directory, document count, section count, index
916
- size, last-modified timestamp, and cache
917
- statistics.
918
-
919
- ### Lifecycle
920
-
921
- 11. **Initialization** -- Given a target directory
922
- path, initialize a new `.kbdb` database. Creates
923
- the directory structure, empty catalog, and empty
924
- index files. Returns the context ID and initial
925
- counts.
926
-
927
- ### Integrity and Maintenance
928
-
929
- 12. **Integrity check** -- Verify section file
930
- checksums match their content. Detect orphaned
931
- sections (not referenced by any document),
932
- orphaned documents (referencing missing sections),
933
- broken embedding references -- markers whose
934
- target does not exist or has the wrong type,
935
- circular reference chains in `$(text:)` and
936
- `$(document:)` markers, and **divergent content**
937
- -- multiple sections sharing the same `source_key`
938
- (same origin) but with different content,
939
- indicating contradictory knowledge that should
940
- have been resolved by supersession.
941
- 13. **Garbage collection** -- Remove section files not
942
- referenced by any document manifest. Additionally
943
- report `stale_sources` -- sections whose
944
- `source_path` points to a file that no longer
945
- contains their heading text. Stale sources are
946
- advisory (not automatically removed) and surfaced
947
- in the GC response for manual review.
948
- 14. **Export** -- Copy sections, documents, and
949
- catalog to a self-contained output directory (no
950
- indexes -- they are rebuildable). Provides a
951
- portable, git-friendly snapshot of the knowledge
952
- base for backup and versioning.
953
- 15. **Index rebuild** -- Reconstruct all index files
954
- from sections and documents. Re-tokenizes all text
955
- and code sections per field, recomputes per-field
956
- term frequencies and token counts (updating
957
- section headers), and rebuilds corpus statistics
958
- including per-field average lengths. Recovers
959
- from index corruption.
960
-
961
- ## Relationship to query-parser.wasm
962
-
963
- Tokenization, keyword extraction, query parsing, and
964
- ranking are provided by `query-parser.wasm` -- a separate
965
- standalone module. See [Query Parser](query-parser.md) for the
966
- full specification of the tokenization pipeline, query
967
- parsing stages, statistical keyword extraction (RAKE,
968
- TF-IDF), and ranking functions (BM25F, proximity
969
- scoring, zone scoring).
970
-
971
- `fs-database.wasm` does **not** import or call
972
- `query-parser.wasm` directly. Instead, `kb-worker.wasm`
973
- orchestrates both modules:
974
-
975
- - **Indexing** -- When a section is added or updated,
976
- `kb-worker.wasm` calls `query-parser.wasm` to
977
- tokenize content per field (title → heading, body
978
- text + description → body, code content → code)
979
- and compute per-field term frequencies and token
980
- counts. It then passes the pre-computed per-field
981
- TFs, positions, and token counts to
982
- `fs-database.wasm` for storage in the inverted
983
- index and section header. Image sections (`image/*`
984
- type) have their binary content stored without
985
- indexing, but their `title` and `description`
986
- attributes are tokenized and indexed.
987
- - **Searching** -- When a query arrives,
988
- `kb-worker.wasm` calls `query-parser.wasm` to parse
989
- the query into a retrieval plan, passes the plan to
990
- `fs-database.wasm` for index lookups (returning
991
- per-field TFs, positions, and token counts), then
992
- calls `query-parser.wasm`'s ranking functions to
993
- score and order the raw results. See
994
- [Query Result](query-result.md) for the full
995
- ranking pipeline.
996
-
997
- ### Result Granularity
998
-
999
- Three result modes are available. See
1000
- [Query Result](query-result.md) for ranking
1001
- algorithms, score computation, and result mode
1002
- details.
1003
-
1004
- - **Sections** -- Individual matching sections,
1005
- identified by kb-id.
1006
- - **Whole documents** -- Full documents containing
1007
- matches, reassembled from the ordered sections in the
1008
- document manifest.
1009
- - **Statistics** -- Result counts and ranking summaries
1010
- without content bodies.
1011
-
1012
- ### Result Structure
1013
-
1014
- Each result includes the section's kb-id, the parent
1015
- document ID (docid), a relevance score, a content
1016
- snippet showing the matching context, and the matched
1017
- terms. Results are ordered by descending score.
1018
-
1019
- ## Worker Architecture and Caching
1020
-
1021
- See [Worker Daemon](worker-daemon.md) for the full
1022
- specification of the cache daemon, worker
1023
- configuration, cache invalidation, and daemon
1024
- lifecycle.
1025
-
1026
- ## API Contracts
1027
-
1028
- The worker client module communicates with the worker
1029
- daemon over IPC using JSON-RPC 2.0 (see
1030
- [Worker Daemon](worker-daemon.md) for the IPC protocol
1031
- and [Worker Client](worker-client.md) for the client
1032
- specification). Inside the daemon,
1033
- `kb-worker.wasm` calls `fs-database.wasm` for database
1034
- operations. All data at the IPC boundary uses JSON
1035
- serialization.
1036
-
1037
- The daemon may use alternative formats (shared
1038
- `WebAssembly.Memory` buffers, pointer/length pairs)
1039
- internally between `kb-worker.wasm` and
1040
- `fs-database.wasm` for performance. The module-internal
1041
- format is an implementation detail that does not affect
1042
- the worker client's IPC API.
1043
-
1044
- ### TypeScript Layer Role
1045
-
1046
- `src/cli.ts` is a thin relay that delegates all
1047
- database work to the worker client module.
1048
- For example, the CLI's responsibilities are limited to:
1049
-
1050
- 1. **Receive** -- Accept the request from CLI arguments.
1051
- 2. **Create worker client** -- Call
1052
- `createWorkerClient({ targetDir })`, which handles
1053
- daemon discovery, spawning, and IPC connection (see
1054
- [Worker Client](worker-client.md)).
1055
- 3. **Call** -- Invoke the appropriate worker client
1056
- method (e.g. `client.search()`,
1057
- `client.addSection()`).
1058
- 4. **Wait** -- Block until the worker client returns
1059
- the typed response.
1060
- 5. **Relay or reformat** -- Return the response based
1061
- on the requested output format:
1062
- - If the requested format is JSON (the default), or
1063
- no format is specified, relay the response directly
1064
- to stdout -- no transformation needed.
1065
- - If the requested format is plain text (CLI only,
1066
- via `--format` flag), reformat the response before
1067
- outputting.
1068
- 6. **Disconnect** -- Call `client.disconnect()`.
1069
-
1070
- The TypeScript layer does not parse, enrich, filter, or
1071
- transform the worker client's response when relaying as
1072
- JSON. The response is the final output. The TypeScript
1073
- layer only performs reformatting when an alternative
1074
- output format is explicitly requested via CLI flags.
1075
-
1076
- ### Operations
1077
-
1078
- These are the operations that `fs-database.wasm`
1079
- exposes to `kb-worker.wasm` inside the daemon. The
1080
- worker client does not call them directly -- it uses
1081
- the low-level IPC protocol documented in
1082
- [Worker Daemon](worker-daemon.md) (query/token
1083
- pattern). `kb-worker.wasm` orchestrates the WASM
1084
- calls and returns results through the daemon's IPC
1085
- layer.
1086
-
1087
- **Initialization:**
1088
-
1089
- ```json
1090
- // Request
1091
- {
1092
- "target_dir": "<absolute path>"
1093
- }
1094
-
1095
- // Response
1096
- {
1097
- "ctx": "<context-id>",
1098
- "document_count": 0,
1099
- "section_count": 0,
1100
- "index_state": "ready"
1101
- }
1102
- ```
1103
-
1104
- **Search:**
1105
-
1106
- ```json
1107
- // Request
1108
- {
1109
- "query": "<keyword/phrase query>",
1110
- "mode": "sections | documents | stats",
1111
- "limit": 20,
1112
- "offset": 0
1113
- }
1114
- ```
1115
-
1116
- Response per mode:
1117
-
1118
- - **sections**:
1119
- ```json
1120
- {
1121
- "results": [
1122
- {
1123
- "kbid": "<26-char hash>",
1124
- "docids": ["<doc id>"],
1125
- "type": "text/markdown",
1126
- "heading": "Section Heading",
1127
- "score": 8.74,
1128
- "confidence": 1.0,
1129
- "snippet": "...matching context...",
1130
- "matched_terms": ["term1", "term2"],
1131
- "matched_fields": ["heading", "body"],
1132
- "created_at": "2026-06-15T10:00:00Z"
1133
- }
1134
- ],
1135
- "total": 42,
1136
- "query_time_ms": 12,
1137
- "query": "original query"
1138
- }
1139
- ```
1140
-
1141
- - **documents**:
1142
- ```json
1143
- {
1144
- "results": [
1145
- {
1146
- "docid": "<doc id>",
1147
- "title": "Document Title",
1148
- "score": 12.3,
1149
- "matching_sections": [
1150
- {
1151
- "kbid": "<26-char hash>",
1152
- "type": "text/markdown",
1153
- "heading": "Section Heading",
1154
- "score": 8.74,
1155
- "snippet": "...matching context..."
1156
- }
1157
- ]
1158
- }
1159
- ],
1160
- "total": 5,
1161
- "query_time_ms": 15,
1162
- "query": "original query"
1163
- }
1164
- ```
1165
-
1166
- - **stats**:
1167
- ```json
1168
- {
1169
- "total_matches": 42,
1170
- "query_time_ms": 8,
1171
- "top_terms": [
1172
- {
1173
- "term": "keyword",
1174
- "frequency": 15,
1175
- "sections_matched": 7
1176
- }
1177
- ],
1178
- "query": "original query"
1179
- }
1180
- ```
1181
-
1182
- **Read sections:**
1183
-
1184
- ```json
1185
- // Request
1186
- {
1187
- "kbids": ["<26-char hash>", "<26-char hash>"]
1188
- }
1189
-
1190
- // Response
1191
- {
1192
- "sections": [
1193
- {
1194
- "kbid": "<26-char hash>",
1195
- "docids": ["<doc id>"],
1196
- "type": "text/markdown",
1197
- "title": "Architecture Overview",
1198
- "description": "High-level overview",
1199
- "content": "Section body text..."
1200
- }
1201
- ]
1202
- }
1203
- ```
1204
-
1205
- **Add section (text):**
1206
-
1207
- ```json
1208
- // Request
1209
- {
1210
- "content": "Section body text...",
1211
- "type": "text/markdown",
1212
- "title": "Architecture Overview",
1213
- "description": "High-level overview",
1214
- "docid": "<doc id>",
1215
- "source_path": "/home/user/docs/architecture.md",
1216
- "tags": ["architecture", "overview"],
1217
- "replace": false
1218
- }
1219
-
1220
- // Response
1221
- {
1222
- "kbid": "<26-char hash>",
1223
- "type": "text/markdown",
1224
- "title": "Architecture Overview",
1225
- "description": "High-level overview",
1226
- "indexed_terms_count": 42,
1227
- "elapsed_ms": 15,
1228
- "attached": false,
1229
- "superseded": null,
1230
- "warnings": [],
1231
- "near_duplicates": []
1232
- }
1233
- ```
1234
-
1235
- **Add section (image):**
1236
-
1237
- ```json
1238
- // Request
1239
- {
1240
- "content": "iVBORw0KGgoAAAANSUhEUgAA...",
1241
- "type": "image/png",
1242
- "title": "Architecture diagram",
1243
- "description": "Five-layer stack diagram",
1244
- "docid": "<doc id>"
1245
- }
1246
-
1247
- // Response
1248
- {
1249
- "kbid": "<26-char hash>",
1250
- "type": "image/png",
1251
- "title": "Architecture diagram",
1252
- "description": "Five-layer stack diagram",
1253
- "indexed_terms_count": 0,
1254
- "elapsed_ms": 8,
1255
- "attached": false
1256
- }
1257
- ```
1258
-
1259
- The `content` field for image sections is the
1260
- base64-encoded binary image data. The binary content
1261
- itself is not indexed, but the `title` (required) and
1262
- `description` (optional) attributes are tokenized and
1263
- indexed -- so `indexed_terms_count` reflects the terms
1264
- extracted from those attributes. The `title` field is
1265
- **required** for image sections -- requests that omit
1266
- it are rejected with an error.
1267
-
1268
- **Add section (code):**
1269
-
1270
- ```json
1271
- // Request
1272
- {
1273
- "content": "export function greet(name: string)...",
1274
- "type": "text/typescript",
1275
- "title": "greet function",
1276
- "docid": "<doc id>"
1277
- }
1278
-
1279
- // Response
1280
- {
1281
- "kbid": "<26-char hash>",
1282
- "type": "text/typescript",
1283
- "title": "greet function",
1284
- "indexed_terms_count": 12,
1285
- "elapsed_ms": 10,
1286
- "attached": false
1287
- }
1288
- ```
1289
-
1290
- Code sections (`text/*` and `application/json`) are
1291
- stored as raw text and indexed through the
1292
- tokenization pipeline. The identifier splitting stage
1293
- handles camelCase and snake_case decomposition of
1294
- code tokens.
1295
-
1296
- **Update section:**
1297
-
1298
- ```json
1299
- // Request
1300
- {
1301
- "kbid": "<26-char hash>",
1302
- "content": "New section body text..."
1303
- }
1304
-
1305
- // Response
1306
- {
1307
- "old_kbid": "<26-char hash>",
1308
- "new_kbid": "<26-char hash>",
1309
- "reindexed_terms_count": 38,
1310
- "elapsed_ms": 12
1311
- }
1312
- ```
1313
-
1314
- **Remove section:**
1315
-
1316
- ```json
1317
- // Request
1318
- {
1319
- "kbid": "<26-char hash>"
1320
- }
1321
-
1322
- // Response
1323
- {
1324
- "removed": true,
1325
- "elapsed_ms": 5
1326
- }
1327
- ```
1328
-
1329
- **Group sections (create document):**
1330
-
1331
- ```json
1332
- // Request
1333
- {
1334
- "docid": "<doc id>",
1335
- "title": "Document Title",
1336
- "kbids": ["<26-char hash>", "<26-char hash>"]
1337
- }
1338
-
1339
- // Response
1340
- {
1341
- "docid": "<doc id>",
1342
- "section_count": 2,
1343
- "elapsed_ms": 8
1344
- }
1345
- ```
1346
-
1347
- **Remove document grouping:**
1348
-
1349
- ```json
1350
- // Request
1351
- {
1352
- "docid": "<doc id>"
1353
- }
1354
-
1355
- // Response
1356
- {
1357
- "removed": true,
1358
- "sections_ungrouped": 2,
1359
- "elapsed_ms": 5
1360
- }
1361
- ```
1362
-
1363
- **Retrieve document:**
1364
-
1365
- ```json
1366
- // Request
1367
- {
1368
- "docid": "<doc id>"
1369
- }
1370
-
1371
- // Response
1372
- {
1373
- "docid": "<doc id>",
1374
- "title": "Document Title",
1375
- "created_at": "2026-06-15T10:00:00Z",
1376
- "sections": [
1377
- {
1378
- "kbid": "<26-char hash>",
1379
- "docids": ["<doc id>"],
1380
- "type": "text/markdown",
1381
- "title": "Architecture Overview",
1382
- "description": "High-level overview",
1383
- "content": "Section body text...",
1384
- "position": 0
1385
- }
1386
- ]
1387
- }
1388
- ```
1389
-
1390
- **Status:**
1391
-
1392
- ```json
1393
- // Request (no parameters)
1394
- {}
1395
-
1396
- // Response
1397
- {
1398
- "target_dir": "/absolute/path",
1399
- "document_count": 5,
1400
- "section_count": 42,
1401
- "index_size_bytes": 2048576,
1402
- "last_modified": "2026-06-15T12:00:00Z",
1403
- "cache_stats": {
1404
- "items_cached": 150,
1405
- "hit_rate": 0.87
1406
- }
1407
- }
1408
- ```
1409
-
1410
- **Integrity check:**
1411
-
1412
- ```json
1413
- // Request (no parameters)
1414
- {}
1415
-
1416
- // Response
1417
- {
1418
- "ok": false,
1419
- "errors": [
1420
- {
1421
- "type": "checksum_mismatch",
1422
- "entity": "a1b2c3d4e5f6g7h8j9k0l1m2n3",
1423
- "detail": "File hash does not match filename"
1424
- },
1425
- {
1426
- "type": "orphan_section",
1427
- "entity": "x9y8z7w6v5u4t3s2r1q0p9o8n7",
1428
- "detail": "Section not referenced by any document"
1429
- },
1430
- {
1431
- "type": "broken_reference",
1432
- "entity": "a1b2c3d4e5f6g7h8j9k0l1m2n3",
1433
- "detail": "$(image: missing0id) references non-existent section"
1434
- }
1435
- ],
1436
- "sections_checked": 42,
1437
- "documents_checked": 5,
1438
- "references_checked": 87,
1439
- "elapsed_ms": 150
1440
- }
1441
- ```
1442
-
1443
- The integrity check verifies:
1444
-
1445
- - **checksum_mismatch** -- section file content hash
1446
- does not match the filename (kbid). Indicates
1447
- corruption or manual editing.
1448
- - **orphan_section** -- section file exists but is not
1449
- referenced by any document manifest. Eligible for
1450
- garbage collection.
1451
- - **orphan_document** -- document manifest references
1452
- one or more section kbids that do not exist on disk.
1453
- - **broken_reference** -- a marker (`$(image:)`,
1454
- `$(code:)`, `$(text:)`, `$(document:)`, `$(link:)`,
1455
- or `$(doc-link:)`) references an entity that does
1456
- not exist.
1457
- - **type_mismatch** -- a marker references an entity
1458
- of the wrong type (e.g. `$(image:)` pointing to a
1459
- text section, or `$(code:)` pointing to an image
1460
- section).
1461
- - **circular_reference** -- a `$(text:)` or
1462
- `$(document:)` marker chain loops back to an entity
1463
- already in the expansion path.
1464
- - **divergent_content** -- two or more sections share
1465
- the same `source_key` but have different content
1466
- (different kbids). Indicates contradictory knowledge
1467
- that should have been resolved by supersession.
1468
-
1469
- When `ok` is `true`, the `errors` array is empty.
1470
-
1471
- **Garbage collection:**
1472
-
1473
- ```json
1474
- // Request (no parameters)
1475
- {}
1476
-
1477
- // Response
1478
- {
1479
- "removed_sections": 3,
1480
- "removed_bytes": 15360,
1481
- "stale_sources": 1,
1482
- "elapsed_ms": 25
1483
- }
1484
- ```
1485
-
1486
- Garbage collection removes section files not listed
1487
- in any document manifest. A section is eligible for
1488
- removal when its `docids` array is empty -- it exists
1489
- on disk but no document references it. GC does not
1490
- remove documents, indexes, or the catalog. After
1491
- removal, all caches are invalidated to prevent stale
1492
- reads.
1493
-
1494
- `stale_sources` reports the number of sections whose
1495
- `source_path` points to a file that no longer
1496
- contains their heading text. These sections may be
1497
- outdated but are not automatically removed -- they
1498
- are reported for manual review.
1499
-
1500
- **Index rebuild:**
1501
-
1502
- ```json
1503
- // Request (no parameters)
1504
- {}
1505
-
1506
- // Response
1507
- {
1508
- "sections_reindexed": 39,
1509
- "terms_indexed": 1500,
1510
- "elapsed_ms": 500
1511
- }
1512
- ```
1513
-
1514
- Index rebuild reconstructs all index files
1515
- (`indexes/` directory) from section files and
1516
- document manifests on disk. Text and code sections
1517
- are re-tokenized per field through the
1518
- [Query Parser](query-parser.md) pipeline and their
1519
- per-field term frequencies re-indexed. Image section
1520
- binary content is skipped, but their `title` and
1521
- `description` attributes are re-tokenized and
1522
- re-indexed. All token counts and per-field corpus
1523
- averages are recomputed. Content markers in all
1524
- text sections are re-parsed to rebuild
1525
- `references.idx` (forward and back-reference
1526
- entries). Equivalent to deleting `indexes/` and
1527
- re-adding every section. After rebuild, all caches
1528
- are invalidated.
1529
-
1530
- **Export:**
1531
-
1532
- ```json
1533
- // Request
1534
- {
1535
- "path": "/home/user/backup/kbdb-export"
1536
- }
1537
-
1538
- // Response
1539
- {
1540
- "path": "/home/user/backup/kbdb-export",
1541
- "sections_exported": 42,
1542
- "documents_exported": 5,
1543
- "elapsed_ms": 120
1544
- }
1545
- ```
1546
-
1547
- Export copies sections, documents, and `catalog.toml`
1548
- to the specified output directory. Indexes are not
1549
- included -- they are derived data and can be rebuilt
1550
- with `rebuild` after importing. The export directory
1551
- is a self-contained, git-friendly snapshot suitable
1552
- for backup, transfer, or version control.
1553
-
1554
- ### Error Responses
1555
-
1556
- Errors are returned as JSON-RPC 2.0 error objects:
1557
-
1558
- ```json
1559
- {
1560
- "error": "Description of the error",
1561
- "code": "ERROR_CODE"
1562
- }
1563
- ```
1564
-
1565
- The worker client throws these as typed errors. The
1566
- caller (CLI or MCP) handles them -- e.g. the CLI maps
1567
- them to stderr output.
1568
-
1569
- ## Tech and Algorithms Behind
1570
-
1571
- This section lists the specific algorithms and data
1572
- structures used, organized by module.
1573
-
1574
- ### fs-database.wasm -- Storage
1575
-
1576
- | Approach | Role |
1577
- |-------------------------------------|-----------------------------------------|
1578
- | Content-Addressable Store (SHA-256, typed header) | Section files named by hash of `section <mime-type> <byte-size>\0<content>` -- type-aware deduplication, integrity |
1579
- | Content Preprocessing Pipeline | BOM removal, line ending normalization, Unicode NFC, whitespace trimming -- applied before hashing for text and code sections |
1580
- | Hash-Sharded Directories | 2-char prefix subdirectories for O(1) section lookup |
1581
- | B+ Tree Indexes | Ordered key lookups in binary index files for fast term search |
1582
- | Inverted Index | Maps terms to kb-ids and positions for search |
1583
- | Atomic Writes (temp + rename) | Crash-safe file writes |
1584
- | TOML Manifests | Human-readable metadata for catalog, documents, and index metadata |
1585
-
1586
- ### query-parser.wasm -- Processing
1587
-
1588
- See [Query Parser](query-parser.md) for full details.
1589
-
1590
- | Algorithm / Technique | Role |
1591
- |-------------------------------------|-----------------------------------------|
1592
- | RAKE | Keyphrase extraction |
1593
- | TF-IDF | Term weighting |
1594
- | Snowball Stemmer (Porter2) | Word root form reduction |
1595
- | Unicode Segmentation | Word boundary detection |
1596
- | N-gram Extraction | Bigrams/trigrams for phrase matching |
1597
- | BM25F | Primary ranking function |
1598
- | Proximity / Zone Scoring | Supplementary ranking signals |
1599
-
1600
- ### Section Identification
1601
-
1602
- | Approach | Details |
1603
- |-----------------------------|------------------------------------------|
1604
- | 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 |
1605
- | SHA-256 + Truncation + Base32 | SHA-256 of the typed header, truncated to 128 bits (16 bytes), base32 lowercase (no padding) -- 26 alphanumeric characters |
1606
- | Content Preprocessing | Text and code: BOM removal, `\r\n`/`\r` to `\n`, Unicode NFC, whitespace trimming. Images: base64 string used as-is |
1607
-
1608
- ## Concurrent Write Behavior
1609
-
1610
- The daemon serializes all write operations through a
1611
- **single-writer lock**. The lock is an exclusive file
1612
- lock on `kbdb-{ctx}.lock` in the OS temporary
1613
- directory, keyed to the same context ID used for the
1614
- PID file and IPC socket.
1615
-
1616
- - **Reads** proceed without acquiring the lock. The
1617
- daemon serves concurrent read requests from
1618
- multiple worker client connections.
1619
- - **Writes** (add section, update section, remove
1620
- section, group/remove document, garbage collection,
1621
- index rebuild, export) acquire the lock before
1622
- modifying any file. If another write is in progress,
1623
- the request queues behind the lock.
1624
- - The lock is released after each write operation
1625
- completes, not held across multiple operations.
1626
- - CLI invocations sharing the same daemon are
1627
- serialized automatically.
1628
- - Direct file access to `.kbdb` outside the daemon is
1629
- not supported and may cause corruption. All
1630
- mutations must go through the daemon's IPC API.
1631
-
1632
- This model is equivalent to SQLite's default locking:
1633
- one writer at a time, concurrent readers, no external
1634
- coordination required.
1635
-
1636
- ## Design Constraints
1637
-
1638
- - `fs-database.wasm` is the sole owner of `.kbdb` I/O.
1639
- The worker client module delegates all storage
1640
- operations to the worker daemon over IPC, and the
1641
- daemon delegates to `fs-database.wasm` via
1642
- `kb-worker.wasm`. `src/cli.ts` does not read or
1643
- write database files directly.
1644
- - Sections are content-addressable and identified by
1645
- their content hash. `fs-database.wasm` never modifies
1646
- a section file in place -- updates produce new files
1647
- with new kb-ids.
1648
- - Source keys are advisory logical identity. The
1649
- content hash (kbid) remains the primary identifier.
1650
- Supersession is opt-in via the `replace` flag --
1651
- default learn behavior is unchanged.
1652
- - Indexes are derived data. They can be rebuilt entirely
1653
- from sections and document manifests. Loss of index
1654
- files is recoverable.
1655
- - The `.gitignore` at the database root excludes binary
1656
- indexes from version control by default. Sections,
1657
- documents, and the catalog are the version-controlled
1658
- source of truth.
1659
- - The database should remain human-inspectable and
1660
- version-controllable where practical. Metadata files
1661
- use TOML; section files use TOML headers with raw
1662
- content bodies.
1663
- - No external database process or server dependency.
1664
- - The database must support concurrent read access from
1665
- multiple CLI invocations via the shared daemon without
1666
- corruption. Write operations are serialized through
1667
- the daemon's single-writer lock.
1668
- - Garbage collection of unreferenced sections is
1669
- `fs-database.wasm`'s responsibility. Sections not
1670
- listed in any document manifest may be removed.
1671
- - The cache daemon is a short-lived background process,
1672
- not a persistent server. See
1673
- [Worker Daemon](worker-daemon.md) for daemon lifecycle
1674
- and configuration.
1675
- - All IPC API request and response data uses JSON
1676
- serialization (JSON-RPC 2.0). Inter-module
1677
- communication (`kb-worker.wasm` to both
1678
- `fs-database.wasm` and `query-parser.wasm`) uses shared
1679
- `WebAssembly.Memory` buffers.