@hviana/sema 0.4.2 → 0.4.3

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 (133) hide show
  1. package/dist/example/demo.d.ts +1 -0
  2. package/dist/example/demo.js +39 -0
  3. package/dist/example/train_base.d.ts +87 -0
  4. package/dist/example/train_base.js +2252 -0
  5. package/dist/src/alphabet.d.ts +7 -0
  6. package/dist/src/alphabet.js +33 -0
  7. package/dist/src/alu/src/alu.d.ts +185 -0
  8. package/dist/src/alu/src/alu.js +440 -0
  9. package/dist/src/alu/src/expr.d.ts +61 -0
  10. package/dist/src/alu/src/expr.js +318 -0
  11. package/dist/src/alu/src/index.d.ts +11 -0
  12. package/dist/src/alu/src/index.js +19 -0
  13. package/dist/src/alu/src/kernel-arith.d.ts +16 -0
  14. package/dist/src/alu/src/kernel-arith.js +264 -0
  15. package/dist/src/alu/src/kernel-bits.d.ts +19 -0
  16. package/dist/src/alu/src/kernel-bits.js +152 -0
  17. package/dist/src/alu/src/kernel-logic.d.ts +4 -0
  18. package/dist/src/alu/src/kernel-logic.js +60 -0
  19. package/dist/src/alu/src/kernel-nd.d.ts +3 -0
  20. package/dist/src/alu/src/kernel-nd.js +208 -0
  21. package/dist/src/alu/src/kernel-numeric.d.ts +54 -0
  22. package/dist/src/alu/src/kernel-numeric.js +366 -0
  23. package/dist/src/alu/src/operation.d.ts +168 -0
  24. package/dist/src/alu/src/operation.js +189 -0
  25. package/dist/src/alu/src/parser.d.ts +221 -0
  26. package/dist/src/alu/src/parser.js +577 -0
  27. package/dist/src/alu/src/resonance.d.ts +55 -0
  28. package/dist/src/alu/src/resonance.js +126 -0
  29. package/dist/src/alu/src/text.d.ts +31 -0
  30. package/dist/src/alu/src/text.js +73 -0
  31. package/dist/src/alu/src/value.d.ts +109 -0
  32. package/dist/src/alu/src/value.js +300 -0
  33. package/dist/src/alu/test/alu.test.d.ts +1 -0
  34. package/dist/src/alu/test/alu.test.js +764 -0
  35. package/dist/src/bytes.d.ts +14 -0
  36. package/dist/src/bytes.js +59 -0
  37. package/dist/src/canon.d.ts +26 -0
  38. package/dist/src/canon.js +57 -0
  39. package/dist/src/config.d.ts +111 -0
  40. package/dist/src/config.js +91 -0
  41. package/dist/src/derive/src/deduction.d.ts +136 -0
  42. package/dist/src/derive/src/deduction.js +159 -0
  43. package/dist/src/derive/src/index.d.ts +8 -0
  44. package/dist/src/derive/src/index.js +11 -0
  45. package/dist/src/derive/src/priority-queue.d.ts +20 -0
  46. package/dist/src/derive/src/priority-queue.js +73 -0
  47. package/dist/src/derive/src/rewrite.d.ts +56 -0
  48. package/dist/src/derive/src/rewrite.js +100 -0
  49. package/dist/src/derive/src/trie.d.ts +90 -0
  50. package/dist/src/derive/src/trie.js +217 -0
  51. package/dist/src/derive/test/derive.test.d.ts +1 -0
  52. package/dist/src/derive/test/derive.test.js +122 -0
  53. package/dist/src/extension.d.ts +37 -0
  54. package/dist/src/extension.js +7 -0
  55. package/dist/src/geometry.d.ts +175 -0
  56. package/dist/src/geometry.js +823 -0
  57. package/dist/src/index.d.ts +17 -0
  58. package/dist/src/index.js +19 -0
  59. package/dist/src/ingest-cache.d.ts +41 -0
  60. package/dist/src/ingest-cache.js +165 -0
  61. package/dist/src/meter.d.ts +176 -0
  62. package/dist/src/meter.js +274 -0
  63. package/dist/src/mind/articulation.d.ts +6 -0
  64. package/dist/src/mind/articulation.js +99 -0
  65. package/dist/src/mind/attention.d.ts +414 -0
  66. package/dist/src/mind/attention.js +2082 -0
  67. package/dist/src/mind/bridge.d.ts +39 -0
  68. package/dist/src/mind/bridge.js +947 -0
  69. package/dist/src/mind/canonical.d.ts +34 -0
  70. package/dist/src/mind/canonical.js +93 -0
  71. package/dist/src/mind/graph-search.d.ts +294 -0
  72. package/dist/src/mind/graph-search.js +996 -0
  73. package/dist/src/mind/index.d.ts +9 -0
  74. package/dist/src/mind/index.js +5 -0
  75. package/dist/src/mind/junction.d.ts +137 -0
  76. package/dist/src/mind/junction.js +342 -0
  77. package/dist/src/mind/learning.d.ts +75 -0
  78. package/dist/src/mind/learning.js +270 -0
  79. package/dist/src/mind/match.d.ts +181 -0
  80. package/dist/src/mind/match.js +655 -0
  81. package/dist/src/mind/mechanisms/alu.d.ts +4 -0
  82. package/dist/src/mind/mechanisms/alu.js +36 -0
  83. package/dist/src/mind/mechanisms/cast.d.ts +89 -0
  84. package/dist/src/mind/mechanisms/cast.js +784 -0
  85. package/dist/src/mind/mechanisms/confluence.d.ts +24 -0
  86. package/dist/src/mind/mechanisms/confluence.js +255 -0
  87. package/dist/src/mind/mechanisms/cover.d.ts +6 -0
  88. package/dist/src/mind/mechanisms/cover.js +227 -0
  89. package/dist/src/mind/mechanisms/extraction.d.ts +33 -0
  90. package/dist/src/mind/mechanisms/extraction.js +300 -0
  91. package/dist/src/mind/mechanisms/recall.d.ts +16 -0
  92. package/dist/src/mind/mechanisms/recall.js +364 -0
  93. package/dist/src/mind/mind.d.ts +337 -0
  94. package/dist/src/mind/mind.js +617 -0
  95. package/dist/src/mind/pipeline-mechanism.d.ts +172 -0
  96. package/dist/src/mind/pipeline-mechanism.js +465 -0
  97. package/dist/src/mind/pipeline.d.ts +49 -0
  98. package/dist/src/mind/pipeline.js +275 -0
  99. package/dist/src/mind/primitives.d.ts +66 -0
  100. package/dist/src/mind/primitives.js +306 -0
  101. package/dist/src/mind/rationale.d.ts +139 -0
  102. package/dist/src/mind/rationale.js +163 -0
  103. package/dist/src/mind/reasoning.d.ts +40 -0
  104. package/dist/src/mind/reasoning.js +280 -0
  105. package/dist/src/mind/recognition.d.ts +20 -0
  106. package/dist/src/mind/recognition.js +504 -0
  107. package/dist/src/mind/resonance.d.ts +23 -0
  108. package/dist/src/mind/resonance.js +0 -0
  109. package/dist/src/mind/trace.d.ts +15 -0
  110. package/dist/src/mind/trace.js +73 -0
  111. package/dist/src/mind/traverse.d.ts +126 -0
  112. package/dist/src/mind/traverse.js +650 -0
  113. package/dist/src/mind/types.d.ts +333 -0
  114. package/dist/src/mind/types.js +130 -0
  115. package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
  116. package/dist/src/rabitq-ivf/src/database.js +201 -0
  117. package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
  118. package/dist/src/rabitq-ivf/src/index.js +4 -0
  119. package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
  120. package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
  121. package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
  122. package/dist/src/rabitq-ivf/src/prng.js +36 -0
  123. package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
  124. package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
  125. package/dist/src/sema.d.ts +31 -0
  126. package/dist/src/sema.js +63 -0
  127. package/dist/src/store-sqlite.d.ts +184 -0
  128. package/dist/src/store-sqlite.js +942 -0
  129. package/dist/src/store.d.ts +678 -0
  130. package/dist/src/store.js +1703 -0
  131. package/dist/src/vec.d.ts +31 -0
  132. package/dist/src/vec.js +109 -0
  133. package/package.json +1 -1
