@hviana/sema 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/AGENTS.md +6 -5
  2. package/CITATION.cff +49 -0
  3. package/HOW_IT_WORKS.md +11 -12
  4. package/README.md +7 -5
  5. package/dist/example/demo.js +25 -25
  6. package/dist/example/train_base.d.ts +19 -21
  7. package/dist/example/train_base.js +1700 -1952
  8. package/dist/src/alphabet.d.ts +3 -3
  9. package/dist/src/alphabet.js +24 -27
  10. package/dist/src/alu/src/alu.d.ts +169 -194
  11. package/dist/src/alu/src/alu.js +374 -414
  12. package/dist/src/alu/src/expr.d.ts +36 -43
  13. package/dist/src/alu/src/expr.js +252 -278
  14. package/dist/src/alu/src/index.d.ts +9 -98
  15. package/dist/src/alu/src/index.js +10 -64
  16. package/dist/src/alu/src/kernel-arith.d.ts +1 -5
  17. package/dist/src/alu/src/kernel-arith.js +195 -285
  18. package/dist/src/alu/src/kernel-bits.d.ts +1 -5
  19. package/dist/src/alu/src/kernel-bits.js +81 -115
  20. package/dist/src/alu/src/kernel-logic.js +33 -63
  21. package/dist/src/alu/src/kernel-nd.js +155 -195
  22. package/dist/src/alu/src/kernel-numeric.d.ts +16 -80
  23. package/dist/src/alu/src/kernel-numeric.js +259 -331
  24. package/dist/src/alu/src/operation.d.ts +112 -132
  25. package/dist/src/alu/src/operation.js +149 -163
  26. package/dist/src/alu/src/parser.d.ts +168 -175
  27. package/dist/src/alu/src/parser.js +382 -421
  28. package/dist/src/alu/src/resonance.d.ts +16 -25
  29. package/dist/src/alu/src/resonance.js +49 -56
  30. package/dist/src/alu/src/text.d.ts +3 -7
  31. package/dist/src/alu/src/text.js +31 -37
  32. package/dist/src/alu/src/value.d.ts +14 -14
  33. package/dist/src/alu/src/value.js +150 -160
  34. package/dist/src/alu/test/alu.test.js +543 -656
  35. package/dist/src/bytes.d.ts +1 -5
  36. package/dist/src/bytes.js +33 -40
  37. package/dist/src/config.d.ts +99 -102
  38. package/dist/src/config.js +78 -83
  39. package/dist/src/derive/src/deduction.d.ts +60 -62
  40. package/dist/src/derive/src/deduction.js +103 -109
  41. package/dist/src/derive/src/index.d.ts +1 -7
  42. package/dist/src/derive/src/priority-queue.d.ts +8 -8
  43. package/dist/src/derive/src/priority-queue.js +57 -61
  44. package/dist/src/derive/src/rewrite.d.ts +15 -18
  45. package/dist/src/derive/src/rewrite.js +59 -67
  46. package/dist/src/derive/src/trie.d.ts +56 -56
  47. package/dist/src/derive/src/trie.js +175 -179
  48. package/dist/src/derive/test/derive.test.js +96 -100
  49. package/dist/src/extension.d.ts +13 -16
  50. package/dist/src/geometry.d.ts +29 -43
  51. package/dist/src/geometry.js +324 -268
  52. package/dist/src/index.d.ts +2 -17
  53. package/dist/src/index.js +2 -12
  54. package/dist/src/ingest-cache.d.ts +25 -30
  55. package/dist/src/ingest-cache.js +108 -127
  56. package/dist/src/mind/articulation.d.ts +1 -5
  57. package/dist/src/mind/articulation.js +77 -112
  58. package/dist/src/mind/attention.d.ts +40 -108
  59. package/dist/src/mind/attention.js +754 -872
  60. package/dist/src/mind/canonical.d.ts +4 -19
  61. package/dist/src/mind/canonical.js +27 -31
  62. package/dist/src/mind/graph-search.d.ts +201 -225
  63. package/dist/src/mind/graph-search.js +742 -821
  64. package/dist/src/mind/index.d.ts +2 -10
  65. package/dist/src/mind/junction.d.ts +31 -58
  66. package/dist/src/mind/junction.js +172 -237
  67. package/dist/src/mind/learning.d.ts +16 -52
  68. package/dist/src/mind/learning.js +143 -165
  69. package/dist/src/mind/match.d.ts +20 -69
  70. package/dist/src/mind/match.js +259 -318
  71. package/dist/src/mind/mechanisms/alu.js +16 -16
  72. package/dist/src/mind/mechanisms/cast.d.ts +9 -13
  73. package/dist/src/mind/mechanisms/cast.js +363 -456
  74. package/dist/src/mind/mechanisms/confluence.d.ts +8 -12
  75. package/dist/src/mind/mechanisms/confluence.js +152 -183
  76. package/dist/src/mind/mechanisms/cover.d.ts +2 -8
  77. package/dist/src/mind/mechanisms/cover.js +148 -210
  78. package/dist/src/mind/mechanisms/extraction.d.ts +8 -34
  79. package/dist/src/mind/mechanisms/extraction.js +234 -288
  80. package/dist/src/mind/mechanisms/recall.d.ts +6 -10
  81. package/dist/src/mind/mechanisms/recall.js +141 -185
  82. package/dist/src/mind/mind.d.ts +194 -155
  83. package/dist/src/mind/mind.js +392 -289
  84. package/dist/src/mind/pipeline-mechanism.d.ts +112 -124
  85. package/dist/src/mind/pipeline-mechanism.js +161 -172
  86. package/dist/src/mind/pipeline.d.ts +4 -14
  87. package/dist/src/mind/pipeline.js +125 -189
  88. package/dist/src/mind/primitives.d.ts +20 -33
  89. package/dist/src/mind/primitives.js +124 -120
  90. package/dist/src/mind/rationale.d.ts +86 -98
  91. package/dist/src/mind/rationale.js +113 -121
  92. package/dist/src/mind/reasoning.d.ts +2 -13
  93. package/dist/src/mind/reasoning.js +129 -166
  94. package/dist/src/mind/recognition.d.ts +1 -4
  95. package/dist/src/mind/recognition.js +198 -210
  96. package/dist/src/mind/resonance.d.ts +5 -22
  97. package/dist/src/mind/resonance.js +0 -0
  98. package/dist/src/mind/trace.d.ts +6 -25
  99. package/dist/src/mind/trace.js +50 -58
  100. package/dist/src/mind/traverse.d.ts +29 -58
  101. package/dist/src/mind/traverse.js +344 -356
  102. package/dist/src/mind/types.d.ts +122 -127
  103. package/dist/src/mind/types.js +60 -69
  104. package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
  105. package/dist/src/rabitq-ivf/src/database.js +201 -0
  106. package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
  107. package/dist/src/{rabitq-hnsw → rabitq-ivf}/src/index.js +1 -3
  108. package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
  109. package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
  110. package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
  111. package/dist/src/rabitq-ivf/src/prng.js +36 -0
  112. package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
  113. package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
  114. package/dist/src/sema.d.ts +9 -13
  115. package/dist/src/sema.js +26 -40
  116. package/dist/src/store-sqlite.d.ts +171 -166
  117. package/dist/src/store-sqlite.js +768 -662
  118. package/dist/src/store.d.ts +597 -630
  119. package/dist/src/store.js +1434 -1581
  120. package/dist/src/vec.d.ts +5 -9
  121. package/dist/src/vec.js +61 -73
  122. package/example/train_base.ts +33 -15
  123. package/index.html +65 -0
  124. package/package.json +1 -1
  125. package/src/alu/README.md +1 -1
  126. package/src/alu/src/index.ts +1 -1
  127. package/src/config.ts +19 -27
  128. package/src/geometry.ts +93 -1
  129. package/src/index.ts +6 -11
  130. package/src/mind/attention.ts +11 -6
  131. package/src/mind/mechanisms/recall.ts +29 -13
  132. package/src/mind/mind.ts +250 -19
  133. package/src/mind/primitives.ts +43 -6
  134. package/src/mind/recognition.ts +26 -8
  135. package/src/mind/traverse.ts +47 -0
  136. package/src/mind/types.ts +20 -21
  137. package/src/rabitq-ivf/README.md +56 -0
  138. package/src/rabitq-ivf/src/database.ts +276 -0
  139. package/src/{rabitq-hnsw → rabitq-ivf}/src/index.ts +2 -5
  140. package/src/rabitq-ivf/src/ivf.ts +1330 -0
  141. package/src/{rabitq-hnsw → rabitq-ivf}/src/prng.ts +1 -1
  142. package/src/store-sqlite.ts +204 -9
  143. package/src/store.ts +27 -34
  144. package/test/08-storage.test.mjs +3 -3
  145. package/test/13-conversation.test.mjs +190 -20
  146. package/test/14-scaling.test.mjs +2 -2
  147. package/test/35-ivf.test.mjs +263 -0
  148. package/test/36-bloom.test.mjs +123 -0
  149. package/dist/src/rabitq-hnsw/src/database.d.ts +0 -202
  150. package/dist/src/rabitq-hnsw/src/database.js +0 -405
  151. package/dist/src/rabitq-hnsw/src/heap.d.ts +0 -22
  152. package/dist/src/rabitq-hnsw/src/heap.js +0 -94
  153. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +0 -125
  154. package/dist/src/rabitq-hnsw/src/hnsw.js +0 -500
  155. package/dist/src/rabitq-hnsw/src/index.d.ts +0 -15
  156. package/dist/src/rabitq-hnsw/src/prng.d.ts +0 -19
  157. package/dist/src/rabitq-hnsw/src/prng.js +0 -38
  158. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +0 -95
  159. package/dist/src/rabitq-hnsw/src/rabitq.js +0 -299
  160. package/dist/src/rabitq-hnsw/src/store.d.ts +0 -162
  161. package/dist/src/rabitq-hnsw/src/store.js +0 -916
  162. package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +0 -1
  163. package/dist/src/rabitq-hnsw/test/hnsw.test.js +0 -1197
  164. package/src/rabitq-hnsw/README.md +0 -303
  165. package/src/rabitq-hnsw/src/database.ts +0 -492
  166. package/src/rabitq-hnsw/src/heap.ts +0 -90
  167. package/src/rabitq-hnsw/src/hnsw.ts +0 -514
  168. package/src/rabitq-hnsw/src/store.ts +0 -994
  169. package/src/rabitq-hnsw/test/hnsw.test.ts +0 -1213
  170. /package/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.ts +0 -0
