@hviana/sema 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +470 -0
- package/AUTHORS.md +12 -0
- package/COMMERCIAL-LICENSE.md +19 -0
- package/CONTRIBUTING.md +15 -0
- package/HOW_IT_WORKS.md +4210 -0
- package/LICENSE.md +117 -0
- package/README.md +290 -0
- package/TRADEMARKS.md +19 -0
- package/example/demo.ts +46 -0
- package/example/train_base.ts +2675 -0
- package/index.html +893 -0
- package/package.json +25 -0
- package/src/alphabet.ts +34 -0
- package/src/alu/README.md +332 -0
- package/src/alu/src/alu.ts +541 -0
- package/src/alu/src/expr.ts +339 -0
- package/src/alu/src/index.ts +115 -0
- package/src/alu/src/kernel-arith.ts +377 -0
- package/src/alu/src/kernel-bits.ts +199 -0
- package/src/alu/src/kernel-logic.ts +102 -0
- package/src/alu/src/kernel-nd.ts +235 -0
- package/src/alu/src/kernel-numeric.ts +466 -0
- package/src/alu/src/operation.ts +344 -0
- package/src/alu/src/parser.ts +574 -0
- package/src/alu/src/resonance.ts +161 -0
- package/src/alu/src/text.ts +83 -0
- package/src/alu/src/value.ts +327 -0
- package/src/alu/test/alu.test.ts +1004 -0
- package/src/bytes.ts +62 -0
- package/src/config.ts +227 -0
- package/src/derive/README.md +295 -0
- package/src/derive/src/deduction.ts +263 -0
- package/src/derive/src/index.ts +24 -0
- package/src/derive/src/priority-queue.ts +70 -0
- package/src/derive/src/rewrite.ts +127 -0
- package/src/derive/src/trie.ts +242 -0
- package/src/derive/test/derive.test.ts +137 -0
- package/src/extension.ts +42 -0
- package/src/geometry.ts +511 -0
- package/src/index.ts +38 -0
- package/src/ingest-cache.ts +224 -0
- package/src/mind/articulation.ts +137 -0
- package/src/mind/attention.ts +1061 -0
- package/src/mind/canonical.ts +107 -0
- package/src/mind/graph-search.ts +1100 -0
- package/src/mind/index.ts +18 -0
- package/src/mind/junction.ts +347 -0
- package/src/mind/learning.ts +246 -0
- package/src/mind/match.ts +507 -0
- package/src/mind/mechanisms/alu.ts +33 -0
- package/src/mind/mechanisms/cast.ts +568 -0
- package/src/mind/mechanisms/confluence.ts +268 -0
- package/src/mind/mechanisms/cover.ts +248 -0
- package/src/mind/mechanisms/extraction.ts +422 -0
- package/src/mind/mechanisms/recall.ts +241 -0
- package/src/mind/mind.ts +483 -0
- package/src/mind/pipeline-mechanism.ts +326 -0
- package/src/mind/pipeline.ts +300 -0
- package/src/mind/primitives.ts +201 -0
- package/src/mind/rationale.ts +275 -0
- package/src/mind/reasoning.ts +198 -0
- package/src/mind/recognition.ts +234 -0
- package/src/mind/resonance.ts +0 -0
- package/src/mind/trace.ts +108 -0
- package/src/mind/traverse.ts +556 -0
- package/src/mind/types.ts +281 -0
- package/src/rabitq-hnsw/README.md +303 -0
- package/src/rabitq-hnsw/src/database.ts +492 -0
- package/src/rabitq-hnsw/src/heap.ts +90 -0
- package/src/rabitq-hnsw/src/hnsw.ts +514 -0
- package/src/rabitq-hnsw/src/index.ts +15 -0
- package/src/rabitq-hnsw/src/prng.ts +39 -0
- package/src/rabitq-hnsw/src/rabitq.ts +308 -0
- package/src/rabitq-hnsw/src/store.ts +994 -0
- package/src/rabitq-hnsw/test/hnsw.test.ts +1213 -0
- package/src/store-sqlite.ts +928 -0
- package/src/store.ts +2148 -0
- package/src/vec.ts +124 -0
- package/test/00-extract.test.mjs +151 -0
- package/test/01-floor.test.mjs +20 -0
- package/test/02-roundtrip.test.mjs +83 -0
- package/test/03-recall.test.mjs +98 -0
- package/test/04-think.test.mjs +627 -0
- package/test/05-concepts.test.mjs +84 -0
- package/test/08-storage.test.mjs +948 -0
- package/test/09-edges.test.mjs +65 -0
- package/test/11-universality.test.mjs +180 -0
- package/test/13-conversation.test.mjs +228 -0
- package/test/14-scaling.test.mjs +503 -0
- package/test/15-decomposition-gap.test.mjs +0 -0
- package/test/16-bridge.test.mjs +250 -0
- package/test/17-intelligence.test.mjs +240 -0
- package/test/18-alu.test.mjs +209 -0
- package/test/19-nd.test.mjs +254 -0
- package/test/20-stability.test.mjs +489 -0
- package/test/21-partial.test.mjs +158 -0
- package/test/22-multihop.test.mjs +130 -0
- package/test/23-rationale.test.mjs +316 -0
- package/test/24-generalization.test.mjs +416 -0
- package/test/25-embedding.test.mjs +75 -0
- package/test/26-cooperative.test.mjs +89 -0
- package/test/27-saturation-drop.test.mjs +637 -0
- package/test/28-unknowable.test.mjs +88 -0
- package/test/29-counterfactual.test.mjs +476 -0
- package/test/30-conflict-resolution.test.mjs +166 -0
- package/test/31-audit.test.mjs +288 -0
- package/test/32-confluence.test.mjs +173 -0
- package/test/33-multi-candidate.test.mjs +222 -0
- package/test/34-cross-region.test.mjs +252 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { Mind } from "../dist/src/index.js";
|
|
4
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
5
|
+
|
|
6
|
+
// =========================================================================
|
|
7
|
+
// THE BRIDGE — synthesising the connector between rewritten parts
|
|
8
|
+
// =========================================================================
|
|
9
|
+
//
|
|
10
|
+
// When `think` rewrites several parts of a query at once, it must decide how to
|
|
11
|
+
// JOIN the results. The connector between two rewrites is NOT simply the bytes
|
|
12
|
+
// the query had between the originals: after a rewrite, the asker's connector
|
|
13
|
+
// may no longer make sense, may be missing entirely, or may need to become
|
|
14
|
+
// something the asker never wrote.
|
|
15
|
+
//
|
|
16
|
+
// The bridge is the heart of the algorithm, not a special case. Whenever parts
|
|
17
|
+
// are rewritten, the graph is consulted for how the RESULTS cohere. Three
|
|
18
|
+
// outcomes, all decided by the graph, never assumed:
|
|
19
|
+
//
|
|
20
|
+
// • nothing — the results stand adjacent with no connector;
|
|
21
|
+
// • the input's — the query's own connector still coheres, so it is kept;
|
|
22
|
+
// • a synthesised one — the graph learned a connector between the results
|
|
23
|
+
// (a space, a ".", or a whole phrase), so it is inserted
|
|
24
|
+
// even though the asker never wrote it.
|
|
25
|
+
//
|
|
26
|
+
// The synthesised case is what these tests pin down. A previous refactor, with
|
|
27
|
+
// no test guarding it, silently dropped synthesis — "icefire" answered
|
|
28
|
+
// "coldhot" with the two rewrites run together, even when the store had learned
|
|
29
|
+
// how "cold" and "hot" connect. That is the regression these tests forbid.
|
|
30
|
+
//
|
|
31
|
+
// The graph signal: resonate the bare concatenation of the two result spans;
|
|
32
|
+
// if a learned form both BEGINS WITH the left result and ENDS WITH the right,
|
|
33
|
+
// the bytes in between are the learned connector. No learned form ⇒ no
|
|
34
|
+
// connector invented (strangers do not get a spurious phrase).
|
|
35
|
+
|
|
36
|
+
const mk = () => new Mind({ seed: 7 });
|
|
37
|
+
|
|
38
|
+
// ── 1. Synthesise a word connector the asker never wrote ─────────────────
|
|
39
|
+
// "ice"→"cold", "fire"→"hot" are independent facts; the store SEPARATELY
|
|
40
|
+
// learned the experience "cold or hot". Asking the two together — with no
|
|
41
|
+
// usable connector between them ("icefire") — must reconstruct the learned
|
|
42
|
+
// join, answering "cold or hot", not "coldhot".
|
|
43
|
+
test("bridge synthesises a learned word connector between rewrites", async () => {
|
|
44
|
+
const m = mk();
|
|
45
|
+
await m.ingest([["ice", "cold"], ["fire", "hot"]]);
|
|
46
|
+
await m.ingest("cold or hot"); // the graph learns how the answers connect
|
|
47
|
+
const r = await m.respondText("icefire");
|
|
48
|
+
assert.equal(r, "cold or hot");
|
|
49
|
+
await m.store.close();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// ── 2. Synthesise a whole-phrase connector ───────────────────────────────
|
|
53
|
+
// The connector can be anything the graph learned, not just punctuation.
|
|
54
|
+
test("bridge synthesises a learned phrase connector", async () => {
|
|
55
|
+
const m = mk();
|
|
56
|
+
await m.ingest([["sky", "blue"], ["grass", "green"]]);
|
|
57
|
+
await m.ingest("blue is the opposite of green");
|
|
58
|
+
const r = await m.respondText("skygrass");
|
|
59
|
+
assert.equal(r, "blue is the opposite of green");
|
|
60
|
+
await m.store.close();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// ── 3. No learned connection ⇒ no connector invented ─────────────────────
|
|
64
|
+
// Synthesis is graph-evidenced, never guessed. Without a learned join the two
|
|
65
|
+
// rewrites simply abut — the bridge must not hallucinate a phrase.
|
|
66
|
+
test("bridge invents nothing when the graph learned no connection", async () => {
|
|
67
|
+
const m = mk();
|
|
68
|
+
await m.ingest([["ice", "cold"], ["fire", "hot"]]);
|
|
69
|
+
const r = await m.respondText("icefire");
|
|
70
|
+
// Both answers present, adjacent, with no fabricated linking text.
|
|
71
|
+
assert.ok(r.includes("cold") && r.includes("hot"));
|
|
72
|
+
assert.ok(
|
|
73
|
+
!r.includes(" or ") && !r.includes(" is "),
|
|
74
|
+
`no connector should be invented, got ${JSON.stringify(r)}`,
|
|
75
|
+
);
|
|
76
|
+
await m.store.close();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// ── 4. The asker's own connector is kept when it still coheres ───────────
|
|
80
|
+
// Synthesis must not override a perfectly good input connector. "ice, fire"
|
|
81
|
+
// with the comma intact stays "cold, hot".
|
|
82
|
+
test("bridge keeps the asker's connector when it coheres", async () => {
|
|
83
|
+
const m = mk();
|
|
84
|
+
await m.ingest([["ice", "cold"], ["fire", "hot"]]);
|
|
85
|
+
const r = await m.respondText("ice, fire");
|
|
86
|
+
assert.ok(
|
|
87
|
+
r.includes("cold") && r.includes("hot") && r.includes(","),
|
|
88
|
+
`the asker's comma should survive, got ${JSON.stringify(r)}`,
|
|
89
|
+
);
|
|
90
|
+
await m.store.close();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ── 4b. Learning a connector phrase must NOT corrupt the parts ───────────
|
|
94
|
+
// THE PREREQUISITE THE BRIDGE STANDS ON. Multi-part rewriting works on its own
|
|
95
|
+
// ("ice fire" → "cold hot"). The moment the store ALSO learns a phrase that
|
|
96
|
+
// happens to contain those answers ("cold or hot"), the cover search must still
|
|
97
|
+
// rewrite each part cleanly — it must not let the phrase's chunks bleed into a
|
|
98
|
+
// part and turn "cold" into "or hot". (Before the fix, this regressed "ice fire"
|
|
99
|
+
// from "cold hot" to "or hothot": the fuse/resolve rules absorbed a completed
|
|
100
|
+
// rewrite into an unrelated learned phrase chunk.)
|
|
101
|
+
test("learning a connector phrase does not corrupt the rewrites", async () => {
|
|
102
|
+
const base = mk();
|
|
103
|
+
await base.ingest([["ice", "cold"], ["fire", "hot"]]);
|
|
104
|
+
const before = await base.respondText("ice fire");
|
|
105
|
+
await base.store.close();
|
|
106
|
+
assert.equal(before, "cold hot");
|
|
107
|
+
|
|
108
|
+
const m = mk();
|
|
109
|
+
await m.ingest([["ice", "cold"], ["fire", "hot"]]);
|
|
110
|
+
await m.ingest("cold or hot"); // a phrase containing both answers
|
|
111
|
+
const after = await m.respondText("ice fire");
|
|
112
|
+
// The two parts still rewrite cleanly; no chunk of the phrase bleeds in.
|
|
113
|
+
assert.equal(
|
|
114
|
+
after.replace(/[^a-z ]/g, "").replace(/ +/g, " ").trim(),
|
|
115
|
+
"cold hot",
|
|
116
|
+
`parts must stay clean, got ${JSON.stringify(after)}`,
|
|
117
|
+
);
|
|
118
|
+
await m.store.close();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// =========================================================================
|
|
122
|
+
// THE BRIDGE IS IN THE SEARCH, NOT A POST-PASS
|
|
123
|
+
// =========================================================================
|
|
124
|
+
//
|
|
125
|
+
// The connector must be chosen DURING the cover search — a weighted rule in the
|
|
126
|
+
// same deduction system as the rewrites — not stitched on afterwards. A post-hoc
|
|
127
|
+
// pass that joins finished parts pairwise, left to right, has two fatal faults
|
|
128
|
+
// these tests forbid:
|
|
129
|
+
//
|
|
130
|
+
// • it cannot see a connector that spans MORE THAN TWO parts (a learned
|
|
131
|
+
// "X then Y then Z" is invisible to pairwise bridge(X,Y), bridge(Y,Z));
|
|
132
|
+
// • joining already-rewritten parts one pair at a time lets small mismatches
|
|
133
|
+
// ACCUMULATE into near-dedup gaps (a dropped or doubled fragment), because no
|
|
134
|
+
// single decision ever sees the whole join.
|
|
135
|
+
//
|
|
136
|
+
// Pricing the connector inside the search makes the cover globally optimal: the
|
|
137
|
+
// derivation that yields the most coherent whole wins, connectors included.
|
|
138
|
+
|
|
139
|
+
// ── 5. A connector spanning three parts is found as one whole ────────────
|
|
140
|
+
// The graph learned "X then Y then Z" linking three answers. Asking a, b, c
|
|
141
|
+
// together must reconstruct that whole linked form — a pairwise post-pass,
|
|
142
|
+
// knowing only two-part forms, would miss it and emit a bare "XYZ".
|
|
143
|
+
test("bridge spans three rewritten parts at once", async () => {
|
|
144
|
+
const m = mk();
|
|
145
|
+
await m.ingest([["a", "X"], ["b", "Y"], ["c", "Z"]]);
|
|
146
|
+
await m.ingest("X then Y then Z"); // the three answers cohere as one phrase
|
|
147
|
+
const r = await m.respondText("abc");
|
|
148
|
+
assert.equal(r, "X then Y then Z");
|
|
149
|
+
await m.store.close();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ── 6. No accumulated gaps across several junctions ──────────────────────
|
|
153
|
+
// Joining many rewrites must never drop or double a fragment. Every recognised
|
|
154
|
+
// answer appears exactly once, in order, with no garbage between.
|
|
155
|
+
test("bridge accumulates no gaps across many parts", async () => {
|
|
156
|
+
const m = mk();
|
|
157
|
+
await m.ingest([["a", "X"], ["b", "Y"], ["c", "Z"]]);
|
|
158
|
+
await m.ingest("X then Y then Z");
|
|
159
|
+
const r = await m.respondText("a b c");
|
|
160
|
+
// Each answer once, in order; no doubled or dropped letter.
|
|
161
|
+
assert.equal(r.replace(/[^XYZ]/g, ""), "XYZ");
|
|
162
|
+
await m.store.close();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// =========================================================================
|
|
166
|
+
// N-ARY CONNECTORS — the whole interior, not pairwise scraps
|
|
167
|
+
// =========================================================================
|
|
168
|
+
//
|
|
169
|
+
// A learned whole that runs three or more answers together (e.g.
|
|
170
|
+
// "cold, hot, and windy") carries DIFFERENT material between different pairs:
|
|
171
|
+
// between the first two it is ", ", but between the first and the LAST it is
|
|
172
|
+
// ", hot, and " — the interior answer plus both separators. Pairwise bridging,
|
|
173
|
+
// stitched greedily left-to-right, takes the first pairwise connector (", ") and
|
|
174
|
+
// then cannot place the rest, emitting "cold, hot" + "windy" → "cold, hotwindy".
|
|
175
|
+
//
|
|
176
|
+
// The N-ary connector resolves the single learned whole that runs ALL the
|
|
177
|
+
// recognised answers in order and records, for the first answer paired with each
|
|
178
|
+
// later one, the EXACT interior between them. The cover then jumps from the first
|
|
179
|
+
// answer straight to the last across the (recognised) interior, voicing the whole
|
|
180
|
+
// coherent phrase. These tests pass only with that mechanism.
|
|
181
|
+
|
|
182
|
+
// ── 7. Three answers cohere with the full interior between them ──────────
|
|
183
|
+
test("n-ary connector voices a three-answer whole", async () => {
|
|
184
|
+
const m = mk();
|
|
185
|
+
await m.ingest([["ice", "cold"], ["fire", "hot"], ["wind", "windy"]]);
|
|
186
|
+
await m.ingest("cold, hot, and windy"); // the three cohere as one phrase
|
|
187
|
+
const r = await m.respondText("icefirewind");
|
|
188
|
+
assert.equal(r, "cold, hot, and windy");
|
|
189
|
+
await m.store.close();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// ── 8. Four answers — the interior grows, still found as one whole ───────
|
|
193
|
+
test("n-ary connector voices a four-answer whole", async () => {
|
|
194
|
+
const m = mk();
|
|
195
|
+
await m.ingest([["a", "W"], ["b", "X"], ["c", "Y"], ["d", "Z"]]);
|
|
196
|
+
await m.ingest("W, X, Y, and Z");
|
|
197
|
+
const r = await m.respondText("abcd");
|
|
198
|
+
assert.equal(r, "W, X, Y, and Z");
|
|
199
|
+
await m.store.close();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// ── 9. The asker's own separator is never crossed by an N-ary jump ───────
|
|
203
|
+
// The N-ary jump fires only across a span that is ITSELF wholly recognised
|
|
204
|
+
// (interior answers). When the asker wrote their own separator between parts,
|
|
205
|
+
// that gap is unrecognised, so the jump must NOT fire and the separator must
|
|
206
|
+
// survive — "ice fire" stays "cold hot", never the learned "cold, hot, and …".
|
|
207
|
+
test("n-ary jump does not swallow the asker's separator", async () => {
|
|
208
|
+
const m = mk();
|
|
209
|
+
await m.ingest([["ice", "cold"], ["fire", "hot"], ["wind", "windy"]]);
|
|
210
|
+
await m.ingest("cold, hot, and windy");
|
|
211
|
+
const r = await m.respondText("ice fire wind");
|
|
212
|
+
// The asker ran them apart; their own spacing is kept, parts cleanly rewritten.
|
|
213
|
+
assert.equal(
|
|
214
|
+
r.replace(/[^a-z ]/g, "").replace(/ +/g, " ").trim(),
|
|
215
|
+
"cold hot windy",
|
|
216
|
+
);
|
|
217
|
+
await m.store.close();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// =========================================================================
|
|
221
|
+
// SYNONYM JUNCTIONS — bridging through a halo sibling
|
|
222
|
+
// =========================================================================
|
|
223
|
+
//
|
|
224
|
+
// When two answers lack a direct learnt container linking them, but a HALO
|
|
225
|
+
// SIBLING of one answer participates in a learnt whole with the other (e.g.
|
|
226
|
+
// "chilly or hot" where "chilly" is a distributional synonym of "cold"), the
|
|
227
|
+
// bridge must find that junction through the sibling and extract the connector.
|
|
228
|
+
// This is the halo-sibling tier between the exact edge junctions (tier 2)
|
|
229
|
+
// and the approximate resonance fallback (tier 3): the container evidence is
|
|
230
|
+
// exact (content-addressed DAG ascent, same as tier 1), but one side is
|
|
231
|
+
// relaxed from the exact answer to a distributional sibling.
|
|
232
|
+
|
|
233
|
+
// ── 10. Connector found through a halo sibling of the left answer ──────────
|
|
234
|
+
// "ice"→"cold", "fire"→"hot" are the base facts. "chilly" shares
|
|
235
|
+
// distributional company with "cold" (both follow "freeze"), so they are halo
|
|
236
|
+
// siblings. The store separately learned "chilly or hot". The bridge must
|
|
237
|
+
// find "chilly or hot" through the halo-sibling relationship and extract
|
|
238
|
+
// " or " as the connector between "cold" and "hot".
|
|
239
|
+
test("bridge finds a connector via a halo-sibling synonym", async () => {
|
|
240
|
+
const m = mk();
|
|
241
|
+
await m.ingest([["ice", "cold"], ["fire", "hot"]]);
|
|
242
|
+
// Make "chilly" a halo sibling of "cold" — both appear after "freeze".
|
|
243
|
+
await m.ingest([["freeze", "cold"], ["freeze", "chilly"]]);
|
|
244
|
+
// Learn the junction phrase — the connector " or " lives between
|
|
245
|
+
// "chilly" and "hot", but NOT between "cold" and "hot" directly.
|
|
246
|
+
await m.ingest("chilly or hot");
|
|
247
|
+
const r = await m.respondText("icefire");
|
|
248
|
+
assert.equal(r, "cold or hot");
|
|
249
|
+
await m.store.close();
|
|
250
|
+
});
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// 17-intelligence.test.mjs — the intelligence regression bed.
|
|
2
|
+
//
|
|
3
|
+
// A corpus that mirrors the trained model's hardest pathology: every record is a
|
|
4
|
+
// shared instruction FRAME ("You are a helpful and harmless assistant…") plus a
|
|
5
|
+
// distinctive instruction → an answer. The shared scaffolding is the overwhelming majority of
|
|
6
|
+
// every context's bytes, so whole-query gist resonance is pulled toward the
|
|
7
|
+
// shared preamble and cannot, by itself, tell the records apart. Answering a
|
|
8
|
+
// reworded or partial query therefore demands the graph's CONNECTIVITY —
|
|
9
|
+
// consensus-climbing the structural parents-DAG to the context the query's
|
|
10
|
+
// distinctive sub-regions agree on — not mere whole-query similarity.
|
|
11
|
+
//
|
|
12
|
+
// These assertions pin down that capability so it cannot silently regress:
|
|
13
|
+
// • exact and distractor-framed queries must always resolve (the floor);
|
|
14
|
+
// • the aggregate score across query STYLES (exact, partial, paraphrase,
|
|
15
|
+
// reorder, distractor) must stay at or above a bar that only the
|
|
16
|
+
// connectivity-driven search clears — pure whole-query resonance scores far
|
|
17
|
+
// below it (it answers only near-verbatim queries);
|
|
18
|
+
// • a hard paraphrase that shares distinctive content resolves to its own
|
|
19
|
+
// answer, not a scaffolding-neighbour's.
|
|
20
|
+
//
|
|
21
|
+
// The bar is deliberately below the current pass count so ordinary codec/ANN
|
|
22
|
+
// jitter never flakes it, while a real loss of the climb (e.g. reverting to
|
|
23
|
+
// whole-query resonance) drops well beneath it and fails.
|
|
24
|
+
|
|
25
|
+
import { test } from "node:test";
|
|
26
|
+
import assert from "node:assert/strict";
|
|
27
|
+
import { Mind } from "../dist/src/index.js";
|
|
28
|
+
import { SQliteStore } from "../dist/src/store-sqlite.js";
|
|
29
|
+
|
|
30
|
+
const SYS =
|
|
31
|
+
"You are a helpful and harmless assistant.\n\nYou are not allowed to use any tools.\n";
|
|
32
|
+
|
|
33
|
+
const FACTS = [
|
|
34
|
+
[
|
|
35
|
+
"Describe the importance of gender equality in the workplace to a high school student in exactly 4 sentences.",
|
|
36
|
+
"Gender equality in the workplace means everyone has the same chance to be hired, paid, and promoted fairly.",
|
|
37
|
+
],
|
|
38
|
+
[
|
|
39
|
+
"Provide a summary of the 1992 Men's Olympic Basketball Team, the Dream Team, in 8 sentences.",
|
|
40
|
+
"The 1992 Dream Team was the greatest basketball roster ever assembled, winning gold in Barcelona.",
|
|
41
|
+
],
|
|
42
|
+
[
|
|
43
|
+
"Write a short story in JSON format about a street-art vendor who helps a young artist.",
|
|
44
|
+
'{"title":"First Mural","vendor":"Slick","artist":"Maya","scene":"a dawn alley of spray cans"}',
|
|
45
|
+
],
|
|
46
|
+
[
|
|
47
|
+
"Explain the importance of age discrimination laws to a high school student in 4 sentences.",
|
|
48
|
+
"Age discrimination laws stop employers from treating people unfairly because of how old they are.",
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
"Write an essay analyzing the cinematography of a classic film with the title in double brackets.",
|
|
52
|
+
"<<Light and Shadow>> Citizen Kane uses deep focus to layer meaning in every single frame.",
|
|
53
|
+
],
|
|
54
|
+
[
|
|
55
|
+
"Create a weekly basketball practice plan with drills for a high school team.",
|
|
56
|
+
"Monday is dribbling and layup drills; Wednesday defense; Friday scrimmage and free throws.",
|
|
57
|
+
],
|
|
58
|
+
[
|
|
59
|
+
"Summarize the plot of a science fiction novel about time travel in three sentences.",
|
|
60
|
+
"A physicist builds a machine that loops her through one fatal afternoon until she rewrites it.",
|
|
61
|
+
],
|
|
62
|
+
[
|
|
63
|
+
"Give tips for managing time as a returning college student with a family.",
|
|
64
|
+
"Block your study hours like appointments, batch errands, and protect one evening a week to rest.",
|
|
65
|
+
],
|
|
66
|
+
[
|
|
67
|
+
"Explain how neighborhood watch programs improve community safety.",
|
|
68
|
+
"Neighborhood watch programs deter crime by organizing residents to notice and report what is unusual.",
|
|
69
|
+
],
|
|
70
|
+
[
|
|
71
|
+
"Write a guided meditation script for managing anxiety before an exam.",
|
|
72
|
+
"Breathe in for four counts, hold, release slowly, and let the worry drain from your shoulders.",
|
|
73
|
+
],
|
|
74
|
+
[
|
|
75
|
+
"Describe how social media platforms change interpersonal communication.",
|
|
76
|
+
"Social media speeds contact but flattens nuance, so tone is read from emoji more than from words.",
|
|
77
|
+
],
|
|
78
|
+
[
|
|
79
|
+
"Summarize the rules of beach volleyball for a first-time spectator.",
|
|
80
|
+
"Beach volleyball pairs two players a side who rally a ball over a net on sand, best of three sets.",
|
|
81
|
+
],
|
|
82
|
+
[
|
|
83
|
+
"Explain the water cycle to a curious eight-year-old in simple terms.",
|
|
84
|
+
"The sun warms water into vapor, clouds gather it, and rain returns it to rivers and the sea again.",
|
|
85
|
+
],
|
|
86
|
+
[
|
|
87
|
+
"Write a cover letter for a junior data analyst position at a fintech startup.",
|
|
88
|
+
"Dear Hiring Team, I turn messy financial data into clear decisions, and your startup excites me.",
|
|
89
|
+
],
|
|
90
|
+
[
|
|
91
|
+
"Describe the health benefits of a Mediterranean diet for older adults.",
|
|
92
|
+
"A Mediterranean diet of olive oil, fish, and vegetables protects the heart and steadies the mind.",
|
|
93
|
+
],
|
|
94
|
+
[
|
|
95
|
+
"Explain how photosynthesis converts sunlight into chemical energy.",
|
|
96
|
+
"Photosynthesis captures sunlight in chlorophyll to bind carbon dioxide and water into sugar.",
|
|
97
|
+
],
|
|
98
|
+
[
|
|
99
|
+
"Give advice for a beginner learning to play acoustic guitar.",
|
|
100
|
+
"Start with clean open chords, keep your wrist loose, and practice short daily rather than long rarely.",
|
|
101
|
+
],
|
|
102
|
+
[
|
|
103
|
+
"Summarize the causes of the fall of the Western Roman Empire.",
|
|
104
|
+
"The Western Roman Empire fell from overreach, currency decay, and waves of migrating peoples.",
|
|
105
|
+
],
|
|
106
|
+
[
|
|
107
|
+
"Describe best practices for securing a home wireless network.",
|
|
108
|
+
"Secure a home network with WPA3, a long unique passphrase, and firmware kept promptly updated.",
|
|
109
|
+
],
|
|
110
|
+
[
|
|
111
|
+
"Write a haiku about the first snowfall in a quiet city.",
|
|
112
|
+
"First snow on lamplight / the city holds its breath once / footprints fill with white.",
|
|
113
|
+
],
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
// [style, query (shared opening prepended at ask time), index of the expected FACT]
|
|
117
|
+
const PROBES = [
|
|
118
|
+
["exact", FACTS[0][0], 0],
|
|
119
|
+
["exact", FACTS[17][0], 17],
|
|
120
|
+
[
|
|
121
|
+
"partial",
|
|
122
|
+
"Describe the importance of gender equality in the workplace.",
|
|
123
|
+
0,
|
|
124
|
+
],
|
|
125
|
+
["partial", "Write a short story in JSON about a street-art vendor.", 2],
|
|
126
|
+
[
|
|
127
|
+
"partial",
|
|
128
|
+
"Provide a summary of the 1992 Men's Olympic Basketball Team.",
|
|
129
|
+
1,
|
|
130
|
+
],
|
|
131
|
+
["partial", "Create a weekly basketball practice plan with drills.", 5],
|
|
132
|
+
["partial", "Explain how photosynthesis works.", 15],
|
|
133
|
+
[
|
|
134
|
+
"paraphrase",
|
|
135
|
+
"Tell a high schooler why gender equality at work matters.",
|
|
136
|
+
0,
|
|
137
|
+
],
|
|
138
|
+
[
|
|
139
|
+
"paraphrase",
|
|
140
|
+
"Give me an overview of the 1992 Dream Team basketball squad.",
|
|
141
|
+
1,
|
|
142
|
+
],
|
|
143
|
+
["paraphrase", "Why do age discrimination laws matter, explained simply?", 3],
|
|
144
|
+
["paraphrase", "What does the Mediterranean diet do for the elderly?", 14],
|
|
145
|
+
["paraphrase", "What happened to the Western Roman Empire and why?", 17],
|
|
146
|
+
[
|
|
147
|
+
"reorder",
|
|
148
|
+
"In the workplace, the importance of gender equality, describe it.",
|
|
149
|
+
0,
|
|
150
|
+
],
|
|
151
|
+
[
|
|
152
|
+
"distractor",
|
|
153
|
+
"I was chatting with a friend and wondered, how does photosynthesis convert sunlight into energy?",
|
|
154
|
+
15,
|
|
155
|
+
],
|
|
156
|
+
[
|
|
157
|
+
"distractor",
|
|
158
|
+
"Quick question before lunch — the health benefits of a Mediterranean diet for older adults?",
|
|
159
|
+
14,
|
|
160
|
+
],
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
// A distinctive interior slice of the expected answer (lossy-first-byte tolerant).
|
|
164
|
+
const marker = (a) => a.replace(/\s+/g, " ").slice(1, 16);
|
|
165
|
+
const hits = (got, want) => got.replace(/\s+/g, " ").includes(marker(want));
|
|
166
|
+
|
|
167
|
+
async function build() {
|
|
168
|
+
const store = new SQliteStore({ path: ":memory:" });
|
|
169
|
+
const mind = new Mind({ seed: 7, store });
|
|
170
|
+
await mind.ingest(FACTS.map(([q, a]) => [SYS + q, a]));
|
|
171
|
+
return { store, mind };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
test("scaffolding-dominated corpus: connectivity answers across query styles", async () => {
|
|
175
|
+
const { store, mind } = await build();
|
|
176
|
+
const got = [];
|
|
177
|
+
for (const [style, q, idx] of PROBES) {
|
|
178
|
+
const ans = (await mind.respondText(SYS + q)).replace(/\s+/g, " ").trim();
|
|
179
|
+
got.push({ style, ok: hits(ans, FACTS[idx][1]), ans });
|
|
180
|
+
}
|
|
181
|
+
await store.close();
|
|
182
|
+
|
|
183
|
+
const by = (s) => got.filter((g) => g.style === s);
|
|
184
|
+
const rate = (s) => by(s).filter((g) => g.ok).length;
|
|
185
|
+
|
|
186
|
+
// Floor: exact reproduction and distractor-framed (distinctive content intact)
|
|
187
|
+
// must ALWAYS resolve — these need no rewording intelligence, only that the
|
|
188
|
+
// search finds present content.
|
|
189
|
+
assert.equal(
|
|
190
|
+
rate("exact"),
|
|
191
|
+
by("exact").length,
|
|
192
|
+
"every exact query must resolve",
|
|
193
|
+
);
|
|
194
|
+
assert.equal(
|
|
195
|
+
rate("distractor"),
|
|
196
|
+
by("distractor").length,
|
|
197
|
+
"distinctive content buried in framing must still resolve",
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
// Aggregate: the climb must clear a bar that whole-query resonance cannot.
|
|
201
|
+
// The bar of 15 absorbs codec/ANN jitter while a reversion to plain whole-
|
|
202
|
+
// query resonance (which answers only near-verbatim queries) scores far
|
|
203
|
+
// lower and fails.
|
|
204
|
+
const total = got.filter((g) => g.ok).length;
|
|
205
|
+
assert.ok(
|
|
206
|
+
total >= 15,
|
|
207
|
+
`intelligence score ${total}/${PROBES.length} — expected ≥ 15 ` +
|
|
208
|
+
`(connectivity-driven climb; pure whole-query resonance scores far less)\n` +
|
|
209
|
+
got.filter((g) => !g.ok).map((g) =>
|
|
210
|
+
` ✗ [${g.style}] ${g.ans.slice(0, 40)}`
|
|
211
|
+
).join("\n"),
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
// At least one genuine paraphrase must resolve to its OWN answer — the
|
|
215
|
+
// signature of the consensus climb, impossible for scaffolding-pulled whole-query
|
|
216
|
+
// resonance.
|
|
217
|
+
assert.ok(
|
|
218
|
+
rate("paraphrase") >= 2,
|
|
219
|
+
`only ${rate("paraphrase")}/${
|
|
220
|
+
by("paraphrase").length
|
|
221
|
+
} paraphrases resolved — ` +
|
|
222
|
+
`expected ≥ 2 (the climb's distinctive-region consensus)`,
|
|
223
|
+
);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// A reworded query whose distinctive content clearly names one record must not
|
|
227
|
+
// be captured by a scaffolding-sharing neighbour — the precise win consensus-climbing
|
|
228
|
+
// delivers over whole-query gist.
|
|
229
|
+
test("a distinctive paraphrase resolves to its own record, not a frame neighbour", async () => {
|
|
230
|
+
const { store, mind } = await build();
|
|
231
|
+
const ans = (await mind.respondText(
|
|
232
|
+
SYS + "Tell a high schooler why gender equality at work matters.",
|
|
233
|
+
)).replace(/\s+/g, " ").trim();
|
|
234
|
+
await store.close();
|
|
235
|
+
assert.ok(
|
|
236
|
+
hits(ans, FACTS[0][1]),
|
|
237
|
+
`gender-equality paraphrase resolved to "${ans.slice(0, 48)}" — ` +
|
|
238
|
+
`expected the gender-equality record`,
|
|
239
|
+
);
|
|
240
|
+
});
|