@hviana/sema 0.1.3 → 0.1.5

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 (123) hide show
  1. package/dist/example/demo.js +25 -25
  2. package/dist/example/train_base.d.ts +21 -19
  3. package/dist/example/train_base.js +1952 -1684
  4. package/dist/src/alphabet.d.ts +3 -3
  5. package/dist/src/alphabet.js +27 -24
  6. package/dist/src/alu/src/alu.d.ts +194 -169
  7. package/dist/src/alu/src/alu.js +414 -374
  8. package/dist/src/alu/src/expr.d.ts +43 -36
  9. package/dist/src/alu/src/expr.js +278 -252
  10. package/dist/src/alu/src/index.d.ts +98 -9
  11. package/dist/src/alu/src/index.js +63 -9
  12. package/dist/src/alu/src/kernel-arith.d.ts +5 -1
  13. package/dist/src/alu/src/kernel-arith.js +285 -195
  14. package/dist/src/alu/src/kernel-bits.d.ts +5 -1
  15. package/dist/src/alu/src/kernel-bits.js +115 -81
  16. package/dist/src/alu/src/kernel-logic.js +63 -33
  17. package/dist/src/alu/src/kernel-nd.js +195 -155
  18. package/dist/src/alu/src/kernel-numeric.d.ts +80 -16
  19. package/dist/src/alu/src/kernel-numeric.js +331 -259
  20. package/dist/src/alu/src/operation.d.ts +132 -112
  21. package/dist/src/alu/src/operation.js +163 -149
  22. package/dist/src/alu/src/parser.d.ts +175 -168
  23. package/dist/src/alu/src/parser.js +421 -382
  24. package/dist/src/alu/src/resonance.d.ts +25 -16
  25. package/dist/src/alu/src/resonance.js +56 -49
  26. package/dist/src/alu/src/text.d.ts +7 -3
  27. package/dist/src/alu/src/text.js +37 -31
  28. package/dist/src/alu/src/value.d.ts +14 -14
  29. package/dist/src/alu/src/value.js +160 -150
  30. package/dist/src/alu/test/alu.test.js +656 -543
  31. package/dist/src/bytes.d.ts +5 -1
  32. package/dist/src/bytes.js +40 -33
  33. package/dist/src/config.d.ts +102 -102
  34. package/dist/src/config.js +83 -83
  35. package/dist/src/derive/src/deduction.d.ts +62 -60
  36. package/dist/src/derive/src/deduction.js +109 -103
  37. package/dist/src/derive/src/index.d.ts +7 -1
  38. package/dist/src/derive/src/priority-queue.d.ts +8 -8
  39. package/dist/src/derive/src/priority-queue.js +61 -57
  40. package/dist/src/derive/src/rewrite.d.ts +18 -15
  41. package/dist/src/derive/src/rewrite.js +67 -59
  42. package/dist/src/derive/src/trie.d.ts +56 -56
  43. package/dist/src/derive/src/trie.js +179 -175
  44. package/dist/src/derive/test/derive.test.js +100 -96
  45. package/dist/src/extension.d.ts +16 -13
  46. package/dist/src/geometry.d.ts +42 -17
  47. package/dist/src/geometry.js +266 -235
  48. package/dist/src/index.d.ts +17 -2
  49. package/dist/src/index.js +7 -1
  50. package/dist/src/ingest-cache.d.ts +30 -25
  51. package/dist/src/ingest-cache.js +127 -108
  52. package/dist/src/mind/articulation.d.ts +5 -1
  53. package/dist/src/mind/articulation.js +112 -77
  54. package/dist/src/mind/attention.d.ts +108 -40
  55. package/dist/src/mind/attention.js +871 -749
  56. package/dist/src/mind/canonical.d.ts +19 -4
  57. package/dist/src/mind/canonical.js +31 -27
  58. package/dist/src/mind/graph-search.d.ts +225 -201
  59. package/dist/src/mind/graph-search.js +821 -742
  60. package/dist/src/mind/index.d.ts +10 -2
  61. package/dist/src/mind/junction.d.ts +58 -31
  62. package/dist/src/mind/junction.js +237 -172
  63. package/dist/src/mind/learning.d.ts +52 -16
  64. package/dist/src/mind/learning.js +165 -143
  65. package/dist/src/mind/match.d.ts +69 -20
  66. package/dist/src/mind/match.js +318 -259
  67. package/dist/src/mind/mechanisms/alu.js +16 -16
  68. package/dist/src/mind/mechanisms/cast.d.ts +13 -9
  69. package/dist/src/mind/mechanisms/cast.js +456 -363
  70. package/dist/src/mind/mechanisms/confluence.d.ts +12 -8
  71. package/dist/src/mind/mechanisms/confluence.js +183 -152
  72. package/dist/src/mind/mechanisms/cover.d.ts +8 -2
  73. package/dist/src/mind/mechanisms/cover.js +210 -148
  74. package/dist/src/mind/mechanisms/extraction.d.ts +34 -8
  75. package/dist/src/mind/mechanisms/extraction.js +288 -234
  76. package/dist/src/mind/mechanisms/recall.d.ts +10 -6
  77. package/dist/src/mind/mechanisms/recall.js +185 -126
  78. package/dist/src/mind/mind.d.ts +154 -129
  79. package/dist/src/mind/mind.js +288 -258
  80. package/dist/src/mind/pipeline-mechanism.d.ts +124 -112
  81. package/dist/src/mind/pipeline-mechanism.js +172 -161
  82. package/dist/src/mind/pipeline.d.ts +14 -4
  83. package/dist/src/mind/pipeline.js +189 -125
  84. package/dist/src/mind/primitives.d.ts +32 -8
  85. package/dist/src/mind/primitives.js +117 -99
  86. package/dist/src/mind/rationale.d.ts +98 -86
  87. package/dist/src/mind/rationale.js +121 -113
  88. package/dist/src/mind/reasoning.d.ts +13 -2
  89. package/dist/src/mind/reasoning.js +166 -129
  90. package/dist/src/mind/recognition.d.ts +4 -1
  91. package/dist/src/mind/recognition.js +208 -183
  92. package/dist/src/mind/resonance.d.ts +22 -5
  93. package/dist/src/mind/resonance.js +0 -0
  94. package/dist/src/mind/trace.d.ts +25 -6
  95. package/dist/src/mind/trace.js +58 -50
  96. package/dist/src/mind/traverse.d.ts +58 -16
  97. package/dist/src/mind/traverse.js +357 -304
  98. package/dist/src/mind/types.d.ts +127 -120
  99. package/dist/src/mind/types.js +69 -60
  100. package/dist/src/rabitq-hnsw/src/database.d.ts +179 -177
  101. package/dist/src/rabitq-hnsw/src/database.js +359 -342
  102. package/dist/src/rabitq-hnsw/src/heap.d.ts +12 -12
  103. package/dist/src/rabitq-hnsw/src/heap.js +80 -75
  104. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +103 -103
  105. package/dist/src/rabitq-hnsw/src/hnsw.js +454 -428
  106. package/dist/src/rabitq-hnsw/src/index.d.ts +6 -1
  107. package/dist/src/rabitq-hnsw/src/prng.d.ts +10 -10
  108. package/dist/src/rabitq-hnsw/src/prng.js +27 -25
  109. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +69 -69
  110. package/dist/src/rabitq-hnsw/src/rabitq.js +262 -246
  111. package/dist/src/rabitq-hnsw/src/store.d.ts +132 -132
  112. package/dist/src/rabitq-hnsw/src/store.js +843 -752
  113. package/dist/src/rabitq-hnsw/test/hnsw.test.js +1054 -805
  114. package/dist/src/sema.d.ts +35 -0
  115. package/dist/src/sema.js +77 -0
  116. package/dist/src/store-sqlite.d.ts +166 -144
  117. package/dist/src/store-sqlite.js +660 -578
  118. package/dist/src/store.d.ts +630 -593
  119. package/dist/src/store.js +1581 -1440
  120. package/dist/src/vec.d.ts +9 -5
  121. package/dist/src/vec.js +73 -61
  122. package/package.json +2 -2
  123. package/src/sema.ts +102 -0
