@dikolab/kbdb 0.4.3 → 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.
- package/CLA.md +68 -0
- package/LICENSE +676 -12
- package/LICENSING.md +58 -0
- package/README.md +50 -27
- package/README.md.bak +320 -0
- package/dist/README.md +320 -0
- package/dist/{chunk-BJXEVALU.mjs → chunk-QLJ33C74.mjs} +44 -44
- package/dist/chunk-QLJ33C74.mjs.map +7 -0
- package/dist/cli.cjs +550 -232
- package/dist/cli.cjs.map +4 -4
- package/dist/cli.d.ts +0 -8
- package/dist/cli.mjs +483 -154
- package/dist/cli.mjs.map +4 -4
- package/dist/kbdb-worker.cjs +36 -5
- package/dist/kbdb-worker.cjs.map +3 -3
- package/dist/mod.cjs +89 -30
- package/dist/mod.cjs.map +4 -4
- package/dist/mod.mjs +88 -30
- package/dist/mod.mjs.map +4 -4
- package/dist/src/cli.d.ts +0 -8
- package/dist/src/shared/cli/constants/defaults.constant.d.ts +1 -1
- package/dist/src/shared/cli/constants/recfile-command-field-map.constant.d.ts +8 -0
- package/dist/src/shared/cli/constants/recfile-field-maps.constant.d.ts +21 -0
- package/dist/src/shared/cli/functions/compute-directory-level.function.d.ts +14 -0
- package/dist/src/shared/cli/functions/dispatch-command.function.d.ts +2 -28
- package/dist/src/shared/cli/functions/format-command-output.function.d.ts +14 -0
- package/dist/src/shared/cli/functions/format-db-not-found.function.d.ts +20 -0
- package/dist/src/shared/cli/functions/format-output.function.d.ts +1 -0
- package/dist/src/shared/cli/functions/format-recfile-output.function.d.ts +11 -0
- package/dist/src/shared/cli/functions/render-recfile-value.function.d.ts +11 -0
- package/dist/src/shared/cli/functions/resolve-db-dir.function.d.ts +24 -0
- package/dist/src/shared/cli/functions/resolve-recfile-key.function.d.ts +9 -0
- package/dist/src/shared/cli/functions/run-db-path.function.d.ts +10 -0
- package/dist/src/shared/cli/functions/run-export.function.d.ts +8 -4
- package/dist/src/shared/cli/functions/run-import.function.d.ts +4 -0
- package/dist/src/shared/cli/functions/run-learn.function.d.ts +12 -0
- package/dist/src/shared/cli/index.d.ts +4 -1
- package/dist/src/shared/cli/typings/cli-options.interface.d.ts +9 -2
- package/dist/src/shared/cli/typings/db-resolution.interface.d.ts +9 -0
- package/dist/src/shared/cli/typings/recfile-field-map.type.d.ts +10 -0
- package/dist/src/shared/version/constants/version.constant.d.ts +1 -1
- package/dist/src/shared/wasm-codec/functions/decode-option-u8.function.d.ts +9 -0
- package/dist/src/shared/wasm-codec/functions/decode-section-records.function.d.ts +4 -0
- package/dist/src/shared/wasm-codec/functions/encode-add-section-params.function.d.ts +2 -45
- package/dist/src/shared/wasm-codec/functions/encode-update-section-params.function.d.ts +10 -1
- package/dist/src/shared/wasm-codec/index.d.ts +1 -1
- package/dist/src/shared/wasm-codec/typings/add-section-input.model.d.ts +6 -0
- package/dist/src/shared/wasm-codec/typings/section-record-trailing.model.d.ts +5 -0
- package/dist/src/shared/wasm-codec/typings/section-record.model.d.ts +5 -0
- package/dist/src/shared/worker-client/functions/build-deno-permissions.function.d.ts +11 -0
- package/dist/src/shared/worker-client/functions/read-crash-reason.function.d.ts +9 -0
- package/dist/src/shared/worker-client/functions/resolve-spawn-script.function.d.ts +6 -0
- package/dist/src/shared/worker-client/functions/spawn-daemon.function.d.ts +1 -1
- package/dist/src/shared/worker-client/typings/section.model.d.ts +5 -0
- package/dist/src/shared/worker-client/typings/worker-client-options.interface.d.ts +0 -6
- package/dist/src/shared/worker-daemon/functions/count-files.function.d.ts +12 -0
- package/dist/src/shared/worker-daemon/functions/handle-import.function.d.ts +2 -0
- package/dist/src/shared/worker-daemon/functions/handle-read-ops.function.d.ts +8 -0
- package/dist/src/shared/worker-daemon/functions/validate-daemon-path.function.d.ts +15 -0
- package/dist/src/worker.d.ts +0 -7
- package/dist/wasm/default-embedding/kbdb_default_embedding_bg.wasm +0 -0
- package/dist/wasm/default-embedding/package.json +1 -1
- package/dist/wasm/fs-database/kbdb_fs_database_bg.wasm +0 -0
- package/dist/wasm/fs-database/package.json +1 -1
- package/dist/wasm/fs-migration/kbdb_fs_migration_bg.wasm +0 -0
- package/dist/wasm/fs-migration/package.json +1 -1
- package/dist/wasm/kb-worker/kbdb_worker_bg.wasm +0 -0
- package/dist/wasm/kb-worker/package.json +1 -1
- package/dist/wasm/query-parser/kbdb_query_parser_bg.wasm +0 -0
- package/dist/wasm/query-parser/package.json +1 -1
- package/dist/worker.d.ts +0 -7
- package/dist/worker.mjs +37 -6
- package/dist/worker.mjs.map +3 -3
- package/package.json +13 -12
- package/dist/chunk-BJXEVALU.mjs.map +0 -7
- package/dist/src/shared/cli/functions/resolve-raw-db-path.function.d.ts +0 -10
- package/docs/details/README.md +0 -30
- package/docs/details/agent-tooling.md +0 -132
- package/docs/details/cli.md +0 -777
- package/docs/details/knowledge-base.md +0 -180
- package/docs/details/library-api.md +0 -495
- package/docs/details/mcp-server.md +0 -482
- package/docs/details/search-and-ranking.md +0 -575
- package/docs/details/storage.md +0 -531
- package/docs/goals/agents.md +0 -751
- package/docs/goals/architecture.svg +0 -198
- package/docs/goals/auto-capture.md +0 -378
- package/docs/goals/benchmarking.md +0 -296
- package/docs/goals/cli.md +0 -2654
- package/docs/goals/concurrency.md +0 -259
- package/docs/goals/content-composer.md +0 -609
- package/docs/goals/content-parser.md +0 -279
- package/docs/goals/database.md +0 -1679
- package/docs/goals/document.md +0 -368
- package/docs/goals/hybrid-search.md +0 -465
- package/docs/goals/mcp.md +0 -1541
- package/docs/goals/overview.md +0 -124
- package/docs/goals/query-parser.md +0 -373
- package/docs/goals/query-result.md +0 -860
- package/docs/goals/semantic-dedup.md +0 -233
- package/docs/goals/skills.md +0 -810
- package/docs/goals/sync.md +0 -217
- package/docs/goals/worker-client.md +0 -1005
- package/docs/goals/worker-daemon.md +0 -1547
- package/docs/modules/overview.md +0 -319
- package/docs/modules/rust/embedding/module.md +0 -91
- package/docs/modules/rust/fs-database/functions/document.md +0 -48
- package/docs/modules/rust/fs-database/functions/integrity.md +0 -79
- package/docs/modules/rust/fs-database/functions/io.md +0 -87
- package/docs/modules/rust/fs-database/functions/module.md +0 -22
- package/docs/modules/rust/fs-database/functions/query-exec.md +0 -44
- package/docs/modules/rust/fs-database/functions/section.md +0 -76
- package/docs/modules/rust/fs-database/indexes/btree.md +0 -35
- package/docs/modules/rust/fs-database/indexes/corpus.md +0 -61
- package/docs/modules/rust/fs-database/indexes/inverted.md +0 -60
- package/docs/modules/rust/fs-database/indexes/module.md +0 -64
- package/docs/modules/rust/fs-database/indexes/positional.md +0 -39
- package/docs/modules/rust/fs-database/indexes/vectors.md +0 -110
- package/docs/modules/rust/fs-database/module.md +0 -150
- package/docs/modules/rust/fs-database/types/catalog.md +0 -53
- package/docs/modules/rust/fs-database/types/module.md +0 -12
- package/docs/modules/rust/fs-database/types/section.md +0 -127
- package/docs/modules/rust/kb-worker/functions/compose.md +0 -57
- package/docs/modules/rust/kb-worker/functions/module.md +0 -20
- package/docs/modules/rust/kb-worker/functions/retrieve.md +0 -194
- package/docs/modules/rust/kb-worker/functions/search.md +0 -173
- package/docs/modules/rust/kb-worker/functions/write.md +0 -172
- package/docs/modules/rust/kb-worker/module.md +0 -171
- package/docs/modules/rust/kb-worker/types/cache.md +0 -90
- package/docs/modules/rust/kb-worker/types/context.md +0 -26
- package/docs/modules/rust/kb-worker/types/module.md +0 -16
- package/docs/modules/rust/kb-worker/types/status.md +0 -29
- package/docs/modules/rust/kb-worker/types/token.md +0 -50
- package/docs/modules/rust/overview.md +0 -531
- package/docs/modules/rust/query-parser/functions/extract.md +0 -56
- package/docs/modules/rust/query-parser/functions/module.md +0 -18
- package/docs/modules/rust/query-parser/functions/parse.md +0 -33
- package/docs/modules/rust/query-parser/functions/pipeline.md +0 -9
- package/docs/modules/rust/query-parser/functions/rank.md +0 -85
- package/docs/modules/rust/query-parser/module.md +0 -131
- package/docs/modules/rust/query-parser/types/cache.md +0 -39
- package/docs/modules/rust/query-parser/types/keyphrase.md +0 -71
- package/docs/modules/rust/query-parser/types/module.md +0 -18
- package/docs/modules/rust/query-parser/types/token.md +0 -46
- package/docs/modules/rust/shared/content-composer/functions.md +0 -114
- package/docs/modules/rust/shared/content-composer/module.md +0 -147
- package/docs/modules/rust/shared/content-composer/types.md +0 -93
- package/docs/modules/rust/shared/content-parser/functions.md +0 -72
- package/docs/modules/rust/shared/content-parser/module.md +0 -99
- package/docs/modules/rust/shared/content-parser/types.md +0 -71
- package/docs/modules/rust/shared/corpus/module.md +0 -24
- package/docs/modules/rust/shared/corpus/types.md +0 -141
- package/docs/modules/rust/shared/document/module.md +0 -25
- package/docs/modules/rust/shared/document/types.md +0 -154
- package/docs/modules/rust/shared/encode/module.md +0 -22
- package/docs/modules/rust/shared/encode/traits.md +0 -304
- package/docs/modules/rust/shared/error/module.md +0 -28
- package/docs/modules/rust/shared/error/types.md +0 -302
- package/docs/modules/rust/shared/kbid/module.md +0 -24
- package/docs/modules/rust/shared/kbid/types.md +0 -147
- package/docs/modules/rust/shared/memory/functions.md +0 -209
- package/docs/modules/rust/shared/memory/module.md +0 -24
- package/docs/modules/rust/shared/module.md +0 -196
- package/docs/modules/rust/shared/pipeline/functions.md +0 -141
- package/docs/modules/rust/shared/pipeline/module.md +0 -62
- package/docs/modules/rust/shared/pipeline/types.md +0 -43
- package/docs/modules/rust/shared/query/module.md +0 -27
- package/docs/modules/rust/shared/query/types.md +0 -236
- package/docs/modules/rust/shared/section/module.md +0 -25
- package/docs/modules/rust/shared/section/types.md +0 -294
- package/docs/modules/rust/shared/term/module.md +0 -25
- package/docs/modules/rust/shared/term/types.md +0 -139
- package/docs/modules/typescript/cli.md +0 -131
- package/docs/modules/typescript/kbdb-worker.md +0 -150
- package/docs/modules/typescript/overview.md +0 -400
- package/docs/modules/typescript/shared/auto-capture/module.md +0 -73
- package/docs/modules/typescript/shared/cli/constants.md +0 -23
- package/docs/modules/typescript/shared/cli/functions.md +0 -809
- package/docs/modules/typescript/shared/cli/module.md +0 -138
- package/docs/modules/typescript/shared/cli/typings.md +0 -197
- package/docs/modules/typescript/shared/embedding/functions.md +0 -200
- package/docs/modules/typescript/shared/embedding/module.md +0 -77
- package/docs/modules/typescript/shared/embedding/typings.md +0 -124
- package/docs/modules/typescript/shared/hash/functions.md +0 -20
- package/docs/modules/typescript/shared/hash/module.md +0 -21
- package/docs/modules/typescript/shared/log/constants.md +0 -82
- package/docs/modules/typescript/shared/log/functions.md +0 -131
- package/docs/modules/typescript/shared/log/module.md +0 -153
- package/docs/modules/typescript/shared/log/typings.md +0 -82
- package/docs/modules/typescript/shared/mcp/classes.md +0 -179
- package/docs/modules/typescript/shared/mcp/functions.md +0 -386
- package/docs/modules/typescript/shared/mcp/module.md +0 -160
- package/docs/modules/typescript/shared/mcp/typings.md +0 -529
- package/docs/modules/typescript/shared/module.md +0 -110
- package/docs/modules/typescript/shared/platform/functions.md +0 -42
- package/docs/modules/typescript/shared/platform/module.md +0 -25
- package/docs/modules/typescript/shared/runtime/functions.md +0 -26
- package/docs/modules/typescript/shared/runtime/module.md +0 -27
- package/docs/modules/typescript/shared/runtime/typings.md +0 -13
- package/docs/modules/typescript/shared/version/module.md +0 -27
- package/docs/modules/typescript/shared/wasm-codec/functions.md +0 -391
- package/docs/modules/typescript/shared/wasm-codec/module.md +0 -98
- package/docs/modules/typescript/shared/worker-client/classes.md +0 -264
- package/docs/modules/typescript/shared/worker-client/functions.md +0 -175
- package/docs/modules/typescript/shared/worker-client/module.md +0 -92
- package/docs/modules/typescript/shared/worker-client/typings.md +0 -511
- package/docs/modules/typescript/shared/worker-daemon/classes.md +0 -239
- package/docs/modules/typescript/shared/worker-daemon/constants.md +0 -37
- package/docs/modules/typescript/shared/worker-daemon/functions.md +0 -234
- package/docs/modules/typescript/shared/worker-daemon/module.md +0 -118
- package/docs/modules/typescript/shared/worker-daemon/typings.md +0 -132
- package/docs/overview.md +0 -191
- package/docs/release-notes/0.1.0.md +0 -189
- package/docs/release-notes/0.1.1.md +0 -46
- package/docs/release-notes/0.1.2.md +0 -38
- package/docs/release-notes/0.1.3.md +0 -42
- package/docs/release-notes/0.2.0.md +0 -147
- package/docs/release-notes/0.3.0.md +0 -89
- package/docs/release-notes/0.3.1.md +0 -121
- package/docs/release-notes/0.3.2.md +0 -64
- package/docs/release-notes/0.4.0.md +0 -80
- package/docs/release-notes/0.4.1.md +0 -149
- package/docs/release-notes/0.4.3.md +0 -60
- package/docs/release-notes/README.md +0 -13
package/docs/goals/database.md
DELETED
|
@@ -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.
|