@@ -0,0 +1,24 @@
1
+ import type { MindContext } from "../types.js";
2
+ import type { PipelineMechanism, Precomputed } from "../pipeline-mechanism.js";
3
+ /** A join answer plus its elementary evidence for think's grounding decider:
4
+ * `accounted` — the query spans whose votes carried the two constraint
5
+ * streams; `moves` — the ladder cost of the acts performed (two constraint
6
+ * matches and one meet, STEP each). `used` carries the pair's exemplar
7
+ * anchors so the reasoning stage does not re-speak them. */
8
+ export interface JoinResult {
9
+ bytes: Uint8Array;
10
+ used: ReadonlySet<number>;
11
+ accounted: Array<[number, number]>;
12
+ moves: number;
13
+ /** A human-readable label for the query bytes the meet left unexplained —
14
+ * purely diagnostic, never priced. */
15
+ unexplained: string;
16
+ }
17
+ /** The main confluence entry point. Given a query, detect whether it weaves
18
+ * two or more INDEPENDENT constraints (ranked anchors supported by disjoint
19
+ * query spans), intersect the constraints' evidence by content-addressed
20
+ * identity, and return the discriminative content the streams share — the
21
+ * entity that satisfies all constraints at once. Null when the query is
22
+ * not conjunctive or nothing lies in the intersection. */
23
+ export declare function confluenceJoin(ctx: MindContext, query: Uint8Array, pre: Precomputed): Promise<JoinResult | null>;
24
+ export declare const confluenceMechanism: PipelineMechanism;
@@ -0,0 +1,255 @@
1
+ // confluence.ts — Confluence Join (Section 4 of the mind).
2
+ //
3
+ // THE CLASS THIS SOLVES: conjunctive queries — answers that are stored in NO
4
+ // single fact and exist only as the INTERSECTION of independent evidence
5
+ // streams. "Which material is translucent and featherlight?" No learnt form
6
+ // contains the answer; each constraint reaches its own set of exemplars, and
7
+ // the entity satisfying both lives exactly where those sets MEET. Every
8
+ // other mechanism produces its answer by following ONE evidence path (a
9
+ // chain, a reverse step, a halo hop, one aligned frame); the consensus climb
10
+ // SUMS votes, fusion CONCATENATES topics, CAST COMPARES two seats — none of
11
+ // them intersects, so this class was previously answered wrong (fusion pairs
12
+ // one fact per constraint, from DIFFERENT entities).
13
+ //
14
+ // THE MEET IS NATIVE, NOT A BYTE SCAN. The store is content-addressed: any
15
+ // content two deposits share IS the same node id, interned once at write
16
+ // time (hash-consing) — so "what do these two facts have in common?" is a
17
+ // SET INTERSECTION OF IDENTITIES the write side already computed, asked
18
+ // through the canonical window read (leafIdRun/findBranch — the same
19
+ // write/read contract recognition runs on). Three identity/structure tests
20
+ // make the whole mechanism:
21
+ //
22
+ // • constraint streams — the consensus climb's ranked anchors, each bound
23
+ // to the query spans whose DISCRIMINATIVE windows it holds by identity
24
+ // (a resonance-voted anchor holding none of the query's discriminative
25
+ // content is no constraint at all); two streams are independent when
26
+ // the content they bind is disjoint;
27
+ // • the meet — window ids present in BOTH anchors and ABSENT from the
28
+ // query: shared-with-query windows are the constraint being re-named
29
+ // (or its scaffolding), so subtracting the query's own window ids
30
+ // leaves exactly the content the question asks FOR — the open seat;
31
+ // • the filler/scaffolding separator — the same structural IDF the climb
32
+ // derives (edgeAncestors' contextsReached): shared content reaching a
33
+ // corpus minority of contexts is an entity, content reaching a majority
34
+ // is frame scaffolding. No statistics, no learning — the same global-
35
+ // quantity-from-capped-local-probes reading that makes the climb's IDF
36
+ // work, pointed at a new question.
37
+ //
38
+ // HONESTY: the meet can only ever name content that structurally exists in
39
+ // two independently learnt exemplars — an empty intersection yields null and
40
+ // the ordinary pipeline decides. Confluence cannot fabricate.
41
+ import { read } from "../primitives.js";
42
+ import { corpusN, reachOf } from "../traverse.js";
43
+ import { dominates } from "../../geometry.js";
44
+ import { STEP } from "../graph-search.js";
45
+ import { unexplainedLabel } from "../rationale.js";
46
+ import { rItem, rNode } from "../trace.js";
47
+ /** The main confluence entry point. Given a query, detect whether it weaves
48
+ * two or more INDEPENDENT constraints (ranked anchors supported by disjoint
49
+ * query spans), intersect the constraints' evidence by content-addressed
50
+ * identity, and return the discriminative content the streams share — the
51
+ * entity that satisfies all constraints at once. Null when the query is
52
+ * not conjunctive or nothing lies in the intersection. */
53
+ export async function confluenceJoin(ctx, query, pre) {
54
+ const W = ctx.space.maxGroup;
55
+ if (query.length < 2 * W || ctx.store.edgeSourceCount() === 0)
56
+ return null;
57
+ const { ranked } = await pre.attention();
58
+ if (ranked.length < 2)
59
+ return null;
60
+ const N = corpusN(ctx);
61
+ // Response-scoped shared memos: the anchor-window identities and the
62
+ // structural-IDF reach live on Precomputed, so any other identity-based
63
+ // mechanism in the same response reuses them.
64
+ const reachMemo = pre.reachMemo;
65
+ const windowsOfAnchor = (anchor) => pre.windowsOf(anchor);
66
+ // The query's own window identities, offset → id (the canonical
67
+ // content-addressed read, canonical.windowIds): whatever the meet shares
68
+ // with the query is the CONSTRAINT being re-named (or its scaffolding),
69
+ // never the open seat the question asks for — subtracted by identity,
70
+ // below.
71
+ // Completed assistant turns are context the engine already produced, not
72
+ // independent constraints asserted by the asker. Treating their windows as
73
+ // fresh constraints makes a cumulative dialogue's confluence work grow with
74
+ // every prior answer and can join the engine's own prose back into a reply.
75
+ // Recognition and attention still see the full transcript; only this
76
+ // mechanism's constraint population excludes answered spans.
77
+ const queryWin = new Map();
78
+ let answered = 0;
79
+ for (const [off, id] of pre.queryWindows) {
80
+ while (answered < ctx.answeredSpans.length &&
81
+ ctx.answeredSpans[answered][1] <= off)
82
+ answered++;
83
+ const span = ctx.answeredSpans[answered];
84
+ if (span && span[0] <= off && off + W <= span[1])
85
+ continue;
86
+ queryWin.set(off, id);
87
+ }
88
+ const queryIds = new Set(queryWin.values());
89
+ // A constraint must bind a CONSTITUENT, not a shard. A genuinely shared
90
+ // form weaves a contiguous RUN of shared discriminative windows — its
91
+ // merged cover span is the form's own length, beyond one perception
92
+ // quantum (2W: the same "two quanta of structure" bar the conjunctive
93
+ // precondition `query.length < 2W` and CAST's weave live under). A long
94
+ // stored document holds thousands of W-windows and will hold a FEW of any
95
+ // query's by accident, but accidental sharing is one window (a merged
96
+ // span of W, at most ~W+overlap bytes: "ow ma", "ys a", "ías " —
97
+ // observed), never a run ("​ translucent", " featherlight" — the genuine
98
+ // constraints). Shard-bound streams are no constraints, and their meets
99
+ // are connective debris (". Sure,", "ngul" — observed).
100
+ const bindsAConstituent = (cover) => cover.some(([cs, ce]) => ce - cs >= 2 * W);
101
+ const streams = [];
102
+ const rankedCapped = ranked.length > pre.k ? ranked.slice(0, pre.k) : ranked;
103
+ // CONJUNCTIVITY EARLY-EXIT: a conjunctive query's top-ranked anchors
104
+ // (largest vote weight) must already form at least two independent
105
+ // constraint streams. When the first W anchors yield fewer than 2, the
106
+ // query has at most one topic — no join to compute. The full pre.k scan
107
+ // would produce the same null result after reading every anchor's bytes
108
+ // and computing window identities (profiled at 18K–50K leaf lookups per
109
+ // refusing query), so cutting the scan short here saves 50–70% of
110
+ // confluence cost on non-conjunctive queries while preserving every
111
+ // genuinely conjunctive case (whose top anchors ARE its constraints).
112
+ const earlyExit = Math.min(rankedCapped.length, ctx.space.maxGroup * 2);
113
+ let exitAfter = earlyExit;
114
+ for (const cand of rankedCapped) {
115
+ if (streams.some((s) => s.anchor === cand.anchor))
116
+ continue;
117
+ const ids = new Set(windowsOfAnchor(cand.anchor).values());
118
+ if (ids.size === 0)
119
+ continue;
120
+ const cover = [];
121
+ const held = [];
122
+ let curC = null;
123
+ let curH = null;
124
+ for (const [off, wid] of queryWin) {
125
+ if (!ids.has(wid))
126
+ continue;
127
+ if (curH !== null && off <= curH[1])
128
+ curH[1] = off + W;
129
+ else
130
+ held.push(curH = [off, off + W]);
131
+ if (dominates(reachOf(ctx, wid, N, reachMemo), N))
132
+ continue; // scaffolding never binds
133
+ if (curC !== null && off <= curC[1])
134
+ curC[1] = off + W;
135
+ else
136
+ cover.push(curC = [off, off + W]);
137
+ }
138
+ if (cover.length > 0 && bindsAConstituent(cover)) {
139
+ streams.push({ anchor: cand.anchor, vote: cand.vote, ids, cover, held });
140
+ }
141
+ // Early-exit: after 2W anchors, a non-conjunctive query is decided.
142
+ if (--exitAfter <= 0 && streams.length < 2)
143
+ return null;
144
+ }
145
+ if (streams.length < 2)
146
+ return null;
147
+ // Two streams are INDEPENDENT constraints when the query content they
148
+ // hold is disjoint — each answers a different part of what was asked.
149
+ const disjoint = (a, b) => a.cover.every(([as, ae]) => b.cover.every(([bs, be]) => be <= as || bs >= ae));
150
+ let met = null;
151
+ for (let i = 0; i < streams.length; i++) {
152
+ for (let j = i + 1; j < streams.length; j++) {
153
+ const a = streams[i];
154
+ const b = streams[j];
155
+ if (!disjoint(a, b))
156
+ continue;
157
+ const wa = windowsOfAnchor(a.anchor);
158
+ const wb = b.ids;
159
+ // ── The MEET: in both anchors, not in the query ────────────────────
160
+ // Offsets of A whose window id is shared with B and absent from the
161
+ // query — merged into maximal contiguous spans (windows overlap, so
162
+ // consecutive shared offsets weave one span).
163
+ const spans = [];
164
+ let cur = null;
165
+ for (const [off, wid] of wa) {
166
+ const inMeet = wb.has(wid) && !queryIds.has(wid);
167
+ if (inMeet) {
168
+ if (cur !== null && off <= cur[1])
169
+ cur[1] = off + W;
170
+ else
171
+ spans.push(cur = [off, off + W]);
172
+ }
173
+ }
174
+ if (spans.length === 0)
175
+ continue;
176
+ const aBytes = read(ctx, a.anchor);
177
+ for (const [s, e] of spans) {
178
+ // Scaffolding gate: the span's MOST discriminative window decides.
179
+ // Content reaching a corpus MAJORITY of contexts discriminates
180
+ // nothing (the same half-dominance convention every wrapper test
181
+ // uses); the query-subtraction above already removed everything the
182
+ // question names, so what survives here is a genuine open-seat
183
+ // entity.
184
+ let reach = Infinity;
185
+ for (let off = s; off + W <= e; off++) {
186
+ const wid = wa.get(off);
187
+ if (wid !== undefined && wb.has(wid) && !queryIds.has(wid)) {
188
+ reach = Math.min(reach, reachOf(ctx, wid, N, reachMemo));
189
+ }
190
+ }
191
+ if (!isFinite(reach) || dominates(reach, N))
192
+ continue;
193
+ const len = e - s;
194
+ if (met === null || reach < met.reach ||
195
+ (reach === met.reach && len > met.len)) {
196
+ met = { bytes: aBytes.subarray(s, e), reach, len, a, b };
197
+ }
198
+ }
199
+ }
200
+ }
201
+ if (met === null)
202
+ return null;
203
+ const t = ctx.trace?.enter("confluence", [rItem(query, "query")]);
204
+ ctx.trace?.step("intersectEvidence", [
205
+ rNode(ctx, met.a.anchor, "constraint", met.a.vote),
206
+ rNode(ctx, met.b.anchor, "constraint", met.b.vote),
207
+ ], [rItem(met.bytes, "meet")], `the discriminative content BOTH constraints' evidence shares, by content-addressed identity (reach ${met.reach} of ${N} contexts)`);
208
+ t?.done([rItem(met.bytes, "answer")], "conjunctive join — the entity where the independent evidence streams meet");
209
+ // Evidence: the query spans whose content the two streams hold by
210
+ // identity (scaffolding included — held, not just the binding cover);
211
+ // the acts were two constraint matches and one meet.
212
+ const accounted = [
213
+ ...met.a.held,
214
+ ...met.b.held,
215
+ ];
216
+ return {
217
+ bytes: met.bytes,
218
+ used: new Set([met.a.anchor, met.b.anchor]),
219
+ accounted,
220
+ moves: 3 * STEP,
221
+ unexplained: unexplainedLabel(query, accounted),
222
+ };
223
+ }
224
+ // ── Pipeline mechanism ──────────────────────────────────────────────────────
225
+ export const confluenceMechanism = {
226
+ name: "confluence",
227
+ provenance: "join",
228
+ async floor(_ctx, query, pre, worthRunning) {
229
+ const W = _ctx.space.maxGroup;
230
+ if (query.length < 2 * W || _ctx.store.edgeSourceCount() === 0)
231
+ return null;
232
+ // Confluence's floor is always exactly 3*STEP when it exists — same
233
+ // investment discipline as CAST's (see cast.ts): when the bound already
234
+ // cannot beat the incumbent, return it UNINVESTED (never first-touch the
235
+ // climb just to be pruned) and let the pipeline record the truthful
236
+ // "cannot beat incumbent" note.
237
+ if (!worthRunning(3 * STEP))
238
+ return 3 * STEP;
239
+ if ((await pre.attention()).ranked.length < 2)
240
+ return null;
241
+ return 3 * STEP;
242
+ },
243
+ async run(ctx, query, pre) {
244
+ const met = await confluenceJoin(ctx, query, pre);
245
+ if (!met)
246
+ return [];
247
+ return [{
248
+ bytes: met.bytes,
249
+ accounted: met.accounted,
250
+ moves: met.moves,
251
+ used: met.used,
252
+ unexplained: met.unexplained,
253
+ }];
254
+ },
255
+ };
@@ -0,0 +1,6 @@
1
+ import type { MindContext } from "../types.js";
2
+ import type { Site } from "../graph-search.js";
3
+ import type { PipelineMechanism } from "../pipeline-mechanism.js";
4
+ export declare function resolveConcepts(ctx: MindContext, sites: Site[]): Promise<Map<number, number>>;
5
+ export declare function resolveConnectors(ctx: MindContext, sites: ReadonlyArray<Site>, query?: Uint8Array): Promise<Map<string, Uint8Array>>;
6
+ export declare const coverMechanism: PipelineMechanism;
@@ -0,0 +1,227 @@
1
+ // mechanisms/cover.ts — Cover (Grounding II): the query's own decomposition
2
+ // composes an answer through ONE lightest-derivation search.
3
+ //
4
+ // Cover consumes recognition directly (its axioms are the query's own
5
+ // decomposition) plus the computed spans any parse()-bearing mechanism
6
+ // contributed: computed spans MASK colliding recognised sites and enter the
7
+ // search at zero cost ("computation always wins", §16.3) — which is also why
8
+ // cover runs FIRST in defaultMechanisms: a computed-backed cover becomes a
9
+ // near-zero-cost incumbent that prunes the other mechanisms through the
10
+ // ordinary admissible-floor check, with no extension special-case anywhere.
11
+ import { read, resolve } from "../primitives.js";
12
+ import { guidedFirst, hubBound } from "../traverse.js";
13
+ import { conceptHop } from "../match.js";
14
+ import { bridge } from "../resonance.js";
15
+ import { liftAnswer, segRestatesQuery } from "../types.js";
16
+ import { decodeText, unexplainedLabel } from "../rationale.js";
17
+ import { indexOf } from "../../bytes.js";
18
+ import { rItem, rNode, traceDerivation } from "../trace.js";
19
+ // ── Concept / connector pre-resolution ──────────────────────────────────────
20
+ export async function resolveConcepts(ctx, sites) {
21
+ const target = new Map();
22
+ const visited = new Set();
23
+ for (const { payload: n } of sites) {
24
+ if (visited.has(n))
25
+ continue;
26
+ visited.add(n);
27
+ if (ctx.store.hasNext(n))
28
+ continue;
29
+ const hop = await conceptHop(ctx, n);
30
+ if (hop !== null)
31
+ target.set(n, hop);
32
+ }
33
+ if (target.size > 0) {
34
+ ctx.trace?.step("resolveConcepts", [...target.keys()].map((n) => rNode(ctx, n, "edgeless-form")), [...target.values()].map((h) => rNode(ctx, h, "concept-sibling")), "borrow a synonym's continuation edge for each edge-less form (a concept/halo hop)");
35
+ }
36
+ return target;
37
+ }
38
+ export async function resolveConnectors(ctx, sites, query) {
39
+ const links = new Map();
40
+ const answerOf = (n) => guidedFirst(ctx, n) ?? n;
41
+ // A site's continuation already present elsewhere in the query is stale
42
+ // transcript evidence: cover still needs the site for structural context,
43
+ // but liftAnswer will trim that continuation as already answered. Building
44
+ // pairwise/n-ary bridges for it can only create connectors that are later
45
+ // discarded, and on cumulative dialogue that dominated the whole search.
46
+ let answered = 0;
47
+ const ordered = [...sites]
48
+ .sort((a, b) => a.start - b.start)
49
+ .filter((s) => {
50
+ while (answered < ctx.answeredSpans.length &&
51
+ ctx.answeredSpans[answered][1] <= s.start)
52
+ answered++;
53
+ const span = ctx.answeredSpans[answered];
54
+ if (span && span[0] <= s.start && s.end <= span[1])
55
+ return false;
56
+ if (query === undefined || ctx.answeredSpans.length === 0)
57
+ return true;
58
+ const continuations = ctx.store.nextFirst(s.payload, hubBound(ctx));
59
+ return !continuations.some((answer) => indexOf(query, read(ctx, answer), 0) >= 0);
60
+ });
61
+ const bridgePair = async (l, r) => {
62
+ if (l === r || links.has(l + "," + r))
63
+ return;
64
+ const link = await bridge(ctx, read(ctx, l), read(ctx, r));
65
+ if (link !== null)
66
+ links.set(l + "," + r, link);
67
+ };
68
+ for (let i = 0; i + 1 < ordered.length; i++) {
69
+ if (ordered[i].end !== ordered[i + 1].start)
70
+ continue;
71
+ const lefts = [ordered[i].payload, answerOf(ordered[i].payload)];
72
+ const rights = [ordered[i + 1].payload, answerOf(ordered[i + 1].payload)];
73
+ for (const l of new Set(lefts)) {
74
+ for (const r of new Set(rights)) {
75
+ await bridgePair(l, r);
76
+ await bridgePair(r, l);
77
+ }
78
+ }
79
+ }
80
+ const orderedNodes = [];
81
+ const seenN = new Set();
82
+ for (const s of ordered) {
83
+ const node = guidedFirst(ctx, s.payload) ?? s.payload;
84
+ if (seenN.has(node))
85
+ continue;
86
+ seenN.add(node);
87
+ orderedNodes.push({ node, bytes: read(ctx, node) });
88
+ }
89
+ if (orderedNodes.length >= 3) {
90
+ const first = orderedNodes[0];
91
+ const W = ctx.space.maxGroup;
92
+ let middleBytes = 0; // Σ bytes of the answers BETWEEN first and m-th
93
+ for (let m = 1; m < orderedNodes.length; m++) {
94
+ const key = first.node + "," + orderedNodes[m].node;
95
+ if (links.has(key)) {
96
+ middleBytes += orderedNodes[m].bytes.length;
97
+ continue;
98
+ }
99
+ // The N-ary interior legitimately holds every intermediate answer
100
+ // plus one W-quantum of glue per joint — pass that allowance so the
101
+ // bridge's phrase-scale cap admits the whole learnt run.
102
+ const allowance = middleBytes + (m + 1) * W;
103
+ const interior = await bridge(ctx, first.bytes, orderedNodes[m].bytes, allowance);
104
+ if (interior !== null)
105
+ links.set(key, interior);
106
+ middleBytes += orderedNodes[m].bytes.length;
107
+ }
108
+ }
109
+ if (links.size > 0) {
110
+ ctx.trace?.step("resolveConnectors", ordered.map((s) => rItem(read(ctx, s.payload), "answer", s.payload)), [...links.entries()].map(([pair, bytes]) => ({
111
+ text: `${pair}: "${decodeText(bytes)}"`,
112
+ role: "connector",
113
+ })), "the bytes the graph splices between adjacent answers (asked of the gist space)");
114
+ }
115
+ return links;
116
+ }
117
+ // ── Pipeline mechanism ──────────────────────────────────────────────────────
118
+ export const coverMechanism = {
119
+ name: "cover",
120
+ provenance: "cover",
121
+ async floor(_ctx, _query, _pre, _worthRunning) {
122
+ return 0;
123
+ },
124
+ async run(ctx, query, pre) {
125
+ const { rec, computed } = pre;
126
+ // Masking: computed spans are authoritative. Remove recognised sites
127
+ // that overlap any computed span before building the cover search.
128
+ const sites = computed.length === 0
129
+ ? rec.sites
130
+ : rec.sites.filter((s) => !computed.some((u) => s.start < u.j && u.i < s.end));
131
+ if (computed.length > 0 && sites.length < rec.sites.length) {
132
+ ctx.trace?.step("maskByComputation", rec.sites.map((s) => rItem(query.subarray(s.start, s.end), "form", s.payload, [
133
+ s.start,
134
+ s.end,
135
+ ])), sites.map((s) => rItem(query.subarray(s.start, s.end), "form", s.payload, [
136
+ s.start,
137
+ s.end,
138
+ ])), "a computation always wins: recognised forms overlapping a computed span are dropped");
139
+ }
140
+ if (sites.length === 0 && computed.length === 0)
141
+ return [];
142
+ const connectors = ctx.meter
143
+ ? await ctx.meter.time("cover.resolveConnectors", () => resolveConnectors(ctx, sites, query))
144
+ : await resolveConnectors(ctx, sites, query);
145
+ let splits = rec.splits;
146
+ let starts = rec.starts;
147
+ if (computed.length > 0) {
148
+ splits = new Set(rec.splits);
149
+ starts = new Set(rec.starts);
150
+ for (const u of computed) {
151
+ splits.add(u.i);
152
+ splits.add(u.j);
153
+ // A computation's own boundaries carry the same fold-level evidence
154
+ // a chunk boundary does — "computation always wins" (see the header
155
+ // comment) extends to being trusted ground for cross-leaf recovery.
156
+ starts.add(u.i);
157
+ starts.add(u.j);
158
+ }
159
+ }
160
+ const concepts = ctx.meter
161
+ ? await ctx.meter.time("cover.resolveConcepts", () => resolveConcepts(ctx, sites))
162
+ : await resolveConcepts(ctx, sites);
163
+ const coverDeps = [
164
+ ctx.trace?.lastIndex("recognise"),
165
+ ctx.trace?.lastIndex("computeExtensions"),
166
+ ctx.trace?.lastIndex("resolveConcepts"),
167
+ ctx.trace?.lastIndex("resolveConnectors"),
168
+ ].filter((x) => x !== undefined);
169
+ // Convert ComputedSpan[] to ComputedResult[] for the graph search.
170
+ const computedResults = computed.map((u) => ({
171
+ i: u.i,
172
+ j: u.j,
173
+ bytes: u.bytes,
174
+ node: resolve(ctx, u.bytes) ?? undefined,
175
+ }));
176
+ const tCover = ctx.trace?.enter("cover", [
177
+ ...sites.map((s) => rItem(query.subarray(s.start, s.end), "form", s.payload, [
178
+ s.start,
179
+ s.end,
180
+ ])),
181
+ ...computedResults.map((u) => rItem(u.bytes, "computed")),
182
+ ], coverDeps.length ? coverDeps : undefined);
183
+ const solved = ctx.search.cover(query.length, sites, concepts, rec.leaves, splits, starts, undefined, connectors, computedResults, ctx.trace ? (steps) => traceDerivation(ctx, steps) : undefined);
184
+ const segs = solved && solved.segs;
185
+ tCover?.done(segs === null
186
+ ? []
187
+ : segs.map((s) => rItem(s.bytes, s.rec ? "chosen" : "bridge", s.node, [s.i, s.j])), segs === null
188
+ ? "no cover of the query composed"
189
+ : "lightest derivation: the chosen spans, left to right");
190
+ if (segs === null)
191
+ return [];
192
+ const W = ctx.space.maxGroup;
193
+ // A chosen span's SUBSTITUTED bytes (an edge followed from a recognised
194
+ // site, not the site's own literal text read back) that equal a byte
195
+ // span the query ALREADY CONTAINS elsewhere restates part of the
196
+ // question — never an answer (see {@link segRestatesQuery}). A
197
+ // recognised site that is itself an entire PRIOR TURN of a multi-turn
198
+ // query is exactly this shape: it carries a genuine learnt
199
+ // continuation, but that continuation is something the asker already
200
+ // said moments later in the SAME query. liftAnswer TRIMS such spans
201
+ // out of both the framing decision and the final concatenation — the
202
+ // OTHER spans a derivation chose are independent evidence and must not
203
+ // be discarded along with the stale one.
204
+ const restated = segs.filter((s) => segRestatesQuery(s, query, query.length, W));
205
+ if (restated.length > 0) {
206
+ ctx.trace?.step("restatedSpan", restated.map((s) => rItem(s.bytes, "substituted", s.node, [s.i, s.j])), [], "the chosen span's substitution already occurs elsewhere in the query — trimmed from the answer, not an answer itself");
207
+ }
208
+ const composed = liftAnswer(segs, query.length, query, W);
209
+ if (composed === null)
210
+ return [];
211
+ ctx.trace?.step("liftAnswer", segs.map((s) => rItem(s.bytes, s.rec ? "chosen" : "scaffolding", s.node, [s.i, s.j])), [rItem(composed, "answer", resolve(ctx, composed) ?? undefined)], "lift the recognised region out of the asker's framing", tCover ? [tCover.index] : undefined);
212
+ // accounted = RECOGNISED, non-restating cover spans only — a trimmed
213
+ // restated span contributes nothing to the composed answer, so it must
214
+ // not be priced as if it did (PASS-carried bytes are priced already;
215
+ // the diagnostic label reflects the same distinction).
216
+ const accounted = segs
217
+ .filter((s) => s.rec && !segRestatesQuery(s, query, query.length, W))
218
+ .map((s) => [s.i, s.j]);
219
+ return [{
220
+ bytes: composed,
221
+ accounted,
222
+ moves: 0,
223
+ weight: solved.cost, // A*LD derivation's g-value IS the weight
224
+ unexplained: unexplainedLabel(query, accounted),
225
+ }];
226
+ },
227
+ };
@@ -0,0 +1,33 @@
1
+ import type { MindContext } from "../types.js";
2
+ import type { PipelineMechanism, Precomputed } from "../pipeline-mechanism.js";
3
+ /** Find the first span-shaped skill exemplar among the ranked anchors from
4
+ * climbAttentionAll and read the analogous span from the query. Returns
5
+ * the extracted bytes PLUS the query spans the skill ACCOUNTED FOR — the
6
+ * located frames AND any read span BOUNDED by located frames on both
7
+ * sides, the elementary evidence think's grounding decider weighs. A
8
+ * bounded read is explained: the skill located both its borders in the
9
+ * query and emitted exactly what sits between them. An OPEN-ENDED read
10
+ * (the exemplar's answer reaches the context's end, so the query is read
11
+ * to its own end with no located right border) remains a guess about where
12
+ * the span stops — it stays unaccounted, priced by exclusion like the
13
+ * cover's bridged bytes. (Accounting frames only — the earlier convention
14
+ * — let a CAST juxtaposition that merely echoed the query's exact site
15
+ * outweigh a correct bounded extraction: the same span counted as
16
+ * explained for one mechanism and not the other, and the asymmetry, not
17
+ * the answers' merits, decided the grounding.) Null when no skill
18
+ * applies. */
19
+ export declare function extractBySkill(ctx: MindContext, query: Uint8Array, pre: Precomputed): Promise<{
20
+ bytes: Uint8Array;
21
+ accounted: Array<[number, number]>;
22
+ unexplained: string;
23
+ } | null>;
24
+ /** Decompose an answer into substrings of its surrounding context, in order —
25
+ * the STRONG span-shape reading (see the section note above). Returns null
26
+ * when no greedy longest-run decomposition exists. Adjacent runs that
27
+ * connect contiguously are merged. */
28
+ export declare function answerRunsInContext(_ctx: MindContext, context: Uint8Array, answer: Uint8Array): Array<{
29
+ start: number;
30
+ end: number;
31
+ ansLen: number;
32
+ }> | null;
33
+ export declare const extractionMechanism: PipelineMechanism;