@@ -9,50 +9,57 @@ import { cosine } from "../vec.js";
9
9
  import { gistOf, read } from "./primitives.js";
10
10
  const structCaches = new WeakMap();
11
11
  function getStructCache(ctx) {
12
- if (ctx.climbMemo === null)
13
- return null;
14
- let c = structCaches.get(ctx.climbMemo);
15
- if (c === undefined) {
16
- structCaches.set(ctx.climbMemo, c = {
17
- hasNext: new Map(),
18
- prevCount: new Map(),
19
- hasParents: new Map(),
20
- });
21
- }
22
- return c;
12
+ if (ctx.climbMemo === null) {
13
+ return null;
14
+ }
15
+ let c = structCaches.get(ctx.climbMemo);
16
+ if (c === undefined) {
17
+ structCaches.set(
18
+ ctx.climbMemo,
19
+ c = {
20
+ hasNext: new Map(),
21
+ prevCount: new Map(),
22
+ hasParents: new Map(),
23
+ },
24
+ );
25
+ }
26
+ return c;
23
27
  }
24
28
  /** Cached {@link Store.hasNext} — pure during one respond(). */
25
29
  function cachedHasNext(ctx, id, cache) {
26
- if (cache === null)
27
- return ctx.store.hasNext(id);
28
- let v = cache.hasNext.get(id);
29
- if (v === undefined) {
30
- v = ctx.store.hasNext(id);
31
- cache.hasNext.set(id, v);
32
- }
33
- return v;
30
+ if (cache === null) {
31
+ return ctx.store.hasNext(id);
32
+ }
33
+ let v = cache.hasNext.get(id);
34
+ if (v === undefined) {
35
+ v = ctx.store.hasNext(id);
36
+ cache.hasNext.set(id, v);
37
+ }
38
+ return v;
34
39
  }
35
40
  /** Cached {@link Store.prevCount} — pure during one respond(). */
36
41
  function cachedPrevCount(ctx, id, cache) {
37
- if (cache === null)
38
- return ctx.store.prevCount(id);
39
- let v = cache.prevCount.get(id);
40
- if (v === undefined) {
41
- v = ctx.store.prevCount(id);
42
- cache.prevCount.set(id, v);
43
- }
44
- return v;
42
+ if (cache === null) {
43
+ return ctx.store.prevCount(id);
44
+ }
45
+ let v = cache.prevCount.get(id);
46
+ if (v === undefined) {
47
+ v = ctx.store.prevCount(id);
48
+ cache.prevCount.set(id, v);
49
+ }
50
+ return v;
45
51
  }
46
52
  /** Cached {@link Store.hasParents} — pure during one respond(). */
