@hviana/sema 0.1.0 → 0.1.1

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 (131) 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 +2216 -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 +212 -0
  26. package/dist/src/alu/src/parser.js +469 -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/config.d.ts +114 -0
  38. package/dist/src/config.js +96 -0
  39. package/dist/src/derive/src/deduction.d.ts +125 -0
  40. package/dist/src/derive/src/deduction.js +155 -0
  41. package/dist/src/derive/src/index.d.ts +7 -0
  42. package/dist/src/derive/src/index.js +11 -0
  43. package/dist/src/derive/src/priority-queue.d.ts +20 -0
  44. package/dist/src/derive/src/priority-queue.js +73 -0
  45. package/dist/src/derive/src/rewrite.d.ts +56 -0
  46. package/dist/src/derive/src/rewrite.js +100 -0
  47. package/dist/src/derive/src/trie.d.ts +90 -0
  48. package/dist/src/derive/src/trie.js +217 -0
  49. package/dist/src/derive/test/derive.test.d.ts +1 -0
  50. package/dist/src/derive/test/derive.test.js +122 -0
  51. package/dist/src/extension.d.ts +37 -0
  52. package/dist/src/extension.js +7 -0
  53. package/dist/src/geometry.d.ts +137 -0
  54. package/dist/src/geometry.js +430 -0
  55. package/dist/src/index.d.ts +15 -0
  56. package/dist/src/index.js +21 -0
  57. package/dist/src/ingest-cache.d.ts +41 -0
  58. package/dist/src/ingest-cache.js +161 -0
  59. package/dist/src/mind/articulation.d.ts +6 -0
  60. package/dist/src/mind/articulation.js +99 -0
  61. package/dist/src/mind/attention.d.ts +72 -0
  62. package/dist/src/mind/attention.js +894 -0
  63. package/dist/src/mind/canonical.d.ts +29 -0
  64. package/dist/src/mind/canonical.js +88 -0
  65. package/dist/src/mind/graph-search.d.ts +270 -0
  66. package/dist/src/mind/graph-search.js +847 -0
  67. package/dist/src/mind/index.d.ts +5 -0
  68. package/dist/src/mind/index.js +5 -0
  69. package/dist/src/mind/junction.d.ts +95 -0
  70. package/dist/src/mind/junction.js +262 -0
  71. package/dist/src/mind/learning.d.ts +47 -0
  72. package/dist/src/mind/learning.js +201 -0
  73. package/dist/src/mind/match.d.ts +111 -0
  74. package/dist/src/mind/match.js +422 -0
  75. package/dist/src/mind/mechanisms/alu.d.ts +4 -0
  76. package/dist/src/mind/mechanisms/alu.js +29 -0
  77. package/dist/src/mind/mechanisms/cast.d.ts +35 -0
  78. package/dist/src/mind/mechanisms/cast.js +447 -0
  79. package/dist/src/mind/mechanisms/confluence.d.ts +24 -0
  80. package/dist/src/mind/mechanisms/confluence.js +213 -0
  81. package/dist/src/mind/mechanisms/cover.d.ts +6 -0
  82. package/dist/src/mind/mechanisms/cover.js +179 -0
  83. package/dist/src/mind/mechanisms/extraction.d.ts +67 -0
  84. package/dist/src/mind/mechanisms/extraction.js +342 -0
  85. package/dist/src/mind/mechanisms/recall.d.ts +13 -0
  86. package/dist/src/mind/mechanisms/recall.js +151 -0
  87. package/dist/src/mind/mind.d.ts +147 -0
  88. package/dist/src/mind/mind.js +300 -0
  89. package/dist/src/mind/pipeline-mechanism.d.ts +142 -0
  90. package/dist/src/mind/pipeline-mechanism.js +213 -0
  91. package/dist/src/mind/pipeline.d.ts +20 -0
  92. package/dist/src/mind/pipeline.js +185 -0
  93. package/dist/src/mind/primitives.d.ts +43 -0
  94. package/dist/src/mind/primitives.js +162 -0
  95. package/dist/src/mind/rationale.d.ts +134 -0
  96. package/dist/src/mind/rationale.js +162 -0
  97. package/dist/src/mind/reasoning.d.ts +15 -0
  98. package/dist/src/mind/reasoning.js +162 -0
  99. package/dist/src/mind/recognition.d.ts +20 -0
  100. package/dist/src/mind/recognition.js +223 -0
  101. package/dist/src/mind/resonance.d.ts +23 -0
  102. package/dist/src/mind/resonance.js +0 -0
  103. package/dist/src/mind/trace.d.ts +15 -0
  104. package/dist/src/mind/trace.js +73 -0
  105. package/dist/src/mind/traverse.d.ts +100 -0
  106. package/dist/src/mind/traverse.js +447 -0
  107. package/dist/src/mind/types.d.ts +174 -0
  108. package/dist/src/mind/types.js +84 -0
  109. package/dist/src/rabitq-hnsw/src/database.d.ts +200 -0
  110. package/dist/src/rabitq-hnsw/src/database.js +388 -0
  111. package/dist/src/rabitq-hnsw/src/heap.d.ts +22 -0
  112. package/dist/src/rabitq-hnsw/src/heap.js +89 -0
  113. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +125 -0
  114. package/dist/src/rabitq-hnsw/src/hnsw.js +474 -0
  115. package/dist/src/rabitq-hnsw/src/index.d.ts +10 -0
  116. package/dist/src/rabitq-hnsw/src/index.js +6 -0
  117. package/dist/src/rabitq-hnsw/src/prng.d.ts +19 -0
  118. package/dist/src/rabitq-hnsw/src/prng.js +36 -0
  119. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +95 -0
  120. package/dist/src/rabitq-hnsw/src/rabitq.js +283 -0
  121. package/dist/src/rabitq-hnsw/src/store.d.ts +162 -0
  122. package/dist/src/rabitq-hnsw/src/store.js +825 -0
  123. package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +1 -0
  124. package/dist/src/rabitq-hnsw/test/hnsw.test.js +948 -0
  125. package/dist/src/store-sqlite.d.ts +149 -0
  126. package/dist/src/store-sqlite.js +702 -0
  127. package/dist/src/store.d.ts +638 -0
  128. package/dist/src/store.js +1618 -0
  129. package/dist/src/vec.d.ts +31 -0
  130. package/dist/src/vec.js +109 -0
  131. package/package.json +1 -1
