@dikolab/kbdb 0.1.6 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) 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.d.ts +25 -0
  11. package/dist/mod.mjs +13 -2
  12. package/dist/mod.mjs.map +2 -2
  13. package/dist/src/cli.d.ts +8 -0
  14. package/dist/src/shared/cli/functions/format-output.function.d.ts +7 -7
  15. package/dist/src/shared/cli/functions/run-search.function.d.ts +25 -16
  16. package/dist/src/shared/cli/index.d.ts +1 -1
  17. package/dist/src/shared/cli/typings/cli-options.interface.d.ts +2 -0
  18. package/dist/src/shared/cli/typings/search-display.model.d.ts +25 -7
  19. package/dist/src/shared/mcp/functions/handle-recall.function.d.ts +29 -0
  20. package/dist/src/shared/mcp/typings/mcp-client.interface.d.ts +30 -11
  21. package/dist/src/shared/version/constants/version.constant.d.ts +1 -1
  22. package/dist/src/shared/wasm-codec/functions/decode-recall-result.function.d.ts +28 -0
  23. package/dist/src/shared/wasm-codec/functions/decode-scored-results.function.d.ts +14 -7
  24. package/dist/src/shared/wasm-codec/functions/encode-recall-params.function.d.ts +21 -0
  25. package/dist/src/shared/wasm-codec/functions/encode-search-params.function.d.ts +1 -1
  26. package/dist/src/shared/wasm-codec/index.d.ts +5 -2
  27. package/dist/src/shared/worker-client/classes/worker-client.class.d.ts +13 -3
  28. package/dist/src/shared/worker-client/index.d.ts +2 -1
  29. package/dist/src/shared/worker-client/typings/recall-result.model.d.ts +94 -0
  30. package/dist/src/shared/worker-client/typings/search-params.model.d.ts +2 -0
  31. package/dist/src/shared/worker-client/typings/search-result.model.d.ts +23 -7
  32. package/dist/src/shared/worker-daemon/classes/worker-daemon.class.d.ts +1 -0
  33. package/dist/src/worker.d.ts +7 -0
  34. package/dist/wasm/fs-database/kbdb_fs_database.d.ts +18 -0
  35. package/dist/wasm/fs-database/kbdb_fs_database.js +30 -0
  36. package/dist/wasm/fs-database/kbdb_fs_database_bg.wasm +0 -0
  37. package/dist/wasm/fs-database/kbdb_fs_database_bg.wasm.d.ts +2 -0
  38. package/dist/wasm/kb-worker/kbdb_worker.d.ts +38 -0
  39. package/dist/wasm/kb-worker/kbdb_worker.js +56 -0
  40. package/dist/wasm/kb-worker/kbdb_worker_bg.wasm +0 -0
  41. package/dist/wasm/kb-worker/kbdb_worker_bg.wasm.d.ts +3 -0
  42. package/dist/worker.mjs +283 -29
  43. package/dist/worker.mjs.map +4 -4
  44. package/docs/details/README.md +26 -0
  45. package/docs/details/cli.md +608 -0
  46. package/docs/details/library-api.md +406 -0
  47. package/docs/details/mcp-server.md +430 -0
  48. package/docs/details/search-and-ranking.md +457 -0
  49. package/docs/details/storage.md +529 -0
  50. package/docs/goals/agents.md +751 -0
  51. package/docs/goals/architecture.svg +198 -0
  52. package/docs/goals/cli.md +2308 -0
  53. package/docs/goals/content-composer.md +609 -0
  54. package/docs/goals/content-parser.md +279 -0
  55. package/docs/goals/database.md +1452 -0
  56. package/docs/goals/document.md +368 -0
  57. package/docs/goals/mcp.md +1467 -0
  58. package/docs/goals/overview.md +124 -0
  59. package/docs/goals/query-parser.md +271 -0
  60. package/docs/goals/query-result.md +693 -0
  61. package/docs/goals/skills.md +738 -0
  62. package/docs/goals/worker-client.md +944 -0
  63. package/docs/goals/worker-daemon.md +1447 -0
  64. package/docs/modules/overview.md +298 -0
  65. package/docs/modules/rust/fs-database/functions/document.md +48 -0
  66. package/docs/modules/rust/fs-database/functions/integrity.md +68 -0
  67. package/docs/modules/rust/fs-database/functions/io.md +87 -0
  68. package/docs/modules/rust/fs-database/functions/module.md +22 -0
  69. package/docs/modules/rust/fs-database/functions/query-exec.md +44 -0
  70. package/docs/modules/rust/fs-database/functions/section.md +59 -0
  71. package/docs/modules/rust/fs-database/indexes/btree.md +35 -0
  72. package/docs/modules/rust/fs-database/indexes/corpus.md +61 -0
  73. package/docs/modules/rust/fs-database/indexes/inverted.md +60 -0
  74. package/docs/modules/rust/fs-database/indexes/module.md +64 -0
  75. package/docs/modules/rust/fs-database/indexes/positional.md +39 -0
  76. package/docs/modules/rust/fs-database/module.md +149 -0
  77. package/docs/modules/rust/fs-database/types/catalog.md +53 -0
  78. package/docs/modules/rust/fs-database/types/module.md +12 -0
  79. package/docs/modules/rust/fs-database/types/section.md +101 -0
  80. package/docs/modules/rust/kb-worker/functions/compose.md +57 -0
  81. package/docs/modules/rust/kb-worker/functions/module.md +20 -0
  82. package/docs/modules/rust/kb-worker/functions/retrieve.md +194 -0
  83. package/docs/modules/rust/kb-worker/functions/search.md +173 -0
  84. package/docs/modules/rust/kb-worker/functions/write.md +172 -0
  85. package/docs/modules/rust/kb-worker/module.md +171 -0
  86. package/docs/modules/rust/kb-worker/types/cache.md +90 -0
  87. package/docs/modules/rust/kb-worker/types/context.md +26 -0
  88. package/docs/modules/rust/kb-worker/types/module.md +16 -0
  89. package/docs/modules/rust/kb-worker/types/status.md +29 -0
  90. package/docs/modules/rust/kb-worker/types/token.md +50 -0
  91. package/docs/modules/rust/overview.md +495 -0
  92. package/docs/modules/rust/query-parser/functions/extract.md +56 -0
  93. package/docs/modules/rust/query-parser/functions/module.md +16 -0
  94. package/docs/modules/rust/query-parser/functions/parse.md +33 -0
  95. package/docs/modules/rust/query-parser/functions/pipeline.md +103 -0
  96. package/docs/modules/rust/query-parser/functions/rank.md +85 -0
  97. package/docs/modules/rust/query-parser/module.md +136 -0
  98. package/docs/modules/rust/query-parser/types/cache.md +39 -0
  99. package/docs/modules/rust/query-parser/types/keyphrase.md +60 -0
  100. package/docs/modules/rust/query-parser/types/module.md +17 -0
  101. package/docs/modules/rust/query-parser/types/token.md +42 -0
  102. package/docs/modules/rust/shared/content-composer/functions.md +114 -0
  103. package/docs/modules/rust/shared/content-composer/module.md +147 -0
  104. package/docs/modules/rust/shared/content-composer/types.md +93 -0
  105. package/docs/modules/rust/shared/content-parser/functions.md +72 -0
  106. package/docs/modules/rust/shared/content-parser/module.md +99 -0
  107. package/docs/modules/rust/shared/content-parser/types.md +71 -0
  108. package/docs/modules/rust/shared/corpus/module.md +24 -0
  109. package/docs/modules/rust/shared/corpus/types.md +141 -0
  110. package/docs/modules/rust/shared/document/module.md +25 -0
  111. package/docs/modules/rust/shared/document/types.md +154 -0
  112. package/docs/modules/rust/shared/encode/module.md +22 -0
  113. package/docs/modules/rust/shared/encode/traits.md +304 -0
  114. package/docs/modules/rust/shared/error/module.md +28 -0
  115. package/docs/modules/rust/shared/error/types.md +302 -0
  116. package/docs/modules/rust/shared/kbid/module.md +24 -0
  117. package/docs/modules/rust/shared/kbid/types.md +147 -0
  118. package/docs/modules/rust/shared/memory/functions.md +209 -0
  119. package/docs/modules/rust/shared/memory/module.md +24 -0
  120. package/docs/modules/rust/shared/module.md +168 -0
  121. package/docs/modules/rust/shared/query/module.md +27 -0
  122. package/docs/modules/rust/shared/query/types.md +236 -0
  123. package/docs/modules/rust/shared/section/module.md +25 -0
  124. package/docs/modules/rust/shared/section/types.md +294 -0
  125. package/docs/modules/rust/shared/term/module.md +25 -0
  126. package/docs/modules/rust/shared/term/types.md +139 -0
  127. package/docs/modules/typescript/cli.md +129 -0
  128. package/docs/modules/typescript/kbdb-worker.md +135 -0
  129. package/docs/modules/typescript/overview.md +378 -0
  130. package/docs/modules/typescript/shared/cli/constants.md +23 -0
  131. package/docs/modules/typescript/shared/cli/functions.md +630 -0
  132. package/docs/modules/typescript/shared/cli/module.md +125 -0
  133. package/docs/modules/typescript/shared/cli/typings.md +172 -0
  134. package/docs/modules/typescript/shared/hash/functions.md +20 -0
  135. package/docs/modules/typescript/shared/hash/module.md +21 -0
  136. package/docs/modules/typescript/shared/mcp/classes.md +179 -0
  137. package/docs/modules/typescript/shared/mcp/functions.md +386 -0
  138. package/docs/modules/typescript/shared/mcp/module.md +160 -0
  139. package/docs/modules/typescript/shared/mcp/typings.md +522 -0
  140. package/docs/modules/typescript/shared/module.md +94 -0
  141. package/docs/modules/typescript/shared/platform/functions.md +42 -0
  142. package/docs/modules/typescript/shared/platform/module.md +25 -0
  143. package/docs/modules/typescript/shared/runtime/functions.md +26 -0
  144. package/docs/modules/typescript/shared/runtime/module.md +27 -0
  145. package/docs/modules/typescript/shared/runtime/typings.md +13 -0
  146. package/docs/modules/typescript/shared/version/module.md +27 -0
  147. package/docs/modules/typescript/shared/wasm-codec/functions.md +391 -0
  148. package/docs/modules/typescript/shared/wasm-codec/module.md +98 -0
  149. package/docs/modules/typescript/shared/worker-client/classes.md +264 -0
  150. package/docs/modules/typescript/shared/worker-client/functions.md +175 -0
  151. package/docs/modules/typescript/shared/worker-client/module.md +84 -0
  152. package/docs/modules/typescript/shared/worker-client/typings.md +508 -0
  153. package/docs/modules/typescript/shared/worker-daemon/classes.md +239 -0
  154. package/docs/modules/typescript/shared/worker-daemon/constants.md +37 -0
  155. package/docs/modules/typescript/shared/worker-daemon/functions.md +221 -0
  156. package/docs/modules/typescript/shared/worker-daemon/module.md +112 -0
  157. package/docs/modules/typescript/shared/worker-daemon/typings.md +126 -0
  158. package/docs/overview.md +188 -0
  159. package/docs/release-notes/0.1.0.md +189 -0
  160. package/docs/release-notes/0.1.1.md +46 -0
  161. package/docs/release-notes/0.1.2.md +38 -0
  162. package/docs/release-notes/0.1.3.md +42 -0
  163. package/docs/release-notes/0.2.0.md +147 -0
  164. package/docs/release-notes/README.md +9 -0
  165. package/mod.ts +26 -0
  166. package/package.json +8 -2
  167. package/src/cli.ts +9 -0
  168. package/src/shared/cli/functions/dispatch-command.function.ts +3 -3
  169. package/src/shared/cli/functions/format-output.function.ts +41 -34
  170. package/src/shared/cli/functions/parse-args.function.ts +7 -0
  171. package/src/shared/cli/functions/run-search.function.ts +53 -27
  172. package/src/shared/cli/index.ts +4 -1
  173. package/src/shared/cli/typings/cli-options.interface.ts +2 -0
  174. package/src/shared/cli/typings/search-display.model.ts +26 -7
  175. package/src/shared/mcp/functions/create-tool-definitions.function.ts +64 -4
  176. package/src/shared/mcp/functions/handle-recall.function.ts +66 -0
  177. package/src/shared/mcp/functions/handle-tool-call.function.ts +11 -1
  178. package/src/shared/mcp/typings/mcp-client.interface.ts +33 -15
  179. package/src/shared/version/constants/version.constant.ts +1 -1
  180. package/src/shared/wasm-codec/functions/decode-recall-result.function.ts +267 -0
  181. package/src/shared/wasm-codec/functions/decode-scored-results.function.ts +81 -27
  182. package/src/shared/wasm-codec/functions/encode-recall-params.function.ts +68 -0
  183. package/src/shared/wasm-codec/functions/encode-search-params.function.ts +4 -1
  184. package/src/shared/wasm-codec/index.ts +11 -3
  185. package/src/shared/worker-client/classes/worker-client.class.ts +24 -4
  186. package/src/shared/worker-client/index.ts +11 -1
  187. package/src/shared/worker-client/typings/recall-result.model.ts +98 -0
  188. package/src/shared/worker-client/typings/search-params.model.ts +2 -0
  189. package/src/shared/worker-client/typings/search-result.model.ts +24 -7
  190. package/src/shared/worker-daemon/classes/worker-daemon.class.ts +25 -4
  191. package/src/wasm/fs-database/kbdb_fs_database.d.ts +18 -0
  192. package/src/wasm/fs-database/kbdb_fs_database.js +30 -0
  193. package/src/wasm/fs-database/kbdb_fs_database_bg.wasm +0 -0
  194. package/src/wasm/fs-database/kbdb_fs_database_bg.wasm.d.ts +2 -0
  195. package/src/wasm/kb-worker/kbdb_worker.d.ts +38 -0
  196. package/src/wasm/kb-worker/kbdb_worker.js +56 -0
  197. package/src/wasm/kb-worker/kbdb_worker_bg.wasm +0 -0
  198. package/src/wasm/kb-worker/kbdb_worker_bg.wasm.d.ts +3 -0
  199. package/src/worker.ts +8 -0
