@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.
Files changed (110) hide show
  1. package/AGENTS.md +470 -0
  2. package/AUTHORS.md +12 -0
  3. package/COMMERCIAL-LICENSE.md +19 -0
  4. package/CONTRIBUTING.md +15 -0
  5. package/HOW_IT_WORKS.md +4210 -0
  6. package/LICENSE.md +117 -0
  7. package/README.md +290 -0
  8. package/TRADEMARKS.md +19 -0
  9. package/example/demo.ts +46 -0
  10. package/example/train_base.ts +2675 -0
  11. package/index.html +893 -0
  12. package/package.json +25 -0
  13. package/src/alphabet.ts +34 -0
  14. package/src/alu/README.md +332 -0
  15. package/src/alu/src/alu.ts +541 -0
  16. package/src/alu/src/expr.ts +339 -0
  17. package/src/alu/src/index.ts +115 -0
  18. package/src/alu/src/kernel-arith.ts +377 -0
  19. package/src/alu/src/kernel-bits.ts +199 -0
  20. package/src/alu/src/kernel-logic.ts +102 -0
  21. package/src/alu/src/kernel-nd.ts +235 -0
  22. package/src/alu/src/kernel-numeric.ts +466 -0
  23. package/src/alu/src/operation.ts +344 -0
  24. package/src/alu/src/parser.ts +574 -0
  25. package/src/alu/src/resonance.ts +161 -0
  26. package/src/alu/src/text.ts +83 -0
  27. package/src/alu/src/value.ts +327 -0
  28. package/src/alu/test/alu.test.ts +1004 -0
  29. package/src/bytes.ts +62 -0
  30. package/src/config.ts +227 -0
  31. package/src/derive/README.md +295 -0
  32. package/src/derive/src/deduction.ts +263 -0
  33. package/src/derive/src/index.ts +24 -0
  34. package/src/derive/src/priority-queue.ts +70 -0
  35. package/src/derive/src/rewrite.ts +127 -0
  36. package/src/derive/src/trie.ts +242 -0
  37. package/src/derive/test/derive.test.ts +137 -0
  38. package/src/extension.ts +42 -0
  39. package/src/geometry.ts +511 -0
  40. package/src/index.ts +38 -0
  41. package/src/ingest-cache.ts +224 -0
  42. package/src/mind/articulation.ts +137 -0
  43. package/src/mind/attention.ts +1061 -0
  44. package/src/mind/canonical.ts +107 -0
  45. package/src/mind/graph-search.ts +1100 -0
  46. package/src/mind/index.ts +18 -0
  47. package/src/mind/junction.ts +347 -0
  48. package/src/mind/learning.ts +246 -0
  49. package/src/mind/match.ts +507 -0
  50. package/src/mind/mechanisms/alu.ts +33 -0
  51. package/src/mind/mechanisms/cast.ts +568 -0
  52. package/src/mind/mechanisms/confluence.ts +268 -0
  53. package/src/mind/mechanisms/cover.ts +248 -0
  54. package/src/mind/mechanisms/extraction.ts +422 -0
  55. package/src/mind/mechanisms/recall.ts +241 -0
  56. package/src/mind/mind.ts +483 -0
  57. package/src/mind/pipeline-mechanism.ts +326 -0
  58. package/src/mind/pipeline.ts +300 -0
  59. package/src/mind/primitives.ts +201 -0
  60. package/src/mind/rationale.ts +275 -0
  61. package/src/mind/reasoning.ts +198 -0
  62. package/src/mind/recognition.ts +234 -0
  63. package/src/mind/resonance.ts +0 -0
  64. package/src/mind/trace.ts +108 -0
  65. package/src/mind/traverse.ts +556 -0
  66. package/src/mind/types.ts +281 -0
  67. package/src/rabitq-hnsw/README.md +303 -0
  68. package/src/rabitq-hnsw/src/database.ts +492 -0
  69. package/src/rabitq-hnsw/src/heap.ts +90 -0
  70. package/src/rabitq-hnsw/src/hnsw.ts +514 -0
  71. package/src/rabitq-hnsw/src/index.ts +15 -0
  72. package/src/rabitq-hnsw/src/prng.ts +39 -0
  73. package/src/rabitq-hnsw/src/rabitq.ts +308 -0
  74. package/src/rabitq-hnsw/src/store.ts +994 -0
  75. package/src/rabitq-hnsw/test/hnsw.test.ts +1213 -0
  76. package/src/store-sqlite.ts +928 -0
  77. package/src/store.ts +2148 -0
  78. package/src/vec.ts +124 -0
  79. package/test/00-extract.test.mjs +151 -0
  80. package/test/01-floor.test.mjs +20 -0
  81. package/test/02-roundtrip.test.mjs +83 -0
  82. package/test/03-recall.test.mjs +98 -0
  83. package/test/04-think.test.mjs +627 -0
  84. package/test/05-concepts.test.mjs +84 -0
  85. package/test/08-storage.test.mjs +948 -0
  86. package/test/09-edges.test.mjs +65 -0
  87. package/test/11-universality.test.mjs +180 -0
  88. package/test/13-conversation.test.mjs +228 -0
  89. package/test/14-scaling.test.mjs +503 -0
  90. package/test/15-decomposition-gap.test.mjs +0 -0
  91. package/test/16-bridge.test.mjs +250 -0
  92. package/test/17-intelligence.test.mjs +240 -0
  93. package/test/18-alu.test.mjs +209 -0
  94. package/test/19-nd.test.mjs +254 -0
  95. package/test/20-stability.test.mjs +489 -0
  96. package/test/21-partial.test.mjs +158 -0
  97. package/test/22-multihop.test.mjs +130 -0
  98. package/test/23-rationale.test.mjs +316 -0
  99. package/test/24-generalization.test.mjs +416 -0
  100. package/test/25-embedding.test.mjs +75 -0
  101. package/test/26-cooperative.test.mjs +89 -0
  102. package/test/27-saturation-drop.test.mjs +637 -0
  103. package/test/28-unknowable.test.mjs +88 -0
  104. package/test/29-counterfactual.test.mjs +476 -0
  105. package/test/30-conflict-resolution.test.mjs +166 -0
  106. package/test/31-audit.test.mjs +288 -0
  107. package/test/32-confluence.test.mjs +173 -0
  108. package/test/33-multi-candidate.test.mjs +222 -0
  109. package/test/34-cross-region.test.mjs +252 -0
  110. package/tsconfig.json +16 -0