@@ -11,156 +11,158 @@ export type Input = string | Uint8Array | Grid | Grid[];
11
11
  /** The host capabilities GraphSearch consults during a cover. MindContext
12
12
  * extends this so the Mind can pass itself as the host. */
13
13
  export interface GraphSearchHost {
14
- resolve(bytes: Uint8Array): number | null;
15
- recogniseSpan?(bytes: Uint8Array): {
16
- sites: ReadonlyArray<Site>;
17
- leaves: ReadonlyArray<Leaf>;
18
- splits: ReadonlySet<number>;
19
- };
20
- chooseNext?(node: number): number | undefined;
14
+ resolve(bytes: Uint8Array): number | null;
15
+ recogniseSpan?(bytes: Uint8Array): {
16
+ sites: ReadonlyArray<Site>;
17
+ leaves: ReadonlyArray<Leaf>;
18
+ splits: ReadonlySet<number>;
19
+ };
20
+ chooseNext?(node: number): number | undefined;
21
21
  }
22
22
  export interface Recognition {
23
- /** Forms that can lead somewhere — they have an edge or a halo. */
24
- sites: Site[];
25
- /** The query's perceived leaves (the search's covering axioms). */
26
- leaves: Leaf[];
27
- /** Sub-leaf positions where a form boundary falls between leaf edges. */
28
- splits: Set<number>;
23
+ /** Forms that can lead somewhere — they have an edge or a halo. */
24
+ sites: Site[];
25
+ /** The query's perceived leaves (the search's covering axioms). */
26
+ leaves: Leaf[];
27
+ /** Sub-leaf positions where a form boundary falls between leaf edges. */
28
+ splits: Set<number>;
29
29
  }