@@ -0,0 +1,162 @@
1
+ // rationale.ts — the inference, told as it happens.
2
+ //
3
+ // Sema's edge over a weight matrix is that every answer is a DERIVATION over
4
+ // explicit facts, not a sample from an opaque distribution. This module turns
5
+ // that derivation into a stream a human (or a debugger) can read: as {@link
6
+ // Mind.respond} thinks, each inference MECHANISM it runs emits a {@link
7
+ // RationaleStep} the moment it completes — what it was handed, what it produced,
8
+ // where it sits in the nesting of mechanisms, and which earlier steps fed it.
9
+ //
10
+ // Nothing here drives the inference; it only WITNESSES it. When no
11
+ // `inspectRationale` callback is supplied the tracer is never constructed and
12
+ // the cost is exactly zero — every emit site in src/mind/mind.ts is guarded by `?.`, and
13
+ // optional-chaining short-circuits its arguments, so the items are not even
14
+ // built (see {@link Mind.respond}).
15
+ //
16
+ // The shape of a step mirrors how Sema reasons. A mechanism is rarely a 1→1
17
+ // map: {@link Mind.recognise} DECOMPOSES one query into many recognised forms;
18
+ // the cover COMBINES many forms back into one answer; resonance fans one gist
19
+ // out into a ranked list of hits. So a step's `inputs` and `outputs` are each a
20
+ // VECTOR — an ordered list of {@link RationaleItem}s, one per element — and the
21
+ // fan-out / fan-in is visible in their lengths.
22
+ /** Decode bytes to text for display, dropping the NUL padding the encoder uses
23
+ * (the same cleanup {@link Mind.respondText} does for its result). */
24
+ export function decodeText(bytes) {
25
+ return new TextDecoder().decode(bytes.filter((b) => b !== 0x00));
26
+ }
27
+ /** The `[start, end)` gaps of `[0, queryLen)` NOT covered by `accounted` —
28
+ * the same union-of-spans reading think's grounding decider prices at PASS
29
+ * per byte, exposed here so a mechanism can turn it into a human label. */
30
+ export function unexplainedSpans(queryLen, accounted) {
31
+ const sorted = accounted
32
+ .map(([s, e]) => [Math.max(0, s), Math.min(queryLen, e)])
33
+ .filter(([s, e]) => e > s)
34
+ .sort((a, b) => a[0] - b[0]);
35
+ const gaps = [];
36
+ let reach = 0;
37
+ for (const [s, e] of sorted) {
38
+ if (s > reach)
39
+ gaps.push([reach, s]);
40
+ if (e > reach)
41
+ reach = e;
42
+ }
43
+ if (reach < queryLen)
44
+ gaps.push([reach, queryLen]);
45
+ return gaps;
46
+ }
47
+ /** A human-readable label for the query bytes a mechanism's `accounted`
48
+ * spans leave unexplained — purely diagnostic (Task 2's negative evidence):
49
+ * it never changes a candidate's weight, only what the rationale trace
50
+ * says the mechanism left on the table. `""` when nothing is unexplained. */
51
+ export function unexplainedLabel(query, accounted) {
52
+ const gaps = unexplainedSpans(query.length, accounted);
53
+ if (gaps.length === 0)
54
+ return "";
55
+ return gaps.map(([s, e]) => decodeText(query.subarray(s, e))).join(" … ");
56
+ }
57
+ /** The live tracer: a stack of open mechanisms over one {@link Mind.respond}.
58
+ *
59
+ * Sema's inference is single-threaded and strictly sequential — every async
60
+ * step is awaited before the next begins, and `respond` holds no two thoughts
61
+ * at once — so a plain stack exactly tracks the current nesting: {@link enter}
62
+ * pushes, {@link Scope.done} pops, and {@link step} (a mechanism with no
63
+ * sub-steps) is the two fused. The tracer never branches the control flow; it
64
+ * only records it. */
65
+ export class Rationale {
66
+ sink;
67
+ next = 0;
68
+ /** Open mechanisms, outermost first. Each frame remembers the last child it
69
+ * has spawned so the next sibling can default its data-flow edge to it. */
70
+ stack = [];
71
+ /** The most recent step index emitted under each mechanism name — the handle
72
+ * a later step uses to name an EARLIER mechanism as its data-flow producer
73
+ * (e.g. cover depends on the latest recognise / computeExtensions). One tracer is
74
+ * built per response and inference is sequential, so "most recent" is exactly
75
+ * "the one that produced the inputs I am about to consume". */
76
+ lastByName = new Map();
77
+ constructor(sink) {
78
+ this.sink = sink;
79
+ }
80
+ /** The index of the most recent step with this mechanism name, or undefined if
81
+ * none has run. Used to wire an explicit producer edge into {@link
82
+ * Scope.done} / {@link step}'s `deps`. */
83
+ lastIndex(name) {
84
+ return this.lastByName.get(name);
85
+ }
86
+ /** The mechanism names currently open, outermost → innermost. */
87
+ path(leaf) {
88
+ const p = this.stack.map((f) => f.name);
89
+ p.push(leaf);
90
+ return p;
91
+ }
92
+ /** The default data-flow edge for a step entering now: the previous sibling
93
+ * inside the current mechanism, else the enclosing mechanism, else nothing
94
+ * (the root). An explicit `deps` overrides this. */
95
+ defaultDeps() {
96
+ const top = this.stack[this.stack.length - 1];
97
+ if (!top)
98
+ return [];
99
+ return [top.lastChild ?? top.index];
100
+ }
101
+ /** Reserve this step's index and register it as the current mechanism's most
102
+ * recent child (so the NEXT sibling chains to it) and as the most recent step
103
+ * of its own NAME (so a later mechanism can name it as a producer). */
104
+ reserve(name) {
105
+ const index = this.next++;
106
+ const top = this.stack[this.stack.length - 1];
107
+ if (top)
108
+ top.lastChild = index;
109
+ this.lastByName.set(name, index);
110
+ return index;
111
+ }
112
+ emit(index, mechanism, inputs, outputs, deps, note) {
113
+ this.sink({
114
+ index,
115
+ mechanism,
116
+ parent: this.stack.length > 0
117
+ ? this.stack[this.stack.length - 1].index
118
+ : -1,
119
+ dependsOn: deps ?? this.defaultDeps(),
120
+ inputs,
121
+ outputs,
122
+ note,
123
+ });
124
+ }
125
+ /** Enter a mechanism that has sub-steps. Captures its inputs and the nesting
126
+ * now; the matching {@link Scope.done} supplies the outputs when it finishes.
127
+ * `deps` overrides the default data-flow edge (previous sibling / parent). */
128
+ enter(name, inputs, deps) {
129
+ const mechanism = this.path(name);
130
+ const resolvedDeps = deps ?? this.defaultDeps();
131
+ const index = this.reserve(name);
132
+ this.stack.push({ index, name, lastChild: null });
133
+ let closed = false;
134
+ const emit = this.emit.bind(this);
135
+ const pop = () => {
136
+ // Pop down to and including this frame — tolerant of a sub-mechanism that
137
+ // forgot to close, so one missed `done` cannot desync the whole stack.
138
+ const at = this.stack.findIndex((f) => f.index === index);
139
+ if (at >= 0)
140
+ this.stack.length = at;
141
+ };
142
+ return {
143
+ index,
144
+ done: (outputs, note) => {
145
+ if (closed)
146
+ return;
147
+ closed = true;
148
+ pop();
149
+ emit(index, mechanism, inputs, outputs, resolvedDeps, note);
150
+ },
151
+ };
152
+ }
153
+ /** Record a mechanism that has no sub-steps — its inputs and outputs are both
154
+ * known at the call site. Returns its index, for a later step to depend on. */
155
+ step(name, inputs, outputs, note, deps) {
156
+ const mechanism = this.path(name);
157
+ const resolvedDeps = deps ?? this.defaultDeps();
158
+ const index = this.reserve(name);
159
+ this.emit(index, mechanism, inputs, outputs, resolvedDeps, note);
160
+ return index;
161
+ }
162
+ }
@@ -0,0 +1,15 @@
1
+ import type { MindContext } from "./types.js";
2
+ import type { Precomputed } from "./pipeline-mechanism.js";
3
+ /** Extend a grounded answer forward across facts (multi-hop reasoning).
4
+ * Pivots on the longest unconsumed learnt context each answer contains,
5
+ * then follows the pivot's continuation to the next fact. Repeats up
6
+ * to `cfg.recallQueryK` hops. `preConsumed` carries node ids already
7
+ * spoken for by the grounding stage (cover/extract/CAST). `pre` is the
8
+ * response's shared pre-computation — the post-grounding stages read the
9
+ * same container the mechanisms did. */
10
+ export declare function reason(ctx: MindContext, query: Uint8Array, answer: Uint8Array, preConsumed: ReadonlySet<number>, pre: Precomputed): Promise<Uint8Array>;
11
+ /** Fuse independent points of attention into one answer (multi-topic).
12
+ * When the consensus climb finds more than one dominant point, each
13
+ * independent point grounds its own answer; they are bridged together
14
+ * by any learnt connector the graph holds between them. */
15
+ export declare function fuseAttention(ctx: MindContext, query: Uint8Array, primary: Uint8Array, pre: Precomputed): Promise<Uint8Array>;
@@ -0,0 +1,162 @@
1
+ // reasoning.ts — multi-hop reasoning + multi-topic fusion (Section 4 of the mind).
2
+ //
3
+ // reason — extend an answer forward across facts (multi-hop)
4
+ // fuseAttention — fuse independent points of attention (multi-topic)
5
+ import { rItem, rNode } from "./trace.js";
6
+ import { bytesEqual, indexOf } from "../bytes.js";
7
+ import { resolve } from "./primitives.js";
8
+ import { corpusN } from "./traverse.js";
9
+ import { follow, haloSiblings, project } from "./match.js";
10
+ import { joinWithBridge, pivotInto } from "./resonance.js";
11
+ /** Extend a grounded answer forward across facts (multi-hop reasoning).
12
+ * Pivots on the longest unconsumed learnt context each answer contains,
13
+ * then follows the pivot's continuation to the next fact. Repeats up
14
+ * to `cfg.recallQueryK` hops. `preConsumed` carries node ids already
15
+ * spoken for by the grounding stage (cover/extract/CAST). `pre` is the
16
+ * response's shared pre-computation — the post-grounding stages read the
17
+ * same container the mechanisms did. */
18
+ export async function reason(ctx, query, answer, preConsumed, pre) {
19
+ // Echo guard: a query that is ITSELF a learnt continuation (some context's
20
+ // answer) is being asked back at the system — hopping forward from it would
21
+ // chain through the very fact that produced it and echo the conversation
22
+ // back. The grounded answer alone is the honest read-out. Deliberately a
23
+ // broad structural gate; pinned by test/31-audit.
24
+ const qId = pre.queryResolved;
25
+ if (qId !== null && ctx.store.prevCount(qId) > 0)
26
+ return answer;
27
+ const consumed = new Set();
28
+ // Consume a node and its neighbours for pivot-cycle prevention — CAPPED at
29
+ // the hub bound, via the store's LIMITed edge reads: a common continuation's
30
+ // reverse fan-in (and a hub context's forward fan-out) is corpus-sized, and
31
+ // no per-hop operation may grow with the corpus. The cap follows the one
32
+ // convention every fan-out decision uses (first √N in the relation's own
33
+ // read order); a pivot suppressed only by a beyond-cap neighbour may now
34
+ // fire — the same visibility trade chooseNext documents.
35
+ const hubBound = Math.ceil(Math.sqrt(corpusN(ctx)));
36
+ const consumeNode = (id) => {
37
+ if (id === null)
38
+ return;
39
+ consumed.add(id);
40
+ for (const p of ctx.store.prevFirst(id, hubBound))
41
+ consumed.add(p);
42
+ };
43
+ const consumeAll = (id) => {
44
+ if (id === null)
45
+ return;
46
+ consumeNode(id);
47
+ for (const n of ctx.store.nextFirst(id, hubBound))
48
+ consumed.add(n);
49
+ };
50
+ // Pre-consume whatever the grounding stage already spoke for. The halo
51
+ // sweep is one ANN query per node — cap it at haloQueryK sweeps (cover
52
+ // grounding can pre-consume one node per recognised site, O(query length));
53
+ // nodes past the cap are still consumed directly, they just skip the
54
+ // synonym expansion.
55
+ let haloSweeps = 0;
56
+ for (const id of preConsumed) {
57
+ consumeNode(id);
58
+ if (haloSweeps >= ctx.cfg.haloQueryK)
59
+ continue;
60
+ const h = ctx.store.halo(id);
61
+ if (!h)
62
+ continue;
63
+ haloSweeps++;
64
+ for (const sib of await haloSiblings(ctx, id, h))
65
+ consumeNode(sib.id);
66
+ }
67
+ let cur = answer;
68
+ const qv = pre.guide; // the response-wide guide IS the query's gist
69
+ let t;
70
+ const startedFrom = answer;
71
+ for (let hop = 0; hop < ctx.cfg.recallQueryK; hop++) {
72
+ const curId = resolve(ctx, cur);
73
+ consumeNode(curId);
74
+ // Forward-absorb: follow only UNCONSUMED continuations. The gate below
75
+ // checks an unconsumed edge EXISTS, but follow()'s chooseNext knows
76
+ // nothing of `consumed` and may still walk to a consumed fixpoint —
77
+ // absorbing it would repeat content the grounding stage already spoke
78
+ // for, so a consumed fixpoint falls through to the pivot step instead.
79
+ if (curId !== null &&
80
+ ctx.store.nextFirst(curId, hubBound).some((n) => !consumed.has(n))) {
81
+ const fwd = await follow(ctx, curId, qv);
82
+ const fwdId = fwd !== null ? resolve(ctx, fwd) : null;
83
+ if (fwd !== null && !bytesEqual(fwd, cur) &&
84
+ (fwdId === null || !consumed.has(fwdId))) {
85
+ consumeAll(curId);
86
+ t ??= ctx.trace?.enter("reason", [
87
+ rItem(startedFrom, "grounded"),
88
+ ]);
89
+ ctx.trace?.step("absorbForward", [rItem(cur, "answer", curId)], [rItem(fwd, "answer", resolve(ctx, fwd) ?? undefined)], "the answer is itself a learnt fact — follow its continuation to the fixpoint");
90
+ cur = fwd;
91
+ continue;
92
+ }
93
+ }
94
+ // Pivot: find the longest unconsumed learnt context the answer contains.
95
+ consumeAll(curId);
96
+ const pivot = await pivotInto(ctx, cur, consumed);
97
+ if (pivot === null)
98
+ break;
99
+ const fc = await follow(ctx, pivot, qv);
100
+ consumeAll(pivot);
101
+ if (fc === null || bytesEqual(fc, cur))
102
+ break;
103
+ t ??= ctx.trace?.enter("reason", [rItem(startedFrom, "grounded")]);
104
+ ctx.trace?.step("pivotStep", [rItem(cur, "answer"), rNode(ctx, pivot, "pivot")], [rItem(fc, "answer", resolve(ctx, fc) ?? undefined)], "pivot on the shared span this answer contains, then step forward across that fact");
105
+ cur = fc;
106
+ }
107
+ t?.done([rItem(cur, "answer", resolve(ctx, cur) ?? undefined)], "the multi-hop chain's fixpoint");
108
+ return cur;
109
+ }
110
+ /** Fuse independent points of attention into one answer (multi-topic).
111
+ * When the consensus climb finds more than one dominant point, each
112
+ * independent point grounds its own answer; they are bridged together
113
+ * by any learnt connector the graph holds between them. */
114
+ export async function fuseAttention(ctx, query, primary, pre) {
115
+ // When the answer is structurally drawn from the query itself
116
+ // (extraction), it already spans all the query's pieces — fusion
117
+ // would only add noise from unrelated stored contexts. The gate is
118
+ // STRICT containment (resolved node in the query's tree, or a contiguous
119
+ // byte run): the old sparse-subsequence test was trivially satisfied by
120
+ // short answers over long queries, silently starving multi-topic queries
121
+ // of fusion.
122
+ if (containsSpan(ctx, query, primary))
123
+ return primary;
124
+ // The committed points of attention ARE the shared climb's roots (same
125
+ // query, same k, same DF mode) — read them from Precomputed instead of
126
+ // re-climbing, so even a traced response pays for the climb once.
127
+ const forest = (await pre.attention()).roots;
128
+ if (forest.length <= 1)
129
+ return primary;
130
+ const pieces = [
131
+ { start: forest[0].start, bytes: primary },
132
+ ];
133
+ const qv = pre.guide; // once, not per root
134
+ const t = ctx.trace?.enter("fuseAttention", [
135
+ rItem(primary, "primary"),
136
+ ...forest.slice(1).map((r) => rNode(ctx, r.anchor, "point", r.vote)),
137
+ ]);
138
+ for (const root of forest.slice(1)) {
139
+ const g = await project(ctx, root.anchor, qv);
140
+ if (g === null || g.length === 0)
141
+ continue;
142
+ if (pieces.some((p) => indexOf(p.bytes, g, 0) >= 0))
143
+ continue;
144
+ pieces.push({ start: root.start, bytes: g });
145
+ }
146
+ if (pieces.length === 1) {
147
+ t?.done([rItem(primary, "answer")], "no further independent point grounded");
148
+ return primary;
149
+ }
150
+ pieces.sort((a, b) => a.start - b.start);
151
+ let out = pieces[0].bytes;
152
+ for (let i = 1; i < pieces.length; i++) {
153
+ // An approximate-resonance miss (or a genuinely unlearnt junction) joins
154
+ // the pieces bare — joinWithBridge surfaces it as a bridgeMiss step.
155
+ out = await joinWithBridge(ctx, out, pieces[i].bytes);
156
+ }
157
+ t?.done([rItem(out, "answer", resolve(ctx, out) ?? undefined)], `fused ${pieces.length} independent points of attention into one answer`);
158
+ return out;
159
+ }
160
+ // (resonance.js is already a static dependency above — `bridge` — so the old
161
+ // dynamic import of pivotInto guarded against a cycle that does not exist.)
162
+ import { containsSpan } from "./mechanisms/extraction.js";
@@ -0,0 +1,20 @@
1
+ import type { MindContext, Recognition, Segment } from "./types.js";
2
+ /** Decompose a byte stream into every stored form that leads somewhere
3
+ * (has a continuation edge or a halo). Two complementary readings:
4
+ *
5
+ * • structural — walk the query's own perceived tree, naming each subtree
6
+ * by findLeaf at the leaves and findBranch above. Catches every form
7
+ * aligned to the query's segmentation.
8
+ *
9
+ * • canonical — re-derive the store's segmentation directly: at each byte,
10
+ * the longest known leaf, chained into flat branches. Names forms the
11
+ * query's own cut cannot, and records sub-leaf boundaries as `splits`.
12
+ *
13
+ * Both O(n · maxGroup) bounded O(1) probes — never a scan of the corpus. */
14
+ export declare function recognise(ctx: MindContext, bytes: Uint8Array): Recognition;
15
+ /** Segment bytes using the geometry's own groupings — leaf-parent
16
+ * nodes from the perceived tree, with consecutive bare leaves merged
17
+ * into one segment. Each segment's gist is perceived from its bytes
18
+ * IN ISOLATION, so the same content has the same gist regardless of
19
+ * where it appears. */
20
+ export declare function segment(ctx: MindContext, bytes: Uint8Array): Segment[];
@@ -0,0 +1,223 @@
1
+ // recognition.ts — Section 2 of the mind:
2
+ // Address + Read over byte streams — decompose a query into its known forms.
3
+ //
4
+ // recognise — structural + canonical decomposition into every stored form
5
+ // that leads somewhere (has a continuation edge or a halo).
6
+ // segment — leaf-parent segmentation using the geometry's own groupings.
7
+ import { rItem } from "./trace.js";
8
+ import { foldTree, gistOf, perceive, resolve } from "./primitives.js";
9
+ import { leadsSomewhere } from "./traverse.js";
10
+ import { chainReach, leafIdAt } from "./canonical.js";
11
+ import { isChunk } from "../sema.js";
12
+ /** Decompose a byte stream into every stored form that leads somewhere
13
+ * (has a continuation edge or a halo). Two complementary readings:
14
+ *
15
+ * • structural — walk the query's own perceived tree, naming each subtree
16
+ * by findLeaf at the leaves and findBranch above. Catches every form
17
+ * aligned to the query's segmentation.
18
+ *
19
+ * • canonical — re-derive the store's segmentation directly: at each byte,
20
+ * the longest known leaf, chained into flat branches. Names forms the
21
+ * query's own cut cannot, and records sub-leaf boundaries as `splits`.
22
+ *
23
+ * Both O(n · maxGroup) bounded O(1) probes — never a scan of the corpus. */
24
+ export function recognise(ctx, bytes) {
25
+ // Per-response memo (see MindContext.recogniseMemo): think, articulate and
26
+ // the pre-consume pass all recognise the same byte objects; each repeat is
27
+ // O(n·maxGroup²) store probes. Skipped while tracing so every call still
28
+ // emits its rationale step.
29
+ if (ctx.recogniseMemo && !ctx.trace) {
30
+ const hit = ctx.recogniseMemo.get(bytes);
31
+ if (hit !== undefined)
32
+ return hit;
33
+ const fresh = recogniseImpl(ctx, bytes);
34
+ ctx.recogniseMemo.set(bytes, fresh);
35
+ return fresh;
36
+ }
37
+ return recogniseImpl(ctx, bytes);
38
+ }
39
+ function recogniseImpl(ctx, bytes) {
40
+ const store = ctx.store;
41
+ const sites = [];
42
+ const leaves = [];
43
+ const splits = new Set();
44
+ if (bytes.length === 0)
45
+ return { sites, leaves, splits };
46
+ // Span-resolve memo for THIS call: the structural pass (sub-runs inside
47
+ // leaf-parents) and the canonical pass (leaf-id chains) probe overlapping
48
+ // spans, and each resolve() is a full fold of the sub-span (fresh subarray
49
+ // objects — the per-response perceive memo cannot see them). Keyed
50
+ // numerically by (start, end); resolve is pure and the store is read-only
51
+ // here, so a hit is exact.
52
+ const spanIds = new Map();
53
+ const resolveSpan = (start, end) => {
54
+ const key = start * (bytes.length + 1) + end;
55
+ let id = spanIds.get(key);
56
+ if (id === undefined) {
57
+ id = resolve(ctx, bytes.subarray(start, end));
58
+ spanIds.set(key, id);
59
+ }
60
+ return id;
61
+ };
62
+ const emit = (start, end, id) => {
63
+ if (leadsSomewhere(ctx, id)) {
64
+ sites.push({ start, end, payload: id });
65
+ }
66
+ };
67
+ // ── structural: the query's own perceived tree ──────────────────────
68
+ const starts = new Set();
69
+ starts.add(0);
70
+ foldTree(ctx, perceive(ctx, bytes), 0, (n, start, end, node) => {
71
+ if (n.kids === null) {
72
+ leaves.push({ start, end, bytes: n.leaf ?? new Uint8Array(0), node });
73
+ }
74
+ if (node !== null)
75
+ emit(start, end, node);
76
+ if (isChunk(n)) {
77
+ starts.add(start);
78
+ // Try every sub-span within this leaf-parent.
79
+ const leafOffsets = [];
80
+ let off = start;
81
+ for (const k of n.kids) {
82
+ leafOffsets.push(off);
83
+ off += k.leaf?.length ?? 0;
84
+ }
85
+ for (let i = 0; i < n.kids.length; i++) {
86
+ const subIds = [];
87
+ for (let j = i; j < n.kids.length; j++) {
88
+ const kj = n.kids[j];
89
+ if (kj.kids !== null || !kj.leaf)
90
+ break;
91
+ const lid = store.findLeaf(kj.leaf);
92
+ if (lid === null)
93
+ break;
94
+ subIds.push(lid);
95
+ const branch = store.findBranch(subIds);
96
+ if (branch === null)
97
+ continue;
98
+ const subEnd = leafOffsets[j] + (kj.leaf?.length ?? 0);
99
+ const resolved = resolveSpan(leafOffsets[i], subEnd);
100
+ if (resolved !== null)
101
+ emit(leafOffsets[i], subEnd, resolved);
102
+ }
103
+ }
104
+ }
105
+ });
106
+ // ── canonical: longest-known-leaf re-segmentation ──────────────────
107
+ const W = ctx.space.maxGroup;
108
+ const singleLeaf = new Array(bytes.length).fill(null);
109
+ for (let p = 0; p < bytes.length; p++) {
110
+ const id = leafIdAt(ctx, bytes, p);
111
+ if (id !== null)
112
+ singleLeaf[p] = { id, end: p + 1 };
113
+ }
114
+ const leafFrom = (p) => {
115
+ if (p >= bytes.length)
116
+ return null;
117
+ return singleLeaf[p];
118
+ };
119
+ const chunkEnd = new Uint32Array(bytes.length);
120
+ const sorted = [...starts].sort((a, b) => a - b);
121
+ for (let si = 0; si < sorted.length; si++) {
122
+ const chunkStart = sorted[si];
123
+ const chunkLimit = si + 1 < sorted.length ? sorted[si + 1] : bytes.length;
124
+ for (let p = chunkStart; p < chunkLimit; p++) {
125
+ chunkEnd[p] = chunkLimit;
126
+ }
127
+ }
128
+ const tryChain = (p, maxIds) => {
129
+ const first = leafFrom(p);
130
+ if (!first)
131
+ return;
132
+ emit(p, first.end, first.id);
133
+ const ids = [first.id];
134
+ let pos = first.end;
135
+ let prevId = null;
136
+ for (let depth = 1; pos < bytes.length && ids.length <= maxIds; depth++) {
137
+ const nx = leafFrom(pos);
138
+ if (!nx)
139
+ break;
140
+ ids.push(nx.id);
141
+ pos = nx.end;
142
+ if (store.findBranch(ids) === null)
143
+ continue;
144
+ const id = resolveSpan(p, pos);
145
+ if (id === null || id === prevId)
146
+ continue;
147
+ prevId = id;
148
+ emit(p, pos, id);
149
+ }
150
+ };
151
+ for (let p = 0; p < bytes.length; p++) {
152
+ if (starts.has(p)) {
153
+ tryChain(p, chainReach(W)); // boundary start — full reach
154
+ }
155
+ else {
156
+ const limit = chunkEnd[p] + W;
157
+ tryChain(p, Math.min(limit - p, chainReach(W)));
158
+ }
159
+ }
160
+ // ── splits: a form boundary that does not fall on a leaf edge ────────
161
+ const leafEdges = new Set([bytes.length]);
162
+ for (const lf of leaves)
163
+ leafEdges.add(lf.start);
164
+ for (const s of sites) {
165
+ if (!leafEdges.has(s.start))
166
+ splits.add(s.start);
167
+ if (!leafEdges.has(s.end))
168
+ splits.add(s.end);
169
+ }
170
+ ctx.trace?.step("recognise", [rItem(bytes, "query")], sites.map((s) => rItem(bytes.subarray(s.start, s.end), "form", s.payload, [
171
+ s.start,
172
+ s.end,
173
+ ])), `decompose the query into ${sites.length} learnt form(s) that lead somewhere` +
174
+ ` (over ${leaves.length} perceived leaves)`);
175
+ return { sites, leaves, splits };
176
+ }
177
+ /** Segment bytes using the geometry's own groupings — leaf-parent
178
+ * nodes from the perceived tree, with consecutive bare leaves merged
179
+ * into one segment. Each segment's gist is perceived from its bytes
180
+ * IN ISOLATION, so the same content has the same gist regardless of
181
+ * where it appears. */
182
+ export function segment(ctx, bytes) {
183
+ const tree = perceive(ctx, bytes);
184
+ const out = [];
185
+ let pendingStart = -1;
186
+ let pendingEnd = -1;
187
+ const flush = () => {
188
+ if (pendingStart >= 0 && pendingEnd > pendingStart) {
189
+ out.push({
190
+ start: pendingStart,
191
+ end: pendingEnd,
192
+ v: gistOf(ctx, bytes.subarray(pendingStart, pendingEnd)),
193
+ });
194
+ }
195
+ pendingStart = -1;
196
+ pendingEnd = -1;
197
+ };
198
+ const walk = (n, start) => {
199
+ if (n.kids === null) {
200
+ const end = start + (n.leaf?.length ?? 0);
201
+ if (pendingStart < 0)
202
+ pendingStart = start;
203
+ pendingEnd = end;
204
+ return end;
205
+ }
206
+ if (isChunk(n)) {
207
+ flush();
208
+ let end = start;
209
+ for (const c of n.kids)
210
+ end += c.leaf?.length ?? 0;
211
+ out.push({ start, end, v: gistOf(ctx, bytes.subarray(start, end)) });
212
+ return end;
213
+ }
214
+ flush();
215
+ let pos = start;
216
+ for (const c of n.kids)
217
+ pos = walk(c, pos);
218
+ return pos;
219
+ };
220
+ walk(tree, 0);
221
+ flush();
222
+ return out;
223
+ }
@@ -0,0 +1,23 @@
1
+ import type { MindContext } from "./types.js";
2
+ /** The connector that belongs BETWEEN two adjacent results — the graded
3
+ * junction ladder described in the module note above. Returns null when
4
+ * the graph holds no evidence that the two ever ran together. */
5
+ export declare function bridge(ctx: MindContext, left: Uint8Array, right: Uint8Array, interiorAllowance?: number): Promise<Uint8Array | null>;
6
+ /** Join two spans with the learnt connector between them, when one exists —
7
+ * the composition step every out-of-search assembly (multi-topic fusion,
8
+ * CAST's substitution and comparison) shares. A miss joins the pieces BARE
9
+ * and is never silent: it emits the same `bridgeMiss` trace step everywhere,
10
+ * so a degraded join is visible in the rationale regardless of which
11
+ * mechanism paid it. (The in-search connector splice in graph-search.ts is
12
+ * the same concept inside the deduction, where the join is a costed rule.) */
13
+ export declare function joinWithBridge(ctx: MindContext, left: Uint8Array, right: Uint8Array): Promise<Uint8Array>;
14
+ /** The pivot a produced answer bridges through: the longest UNCONSUMED learnt
15
+ * CONTEXT (a node bearing a continuation edge) whose bytes `answer` literally
16
+ * contains. Candidates are gathered by resonating the answer's sub-regions
17
+ * (breadth-first, leaves skipped, probes capped by branch count), then
18
+ * confirmed by exact byte containment — a near-resonance alone never hops. */
19
+ export declare function pivotInto(ctx: MindContext, answer: Uint8Array, consumed: ReadonlySet<number>): Promise<number | null>;
20
+ export declare function meaningOf(ctx: MindContext, bytes: Uint8Array, anchors: ReadonlyArray<{
21
+ name: string;
22
+ form: Uint8Array;
23
+ }>): Promise<string | null>;
Binary file
@@ -0,0 +1,15 @@
1
+ import type { MindContext } from "./types.js";
2
+ import type { DerivationStep } from "./graph-search.js";
3
+ import type { RationaleItem } from "./rationale.js";
4
+ export declare function rItem(bytes: Uint8Array, role?: string, node?: number, span?: [number, number]): RationaleItem;
5
+ export declare function rNode(ctx: MindContext, id: number, role?: string, score?: number): RationaleItem;
6
+ export declare function rDeriv(ctx: MindContext, it: DerivationStep["conclusion"], role?: string): RationaleItem;
7
+ /** The standard FALL-THROUGH closer every self-gating mechanism ends with:
8
+ * close the open scope with no outputs and the reason, and return null so
9
+ * the caller can `return fail("…")` in one expression. `t` is the scope an
10
+ * enclosing `ctx.trace?.enter(...)` returned (undefined when not tracing). */
11
+ export declare function traceFail(t: {
12
+ done(outputs: RationaleItem[], note?: string): void;
13
+ } | undefined): (note: string) => null;
14
+ export declare const MOVE_NOTE: Record<string, string>;
15
+ export declare function traceDerivation(ctx: MindContext, steps: ReadonlyArray<DerivationStep>): void;