47
53
  function cachedHasParents(ctx, id, cache) {
48
- if (cache === null)
49
- return ctx.store.hasParents(id);
50
- let v = cache.hasParents.get(id);
51
- if (v === undefined) {
52
- v = ctx.store.hasParents(id);
53
- cache.hasParents.set(id, v);
54
- }
55
- return v;
54
+ if (cache === null) {
55
+ return ctx.store.hasParents(id);
56
+ }
57
+ let v = cache.hasParents.get(id);
58
+ if (v === undefined) {
59
+ v = ctx.store.hasParents(id);
60
+ cache.hasParents.set(id, v);
61
+ }
62
+ return v;
56
63
  }
57
64
  // ── Graph climbing ───────────────────────────────────────────────────────
58
65
  /** Climb the structural DAG from a node to its edge-bearing ancestor contexts.
@@ -67,152 +74,165 @@ function cachedHasParents(ctx, id, cache) {
67
74
  * chunk's prefixes — so without the memo every repeat re-pays the full
68
75
  * fan-out reads. */
69
76
  export function edgeAncestors(ctx, id, contextCount, memo) {
70
- const hit = memo?.get(id);
71
- if (hit !== undefined)
72
- return hit;
73
- const bound = Math.ceil(Math.sqrt(contextCount));
74
- const roots = [];
75
- const seen = new Set([id]);
76
- const ctxSeen = new Set();
77
- let saturated = false;
78
- // EXPAND-UNTIL-DECIDED: a reach is consumed either as a VOTE (which needs
79
- // contextsReached exactly, and only while √N beyond that the region is
80
- // non-discriminative) or as an ABSTENTION (saturatedwhose roots and
81
- // counts no consumer reads). So the climb may STOP the moment the answer
82
- // is decided:
83
- // a node whose prev fan-in alone exceeds √N decides it (its
84
- // predecessors are √N+ distinct contexts) no read needed, prevCount
85
- // is an indexed O(1);
86
- // distinct contexts crossing √N decides it;
87
- // • a node with more than √N parents decides its own expansion (the
88
- // classic hub guard; the walk aborts rather than continue, which no
89
- // consumer can distinguish saturated reaches are never voted).
90
- // Below every decision threshold the walk is EXACT — identical roots and
91
- // contexts to the unbounded climb because prevFirst(√N) IS the full prev
92
- // list and parentsFirst(√N+1) IS the full parent list whenever they do not
93
- // decide. Work is bounded by √N contexts × the climb's local structure,
94
- // never by the corpus.
95
- const structCache = getStructCache(ctx);
96
- // LATERAL-BRANCH ACCOUNT — the cumulative dual of the per-node hub guard.
97
- // Within one deposit the ascent is a CHAIN (each node's first parent);
98
- // every parent BEYOND a node's first is an entry into another containing
99
- // structure (hash-consing: a shared subtree's extra parents are other
100
- // deposits' chunks). The per-node guard already declares a node with more
101
- // than √N parents non-discriminative; a climb whose ACCUMULATED lateral
102
- // entries exceed √N has spread across just as many distinct containing
103
- // structures the same commonness, distributed along the cone instead of
104
- // concentrated at one node and is decided: saturated. A deep chain in
105
- // ONE structure accrues no laterals, so legitimate deep scaffolding (a
106
- // fragment far down a long cumulative context) still climbs to its root
107
- // at any depth; what dies is the cross-structure drift that visited tens
108
- // of thousands of edge-free interiors (profiled on a 17.7M-node store:
109
- // ~20K distinct nodes per climb family, >95% unique not memoisable)
110
- // while the context account never decided.
111
- let lateral = 0;
112
- const visit = (x) => {
113
- const hasNx = cachedHasNext(ctx, x, structCache);
114
- const pc = cachedPrevCount(ctx, x, structCache);
115
- if (hasNx || pc > 0) {
116
- roots.push(x);
117
- if (hasNx)
118
- ctxSeen.add(x);
119
- if (pc > bound)
120
- return false; // decided: ≥ pc > √N distinct contexts
121
- for (const p of ctx.store.prevFirst(x, bound))
122
- ctxSeen.add(p);
123
- if (ctxSeen.size > bound)
124
- return false; // decided
125
- }
126
- const parents = ctx.store.parentsFirst(x, bound + 1);
127
- if (parents.length > bound)
128
- return false; // decided: hub
129
- let fresh = 0;
130
- for (const p of parents) {
131
- if (!seen.has(p)) {
132
- seen.add(p);
133
- stack.push(p);
134
- fresh++;
135
- }
77
+ const hit = memo?.get(id);
78
+ if (hit !== undefined) {
79
+ return hit;
80
+ }
81
+ const bound = Math.ceil(Math.sqrt(contextCount));
82
+ const roots = [];
83
+ const seen = new Set([id]);
84
+ const ctxSeen = new Set();
85
+ let saturated = false;
86
+ // EXPAND-UNTIL-DECIDED: a reach is consumed either as a VOTE (which needs
87
+ // contextsReached exactly, and only while √N beyond that the region is
88
+ // non-discriminative) or as an ABSTENTION (saturated whose roots and
89
+ // counts no consumer reads). So the climb may STOP the moment the answer
90
+ // is decided:
91
+ // a node whose prev fan-in alone exceeds √N decides it (its
92
+ // predecessors are √N+ distinct contexts) — no read needed, prevCount
93
+ // is an indexed O(1);
94
+ // • distinct contexts crossing √N decides it;
95
+ // a node with more than √N parents decides its own expansion (the
96
+ // classic hub guard; the walk aborts rather than continue, which no
97
+ // consumer can distinguish saturated reaches are never voted).
98
+ // Below every decision threshold the walk is EXACT identical roots and
99
+ // contexts to the unbounded climb — because prevFirst(√N) IS the full prev
100
+ // list and parentsFirst(√N+1) IS the full parent list whenever they do not
101
+ // decide. Work is bounded by √N contexts × the climb's local structure,
102
+ // never by the corpus.
103
+ const structCache = getStructCache(ctx);
104
+ // LATERAL-BRANCH ACCOUNT the cumulative dual of the per-node hub guard.
105
+ // Within one deposit the ascent is a CHAIN (each node's first parent);
106
+ // every parent BEYOND a node's first is an entry into another containing
107
+ // structure (hash-consing: a shared subtree's extra parents are other
108
+ // deposits' chunks). The per-node guard already declares a node with more
109
+ // than √N parents non-discriminative; a climb whose ACCUMULATED lateral
110
+ // entries exceed √N has spread across just as many distinct containing
111
+ // structures the same commonness, distributed along the cone instead of
112
+ // concentrated at one node and is decided: saturated. A deep chain in
113
+ // ONE structure accrues no laterals, so legitimate deep scaffolding (a
114
+ // fragment far down a long cumulative context) still climbs to its root
115
+ // at any depth; what dies is the cross-structure drift that visited tens
116
+ // of thousands of edge-free interiors (profiled on a 17.7M-node store:
117
+ // ~20K distinct nodes per climb family, >95% unique — not memoisable)
118
+ // while the context account never decided.
119
+ let lateral = 0;
120
+ const visit = (x) => {
121
+ const hasNx = cachedHasNext(ctx, x, structCache);
122
+ const pc = cachedPrevCount(ctx, x, structCache);
123
+ if (hasNx || pc > 0) {
124
+ roots.push(x);
125
+ if (hasNx) {
126
+ ctxSeen.add(x);
127
+ }
128
+ if (pc > bound) {
129
+ return false; // decided: ≥ pc > √N distinct contexts
130
+ }
131
+ for (const p of ctx.store.prevFirst(x, bound)) {
132
+ ctxSeen.add(p);
133
+ }
134
+ if (ctxSeen.size > bound) {
135
+ return false; // decided
136
+ }
137
+ }
138
+ const parents = ctx.store.parentsFirst(x, bound + 1);
139
+ if (parents.length > bound) {
140
+ return false; // decided: hub
141
+ }
142
+ let fresh = 0;
143
+ for (const p of parents) {
144
+ if (!seen.has(p)) {
145
+ seen.add(p);
146
+ stack.push(p);
147
+ fresh++;
148
+ }
149
+ }
150
+ if (fresh > 1) {
151
+ lateral += fresh - 1;
152
+ if (lateral > bound) {
153
+ return false; // decided: cone-wide hub
154
+ }
155
+ }
156
+ return true;
157
+ };
158
+ const stack = [];
159
+ const containment = !cachedHasParents(ctx, id, structCache);
160
+ if (!containment) {
161
+ stack.push(id);
162
+ }
163
+ // The containment seed is STREAMED in pages of √N: a distinctive window's
164
+ // containers (which converge on one or two contexts, however many chunks
165
+ // of one deposit repeat it) are walked IN FULL — exact — while a common
166
+ // window's corpus-sized container list is abandoned at the first decision
167
+ // above, after O(√N) pages at most (each page adds containers whose climbs
168
+ // add contexts; √N distinct contexts decide).
169
+ let containerOff = 0;
170
+ let containersExhausted = !containment;
171
+ climb: for (;;) {
172
+ if (stack.length === 0) {
173
+ if (containersExhausted) {
174
+ break;
175
+ }
176
+ const page = ctx.store.containersSlice(id, containerOff, bound);
177
+ containerOff += page.length;
178
+ if (page.length < bound) {
179
+ containersExhausted = true;
180
+ }
181
+ for (const c of page) {
182
+ if (!seen.has(c)) {
183
+ seen.add(c);
184
+ stack.push(c);
136
185
  }
137
- if (fresh > 1) {
138
- lateral += fresh - 1;
139
- if (lateral > bound)
140
- return false; // decided: cone-wide hub
186
+ }
187
+ if (stack.length === 0) {
188
+ if (containerOff === 0) {
189
+ stack.push(id); // no containers at all
190
+ } else {
191
+ break;
141
192
  }
142
- return true;
143
- };
144
- const stack = [];
145
- const containment = !cachedHasParents(ctx, id, structCache);
146
- if (!containment)
147
- stack.push(id);
148
- // The containment seed is STREAMED in pages of √N: a distinctive window's
149
- // containers (which converge on one or two contexts, however many chunks
150
- // of one deposit repeat it) are walked IN FULL — exact — while a common
151
- // window's corpus-sized container list is abandoned at the first decision
152
- // above, after O(√N) pages at most (each page adds containers whose climbs
153
- // add contexts; √N distinct contexts decide).
154
- let containerOff = 0;
155
- let containersExhausted = !containment;
156
- climb: for (;;) {
157
- if (stack.length === 0) {
158
- if (containersExhausted)
159
- break;
160
- const page = ctx.store.containersSlice(id, containerOff, bound);
161
- containerOff += page.length;
162
- if (page.length < bound)
163
- containersExhausted = true;
164
- for (const c of page) {
165
- if (!seen.has(c)) {
166
- seen.add(c);
167
- stack.push(c);
168
- }
169
- }
170
- if (stack.length === 0) {
171
- if (containerOff === 0)
172
- stack.push(id); // no containers at all
173
- else
174
- break;
175
- }
193
+ }
194
+ }
195
+ while (stack.length > 0) {
196
+ let x = stack.pop();
197
+ // TRANSPARENT-CHAIN HOP: a node with no edges in or out and exactly one
198
+ // parent contributes nothing here — no root, no context, no lateral
199
+ // entry so the run to its first non-transparent ancestor is skipped
200
+ // in ONE store read (Store.chainRun) instead of three probes per node.
201
+ // The interior nodes still enter `seen`, exactly as a node-at-a-time
202
+ // ascent would have recorded them at push time, so sibling entries into
203
+ // the same chain keep identical fresh/lateral accounting; and if the
204
+ // terminal was already seen (another chain merged into this one first),
205
+ // it is not visited twice — the same dedup the push-time seen-check
206
+ // used to provide.
207
+ const run = ctx.store.chainRun(x);
208
+ if (run.length > 1) {
209
+ const top = run[run.length - 1];
210
+ const dup = seen.has(top);
211
+ for (let i = 1; i < run.length; i++) {
212
+ seen.add(run[i]);
176
213
  }
177
- while (stack.length > 0) {
178
- let x = stack.pop();
179
- // TRANSPARENT-CHAIN HOP: a node with no edges in or out and exactly one
180
- // parent contributes nothing here — no root, no context, no lateral
181
- // entry — so the run to its first non-transparent ancestor is skipped
182
- // in ONE store read (Store.chainRun) instead of three probes per node.
183
- // The interior nodes still enter `seen`, exactly as a node-at-a-time
184
- // ascent would have recorded them at push time, so sibling entries into
185
- // the same chain keep identical fresh/lateral accounting; and if the
186
- // terminal was already seen (another chain merged into this one first),
187
- // it is not visited twice — the same dedup the push-time seen-check
188
- // used to provide.
189
- const run = ctx.store.chainRun(x);
190
- if (run.length > 1) {
191
- const top = run[run.length - 1];
192
- const dup = seen.has(top);
193
- for (let i = 1; i < run.length; i++)
194
- seen.add(run[i]);
195
- if (dup)
196
- continue;
197
- x = top;
198
- }
199
- if (!visit(x)) {
200
- saturated = true;
201
- break climb;
202
- }
214
+ if (dup) {
215
+ continue;
203
216
  }
217
+ x = top;
218
+ }
219
+ if (!visit(x)) {
220
+ saturated = true;
221
+ break climb;
222
+ }
204
223
  }
205
- const reach = { roots, contextsReached: ctxSeen.size, saturated };
206
- memo?.set(id, reach);
207
- return reach;
224
+ }
225
+ const reach = { roots, contextsReached: ctxSeen.size, saturated };
226
+ memo?.set(id, reach);
227
+ return reach;
208
228
  }
209
229
  /** Convenience: forward edges of a node. */
210
230
  export function nextOf(ctx, id) {
211
- return ctx.store.next(id);
231
+ return ctx.store.next(id);
212
232
  }
213
233
  /** Convenience: reverse edges of a node. */
214
234
  export function prevOf(ctx, id) {
215
- return ctx.store.prev(id);
235
+ return ctx.store.prev(id);
216
236
  }
217
237
  /** Whether a node LEADS SOMEWHERE — it bears a continuation edge or a halo.
218
238
  * The admission predicate recognition filters sites with (HOW_IT_WORKS
@@ -223,10 +243,11 @@ export function prevOf(ctx, id) {
223
243
  * indexed point probe per candidate, and the candidates that reach this
224
244
  * check have already been filtered by hasNext above in edgeAncestors. */
225
245
  export function leadsSomewhere(ctx, id) {
226
- const memo = getStructCache(ctx);
227
- if (cachedHasNext(ctx, id, memo))
228
- return true;
229
- return ctx.store.hasHalo(id);
246
+ const memo = getStructCache(ctx);
247
+ if (cachedHasNext(ctx, id, memo)) {
248
+ return true;
249
+ }
250
+ return ctx.store.hasHalo(id);
230
251
  }
231
252
  /** The structural IDF read of ONE node: how many distinct learnt contexts
232
253
  * its containment/edge climb reaches, or Infinity when it reaches none or
@@ -236,24 +257,25 @@ export function leadsSomewhere(ctx, id) {
236
257
  * reaching a corpus MINORITY of contexts discriminates (an entity, a
237
258
  * filler); content reaching a majority is frame scaffolding. */
238
259
  export function reachOf(ctx, id, contextCount, memo) {
239
- const r = edgeAncestors(ctx, id, contextCount, memo);
240
- if (r.saturated || r.roots.length === 0)
241
- return Infinity;
242
- return Math.max(1, r.contextsReached);
260
+ const r = edgeAncestors(ctx, id, contextCount, memo);
261
+ if (r.saturated || r.roots.length === 0) {
262
+ return Infinity;
263
+ }
264
+ return Math.max(1, r.contextsReached);
243
265
  }
244
266
  /** The corpus scale N — the count of DISTINCT learnt contexts, floored at 2
245
267
  * so its derived readings (ln N in the consensus floor, √N in the hub bound)
246
268
  * stay meaningful on a near-empty store. The one definition every consumer
247
269
  * of "how big is this corpus?" reads. */
248
270
  export function corpusN(ctx) {
249
- return Math.max(2, ctx.store.edgeSourceCount());
271
+ return Math.max(2, ctx.store.edgeSourceCount());
250
272
  }
251
273
  /** The hub bound √N itself (≥ 2 always, since N is floored at 2) — for
252
274
  * consumers that pass it to the store's LIMITed reads instead of capping a
253
275
  * materialised list. {@link hubCap} is the list-side reading of the same
254
276
  * convention. */
255
277
  export function hubBound(ctx) {
256
- return Math.ceil(Math.sqrt(corpusN(ctx)));
278
+ return Math.ceil(Math.sqrt(corpusN(ctx)));
257
279
  }
258
280
  /** Cap a candidate list at the hub bound √N (insertion order) — the ONE
259
281
  * fan-out convention every walk and disambiguation uses (see HOW_IT_WORKS
@@ -261,32 +283,35 @@ export function hubBound(ctx) {
261
283
  * connections carry ~no discriminative information; materialising or scoring
262
284
  * them all would make single decisions scale with the corpus. */
263
285
  export function hubCap(ctx, ids) {
264
- const bound = hubBound(ctx);
265
- return ids.length > bound ? ids.slice(0, bound) : ids;
286
+ const bound = hubBound(ctx);
287
+ return ids.length > bound ? ids.slice(0, bound) : ids;
266
288
  }
267
289
  /** Whether `descendant` lies within `ancestor`'s subtree — a structural DAG
268
290
  * relation read off the hash-consed `kids` lists, by a bounded explicit-stack
269
291
  * descent. Used by articulation to keep a voice from revoicing a fragment
270
292
  * OF that voice. */
271
293
  export function contains(ctx, ancestor, descendant) {
272
- if (ancestor === descendant)
294
+ if (ancestor === descendant) {
295
+ return true;
296
+ }
297
+ const seen = new Set([ancestor]);
298
+ const stack = [ancestor];
299
+ while (stack.length > 0) {
300
+ const rec = ctx.store.get(stack.pop());
301
+ if (!rec?.kids) {
302
+ continue;
303
+ }
304
+ for (const k of rec.kids) {
305
+ if (k === descendant) {
273
306
  return true;
274
- const seen = new Set([ancestor]);
275
- const stack = [ancestor];
276
- while (stack.length > 0) {
277
- const rec = ctx.store.get(stack.pop());
278
- if (!rec?.kids)
279
- continue;
280
- for (const k of rec.kids) {
281
- if (k === descendant)
282
- return true;
283
- if (!seen.has(k)) {
284
- seen.add(k);
285
- stack.push(k);
286
- }
287
- }
307
+ }
308
+ if (!seen.has(k)) {
309
+ seen.add(k);
310
+ stack.push(k);
311
+ }
288
312
  }
289
- return false;
313
+ }
314
+ return false;
290
315
  }
291
316
  // ── Edge disambiguation (Section 6) ──────────────────────────────────────
292
317
  /** The best-scoring item by cosine against `query`, among items scoring at
@@ -295,51 +320,61 @@ export function contains(ctx, ancestor, descendant) {
295
320
  * caller needs: `true` keeps the first-seen leader on a tie (`>`), the
296
321
  * default lets a later equal score take it (`>=`). */
297
322
  export function argmaxBy(items, scoreOf, threshold, strict = false) {
298
- let best = null;
299
- for (const item of items) {
300
- const score = scoreOf(item);
301
- const bar = best?.score ?? threshold;
302
- if (strict ? score > bar : score >= bar)
303
- best = { item, score };
323
+ let best = null;
324
+ for (const item of items) {
325
+ const score = scoreOf(item);
326
+ const bar = best?.score ?? threshold;
327
+ if (strict ? score > bar : score >= bar) {
328
+ best = { item, score };
304
329
  }
305
- return best;
330
+ }
331
+ return best;
306
332
  }
307
333
  export function argmaxCosine(query, items, vecOf, threshold, strict = false) {
308
- return argmaxBy(items, (item) => {
309
- const v = vecOf(item);
310
- return v ? cosine(query, v) : -Infinity;
311
- }, threshold, strict);
334
+ return argmaxBy(
335
+ items,
336
+ (item) => {
337
+ const v = vecOf(item);
338
+ return v ? cosine(query, v) : -Infinity;
339
+ },
340
+ threshold,
341
+ strict,
342
+ );
312
343
  }
313
344
  /** The guided-or-first continuation of a node, as answer-shaped bytes source:
314
345
  * chooseNext under the response guide, falling back to the FIRST-inserted
315
346
  * edge — the one no-guide convention chooseNext, project() and the search's
316
347
  * formRules all share. undefined when the node has no continuation. */
317
348
  export function guidedFirst(ctx, id) {
318
- const pick = guidedNext(ctx, id);
319
- if (pick !== undefined)
320
- return pick;
321
- // No guide in flight (or nothing chosen): the first-inserted edge, read
322
- // with LIMIT 1 never the full fan-out.
323
- const nx = ctx.store.nextFirst(id, 1);
324
- return nx.length > 0 ? nx[0] : undefined;
349
+ const pick = guidedNext(ctx, id);
350
+ if (pick !== undefined) {
351
+ return pick;
352
+ }
353
+ // No guide in flight (or nothing chosen): the first-inserted edge, read
354
+ // with LIMIT 1 — never the full fan-out.
355
+ const nx = ctx.store.nextFirst(id, 1);
356
+ return nx.length > 0 ? nx[0] : undefined;
325
357
  }
326
358
  export function guidedNext(ctx, node) {
327
- if (ctx._edgeGuide === null)
328
- return undefined;
329
- // The pick memo is BYPASSED while a rationale trace is attached — the same
330
- // policy climbMemo and recogniseMemo follow (every mechanism must emit its
331
- // own steps; a memo hit would swallow the repeat's `disambiguate` step).
332
- // Consistency does not need the memo: chooseNext is a pure function of the
333
- // (read-only) store and the guide, so recomputation yields the same pick.
334
- if (!ctx.trace) {
335
- const memo = ctx._edgeChoice.get(node);
336
- if (memo !== undefined)
337
- return memo === -1 ? undefined : memo;
359
+ if (ctx._edgeGuide === null) {
360
+ return undefined;
361
+ }
362
+ // The pick memo is BYPASSED while a rationale trace is attached — the same
363
+ // policy climbMemo and recogniseMemo follow (every mechanism must emit its
364
+ // own steps; a memo hit would swallow the repeat's `disambiguate` step).
365
+ // Consistency does not need the memo: chooseNext is a pure function of the
366
+ // (read-only) store and the guide, so recomputation yields the same pick.
367
+ if (!ctx.trace) {
368
+ const memo = ctx._edgeChoice.get(node);
369
+ if (memo !== undefined) {
370
+ return memo === -1 ? undefined : memo;
338
371
  }
339
- const pick = chooseNext(ctx, node, ctx._edgeGuide);
340
- if (!ctx.trace)
341
- ctx._edgeChoice.set(node, pick ?? -1);
342
- return pick;
372
+ }
373
+ const pick = chooseNext(ctx, node, ctx._edgeGuide);
374
+ if (!ctx.trace) {
375
+ ctx._edgeChoice.set(node, pick ?? -1);
376
+ }
377
+ return pick;
343
378
  }
344
379
  /** Disambiguate among a node's learnt continuations by distributional
345
380
  * support. NOTE the `guide` contract: its VALUE is deliberately unused —
@@ -354,61 +389,71 @@ export function guidedNext(ctx, node) {
354
389
  * therefore scores by guide cosine. The two directions consult different
355
390
  * halves of the evidence on purpose. */
356
391
  export function chooseNext(ctx, id, guide) {
357
- // CAPPED read: only the first √N continuations are ever candidates (the
358
- // documented hub trade), so only they are read — a hub context's full
359
- // fan-out is corpus-sized and must never be materialised. hubBound ≥ 2,
360
- // so the single-continuation fast path below stays exact.
361
- const nx = ctx.store.nextFirst(id, hubBound(ctx));
362
- if (nx.length === 0)
363
- return undefined;
364
- if (nx.length === 1 || !guide)
365
- return nx[0];
366
- // Cap candidates at √N — the same bound the original chooseAmong used.
367
- // A hub context can accumulate thousands of continuations; the best-fit
368
- // one is among the first √N by insertion order (edges are never deleted,
369
- // so the oldest are the most established). A strongly-supported edge
370
- // inserted beyond the cap is invisible here the deliberate trade
371
- // against paying O(fan-out) count reads on every disambiguation.
372
- const capped = nx; // already the hub-capped prefix, by the read above
373
- // Distributional-evidence disambiguation, consulting BOTH read-outs of the
374
- // evidence the training poured:
375
- // 1. prevCount how many DISTINCT contexts predict this candidate (one
376
- // indexed COUNT; never a materialisation — a common continuation's
377
- // reverse fan-in is corpus-sized). Diversity of independent evidence
378
- // is the primary signal: three different formulations agreeing beat
379
- // one formulation repeated.
380
- // 2. haloMass how many episode signatures were poured into the
381
- // candidate's halo (repetition counts). The tie-break among equally
382
- // diverse candidates: a fact reinforced across many episodes is more
383
- // corroborated than one seen once, and this is the DIRECT measure of
384
- // that consulting only the structural count would leave poured
385
- // evidence on the table.
386
- // When both are equal, first-inserted wins (backward compatible).
387
- let best = capped[0];
388
- let bestSupport = ctx.store.prevCount(best);
389
- let bestMass = ctx.store.haloMass(best);
390
- for (let i = 1; i < capped.length; i++) {
391
- const support = ctx.store.prevCount(capped[i]);
392
- if (support < bestSupport)
393
- continue;
394
- const mass = ctx.store.haloMass(capped[i]);
395
- if (support > bestSupport || mass > bestMass) {
396
- best = capped[i];
397
- bestSupport = support;
398
- bestMass = mass;
399
- }
392
+ // CAPPED read: only the first √N continuations are ever candidates (the
393
+ // documented hub trade), so only they are read — a hub context's full
394
+ // fan-out is corpus-sized and must never be materialised. hubBound ≥ 2,
395
+ // so the single-continuation fast path below stays exact.
396
+ const nx = ctx.store.nextFirst(id, hubBound(ctx));
397
+ if (nx.length === 0) {
398
+ return undefined;
399
+ }
400
+ if (nx.length === 1 || !guide) {
401
+ return nx[0];
402
+ }
403
+ // Cap candidates at √N the same bound the original chooseAmong used.
404
+ // A hub context can accumulate thousands of continuations; the best-fit
405
+ // one is among the first √N by insertion order (edges are never deleted,
406
+ // so the oldest are the most established). A strongly-supported edge
407
+ // inserted beyond the cap is invisible here the deliberate trade
408
+ // against paying O(fan-out) count reads on every disambiguation.
409
+ const capped = nx; // already the hub-capped prefix, by the read above
410
+ // Distributional-evidence disambiguation, consulting BOTH read-outs of the
411
+ // evidence the training poured:
412
+ // 1. prevCount how many DISTINCT contexts predict this candidate (one
413
+ // indexed COUNT; never a materialisation a common continuation's
414
+ // reverse fan-in is corpus-sized). Diversity of independent evidence
415
+ // is the primary signal: three different formulations agreeing beat
416
+ // one formulation repeated.
417
+ // 2. haloMass how many episode signatures were poured into the
418
+ // candidate's halo (repetition counts). The tie-break among equally
419
+ // diverse candidates: a fact reinforced across many episodes is more
420
+ // corroborated than one seen once, and this is the DIRECT measure of
421
+ // that consulting only the structural count would leave poured
422
+ // evidence on the table.
423
+ // When both are equal, first-inserted wins (backward compatible).
424
+ let best = capped[0];
425
+ let bestSupport = ctx.store.prevCount(best);
426
+ let bestMass = ctx.store.haloMass(best);
427
+ for (let i = 1; i < capped.length; i++) {
428
+ const support = ctx.store.prevCount(capped[i]);
429
+ if (support < bestSupport) {
430
+ continue;
400
431
  }
401
- // Trace is built lazily — the filter + map below only execute when a
402
- // trace listener is attached, so the common (no-trace) path pays only
403
- // for the prevCount calls in the loop above, never for extra rItemShort
404
- // byte-reads.
405
- if (ctx.trace) {
406
- const others = capped.filter((c) => c !== best);
407
- ctx.trace.step("disambiguate", [rItemShort(ctx, best, "halo-evidence", bestSupport)], others.map((c) => rItemShort(ctx, c, "candidate", ctx.store.prevCount(c))), `${capped.length} continuations — distributional evidence selects ` +
408
- `the most corroborated (distinct contexts ${bestSupport}, ` +
409
- `poured mass ${bestMass})`);
432
+ const mass = ctx.store.haloMass(capped[i]);
433
+ if (support > bestSupport || mass > bestMass) {
434
+ best = capped[i];
435
+ bestSupport = support;
436
+ bestMass = mass;
410
437
  }
411
- return best;
438
+ }
439
+ // Trace is built lazily — the filter + map below only execute when a
440
+ // trace listener is attached, so the common (no-trace) path pays only
441
+ // for the prevCount calls in the loop above, never for extra rItemShort
442
+ // byte-reads.
443
+ if (ctx.trace) {
444
+ const others = capped.filter((c) => c !== best);
445
+ ctx.trace.step(
446
+ "disambiguate",
447
+ [rItemShort(ctx, best, "halo-evidence", bestSupport)],
448
+ others.map((c) =>
449
+ rItemShort(ctx, c, "candidate", ctx.store.prevCount(c))
450
+ ),
451
+ `${capped.length} continuations — distributional evidence selects ` +
452
+ `the most corroborated (distinct contexts ${bestSupport}, ` +
453
+ `poured mass ${bestMass})`,
454
+ );
455
+ }
456
+ return best;
412
457
  }
413
458
  /** The perceived gist of a candidate node, through the session gist cache.
414
459
  * Re-gisting a candidate is a full river fold of its bytes — the measured
@@ -418,30 +463,38 @@ export function chooseNext(ctx, id, guide) {
418
463
  * every "score node ids against a guide" decision (chooseAmong here, the
419
464
  * bridge's junction pick) so they share ONE cache and one convention. */
420
465
  export function candidateGist(ctx, c) {
421
- const hit = ctx._gistCache.get(c);
422
- if (hit !== undefined)
423
- return hit;
424
- const b = read(ctx, c);
425
- if (b.length === 0)
426
- return null;
427
- const g = gistOf(ctx, b);
428
- ctx._gistCache.set(c, g);
429
- return g;
466
+ const hit = ctx._gistCache.get(c);
467
+ if (hit !== undefined) {
468
+ return hit;
469
+ }
470
+ const b = read(ctx, c);
471
+ if (b.length === 0) {
472
+ return null;
473
+ }
474
+ const g = gistOf(ctx, b);
475
+ ctx._gistCache.set(c, g);
476
+ return g;
430
477
  }
431
478
  export function chooseAmong(ctx, candidates, guide) {
432
- const capped = hubCap(ctx, candidates);
433
- const found = argmaxCosine(guide, capped, (c) => candidateGist(ctx, c), -Infinity, true);
434
- return found
435
- ? { id: found.item, score: found.score }
436
- : { id: candidates[0], score: -Infinity };
479
+ const capped = hubCap(ctx, candidates);
480
+ const found = argmaxCosine(
481
+ guide,
482
+ capped,
483
+ (c) => candidateGist(ctx, c),
484
+ -Infinity,
485
+ true,
486
+ );
487
+ return found
488
+ ? { id: found.item, score: found.score }
489
+ : { id: candidates[0], score: -Infinity };
437
490
  }
438
491
  // ── Trace shim (used by chooseNext before trace module is loaded) ────────
439
492
  import { decodeText } from "./rationale.js";
440
493
  function rItemShort(ctx, id, role, score) {
441
- return {
442
- text: decodeText(read(ctx, id)),
443
- node: id,
444
- role,
445
- score,
446
- };
494
+ return {
495
+ text: decodeText(read(ctx, id)),
496
+ node: id,
497
+ role,
498
+ score,
499
+ };
447
500
  }