30
30
  /** How the consensus climb weights a region's Document-Frequency reach. */
31
31
  export type DFMode = "inverse" | "direct" | "combined";
32
32
  /** One POINT OF ATTENTION the consensus climb resolved. */
33
33
  export interface Attention {
34
- /** The learnt context this point resolves to. */
35
- anchor: number;
36
- /** IDF-weighted consensus vote — the strength that orders points. */
37
- vote: number;
38
- /** The union of the query byte-spans whose evidence supports this point. */
39
- start: number;
40
- end: number;
34
+ /** The learnt context this point resolves to. */
35
+ anchor: number;
36
+ /** IDF-weighted consensus vote — the strength that orders points. */
37
+ vote: number;
38
+ /** The union of the query byte-spans whose evidence supports this point. */
39
+ start: number;
40
+ end: number;
41
41
  }
42
42
  /** Both read-outs of one consensus climb. */
43
43
  export interface AttentionRead {
44
- roots: Attention[];
45
- ranked: Attention[];
44
+ roots: Attention[];
45
+ ranked: Attention[];
46
46
  }
47
47
  /** A positioned region of a byte stream paired with its gist. */
48
48
  export interface Segment {
49
- start: number;
50
- end: number;
51
- v: Vec;
49
+ start: number;
50
+ end: number;
51
+ v: Vec;
52
52
  }
53
53
  /** A region of the query's perceived tree for the consensus climb. */