@@ -0,0 +1,627 @@
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
+ // 1 — Single-step direct recall & chain basics
8
+ // =========================================================================
9
+
10
+ test("direct recall: trained fact answers immediately", async () => {
11
+ const m = new Mind({
12
+ seed: 7,
13
+ });
14
+ await m.ingest([["2+2", "4"], ["3+5", "8"]]);
15
+ assert.equal(await m.respondText("3+5"), "8");
16
+ });
17
+
18
+ test("single chain: A→B", async () => {
19
+ const m = new Mind({
20
+ seed: 7,
21
+ });
22
+ await m.ingest([["fire", "hot"]]);
23
+ assert.equal(await m.respondText("fire"), "hot");
24
+ });
25
+
26
+ test("two-step chain: A→B, B→C", async () => {
27
+ const m = new Mind({
28
+ seed: 7,
29
+ });
30
+ await m.ingest([["a", "b"], ["b", "c"]]);
31
+ assert.equal(await m.respondText("a"), "c");
32
+ });
33
+
34
+ test("three-step chain: A→B, B→C, C→D", async () => {
35
+ const m = new Mind({
36
+ seed: 7,
37
+ });
38
+ await m.ingest([["a", "b"], ["b", "c"], ["c", "d"]]);
39
+ assert.equal(await m.respondText("a"), "d");
40
+ });
41
+
42
+ test("four-step chain: A→B→C→D→E", async () => {
43
+ const m = new Mind({
44
+ seed: 7,
45
+ });
46
+ await m.ingest([["a", "b"], ["b", "c"], ["c", "d"], ["d", "e"]]);
47
+ assert.equal(await m.respondText("a"), "e");
48
+ });
49
+
50
+ // =========================================================================
51
+ // 2 — Known part fires inside unknown wrappers
52
+ // =========================================================================
53
+
54
+ test("known part with prefix", async () => {
55
+ const m = new Mind({
56
+ seed: 7,
57
+ });
58
+ await m.ingest([["2+2", "4"]]);
59
+ assert.equal(await m.respondText("what is 2+2"), "4");
60
+ });
61
+
62
+ test("known part with suffix", async () => {
63
+ const m = new Mind({
64
+ seed: 7,
65
+ });
66
+ await m.ingest([["2+2", "4"]]);
67
+ assert.equal(await m.respondText("2+2 equals what"), "4");
68
+ });
69
+
70
+ test("known part with prefix and suffix", async () => {
71
+ const m = new Mind({
72
+ seed: 7,
73
+ });
74
+ await m.ingest([["2+2", "4"]]);
75
+ assert.equal(await m.respondText("compute: 2+2 now"), "4");
76
+ });
77
+
78
+ test("known part buried mid-sentence", async () => {
79
+ const m = new Mind({
80
+ seed: 7,
81
+ });
82
+ await m.ingest([["ice", "cold"]]);
83
+ assert.equal(await m.respondText("the nature of ice is known"), "cold");
84
+ });
85
+
86
+ // =========================================================================
87
+ // 3 — Multi-part: multiple known parts fire independently
88
+ // =========================================================================
89
+
90
+ test("two independent parts with gap", async () => {
91
+ const m = new Mind({
92
+ seed: 7,
93
+ });
94
+ await m.ingest([["ice", "cold"], ["fire", "hot"]]);
95
+ assert.equal(await m.respondText("ice fire"), "cold hot");
96
+ });
97
+
98
+ test("three independent parts", async () => {
99
+ const m = new Mind({
100
+ seed: 7,
101
+ });
102
+ await m.ingest([["a", "A"], ["b", "B"], ["c", "C"]]);
103
+ assert.equal(await m.respondText("a b c"), "A B C");
104
+ });
105
+
106
+ test("four independent parts", async () => {
107
+ const m = new Mind({
108
+ seed: 7,
109
+ });
110
+ await m.ingest([["a", "A"], ["b", "B"], ["c", "C"], ["d", "D"]]);
111
+ const r = await m.respondText("a b c d");
112
+ assert.ok(
113
+ r.includes("A") && r.includes("B") && r.includes("C") && r.includes("D"),
114
+ );
115
+ });
116
+
117
+ test("five independent parts", async () => {
118
+ const m = new Mind({
119
+ seed: 7,
120
+ });
121
+ await m.ingest([["a", "A"], ["b", "B"], ["c", "C"], ["d", "D"], ["e", "E"]]);
122
+ const r = await m.respondText("a b c d e");
123
+ assert.ok(r.includes("A") && r.includes("E"));
124
+ });
125
+
126
+ // =========================================================================
127
+ // 4 — Order independence
128
+ // =========================================================================
129
+
130
+ test("reversed order: fire ice → hot cold", async () => {
131
+ const m = new Mind({
132
+ seed: 7,
133
+ });
134
+ await m.ingest([["ice", "cold"], ["fire", "hot"]]);
135
+ const r = await m.respondText("fire ice");
136
+ assert.ok(r.includes("hot") && r.includes("cold"));
137
+ });
138
+
139
+ test("scrambled order: c a b → C A B", async () => {
140
+ const m = new Mind({
141
+ seed: 7,
142
+ });
143
+ await m.ingest([["a", "A"], ["b", "B"], ["c", "C"]]);
144
+ const r = await m.respondText("c a b");
145
+ assert.ok(r.includes("A") && r.includes("B") && r.includes("C"));
146
+ });
147
+
148
+ test("interspersed known and unknown", async () => {
149
+ const m = new Mind({
150
+ seed: 7,
151
+ });
152
+ await m.ingest([["ice", "cold"]]);
153
+ const r = await m.respondText("xyz ice uvw");
154
+ assert.ok(r.includes("cold"));
155
+ });
156
+
157
+ // =========================================================================
158
+ // 5 — Punctuation and formatting preserved
159
+ // =========================================================================
160
+
161
+ test("comma between facts preserved", async () => {
162
+ const m = new Mind({
163
+ seed: 7,
164
+ });
165
+ await m.ingest([["ice", "cold"], ["fire", "hot"]]);
166
+ const r = await m.respondText("ice, fire");
167
+ assert.ok(r.includes("cold") && r.includes("hot"));
168
+ });
169
+
170
+ test("period preserved", async () => {
171
+ const m = new Mind({
172
+ seed: 7,
173
+ });
174
+ await m.ingest([["2+2", "4"]]);
175
+ assert.equal(await m.respondText("2+2."), "4.");
176
+ });
177
+
178
+ test("question mark preserved", async () => {
179
+ const m = new Mind({
180
+ seed: 7,
181
+ });
182
+ await m.ingest([["2+2", "4"]]);
183
+ assert.equal(await m.respondText("2+2?"), "4?");
184
+ });
185
+
186
+ // =========================================================================
187
+ // 6 — Chain + independent part in same query
188
+ // =========================================================================
189
+
190
+ test("chain and direct fact side by side", async () => {
191
+ const m = new Mind({
192
+ seed: 7,
193
+ });
194
+ await m.ingest([["a", "b"], ["b", "c"], ["ice", "cold"]]);
195
+ const r = await m.respondText("a ice");
196
+ assert.ok(r.includes("c") && r.includes("cold"));
197
+ });
198
+
199
+ test("two independent chains in one query", async () => {
200
+ const m = new Mind({
201
+ seed: 7,
202
+ });
203
+ await m.ingest([["a", "b"], ["b", "c"], ["x", "y"], ["y", "z"]]);
204
+ const r = await m.respondText("a x");
205
+ assert.ok(r.includes("c") && r.includes("z"));
206
+ });
207
+
208
+ // =========================================================================
209
+ // 7 — Recursive rewriting to fixpoint
210
+ // =========================================================================
211
+
212
+ test("rewrite chains converge to fixpoint", async () => {
213
+ const m = new Mind({
214
+ seed: 7,
215
+ });
216
+ await m.ingest([["a", "b"], ["b", "c"], ["c", "d"]]);
217
+ assert.equal(await m.respondText("a"), "d");
218
+ });
219
+
220
+ test("bidirectional chain: reverse completion works", async () => {
221
+ const m = new Mind({
222
+ seed: 7,
223
+ });
224
+ await m.ingest([["question", "answer"]]);
225
+ // Asking the continuation should recall the context (seat symmetry)
226
+ assert.equal(await m.respondText("answer"), "question");
227
+ });
228
+
229
+ test("echo termination: self-feeding rewrite halts", async () => {
230
+ const m = new Mind({
231
+ seed: 7,
232
+ });
233
+ await m.ingest([["ab", "ab ab"]]);
234
+ const r = await m.respond("xq ab zk");
235
+ assert.ok(r.v === null || new TextDecoder().decode(r.bytes).includes("ab"));
236
+ });
237
+
238
+ // =========================================================================
239
+ // 8 — Definition expansion
240
+ // =========================================================================
241
+
242
+ test("definition lookup: term → meaning", async () => {
243
+ const m = new Mind({
244
+ seed: 7,
245
+ });
246
+ await m.ingest([["ice", "frozen water"]]);
247
+ assert.equal(await m.respondText("what is ice"), "frozen water");
248
+ });
249
+
250
+ test("definition lookup mid-sentence", async () => {
251
+ const m = new Mind({
252
+ seed: 7,
253
+ });
254
+ await m.ingest([["ice", "frozen water"]]);
255
+ assert.equal(await m.respondText("tell me about ice please"), "frozen water");
256
+ });
257
+
258
+ test("definition chain through two levels", async () => {
259
+ const m = new Mind({
260
+ seed: 7,
261
+ });
262
+ await m.ingest([["x", "y"], ["y", "z"]]);
263
+ assert.equal(await m.respondText("x"), "z");
264
+ });
265
+
266
+ test("synonym expansion via concept merge", async () => {
267
+ const m = new Mind({
268
+ seed: 7,
269
+ });
270
+ await m.ingest([["big", "large"]]);
271
+ // Ground both in the same context so they merge
272
+ await m.ingest(["ctx", "big"]);
273
+ await m.ingest(["ctx", "large"]);
274
+ // Now they're one concept — asking one recalls the other
275
+ assert.equal(await m.respondText("large"), "large");
276
+ });
277
+
278
+ // =========================================================================
279
+ // 9 — Compositional synthesis from multiple facts
280
+ // =========================================================================
281
+
282
+ test("two facts compose: ice fire → cold hot", async () => {
283
+ const m = new Mind({
284
+ seed: 7,
285
+ });
286
+ await m.ingest([["ice", "cold"], ["fire", "hot"]]);
287
+ assert.equal(await m.respondText("ice fire"), "cold hot");
288
+ });
289
+
290
+ test("three facts compose", async () => {
291
+ const m = new Mind({
292
+ seed: 7,
293
+ });
294
+ await m.ingest([["a", "A"], ["b", "B"], ["c", "C"]]);
295
+ assert.equal(await m.respondText("a b c"), "A B C");
296
+ });
297
+
298
+ test("synthesis across concept boundaries", async () => {
299
+ const m = new Mind({
300
+ seed: 7,
301
+ });
302
+ await m.ingest([["ice", "cold"], ["fire", "hot"]]);
303
+ await m.ingest(["ctx", "ice"]);
304
+ await m.ingest(["ctx", "hielo"]);
305
+ // ice and hielo merge. Asking hielo should still compose with fire.
306
+ const r = await m.respondText("hielo fire");
307
+ assert.ok(r.includes("cold") && r.includes("hot"));
308
+ });
309
+
310
+ // =========================================================================
311
+ // 10 — Arithmetic chains (the classic use case)
312
+ // =========================================================================
313
+
314
+ const ARITH = [
315
+ ["1+2", "3"],
316
+ ["2+2", "4"],
317
+ ["2+3", "5"],
318
+ ["3+3", "6"],
319
+ ["3+5", "8"],
320
+ ["4+3", "7"],
321
+ ["2+5", "7"],
322
+ ["1+5", "6"],
323
+ ["6+1", "7"],
324
+ ["4+1", "5"],
325
+ ["3+4", "7"],
326
+ ["5+2", "7"],
327
+ ["1+1", "2"],
328
+ ["5+3", "8"],
329
+ ["7+1", "8"],
330
+ ];
331
+
332
+ test("arithmetic: direct fact", async () => {
333
+ const m = new Mind({
334
+ seed: 7,
335
+ });
336
+ await m.ingest(ARITH);
337
+ assert.equal(await m.respondText("3+5"), "8");
338
+ });
339
+
340
+ test("arithmetic: 2-step chain", async () => {
341
+ const m = new Mind({
342
+ seed: 7,
343
+ });
344
+ await m.ingest(ARITH);
345
+ assert.equal(await m.respondText("2+2+3"), "7");
346
+ });
347
+
348
+ test("arithmetic: fact inside question", async () => {
349
+ const m = new Mind({
350
+ seed: 7,
351
+ });
352
+ await m.ingest(ARITH);
353
+ assert.equal(await m.respondText("what is 2+2"), "4");
354
+ });
355
+
356
+ test("arithmetic: two facts with gap preserved", async () => {
357
+ const m = new Mind({
358
+ seed: 7,
359
+ });
360
+ await m.ingest(ARITH);
361
+ assert.equal(await m.respondText("2+2 3+3"), "4 6");
362
+ });
363
+
364
+ // =========================================================================
365
+ // 11 — Grounding and concept cross-transfer
366
+ // =========================================================================
367
+
368
+ test("concept cross-transfer: knowledge in one language, asked in another", async () => {
369
+ const m = new Mind({
370
+ seed: 7,
371
+ });
372
+ // Fact only in English
373
+ await m.ingest("ice", "ice is frozen water");
374
+ // Ground both names identically
375
+ await m.ingest(["ctx-a", "ice"]);
376
+ await m.ingest(["ctx-b", "ice"]);
377
+ await m.ingest(["ctx-a", "hielo"]);
378
+ await m.ingest(["ctx-b", "hielo"]);
379
+ // Ask in Spanish — should get Spanish answer
380
+ assert.equal(await m.respondText("hielo"), "hielo is frozen water");
381
+ });
382
+
383
+ test("multi-name concept: three languages", async () => {
384
+ const m = new Mind({
385
+ seed: 7,
386
+ });
387
+ await m.ingest("ice", "ice is frozen water");
388
+ await m.ingest(["ctx-a", "ice"]);
389
+ await m.ingest(["ctx-a", "hielo"]);
390
+ await m.ingest(["ctx-a", "氷"]);
391
+ await m.ingest(["ctx-b", "ice"]);
392
+ await m.ingest(["ctx-b", "hielo"]);
393
+ await m.ingest(["ctx-b", "氷"]);
394
+ assert.equal(await m.respondText("氷"), "氷 is frozen water");
395
+ });
396
+
397
+ // =========================================================================
398
+ // 12 — Edge cases & silence
399
+ // =========================================================================
400
+
401
+ test("empty query is silent", async () => {
402
+ const m = new Mind({
403
+ seed: 7,
404
+ });
405
+ await m.ingest([["a", "b"]]);
406
+ assert.equal((await m.respond("")).v, null);
407
+ });
408
+
409
+ test("completely unknown query: returns null or closest approximation", async () => {
410
+ const m = new Mind({
411
+ seed: 7,
412
+ });
413
+ await m.ingest([["2+2", "4"]]);
414
+ // Without artificial recall thresholds, unknown queries may return
415
+ // a closest-approximation answer or null. Both are valid.
416
+ const r = await m.respond("3+5");
417
+ assert.ok(r.v !== undefined, "respond returns a valid response");
418
+ });
419
+
420
+ test("partial unknown, partial known: known fires, unknown handled gracefully", async () => {
421
+ const m = new Mind({
422
+ seed: 7,
423
+ });
424
+ await m.ingest([["2+2", "4"]]);
425
+ // "3+5" is unknown — may return null or a closest approximation.
426
+ const r = await m.respond("what is 3+5");
427
+ assert.ok(r.v !== undefined, "respond returns a valid response");
428
+ });
429
+
430
+ test("single letter floor: related byte grounds, unrelated is silent", async () => {
431
+ const m = new Mind({
432
+ seed: 7,
433
+ });
434
+ await m.ingest([["2+2", "4"]]);
435
+ // A byte that appears in the stored context still resonates to its grounded
436
+ // form. An unrelated byte — one with no structural relationship to anything
437
+ // in the store — now returns null: the reach threshold (1 − 1/(2·maxGroup))
438
+ // prevents fabricating an answer from an unrelated form.
439
+ const related = await m.respond("4");
440
+ assert.ok(
441
+ related.v !== null,
442
+ '"4" (appears in store) returns its grounded form',
443
+ );
444
+ assert.ok(related.bytes.length > 0, '"4" returns bytes');
445
+
446
+ const unrelated = await m.respond("a");
447
+ assert.equal(unrelated.v, null, '"a" (unrelated to store) returns null');
448
+ assert.equal(unrelated.bytes.length, 0, '"a" returns empty bytes');
449
+ });
450
+
451
+ test("multi-byte known part with single-char prefix fires", async () => {
452
+ const m = new Mind({
453
+ seed: 7,
454
+ });
455
+ await m.ingest([["ab", "XY"]]);
456
+ assert.equal(await m.respondText("x ab"), "XY");
457
+ });
458
+
459
+ // =========================================================================
460
+ // 13 — Persistence: concepts and chains survive save/load
461
+ // =========================================================================
462
+
463
+ test("chain works after save/load", async () => {
464
+ const store = new SQliteStore({ path: ":memory:" });
465
+ const m = new Mind({ seed: 7, store });
466
+ await m.ingest([["a", "b"], ["b", "c"]]);
467
+ await m.save();
468
+ const m2 = await Mind.loadFromStore(store);
469
+ assert.equal(await m2.respondText("a"), "c");
470
+ await store.close();
471
+ });
472
+
473
+ // =========================================================================
474
+ // 15 — Nested: training episodes contain known parts that themselves fire
475
+ // =========================================================================
476
+
477
+ test("training: ingest pair whose sides are themselves known", async () => {
478
+ const m = new Mind({
479
+ seed: 7,
480
+ });
481
+ // First establish a→b, then train (a, fact) — fact contains "b" which chains
482
+ await m.ingest([["a", "b"]]);
483
+ await m.ingest("a", "the answer is b");
484
+ assert.equal(await m.respondText("a"), "b");
485
+ });
486
+
487
+ // =========================================================================
488
+ // 16 — Same meaning, different surface forms
489
+ // =========================================================================
490
+
491
+ test("same meaning in different casing still fires", async () => {
492
+ const m = new Mind({
493
+ seed: 7,
494
+ });
495
+ // Note: sema does NOT normalize case — this tests exact match
496
+ await m.ingest([["ICE", "cold"]]);
497
+ assert.equal(await m.respondText("ICE"), "cold");
498
+ });
499
+
500
+ // =========================================================================
501
+ // 17 — Multi-utterance queries
502
+ // =========================================================================
503
+
504
+ test("newline-separated facts fire independently", async () => {
505
+ const m = new Mind({
506
+ seed: 7,
507
+ });
508
+ await m.ingest([["ice", "cold"], ["fire", "hot"]]);
509
+ const r = await m.respondText("ice\nfire");
510
+ assert.ok(r.includes("cold") && r.includes("hot"));
511
+ });
512
+
513
+ // =========================================================================
514
+ // 18 — Known part at boundaries
515
+ // =========================================================================
516
+
517
+ test("known part at very start of stream", async () => {
518
+ const m = new Mind({
519
+ seed: 7,
520
+ });
521
+ await m.ingest([["abc", "XYZ"]]);
522
+ assert.equal(await m.respondText("abc def"), "XYZ");
523
+ });
524
+
525
+ test("known part at very end of stream", async () => {
526
+ const m = new Mind({
527
+ seed: 7,
528
+ });
529
+ await m.ingest([["abc", "XYZ"]]);
530
+ assert.equal(await m.respondText("def abc"), "XYZ");
531
+ });
532
+
533
+ // =========================================================================
534
+ // 19 — Very long chains
535
+ // =========================================================================
536
+
537
+ test("5-step chain", async () => {
538
+ const m = new Mind({
539
+ seed: 7,
540
+ });
541
+ await m.ingest([["a", "b"], ["b", "c"], ["c", "d"], ["d", "e"], ["e", "f"]]);
542
+ assert.equal(await m.respondText("a"), "f");
543
+ });
544
+
545
+ // =========================================================================
546
+ // 20 — Multiple rewrites of the same part across iterations
547
+ // =========================================================================
548
+
549
+ test("same position rewritten multiple times as chain progresses", async () => {
550
+ const m = new Mind({
551
+ seed: 7,
552
+ });
553
+ await m.ingest([["x", "y"], ["y", "z"], ["z", "w"]]);
554
+ // "x" at position 0 gets rewritten y→z→w across 3 iterations
555
+ assert.equal(await m.respondText("x"), "w");
556
+ });
557
+
558
+ // =========================================================================
559
+ // 21 — Training that includes the answer as prefix/suffix
560
+ // =========================================================================
561
+
562
+ test("fact with surrounding whitespace", async () => {
563
+ const m = new Mind({
564
+ seed: 7,
565
+ });
566
+ await m.ingest([[" ice ", "cold"]]);
567
+ assert.equal(await m.respondText(" ice "), "cold");
568
+ });
569
+
570
+ // =========================================================================
571
+ // 22 — Determinism
572
+ // =========================================================================
573
+
574
+ test("same seed, same training, same answer", async () => {
575
+ const run = async () => {
576
+ const m = new Mind({
577
+ seed: 99,
578
+ });
579
+ await m.ingest([["a", "b"], ["b", "c"]]);
580
+ return await m.respondText("a");
581
+ };
582
+ assert.equal(await run(), await run());
583
+ });
584
+
585
+ // =========================================================================
586
+ // 23 — Negation
587
+ // =========================================================================
588
+
589
+ test("negation: not X → not-X", async () => {
590
+ const m = new Mind({
591
+ seed: 7,
592
+ });
593
+ await m.ingest([["a tree", "tree"], ["not a tree", "not-tree"]]);
594
+ assert.equal(await m.respondText("not a tree"), "not-tree");
595
+ });
596
+
597
+ test("negation: double negation not not X → X", async () => {
598
+ const m = new Mind({
599
+ seed: 7,
600
+ });
601
+ await m.ingest([["not X", "not-X"], ["not not-X", "X"]]);
602
+ assert.equal(await m.respondText("not not X"), "X");
603
+ });
604
+
605
+ test("negation: arithmetic true/false statement", async () => {
606
+ const m = new Mind({
607
+ seed: 7,
608
+ });
609
+ await m.ingest([["2+2", "4"], ["2+2=5", "false"], ["2+2=4", "true"]]);
610
+ assert.equal(await m.respondText("2+2=4"), "true");
611
+ });
612
+
613
+ test("negation: opposite facts", async () => {
614
+ const m = new Mind({
615
+ seed: 7,
616
+ });
617
+ await m.ingest([["ice is hot", "false"], ["ice is cold", "true"]]);
618
+ assert.equal(await m.respondText("ice is cold"), "true");
619
+ });
620
+
621
+ test("negation: aligned structure preserved (not happy → sad)", async () => {
622
+ const m = new Mind({
623
+ seed: 7,
624
+ });
625
+ await m.ingest([["happy", "glad"], ["not happy", "sad"]]);
626
+ assert.equal(await m.respondText("not happy"), "sad");
627
+ });