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