54
54
  export interface Region {
55
- v: Vec;
56
- start: number;
57
- end: number;
58
- chunk: boolean;
59
- /** Whether the region's bytes resolve to a KNOWN node (content-addressed,
60
- * exact). Exact regions vote with full weight; approximate ones pay the
61
- * contrastive margin (see voteRegions) — under the linear fold a raw
62
- * resonance score is byte-overlap, evidence only in excess of its best
63
- * rival conclusion. */
64
- known: boolean;
55
+ v: Vec;
56
+ start: number;
57
+ end: number;
58
+ chunk: boolean;
59
+ /** Whether the region's bytes resolve to a KNOWN node (content-addressed,
60
+ * exact). Exact regions vote with full weight; approximate ones pay the
61
+ * contrastive margin (see voteRegions) — under the linear fold a raw
62
+ * resonance score is byte-overlap, evidence only in excess of its best
63
+ * rival conclusion. */
64
+ known: boolean;
65
65
  }
66
66
  /** Per-region vote data from the consensus climb's resonance pass. */
67
67
  export interface RegionVote {
68
- start: number;
69
- end: number;
70
- canonicalFailed: boolean;
71
- roots: readonly number[];
72
- w: number;
73
- wFocus: number;
68
+ start: number;
69
+ end: number;
70
+ canonicalFailed: boolean;
71
+ roots: readonly number[];
72
+ w: number;
73
+ wFocus: number;
74
74
  }
75
75
  /** The edge-bearing contexts reached by climbing from a node, plus saturation info. */
76
76
  export interface AncestorReach {
77
- roots: number[];
78
- contextsReached: number;
79
- saturated: boolean;
77
+ roots: number[];
78
+ contextsReached: number;
79
+ saturated: boolean;
80
80
  }
81
81
  /** Saturated-interval information for the noise-drop gate. */
82
82
  export interface SaturationInfo {
83
- leadingEnd: number;
84
- hasLeading: boolean;
85
- intervals: Array<{
86
- start: number;
87
- end: number;
88
- }>;
83
+ leadingEnd: number;
84
+ hasLeading: boolean;
85
+ intervals: Array<{
86
+ start: number;
87
+ end: number;
88
+ }>;
89
89
  }
90
90
  /** The items of poolVotes' deduction system. */
91
91
  export type AItem = {
92
- kind: "region";
93
- ri: number;
92
+ kind: "region";
93
+ ri: number;
94
94
  } | {
95
- kind: "anchor";
96
- id: number;
95
+ kind: "anchor";
96
+ id: number;
97
97
  } | {
98
- kind: "anchorFocus";
99
- id: number;
98
+ kind: "anchorFocus";
99
+ id: number;
100
100
  };
