@hviana/sema 0.4.7 → 0.5.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 +290 -77
- package/HOW_IT_WORKS.md +2170 -735
- package/dist/example/train_base.d.ts +9 -3
- package/dist/example/train_base.js +21 -4
- package/dist/src/canon.d.ts +19 -0
- package/dist/src/canon.js +28 -0
- package/dist/src/geometry.d.ts +52 -0
- package/dist/src/geometry.js +87 -1
- package/dist/src/mind/bridge.js +27 -1
- package/dist/src/mind/frame-filler.d.ts +15 -0
- package/dist/src/mind/frame-filler.js +535 -0
- package/dist/src/mind/learning.js +6 -11
- package/dist/src/mind/mechanisms/cast.js +72 -2
- package/dist/src/mind/mechanisms/cover.js +6 -1
- package/dist/src/mind/mechanisms/extraction.js +27 -0
- package/dist/src/mind/mechanisms/recall.js +214 -34
- package/dist/src/mind/mind.d.ts +49 -1
- package/dist/src/mind/mind.js +137 -10
- package/dist/src/mind/pipeline-mechanism.d.ts +7 -0
- package/dist/src/mind/pipeline.js +29 -1
- package/dist/src/mind/prefix-completion.d.ts +59 -0
- package/dist/src/mind/prefix-completion.js +270 -0
- package/dist/src/mind/primitives.d.ts +29 -10
- package/dist/src/mind/primitives.js +52 -61
- package/dist/src/mind/recognition.js +119 -9
- package/dist/src/mind/traverse.d.ts +32 -0
- package/dist/src/mind/traverse.js +52 -0
- package/dist/src/mind/types.d.ts +55 -16
- package/dist/src/mind/types.js +68 -19
- package/dist/src/store.d.ts +21 -0
- package/dist/src/store.js +21 -0
- package/example/train_base.ts +21 -4
- package/package.json +1 -1
- package/src/canon.ts +28 -0
- package/src/geometry.ts +100 -1
- package/src/mind/bridge.ts +34 -0
- package/src/mind/frame-filler.ts +604 -0
- package/src/mind/learning.ts +5 -9
- package/src/mind/mechanisms/cast.ts +70 -2
- package/src/mind/mechanisms/cover.ts +6 -1
- package/src/mind/mechanisms/extraction.ts +27 -0
- package/src/mind/mechanisms/recall.ts +236 -37
- package/src/mind/mind.ts +154 -14
- package/src/mind/pipeline-mechanism.ts +7 -0
- package/src/mind/pipeline.ts +33 -1
- package/src/mind/prefix-completion.ts +314 -0
- package/src/mind/primitives.ts +59 -70
- package/src/mind/recognition.ts +117 -6
- package/src/mind/traverse.ts +52 -0
- package/src/mind/types.ts +98 -42
- package/src/store.ts +25 -0
- package/test/13-conversation.test.mjs +13 -0
- package/test/57-fusion-order.test.mjs +65 -0
- package/test/66-query-edge-whitespace.test.mjs +99 -0
- package/test/67-climb-anchor-breadth.test.mjs +113 -0
- package/test/68-extraction-unanchored.test.mjs +79 -0
- package/test/69-frame-filler.test.mjs +115 -0
- package/test/70-prefix-completion.test.mjs +170 -0
- package/test/71-embedded-canon-equivalence.test.mjs +121 -0
- package/test/72-prefix-candidate-supply.test.mjs +114 -0
- package/test/73-scaffolding-only-bridge-abstains.test.mjs +178 -0
- package/test/74-prefix-trap-not-sprung-early.test.mjs +114 -0
- package/test/75-multiturn-context-optimisation.test.mjs +1082 -0
package/src/geometry.ts
CHANGED
|
@@ -688,6 +688,97 @@ function contentFoldSpan(
|
|
|
688
688
|
return segs[0];
|
|
689
689
|
}
|
|
690
690
|
|
|
691
|
+
/** A plain content fold's reusable state: the level-0 cut edges over the whole
|
|
692
|
+
* stream and each segment's independently-folded root. See
|
|
693
|
+
* {@link contentFoldIncremental}. */
|
|
694
|
+
export interface ContentFold {
|
|
695
|
+
edges: number[];
|
|
696
|
+
segs: Folded[];
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/** {@link contentFoldSpan} over a WHOLE stream, reusing the segments a previous
|
|
700
|
+
* fold of a byte-identical prefix already produced.
|
|
701
|
+
*
|
|
702
|
+
* WHY THIS IS SOUND, AND WHY IT NEEDS NO BOUNDARIES. A level-0 segment is a
|
|
703
|
+
* pure function of its own bytes ({@link flatFold} reads nothing else), so
|
|
704
|
+
* reusing one whose [start,end) is unchanged is bit-identical to refolding it
|
|
705
|
+
* — the cache can never change the tree, only skip work. And the cuts
|
|
706
|
+
* themselves are stable under APPEND: {@link contentLevels} decides each cut
|
|
707
|
+
* from a rolling hash over a local window, so bytes added at the right edge
|
|
708
|
+
* cannot move a cut to their left (measured over a growing 12-turn context:
|
|
709
|
+
* 100% of prior cuts survive every append, zero tail churn). Together those
|
|
710
|
+
* two facts are the whole optimisation — a grown stream refolds only the
|
|
711
|
+
* segments at its right edge.
|
|
712
|
+
*
|
|
713
|
+
* This is the reuse the conversation path wants, and it costs NOTHING in
|
|
714
|
+
* structure: the tree is exactly the tree {@link bytesToTree} builds for the
|
|
715
|
+
* same bytes with no boundary set at all. Turn boundaries buy prefix-ROOT
|
|
716
|
+
* identity, which is a different property from incremental reuse; conflating
|
|
717
|
+
* the two is what put an imposed boundary set on the inference path and left
|
|
718
|
+
* it folding differently from the deposits it was querying.
|
|
719
|
+
*
|
|
720
|
+
* `groupByLevel` above the segments is re-run whole. It operates on segment
|
|
721
|
+
* ROOTS (a few dozen items for a several-hundred-byte context), not on bytes,
|
|
722
|
+
* and only its right edge actually changes shape — measured at ~40 rebuilt
|
|
723
|
+
* nodes per turn, flat as the context grows sevenfold.
|
|
724
|
+
*
|
|
725
|
+
* PRECONDITION — `prev` MUST have been folded over a BYTE-IDENTICAL PREFIX of
|
|
726
|
+
* `bytes`. Reuse is keyed on a segment's [start,end) OFFSETS, which is what
|
|
727
|
+
* makes it O(1) per segment; offsets alone cannot witness that the underlying
|
|
728
|
+
* bytes agree. Hand it a fold of DIFFERENT bytes whose cuts happen to land
|
|
729
|
+
* in the same places and it will splice those foreign segments in — measured,
|
|
730
|
+
* a deliberately mismatched `prev` produced a wrong tree on 336 of 400 random
|
|
731
|
+
* streams. Verifying the bytes here would cost O(prefix) and defeat the
|
|
732
|
+
* whole point, so the obligation sits with the caller, and every caller
|
|
733
|
+
* discharges it structurally rather than by care: `perceiveDeposit` looks the
|
|
734
|
+
* entry up under `latin1Key(bytes.subarray(0, L))` — the prefix's own bytes
|
|
735
|
+
* ARE the cache key — and a conversation's fold state advances only by
|
|
736
|
+
* append. A new caller that cannot make the same structural argument must
|
|
737
|
+
* pass no `prev` at all; the cold path is always correct.
|
|
738
|
+
* ({@link stablePrefixFoldIncremental} carries the identical precondition for
|
|
739
|
+
* the identical reason.) */
|
|
740
|
+
export function contentFoldIncremental(
|
|
741
|
+
space: Space,
|
|
742
|
+
alphabet: Alphabet,
|
|
743
|
+
bytes: Uint8Array,
|
|
744
|
+
prev?: ContentFold,
|
|
745
|
+
): { tree: Sema; fold: ContentFold } {
|
|
746
|
+
if (bytes.length === 0) {
|
|
747
|
+
return {
|
|
748
|
+
tree: sema(alphabet.vecs[0], new Uint8Array(0), null),
|
|
749
|
+
fold: { edges: [0], segs: [] },
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
const { cuts, levels } = contentLevels(space, bytes);
|
|
753
|
+
const edges = [0, ...cuts, bytes.length];
|
|
754
|
+
const segs: Folded[] = [];
|
|
755
|
+
for (let i = 0; i + 1 < edges.length; i++) {
|
|
756
|
+
const hit = prev !== undefined && prev.edges[i] === edges[i] &&
|
|
757
|
+
prev.edges[i + 1] === edges[i + 1]
|
|
758
|
+
? prev.segs[i]
|
|
759
|
+
: undefined;
|
|
760
|
+
segs.push(hit ?? flatFold(space, alphabet, bytes, edges[i], edges[i + 1]));
|
|
761
|
+
}
|
|
762
|
+
const folded = segs.length > 1
|
|
763
|
+
? groupByLevel(space, segs, levels, 1)
|
|
764
|
+
: segs[0];
|
|
765
|
+
// THE ROOT IS NORMALIZED IN PLACE, A CACHED SEGMENT NEVER IS. With one
|
|
766
|
+
// segment — or with a grouping that passes a lone item through — `folded`
|
|
767
|
+
// IS a cached seg, and a later turn will reuse it as an interior node whose
|
|
768
|
+
// magnitude must stay byte-proportional. Copy before normalizing, exactly
|
|
769
|
+
// as the stable-prefix twin does. A single LEAF is copied too: its vector
|
|
770
|
+
// is the shared alphabet entry and must never be written.
|
|
771
|
+
const aliased = segs.some((s) => s.tree === folded.tree);
|
|
772
|
+
let tree = folded.tree;
|
|
773
|
+
if (aliased) {
|
|
774
|
+
tree = tree.kids === null
|
|
775
|
+
? sema(tree.v, tree.leaf, null)
|
|
776
|
+
: sema(Float32Array.from(tree.v), null, tree.kids);
|
|
777
|
+
}
|
|
778
|
+
if (tree.kids !== null) normalize(tree.v);
|
|
779
|
+
return { tree, fold: { edges, segs } };
|
|
780
|
+
}
|
|
781
|
+
|
|
691
782
|
/** Group a row of items by the level of the cut BETWEEN them: items separated
|
|
692
783
|
* by a cut of level < L belong to the same parent, and a cut of level ≥ L ends
|
|
693
784
|
* it. Recurses upward until one root remains, so the shape at every level is
|
|
@@ -923,9 +1014,17 @@ export function stablePrefixFoldIncremental(
|
|
|
923
1014
|
boundaries: readonly number[],
|
|
924
1015
|
prev?: StableFold,
|
|
925
1016
|
): { tree: Sema; fold: StableFold } {
|
|
1017
|
+
// SORTED, like {@link bytesToTree} does before calling the non-incremental
|
|
1018
|
+
// twin. The filter below is sequential (`b > prevB`), so an out-of-order
|
|
1019
|
+
// entry is silently DROPPED rather than rejected — and these two functions
|
|
1020
|
+
// are documented as producing the same cuts, so a caller that hands the
|
|
1021
|
+
// same set to each and gets different trees has hit a trap, not a contract.
|
|
1022
|
+
// Sorting here makes the twins genuinely interchangeable; the set is one
|
|
1023
|
+
// entry per conversation turn, so the cost is nil.
|
|
1024
|
+
const sorted = [...boundaries].sort((a, b) => a - b);
|
|
926
1025
|
const cuts: number[] = [];
|
|
927
1026
|
let prevB = 0;
|
|
928
|
-
for (const b of
|
|
1027
|
+
for (const b of sorted) {
|
|
929
1028
|
if (b > prevB && b < bytes.length) {
|
|
930
1029
|
cuts.push(b);
|
|
931
1030
|
prevB = b;
|
package/src/mind/bridge.ts
CHANGED
|
@@ -97,6 +97,7 @@ import type { MindContext } from "./types.js";
|
|
|
97
97
|
import { foldTree, perceive, read } from "./primitives.js";
|
|
98
98
|
import { chainReach, leafIdRun } from "./canonical.js";
|
|
99
99
|
import {
|
|
100
|
+
allWindowsAreScaffolding,
|
|
100
101
|
corpusN,
|
|
101
102
|
edgeAncestors,
|
|
102
103
|
hubBound,
|
|
@@ -383,6 +384,39 @@ async function bridgeImpl(
|
|
|
383
384
|
);
|
|
384
385
|
return null;
|
|
385
386
|
}
|
|
387
|
+
// NO DISCRIMINATING LITERAL EVIDENCE — abstain (§2.13). A bridge grounds
|
|
388
|
+
// through the literal spans it did NOT substitute; those anchors are the
|
|
389
|
+
// whole of its evidence. When every one of them is SATURATED — containment
|
|
390
|
+
// clamped at the √N hub bound, i.e. the window is corpus-global scaffolding
|
|
391
|
+
// — the query's unsubstituted part discriminates nothing, and the single
|
|
392
|
+
// substituted span is carrying the entire semantic load. That is not a
|
|
393
|
+
// corroborated bridge; it is a template match, and it FABRICATES.
|
|
394
|
+
//
|
|
395
|
+
// Measured on the trained store (hubBound 571). "What is the capital of"
|
|
396
|
+
// has 19 anchors, ALL saturated ("What":572, "hat ":572, "at i":572 …), and
|
|
397
|
+
// bridged to an unrelated trained context about an integral, voiced
|
|
398
|
+
// confidently. Every query the bridge answers CORRECTLY has at least one
|
|
399
|
+
// unsaturated anchor, by a wide margin and with no near miss:
|
|
400
|
+
// "Who is the author of Hamlet?" → "let?":12, "How do you say 'thank you'
|
|
401
|
+
// in French?" → "y 't":3, "…largest planet…" → "tem?":31, "What is the
|
|
402
|
+
// capital of France?" → "f Fr":114. The honest-silence probes sit on the
|
|
403
|
+
// same side as the correct ones ("Zamu":3), so this gate is not what makes
|
|
404
|
+
// them silent and cannot be credited for them.
|
|
405
|
+
//
|
|
406
|
+
// This introduces NO new threshold: `bound` is the same √N reading of "hub"
|
|
407
|
+
// the anchor scan already clamps its own containment read to (§2.2, §2.7).
|
|
408
|
+
if (allWindowsAreScaffolding(ctx, query)) {
|
|
409
|
+
ctx.trace?.step(
|
|
410
|
+
"substitutionBridge",
|
|
411
|
+
[rItem(query, "query")],
|
|
412
|
+
[],
|
|
413
|
+
"every query window that could anchor is corpus-global scaffolding — " +
|
|
414
|
+
"no literal evidence to corroborate a substitution",
|
|
415
|
+
undefined,
|
|
416
|
+
diagnostics!,
|
|
417
|
+
);
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
386
420
|
// CORROBORATION (see the module-level doc) over the precomputed window
|
|
387
421
|
// facts: the query span [qs,qe) attests when every full W-window inside
|
|
388
422
|
// it is a stored flat form and at least one is reused across ≥ 2
|