@@ -0,0 +1,529 @@
1
+ # Storage Architecture
2
+
3
+ kbdb stores everything in a `.kbdb` folder inside the
4
+ target directory. There is no external database, no
5
+ cloud dependency, and no running server required for
6
+ data persistence. All metadata files use TOML for
7
+ human inspectability; indexes are binary but derived
8
+ and rebuildable from source data. The entire
9
+ directory is portable -- it can be copied, moved, or
10
+ version-controlled.
11
+
12
+ ## Directory Layout
13
+
14
+ ```
15
+ .kbdb/
16
+ +-- catalog.toml
17
+ +-- sections/
18
+ | +-- {ab}/
19
+ | | +-- {cdef...}.sec
20
+ | +-- ...
21
+ +-- documents/
22
+ | +-- {docid}.toml
23
+ +-- indexes/
24
+ | +-- meta.toml
25
+ | +-- terms.idx
26
+ | +-- positions.idx
27
+ | +-- corpus.dat
28
+ +-- worker.toml
29
+ +-- .gitignore
30
+ ```
31
+
32
+ | Path | Format | Description |
33
+ |-------------------------------|--------|-------------------------------|
34
+ | `catalog.toml` | TOML | Database table of contents: section count, document count, timestamps, integrity checksums |
35
+ | `sections/{ab}/{cdef...}.sec` | TOML + raw | Section files: TOML header + content body, sharded by first two characters of kbid |
36
+ | `documents/{docid}.toml` | TOML | Document manifests: title + ordered section list |
37
+ | `indexes/meta.toml` | TOML | Index metadata: rebuild timestamp, term count, statistics summary |
38
+ | `indexes/terms.idx` | Binary | Inverted index (B+ tree): maps terms to posting lists with per-field term frequencies |
39
+ | `indexes/positions.idx` | Binary | Positional index (B+ tree): maps (term, kbid) to token offset arrays |
40
+ | `indexes/corpus.dat` | Binary | Corpus statistics: section count, average lengths per field, per-term document frequencies |
41
+ | `worker.toml` | TOML | Cache daemon configuration |
42
+ | `.gitignore` | Text | Excludes `indexes/` from version control |
43
+
44
+ The layout mirrors a PostgreSQL directory-format
45
+ dump: a human-readable catalog at the root, one
46
+ subdirectory per data table, and a separate directory
47
+ for binary indexes.
48
+
49
+ ## Content-Addressed IDs (kbid)
50
+
51
+ Every section is identified by a deterministic hash
52
+ of its content. The same content always produces the
53
+ same ID, enabling automatic deduplication.
54
+
55
+ ### Hashing Process
56
+
57
+ ```
58
+ Input content
59
+ |
60
+ v
61
+ Preprocess (text/code only)
62
+ |
63
+ v
64
+ Construct typed header:
65
+ "section <mime-type> <byte-size>\0<content>"
66
+ |
67
+ v
68
+ SHA-256 hash
69
+ |
70
+ v
71
+ Truncate to first 16 bytes (128 bits)
72
+ |
73
+ v
74
+ Base32 lowercase encoding (no padding)
75
+ |
76
+ v
77
+ 26-character alphanumeric kbid
78
+ ```
79
+
80
+ ### Hash Input Format
81
+
82
+ The hash input follows the same pattern as Git
83
+ objects (`blob <size>\0<content>`):
84
+
85
+ ```
86
+ section <mime-type> <byte-size>\0<preprocessed-content>
87
+ ```
88
+
89
+ - `section` -- literal string prefix (type tag)
90
+ - `<mime-type>` -- the section's MIME type string
91
+ (e.g. `text/markdown`, `text/typescript`,
92
+ `image/png`)
93
+ - `<byte-size>` -- byte length of the preprocessed
94
+ content, as a decimal ASCII string
95
+ - `\0` -- literal null byte separator
96
+ - `<preprocessed-content>` -- the content after
97
+ preprocessing (text/code) or the base64 string
98
+ as-is (images)
99
+
100
+ ### Worked Example
101
+
102
+ ```
103
+ Given:
104
+ type = "text/markdown"
105
+ content = "Hello, world!" (13 bytes after preprocessing)
106
+
107
+ Hash input (41 bytes):
108
+ section text/markdown 13\0Hello, world!
109
+
110
+ +-- header: "section text/markdown 13"
111
+ +-- null byte: \0
112
+ +-- content: "Hello, world!"
113
+
114
+ kbid = base32(sha256(hash_input)[..16])
115
+ = 26-character lowercase alphanumeric string
116
+ ```
117
+
118
+ ### Properties
119
+
120
+ - **Deterministic** -- identical content with the
121
+ same MIME type always produces the same kbid.
122
+ - **Type-aware** -- the MIME type is part of the hash
123
+ input, so identical raw content stored as different
124
+ types (e.g. `text/markdown` vs `text/typescript`)
125
+ produces different kbids.
126
+ - **Collision-resistant** -- SHA-256 truncated to 128
127
+ bits provides sufficient collision resistance for a
128
+ local knowledge base.
129
+ - **URL-safe** -- base32 lowercase encoding uses only
130
+ alphanumeric characters.
131
+
132
+ ## Content Preprocessing
133
+
134
+ Before computing the kbid, text and code sections
135
+ pass through a preprocessing pipeline that
136
+ normalizes encoding artifacts. This ensures that
137
+ semantically identical content -- differing only in
138
+ BOM, line endings, Unicode representation, or
139
+ surrounding whitespace -- produces the same kbid.
140
+
141
+ ### Pipeline Steps (Applied in Order)
142
+
143
+ 1. **BOM removal** -- Strip the UTF-8 byte order mark
144
+ (U+FEFF) if present at the start of the content.
145
+ The BOM is an encoding artifact, not meaningful
146
+ content.
147
+
148
+ 2. **Line ending normalization** -- Convert all
149
+ `\r\n` (Windows) and bare `\r` (old Mac) sequences
150
+ to `\n` (Unix). Content created on different
151
+ platforms produces the same kbid.
152
+
153
+ 3. **Unicode NFC normalization** -- Compose decomposed
154
+ Unicode sequences into their canonical form. For
155
+ example, `e` + combining acute accent becomes `e`
156
+ (U+00E9). This is the same normalization used in
157
+ the tokenization pipeline.
158
+
159
+ 4. **Whitespace trimming** -- Remove all leading and
160
+ trailing whitespace characters (spaces, tabs,
161
+ newlines, carriage returns, and other Unicode
162
+ whitespace and control characters).
163
+
164
+ ### Scope
165
+
166
+ This pipeline applies to **text and code sections
167
+ only**. Image sections use the base64-encoded string
168
+ as-is -- no preprocessing is applied.
169
+
170
+ The preprocessed content is what gets stored on disk.
171
+ The pipeline runs once at ingest time; subsequent
172
+ reads return the already-preprocessed content. The
173
+ `<byte-size>` in the hash header is computed from the
174
+ content after preprocessing.
175
+
176
+ ## Section File Format
177
+
178
+ Each section file has a TOML header followed by a
179
+ `---` separator and the raw content body. The header
180
+ is human-readable; the content after the separator is
181
+ stored verbatim.
182
+
183
+ ### Header Fields
184
+
185
+ | Field | Type | Required | Description |
186
+ |-----------------------|--------|----------|-----------------------|
187
+ | `kbid` | string | Yes | 26-character content hash |
188
+ | `docids` | array | Yes | Document IDs referencing this section |
189
+ | `type` | string | Yes | MIME type (`text/markdown`, `text/typescript`, `image/png`, etc.) |
190
+ | `title` | string | Images: yes, others: no | Short descriptive title. Indexed with heading weight (2.0x) |
191
+ | `description` | string | No | Longer description. Indexed with body weight (1.0x) |
192
+ | `size` | u32 | Yes | Content byte size |
193
+ | `token_count` | u32 | Yes | Total tokens after tokenization |
194
+ | `heading_token_count` | u32 | Yes | Tokens from `title` |
195
+ | `body_token_count` | u32 | Yes | Tokens from body text + `description` |
196
+ | `code_token_count` | u32 | Yes | Tokens from code content |
197
+ | `created_at` | string | Yes | ISO 8601 timestamp |
198
+ | `checksum` | string | Yes | Integrity checksum |
199
+
200
+ The `title` and `description` fields are not included
201
+ in the kbid hash -- changing them does not change the
202
+ section's identity.
203
+
204
+ Token count fields are pre-computed at insertion time
205
+ by `query-parser.wasm` and stored for reuse at query
206
+ time. This trades a small write-time cost for
207
+ significant search speed.
208
+
209
+ ### Example: Text Section
210
+
211
+ File: `sections/ab/cdef1234...sec`
212
+
213
+ ```
214
+ kbid = "abcdef1234567890abcdef1234"
215
+ docids = ["doc-5678"]
216
+ type = "text/markdown"
217
+ title = "Architecture Overview"
218
+ description = "High-level overview of the system"
219
+ size = 1234
220
+ token_count = 187
221
+ heading_token_count = 2
222
+ body_token_count = 187
223
+ code_token_count = 0
224
+ created_at = "2026-06-15T10:00:00Z"
225
+ checksum = "sha256:abcdef..."
226
+ ---
227
+ Section body text here.
228
+
229
+ $(image: 7fa93b2e4c8d1076a93b2e4c8d)
230
+
231
+ $(code: c4e8a91d3f7b20564e8a91d3f7)
232
+
233
+ This content continues after the embedded sections.
234
+ No escaping or encoding is applied.
235
+ ```
236
+
237
+ ### Example: Code Section
238
+
239
+ File: `sections/c4/e8a91d...sec`
240
+
241
+ ```
242
+ kbid = "c4e8a91d3f7b20564e8a91d3f7"
243
+ docids = ["doc-5678"]
244
+ type = "text/typescript"
245
+ title = "greet function"
246
+ size = 256
247
+ token_count = 22
248
+ heading_token_count = 2
249
+ body_token_count = 0
250
+ code_token_count = 22
251
+ created_at = "2026-06-15T10:15:00Z"
252
+ checksum = "sha256:1a2b3c..."
253
+ ---
254
+ export function greet(name: string): string {
255
+ return `Hello, ${name}!`;
256
+ }
257
+ ```
258
+
259
+ ### Example: Image Section
260
+
261
+ File: `sections/7f/a93b2e...sec`
262
+
263
+ ```
264
+ kbid = "7fa93b2e4c8d1076a93b2e4c8d"
265
+ docids = ["doc-5678"]
266
+ type = "image/png"
267
+ title = "Architecture diagram"
268
+ description = "Five-layer stack diagram"
269
+ size = 48576
270
+ token_count = 5
271
+ heading_token_count = 2
272
+ body_token_count = 3
273
+ code_token_count = 0
274
+ created_at = "2026-06-15T10:30:00Z"
275
+ checksum = "sha256:fedcba..."
276
+ ---
277
+ iVBORw0KGgoAAAANSUhEUgAA...base64 data...
278
+ ```
279
+
280
+ The image section has `token_count = 5` from title
281
+ and description tokens only. Binary content produces
282
+ zero tokens.
283
+
284
+ ### Section Types
285
+
286
+ | Type | Content after `---` | Indexed? |
287
+ |---------------------|------------------------|----------|
288
+ | `text/markdown` | Raw markdown text | Yes (body field) |
289
+ | `text/*` (code) | Raw source code | Yes (code field) |
290
+ | `application/json` | Raw JSON | Yes (code field) |
291
+ | `image/*` | Base64-encoded binary | No (metadata only) |
292
+
293
+ Code sections are processed through the same
294
+ tokenization pipeline as text, including identifier
295
+ splitting for camelCase and snake_case terms. Image
296
+ binary content is skipped, but `title` (required) and
297
+ `description` (optional) are tokenized and indexed.
298
+
299
+ ## Document Manifests
300
+
301
+ A document groups sections under a title with a
302
+ defined order. Documents do not contain content
303
+ directly -- they reference sections by kbid. The
304
+ relationship is many-to-many: a section may appear in
305
+ multiple documents, and a document contains multiple
306
+ sections.
307
+
308
+ ### Format
309
+
310
+ ```toml
311
+ [document]
312
+ docid = "doc-5678"
313
+ title = "Getting Started Guide"
314
+ created_at = "2026-06-15T10:00:00Z"
315
+
316
+ [[sections]]
317
+ kbid = "abcdef1234567890abcdef1234"
318
+ position = 0
319
+
320
+ [[sections]]
321
+ kbid = "fedcba4321098765fedcba4321"
322
+ position = 1
323
+ ```
324
+
325
+ ### Fields
326
+
327
+ | Field | Description |
328
+ |--------------------------|--------------------------|
329
+ | `document.docid` | Document identifier |
330
+ | `document.title` | Human-readable title |
331
+ | `document.created_at` | ISO 8601 timestamp |
332
+ | `sections[].kbid` | Section content hash |
333
+ | `sections[].position` | Zero-based display order |
334
+
335
+ Documents are human-readable and diffable in git.
336
+ When a document is retrieved, its sections are
337
+ returned in position order with full content.
338
+
339
+ ## Catalog (`catalog.toml`)
340
+
341
+ The catalog is the database's table of contents --
342
+ the first file read when opening a database. It
343
+ records high-level state without requiring a
344
+ directory scan.
345
+
346
+ ```toml
347
+ [database]
348
+ version = 1
349
+ created_at = "2026-06-15T10:00:00Z"
350
+ last_modified = "2026-06-15T12:00:00Z"
351
+
352
+ [counts]
353
+ sections = 42
354
+ documents = 5
355
+
356
+ [integrity]
357
+ checksum = "sha256:abcdef..."
358
+ ```
359
+
360
+ Updated on every write operation. If a crash occurs
361
+ mid-write, the catalog remains consistent with the
362
+ last complete state (see Write Safety below).
363
+
364
+ ## MIME Types
365
+
366
+ kbdb uses standard MIME type strings to identify
367
+ section content:
368
+
369
+ | Category | MIME types | Canonical |
370
+ |-----------|-------------------------------------|-----------------|
371
+ | Text | `text/markdown` | `text/markdown` |
372
+ | Code | `text/typescript`, `text/javascript`, `text/html`, `text/css`, `text/xml`, `text/plain`, `application/json` | Varies |
373
+ | Image | `image/png`, `image/jpeg`, `image/webp` | Varies |
374
+
375
+ The bare string `"text"` is not used. Text sections
376
+ use `text/markdown` as their canonical type. The MIME
377
+ type is part of the kbid hash, so the same raw
378
+ content stored under different types produces
379
+ different section files.
380
+
381
+ ## Write Safety
382
+
383
+ All write operations are designed to be crash-safe:
384
+
385
+ - **Atomic writes** -- New files are written to a
386
+ temporary path and then renamed into place. A crash
387
+ during the write leaves no partial files -- either
388
+ the old file remains or the new file is complete.
389
+
390
+ - **Catalog updated last** -- The catalog is written
391
+ after all data files. A crash mid-write leaves the
392
+ catalog consistent with the last fully committed
393
+ state. The worst case is an unreferenced section
394
+ file on disk, which garbage collection handles.
395
+
396
+ - **Indexes rebuildable** -- Index files are derived
397
+ data. If index files are corrupted or lost, they
398
+ can be rebuilt entirely from sections and document
399
+ manifests. The `.gitignore` excludes indexes from
400
+ version control because they are not source data.
401
+
402
+ ### Write Sequence
403
+
404
+ ```
405
+ 1. Write section file to temp path
406
+ |
407
+ v
408
+ 2. Rename temp file into sections/{ab}/{kbid}.sec
409
+ |
410
+ v
411
+ 3. Update document manifest (if docid provided)
412
+ |
413
+ v
414
+ 4. Update indexes (inverted + positional + corpus)
415
+ |
416
+ v
417
+ 5. Update catalog.toml (counts, timestamp, checksum)
418
+ ```
419
+
420
+ Each step is atomic. The catalog update in step 5
421
+ commits the entire operation. Steps 1-4 can fail
422
+ without corrupting the database.
423
+
424
+ ## Content Markers
425
+
426
+ Text sections can contain markers -- escape sequences
427
+ that embed or link to other sections and documents.
428
+ Markers are stored verbatim in the content body and
429
+ resolved at read time, not at write time.
430
+
431
+ ### Marker Types
432
+
433
+ | Marker | Category | Target type | Render behavior |
434
+ |-----------------------------|-----------|--------------------------|------------------------|
435
+ | `$(image: {kbid})` | Embedding | `image/*` section | Inline image |
436
+ | `$(code: {kbid})` | Embedding | `text/*` or `application/json` section | Fenced code block |
437
+ | `$(text: {kbid})` | Embedding | `text/markdown` section | Inline markdown (recursive) |
438
+ | `$(document: {docid})` | Embedding | Document manifest | All sections expanded (recursive) |
439
+ | `$(link: {kbid})` | Link | Any section | Cross-reference |
440
+ | `$(doc-link: {docid})` | Link | Document manifest | Cross-reference |
441
+
442
+ ### Syntax
443
+
444
+ Markers use the format `$(type: value)`:
445
+
446
+ ```
447
+ $(image: 7fa93b2e4c8d1076a93b2e4c8d)
448
+ $(code: c4e8a91d3f7b20564e8a91d3f7)
449
+ $(text: a1b2c3d4e5f6a1b2c3d4e5f6a1)
450
+ $(document: api-reference-v2)
451
+ $(link: d5e6f7a8b9c0d5e6f7a8b9c0d5)
452
+ $(doc-link: getting-started-guide)
453
+ ```
454
+
455
+ Rules:
456
+ - A colon and a space separate type from value.
457
+ - Markers cannot be nested.
458
+ - Unrecognized types pass through unchanged.
459
+ - Unclosed markers are treated as plain text.
460
+
461
+ ### Recursive Resolution
462
+
463
+ The `$(text:)` and `$(document:)` markers trigger
464
+ recursive expansion -- the embedded content may
465
+ itself contain markers that must be resolved.
466
+
467
+ Circular references are detected using a visited set:
468
+
469
+ ```
470
+ Expand marker $(text: A)
471
+ |
472
+ v
473
+ Add A to visited set: {A}
474
+ |
475
+ v
476
+ Section A contains $(text: B)
477
+ |
478
+ v
479
+ Add B to visited set: {A, B}
480
+ |
481
+ v
482
+ Section B contains $(text: A)
483
+ |
484
+ v
485
+ A is already in visited set -> STOP (circular)
486
+ ```
487
+
488
+ Circular references are reported as errors during
489
+ both integrity checks and render-time expansion.
490
+
491
+ ### Tokenization
492
+
493
+ All six marker types are stripped from content before
494
+ tokenization and indexing (stage 1 of the
495
+ tokenization pipeline). Markers are structural
496
+ references, not searchable text -- they do not appear
497
+ in the search index.
498
+
499
+ ## Deduplication
500
+
501
+ Sections are content-addressable. When identical
502
+ content (after preprocessing) is added with the same
503
+ MIME type:
504
+
505
+ | Scenario | Result |
506
+ |-----------------------|-----------------------------|
507
+ | Different `docid` | Existing section's `docids` array updated to include the new docid. No new file created. |
508
+ | No `docid` | Section skipped entirely. |
509
+ | Same `docid` already present | Idempotent -- no change. |
510
+
511
+ A section can belong to zero or more documents.
512
+ Updating a section's content creates a new file with
513
+ a new kbid; the old file becomes eligible for garbage
514
+ collection.
515
+
516
+ ## Storage Performance
517
+
518
+ The directory layout is optimized for read speed:
519
+
520
+ | Operation | Complexity | Mechanism |
521
+ |-------------------|------------|-------------------------|
522
+ | Section lookup | O(1) | Hash-sharded directories allow direct file access by kbid |
523
+ | Term lookup | O(log n) | B+ tree indexes |
524
+ | Catalog discovery | O(1) | `catalog.toml` provides counts without scanning |
525
+ | Document listing | O(1) | Direct file access by docid |
526
+
527
+ Write operations prioritize safety over speed. The
528
+ atomic write + catalog-last pattern ensures no
529
+ partial state is visible after a crash.