101
101
  export interface MindContext extends GraphSearchHost {
102
- store: Store;
103
- space: Space;
104
- alphabet: Alphabet;
105
- cfg: MindConfig;
106
- search: GraphSearch;
107
- trace: Rationale | null;
108
- climbMemo: WeakMap<Uint8Array, Map<string, AttentionRead>> | null;
109
- /** Per-response memo of {@link recognise} keyed by the byte-array OBJECT
110
- * (think, articulate, and the post-grounding pre-consume all recognise the
111
- * same query/answer objects). Valid because the store is read-only while
112
- * a response is in flight; bypassed when a trace is attached so every
113
- * recognise still emits its rationale step. Null outside respond(). */
114
- recogniseMemo: WeakMap<Uint8Array, Recognition> | null;
115
- /** Per-response memo of {@link perceive} keyed by the byte-array OBJECT —
116
- * the GENERAL memo the result-level ones (recogniseMemo, climbMemo,
117
- * _gistCache) each partially compensate for: resolve(), gistOf(), and
118
- * every mechanism's re-perception of the same query/answer object hit it
119
- * (a reason hop used to fold the same answer three times). Valid because
120
- * the store is read-only while a response is in flight and perception is
121
- * a pure function of bytes; only inference-shaped calls (plain Uint8Array,
122
- * no leafAt/lookup capabilities) are memoised, so the deposit path never
123
- * sees it. Keyed by CONTENT (latin1 of the bytes), not object identity —
124
- * mechanisms materialise the same span in fresh subarrays constantly
125
- * (measured on a trained store: 46% of one response's perceptions were
126
- * byte-identical repeats an identity key missed). NOT bypassed under
127
- * trace perception emits no rationale steps, so there is nothing a memo
128
- * hit could swallow. Null outside respond(). */
129
- perceiveMemo: Map<string, Sema> | null;
130
- _edgeGuide: Vec | null;
131
- _edgeChoice: Map<number, number>;
132
- _prevSeen: Set<number> | null;
133
- /** Session cache of node-id → perceived gist, for candidate scoring
134
- * ({@link chooseAmong} in the reverse projection's recall path re-gists up to
135
- * √N contexts per pick the measured bottleneck there). `chooseNext` does
136
- * NOT use this cache; forward-edge disambiguation uses prevOf counts
137
- * (distributional evidence) instead of gist comparison, because for short
138
- * answer candidates the gist is dominated by accidental byte-pattern
139
- * correlations. A node's bytes are immutable and perception is a pure
140
- * function of bytes, so an entry stays valid for the store's lifetime —
141
- * never invalidated. Bounded LRU (byte-sized); a miss only re-perceives,
142
- * never a correctness risk. */
143
- _gistCache: BoundedMap<number, Vec>;
144
- /** DEPOSIT-path stable-prefix tree cache: content key (latin1) of a
145
- * deposited input → its plain-fold level PYRAMID, so the NEXT deposit
146
- * whose prefix it is (a conversation's accumulated context) reuses every
147
- * full aligned block and refolds only the right edge of each level —
148
- * O(turn) per deposit instead of O(context), with the tree BIT-IDENTICAL
149
- * to a from-scratch fold (see FoldPyramid). Purely a performance cache.
150
- * Entry-count bounded (a pyramid is ~KB/byte of content; only the few
151
- * live conversation chains matter). */
152
- _depositTrees: BoundedMap<string, FoldPyramid>;
153
- /** The byte lengths present in {@link _depositTrees} — the candidate
154
- * prefix lengths probed (longest first). Drifts on eviction (a stale
155
- * length only costs a miss); cleared with the map when it outgrows the
156
- * probe budget. */
157
- _depositLens: Set<number>;
158
- /** Mind-lifetime intern memo by NODE IDENTITY: perceived-tree node → its
159
- * content-addressed id. Valid forever (ids are permanent, Sema nodes
160
- * immutable); WeakMap, so entries live exactly as long as the pyramid
161
- * cache keeps the shared subtrees alive. Lets internTreeIds skip whole
162
- * shared subtrees and indexSubSpans keep its seenBefore window skip. */
163
- _internIds: WeakMap<Sema, number>;
102
+ store: Store;
103
+ space: Space;
104
+ alphabet: Alphabet;
105
+ cfg: MindConfig;
106
+ search: GraphSearch;
107
+ trace: Rationale | null;
108
+ /** Memo of the consensus climb — content-keyed (latin1) so results
109
+ * persist across conversation turns where the same byte spans recur.
110
+ * Null outside respond(); during respondTurn() the conversation's
111
+ * persistent map is swapped in. */
112
+ climbMemo: Map<string, Map<string, AttentionRead>> | null;
113
+ /** Memo of {@link recognise} content-keyed (latin1) so recognised
114
+ * forms carry forward across conversation turns. Bypassed while a
115
+ * trace is attached. Null outside respond(). */
116
+ recogniseMemo: Map<string, Recognition> | null;
117
+ /** Memo of {@link perceive} content-keyed (latin1). The general
118
+ * cache the result-level memos each partially compensate for. NOT
119
+ * bypassed under trace perception emits no rationale steps.
120
+ * Null outside respond(). */
121
+ perceiveMemo: Map<string, Sema> | null;
122
+ /** Subtree-resolution cache: Sema node its store id and byte length.
123
+ * Populated by {@link foldTree} during inference; checked before
124
+ * walking children. When a conversation's pyramid reuses prefix
125
+ * subtrees, this cache lets {@link recognise} skip them entirely
126
+ * O(suffix) instead of O(context). Mind-lifetime (WeakMap keys are
127
+ * the Sema objects the pyramid keeps alive). */
128
+ _resolvedSubtrees: WeakMap<Sema, {
129
+ id: number;
130
+ len: number;
131
+ }> | null;
132
+ _edgeGuide: Vec | null;
133
+ _edgeChoice: Map<number, number>;
134
+ _prevSeen: Set<number> | null;
135
+ /** Session cache of node-id perceived gist, for candidate scoring
136
+ * ({@link chooseAmong} in the reverse projection's recall path re-gists up to
137
+ * √N contexts per pick the measured bottleneck there). `chooseNext` does
138
+ * NOT use this cache; forward-edge disambiguation uses prevOf counts
139
+ * (distributional evidence) instead of gist comparison, because for short
140
+ * answer candidates the gist is dominated by accidental byte-pattern
141
+ * correlations. A node's bytes are immutable and perception is a pure
142
+ * function of bytes, so an entry stays valid for the store's lifetime —
143
+ * never invalidated. Bounded LRU (byte-sized); a miss only re-perceives,
144
+ * never a correctness risk. */
145
+ _gistCache: BoundedMap<number, Vec>;
146
+ /** DEPOSIT-path stable-prefix tree cache: content key (latin1) of a
147
+ * deposited input its plain-fold level PYRAMID, so the NEXT deposit
148
+ * whose prefix it is (a conversation's accumulated context) reuses every
149
+ * full aligned block and refolds only the right edge of each level —
150
+ * O(turn) per deposit instead of O(context), with the tree BIT-IDENTICAL
151
+ * to a from-scratch fold (see FoldPyramid). Purely a performance cache.
152
+ * Entry-count bounded (a pyramid is ~KB/byte of content; only the few
153
+ * live conversation chains matter). */
154
+ _depositTrees: BoundedMap<string, FoldPyramid>;
155
+ /** The byte lengths present in {@link _depositTrees} the candidate
156
+ * prefix lengths probed (longest first). Drifts on eviction (a stale
157
+ * length only costs a miss); cleared with the map when it outgrows the
158
+ * probe budget. */
159
+ _depositLens: Set<number>;
160
+ /** Mind-lifetime intern memo by NODE IDENTITY: perceived-tree node its
161
+ * content-addressed id. Valid forever (ids are permanent, Sema nodes
162
+ * immutable); WeakMap, so entries live exactly as long as the pyramid
163
+ * cache keeps the shared subtrees alive. Lets internTreeIds skip whole
164
+ * shared subtrees and indexSubSpans keep its seenBefore window skip. */
165
+ _internIds: WeakMap<Sema, number>;
164
166
  }
165
167
  /** Read a whole node's bytes. */
166
168
  export declare const ALL = 2147483647;
@@ -168,14 +170,7 @@ export declare const ALL = 2147483647;
168
170
  export declare function spliceAll(segs: Seg[]): Uint8Array | null;
169
171
  /** Lift the answer out of the cover for think: the recognised region, free of
170
172
  * the asker's surrounding (unrecognised) framing. */
171
- export declare function liftAnswer(
172
- segs: Seg[],
173
- queryLen: number,
174
- ): Uint8Array | null;
173
+ export declare function liftAnswer(segs: Seg[], queryLen: number): Uint8Array | null;
175
174
  /** The CHANGED NODES of a freshly-perceived `tree` against the node ids a previous
176
175
  * tracked deposit interned (`prevSeen`). */
177
- export declare function changedNodes(
178
- tree: Sema,
179
- ids: Map<Sema, number>,
180
- prevSeen: Set<number>,
181
- ): Sema[];
176
+ export declare function changedNodes(tree: Sema, ids: Map<Sema, number>, prevSeen: Set<number>): Sema[];
@@ -11,83 +11,74 @@ import { dominates } from "../geometry.js";
11
11
  export const ALL = 0x7fffffff;
12
12
  /** Splice every chosen span in order — the whole cover as one byte string. */
13
13
  export function spliceAll(segs) {
14
- if (!segs.some((s) => s.rec)) {
15
- return null;
16
- }
17
- return concatBytes(segs.map((s) => s.bytes));
14
+ if (!segs.some((s) => s.rec))
15
+ return null;
16
+ return concatBytes(segs.map((s) => s.bytes));
18
17
  }
19
18
  /** Lift the answer out of the cover for think: the recognised region, free of
20
19
  * the asker's surrounding (unrecognised) framing. */
21
20
  export function liftAnswer(segs, queryLen) {
22
- const recognised = [];
23
- for (let k = 0; k < segs.length; k++) {
24
- if (segs[k].rec) {
25
- recognised.push(k);
21
+ const recognised = [];
22
+ for (let k = 0; k < segs.length; k++)
23
+ if (segs[k].rec)
24
+ recognised.push(k);
25
+ if (recognised.length === 0)
26
+ return null;
27
+ if (recognised.length === 1) {
28
+ const s = segs[recognised[0]];
29
+ if (dominates(s.j - s.i, queryLen)) {
30
+ return concatBytes(segs.map((x) => x.bytes));
31
+ }
32
+ return s.bytes;
26
33
  }
27
- }
28
- if (recognised.length === 0) {
29
- return null;
30
- }
31
- if (recognised.length === 1) {
32
- const s = segs[recognised[0]];
33
- if (dominates(s.j - s.i, queryLen)) {
34
- return concatBytes(segs.map((x) => x.bytes));
35
- }
36
- return s.bytes;
37
- }
38
- const lo = recognised[0];
39
- const hi = recognised[recognised.length - 1];
40
- return concatBytes(segs.slice(lo, hi + 1).map((x) => x.bytes));
34
+ const lo = recognised[0];
35
+ const hi = recognised[recognised.length - 1];
36
+ return concatBytes(segs.slice(lo, hi + 1).map((x) => x.bytes));
41
37
  }
42
38
  /** The CHANGED NODES of a freshly-perceived `tree` against the node ids a previous
43
39
  * tracked deposit interned (`prevSeen`). */
44
40
  export function changedNodes(tree, ids, prevSeen) {
45
- const newCount = new Map();
46
- const count = (n) => {
47
- const memo = newCount.get(n);
48
- if (memo !== undefined) {
49
- return memo;
50
- }
51
- const id = ids.get(n);
52
- // PRUNE: a node whose id the previous deposit already interned is old,
53
- // and content addressing makes that transitive the same id names the
54
- // same content, so every descendant was interned then too. The whole
55
- // subtree counts 0 without walking it; with the pyramid fold sharing a
56
- // conversation's prefix subtree, this is what keeps the changed-nodes
57
- // read O(new nodes) instead of O(context). (A node internTreeIds
58
- // memo-skipped has an id here exactly when it is such a shared root.)
59
- if (id !== undefined && prevSeen.has(id)) {
60
- newCount.set(n, 0);
61
- return 0;
62
- }
63
- let c = 1; // reachable only when NOT pruned above ⇒ this node is new
64
- if (n.kids) {
65
- for (const k of n.kids) {
66
- c += count(k);
67
- }
68
- }
69
- newCount.set(n, c);
70
- return c;
71
- };
72
- const total = count(tree);
73
- if (total === 0) {
74
- return [tree];
75
- }
76
- let n = tree;
77
- for (;;) {
78
- if (n.kids === null) {
79
- return [n];
80
- }
81
- let holder = null;
82
- for (const k of n.kids) {
83
- if (newCount.get(k) === total) {
84
- holder = k;
85
- break;
86
- }
87
- }
88
- if (holder === null) {
89
- return [n];
41
+ const newCount = new Map();
42
+ const count = (n) => {
43
+ const memo = newCount.get(n);
44
+ if (memo !== undefined)
45
+ return memo;
46
+ const id = ids.get(n);
47
+ // PRUNE: a node whose id the previous deposit already interned is old,
48
+ // and content addressing makes that transitive the same id names the
49
+ // same content, so every descendant was interned then too. The whole
50
+ // subtree counts 0 without walking it; with the pyramid fold sharing a
51
+ // conversation's prefix subtree, this is what keeps the changed-nodes
52
+ // read O(new nodes) instead of O(context). (A node internTreeIds
53
+ // memo-skipped has an id here exactly when it is such a shared root.)
54
+ if (id !== undefined && prevSeen.has(id)) {
55
+ newCount.set(n, 0);
56
+ return 0;
57
+ }
58
+ let c = 1; // reachable only when NOT pruned above ⇒ this node is new
59
+ if (n.kids) {
60
+ for (const k of n.kids)
61
+ c += count(k);
62
+ }
63
+ newCount.set(n, c);
64
+ return c;
65
+ };
66
+ const total = count(tree);
67
+ if (total === 0)
68
+ return [tree];
69
+ let n = tree;
70
+ for (;;) {
71
+ if (n.kids === null)
72
+ return [n];
73
+ let holder = null;
74
+ for (const k of n.kids) {
75
+ if (newCount.get(k) === total) {
76
+ holder = k;
77
+ break;
78
+ }
79
+ }
80
+ if (holder === null)
81
+ return [n];
82
+ n = holder;
90
83
  }
91
- n = holder;
92
- }
93
84
  }
@@ -0,0 +1,113 @@
1
+ /** External ids are integers only. */
2
+ export type ExternalId = number;
3
+ export interface DatabaseOptions {
4
+ /** Path to the SQLite database file (use ":memory:" for a transient store). */
5
+ dbPath: string;
6
+ /** Vector dimensionality. Required for a new database; ignored when reopening. */
7
+ dim?: number;
8
+ /** Query breadth: clusters probed per query = ceil(efSearch / 4). Default
9
+ * 64 (16 probes). Tunable at runtime and at reopen. */
10
+ efSearch?: number;
11
+ /** Bits used to scalar-quantise the query for the RaBitQ estimator.
12
+ * Query-side only (stored codes are independent of it), so it is tunable
13
+ * at reopen, like efSearch. Default 8. */
14
+ queryBits?: number;
15
+ /** Number of sign-flip + Hadamard rounds in the random rotation. Default 3. */
16
+ rotationRounds?: number;
17
+ /** Seed for the rotation. Default fixed. */
18
+ seed?: number;
19
+ /** Optional centroid the vectors are centered by before quantisation. */
20
+ centroid?: ArrayLike<number>;
21
+ /** SQLite page-cache budget in MiB. Purely a latency knob: correctness and
22
+ * the per-operation storage-read count are identical with it at 0. */
23
+ cacheSizeMb?: number;
24
+ }
25
+ export interface QueryResult {
26
+ id: ExternalId;
27
+ /** Estimated cosine distance (1 - cosine). */
28
+ distance: number;
29
+ }
30
+ export interface StorageStats {
31
+ float32BytesPerVector: number;
32
+ codeBytesPerVector: number;
33
+ bytesPerVector: number;
34
+ compressionRatio: number;
35
+ }
36
+ /**
37
+ * A persistent vector database: an adaptive PARTITIONED (IVF) index over
38
+ * 1-bit RaBitQ codes (cosine), stored entirely in SQLite at `dbPath`. See
39
+ * ivf.ts for the index design. The original float vectors are never
40
+ * retained — only the sign codes — so a 1024-d vector costs 128 bytes.
41
+ *
42
+ * Inserting is route-and-append: cost is essentially FLAT in collection size
43
+ * (one RAM scan of the pivot table + one chunk append — no per-insert graph
44
+ * walk). Query cost is bounded by nprobe × cluster size — constant in N
45
+ * once the collection is past its first splits.
46
+ */
47
+ export declare class VectorDatabase {
48
+ private readonly index;
49
+ constructor(options: DatabaseOptions);
50
+ get dim(): number;
51
+ /** Number of live (non-deleted) vectors. */
52
+ get size(): number;
53
+ /** Physical slot count including tombstones from deletes/updates. */
54
+ get physicalSize(): number;
55
+ /** Number of clusters (partitions) currently in the index. */
56
+ get clusterCount(): number;
57
+ get efSearch(): number;
58
+ set efSearch(value: number);
59
+ /** Distance computations performed during the most recent query. */
60
+ get lastQueryDistanceComputations(): number;
61
+ /** Storage row reads issued by the most recent query — the honest,
62
+ * cache-independent scalability metric. */
63
+ get lastQueryStorageReads(): number;
64
+ get storage(): StorageStats;
65
+ private checkId;
66
+ /** Convert a value to code bytes, selecting by length: `codeWords`
67
+ * elements → an existing 1-bit code; otherwise a raw `dim`-vector. */
68
+ private toCodeBytes;
69
+ has(id: ExternalId): boolean;
70
+ /** Stream every live external id (bounded memory). */
71
+ keys(): IterableIterator<ExternalId>;
72
+ /** Stream live entries whose INTERNAL id is > `after` — a durable
73
+ * incremental watermark (internal ids are monotone at insert and preserved
74
+ * by update and compact). */
75
+ keysSince(after: number): IterableIterator<{
76
+ ext: ExternalId;
77
+ internal: number;
78
+ }>;
79
+ /** Read the stored 1-bit code for an id (a copy as a Uint32Array), or null. */
80
+ get(id: ExternalId): Uint32Array | null;
81
+ insert(id: ExternalId, value: ArrayLike<number>): void;
82
+ update(id: ExternalId, value: ArrayLike<number>): void;
83
+ upsert(id: ExternalId, value: ArrayLike<number>): void;
84
+ /**
85
+ * Upsert many vectors under ONE transaction — one WAL commit for the whole
86
+ * batch instead of one per vector, with chunk appends to the same cluster
87
+ * coalesced in the index's write-back buffer.
88
+ */
89
+ upsertMany(entries: Array<{
90
+ id: ExternalId;
91
+ vector: ArrayLike<number>;
92
+ }>): void;
93
+ /** Delete many ids under ONE transaction. Absent ids are skipped.
94
+ * Returns the number of vectors actually removed. */
95
+ deleteMany(ids: ExternalId[]): number;
96
+ /** Delete the vector bound to an id. Returns false if absent. */
97
+ delete(id: ExternalId): boolean;
98
+ /** Heat the SQLite page cache with sequential scans (latency only). */
99
+ warmCache(): number;
100
+ /** Reclaim tombstoned slots by rewriting the clusters that carry any, then
101
+ * VACUUM. Internal ids and cluster assignment are preserved. */
102
+ compact(): void;
103
+ /**
104
+ * k-NN search. The argument's length selects the mode:
105
+ * - `dim` elements → a raw vector (accurate estimator)
106
+ * - `codeWords` elements → a 1-bit code, by sign-bit Hamming
107
+ */
108
+ query(query: ArrayLike<number>, k?: number, opts?: {
109
+ ef?: number;
110
+ }): QueryResult[];
111
+ /** Close the underlying database. The instance must not be used afterwards. */
112
+ close(): void;
113
+ }