@hviana/sema 0.2.2 → 0.2.4
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/dist/src/mind/articulation.js +1 -1
- package/dist/src/mind/attention.d.ts +18 -2
- package/dist/src/mind/attention.js +88 -18
- package/dist/src/mind/bridge.d.ts +30 -0
- package/dist/src/mind/bridge.js +569 -0
- package/dist/src/mind/graph-search.d.ts +16 -1
- package/dist/src/mind/graph-search.js +34 -5
- package/dist/src/mind/match.d.ts +15 -2
- package/dist/src/mind/match.js +3 -8
- package/dist/src/mind/mechanisms/alu.js +8 -1
- package/dist/src/mind/mechanisms/cast.d.ts +54 -0
- package/dist/src/mind/mechanisms/cast.js +303 -48
- package/dist/src/mind/mechanisms/cover.js +24 -32
- package/dist/src/mind/mechanisms/extraction.js +75 -30
- package/dist/src/mind/mechanisms/recall.js +66 -0
- package/dist/src/mind/mind.d.ts +1 -0
- package/dist/src/mind/mind.js +6 -1
- package/dist/src/mind/pipeline.js +34 -2
- package/dist/src/mind/reasoning.d.ts +20 -1
- package/dist/src/mind/reasoning.js +84 -6
- package/dist/src/mind/recognition.js +157 -13
- package/dist/src/mind/traverse.js +16 -0
- package/dist/src/mind/types.d.ts +65 -2
- package/dist/src/mind/types.js +53 -7
- package/dist/src/store.d.ts +12 -3
- package/dist/src/store.js +9 -3
- package/package.json +1 -1
- package/src/mind/articulation.ts +1 -0
- package/src/mind/attention.ts +105 -17
- package/src/mind/bridge.ts +596 -0
- package/src/mind/graph-search.ts +59 -2
- package/src/mind/match.ts +19 -5
- package/src/mind/mechanisms/alu.ts +8 -1
- package/src/mind/mechanisms/cast.ts +336 -46
- package/src/mind/mechanisms/cover.ts +31 -36
- package/src/mind/mechanisms/extraction.ts +101 -40
- package/src/mind/mechanisms/recall.ts +79 -0
- package/src/mind/mind.ts +7 -1
- package/src/mind/pipeline.ts +37 -2
- package/src/mind/reasoning.ts +97 -5
- package/src/mind/recognition.ts +160 -12
- package/src/mind/traverse.ts +17 -0
- package/src/mind/types.ts +110 -6
- package/src/store.ts +19 -5
- package/test/35-attention-confidence.test.mjs +139 -0
- package/test/36-already-answered-fusion.test.mjs +128 -0
- package/test/37-cluster-dispersion-fusion.test.mjs +190 -0
- package/test/38-reason-restate-guard.test.mjs +94 -0
- package/test/39-cast-restate-guard.test.mjs +102 -0
- package/test/40-choosenext-scale-guard.test.mjs +75 -0
- package/test/41-seatofnode-direction.test.mjs +85 -0
- package/test/42-recognise-trace-idempotence.test.mjs +106 -0
- package/test/43-cast-analog-seat.test.mjs +244 -0
- package/test/44-recognise-edge-whitespace.test.mjs +63 -0
- package/test/45-liftanswer-restated-trim.test.mjs +60 -0
- package/test/46-recognise-multibyte-edge.test.mjs +85 -0
- package/test/47-cast-comparison-coverage.test.mjs +134 -0
- package/test/48-recognise-turn-connective.test.mjs +125 -0
- package/test/49-natural-units-synonym-bridge.test.mjs +175 -0
- package/test/50-cast-analog-consensus-floor.test.mjs +179 -0
package/src/mind/attention.ts
CHANGED
|
@@ -57,8 +57,17 @@ export async function climbAttention(
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/** Full read-out of one consensus climb: both the roots (dominant points of
|
|
60
|
-
* attention) and the entire ranked list. Cached via ctx.climbMemo
|
|
61
|
-
*
|
|
60
|
+
* attention) and the entire ranked list. Cached via ctx.climbMemo, ALWAYS —
|
|
61
|
+
* see {@link recognise} for why this memo (and recognise()'s own) must
|
|
62
|
+
* never be skipped while tracing: computeAttention's collectRegions walks
|
|
63
|
+
* the query's perceived tree via the same foldTree whose subtree-resolution
|
|
64
|
+
* fast path makes a second call on identical bytes non-idempotent once
|
|
65
|
+
* ctx._resolvedSubtrees is warm (which a multi-turn conversation's shared
|
|
66
|
+
* prefix subtrees guarantee by the second turn). A cache hit still emits
|
|
67
|
+
* a trace step — abbreviated, since the full per-sub-region voting detail
|
|
68
|
+
* {@link traceAttention} builds isn't preserved by the cached read-out —
|
|
69
|
+
* so a traced response is never silently blacked out for a repeated
|
|
70
|
+
* query. */
|
|
62
71
|
export async function climbAttentionAll(
|
|
63
72
|
ctx: MindContext,
|
|
64
73
|
query: Uint8Array,
|
|
@@ -66,8 +75,8 @@ export async function climbAttentionAll(
|
|
|
66
75
|
mode: DFMode = "inverse",
|
|
67
76
|
): Promise<AttentionRead> {
|
|
68
77
|
// Content-keyed memo — works for both single-turn respond() and multi-turn
|
|
69
|
-
// respondTurn().
|
|
70
|
-
if (ctx.climbMemo
|
|
78
|
+
// respondTurn().
|
|
79
|
+
if (ctx.climbMemo) {
|
|
71
80
|
const contentKey = latin1Key(query);
|
|
72
81
|
const modeKey = `${k}:${mode}`;
|
|
73
82
|
let byRead = ctx.climbMemo.get(contentKey);
|
|
@@ -75,7 +84,16 @@ export async function climbAttentionAll(
|
|
|
75
84
|
ctx.climbMemo.set(contentKey, byRead = new Map());
|
|
76
85
|
}
|
|
77
86
|
const hit = byRead.get(modeKey);
|
|
78
|
-
if (hit !== undefined)
|
|
87
|
+
if (hit !== undefined) {
|
|
88
|
+
ctx.trace?.step(
|
|
89
|
+
"climbConsensus",
|
|
90
|
+
[rItem(query, "query")],
|
|
91
|
+
hit.roots.map((r) => rNode(ctx, r.anchor, "anchor", r.vote)),
|
|
92
|
+
`(cached) consensus already computed for this query — ` +
|
|
93
|
+
`${hit.roots.length} point(s) of attention`,
|
|
94
|
+
);
|
|
95
|
+
return hit;
|
|
96
|
+
}
|
|
79
97
|
const read = await computeAttention(ctx, query, k, mode);
|
|
80
98
|
byRead.set(modeKey, read);
|
|
81
99
|
return read;
|
|
@@ -417,6 +435,11 @@ export function poolVotes(
|
|
|
417
435
|
votes: Map<number, number>;
|
|
418
436
|
votesIdf: Map<number, number>;
|
|
419
437
|
support: Map<number, { start: number; end: number; w: number }>;
|
|
438
|
+
/** Per-anchor SCALE-INVARIANT support: Σ RegionVote.absorbed over the
|
|
439
|
+
* distinct contributing regions — see Attention.breadth. */
|
|
440
|
+
regionSupport: Map<number, number>;
|
|
441
|
+
/** Per-anchor contributing region spans — see Attention.clusters. */
|
|
442
|
+
regionSpans: Map<number, Array<[number, number]>>;
|
|
420
443
|
steps: DerivationStep[];
|
|
421
444
|
} {
|
|
422
445
|
const eligible: number[] = [];
|
|
@@ -496,6 +519,8 @@ export function poolVotes(
|
|
|
496
519
|
number,
|
|
497
520
|
{ start: number; end: number; w: number }
|
|
498
521
|
>();
|
|
522
|
+
const regionSupport = new Map<number, number>();
|
|
523
|
+
const regionSpans = new Map<number, Array<[number, number]>>();
|
|
499
524
|
const steps: DerivationStep[] = [];
|
|
500
525
|
let order = 0;
|
|
501
526
|
for (const pc of pool.values()) {
|
|
@@ -503,13 +528,19 @@ export function poolVotes(
|
|
|
503
528
|
votes.set(pc.item.id, pc.cost);
|
|
504
529
|
const premises: DerivationItem[] = [];
|
|
505
530
|
const seenRi = new Set<number>();
|
|
531
|
+
let breadthSum = 0;
|
|
532
|
+
const spans: Array<[number, number]> = [];
|
|
506
533
|
for (const c of pc.contributions) {
|
|
507
534
|
const p0 = c.premises[0].item;
|
|
508
535
|
if (p0.kind !== "region" || seenRi.has(p0.ri)) continue;
|
|
509
536
|
seenRi.add(p0.ri);
|
|
510
537
|
const rv = regionVotes[p0.ri];
|
|
538
|
+
breadthSum += rv.absorbed ?? 1;
|
|
511
539
|
premises.push({ kind: "form", span: [rv.start, rv.end] });
|
|
540
|
+
spans.push([rv.start, rv.end]);
|
|
512
541
|
}
|
|
542
|
+
regionSupport.set(pc.item.id, breadthSum);
|
|
543
|
+
regionSpans.set(pc.item.id, spans);
|
|
513
544
|
steps.push({
|
|
514
545
|
order: order++,
|
|
515
546
|
move: "pool-vote",
|
|
@@ -536,7 +567,35 @@ export function poolVotes(
|
|
|
536
567
|
}
|
|
537
568
|
}
|
|
538
569
|
}
|
|
539
|
-
return { votes, votesIdf, support, steps };
|
|
570
|
+
return { votes, votesIdf, support, regionSupport, regionSpans, steps };
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** The number of DISTINCT clusters a root's contributing regions form —
|
|
574
|
+
* see Attention.clusters. Two regions belong to the same cluster iff the
|
|
575
|
+
* gap between them is strictly less than one river-fold quantum W: at
|
|
576
|
+
* that distance there is no room for a genuinely separate, independently
|
|
577
|
+
* perceivable unit of content between them (the same "smallest meaningful
|
|
578
|
+
* distinction" quantum {@link reachThreshold}'s own doc invokes). A gap
|
|
579
|
+
* of a full quantum or more means real, separate structure could sit
|
|
580
|
+
* between the two spans, so they count as independent corroboration.
|
|
581
|
+
* Strict `<` (not `<=`): verified against gap 3.1's own "gender equality"
|
|
582
|
+
* root, whose two genuine clusters sit EXACTLY W bytes apart — `<= W`
|
|
583
|
+
* would wrongly merge them into one and break that pinned requirement. */
|
|
584
|
+
function countClusters(spans: readonly [number, number][], W: number): number {
|
|
585
|
+
if (spans.length === 0) return 0;
|
|
586
|
+
const sorted = [...spans].sort((a, b) => a[0] - b[0]);
|
|
587
|
+
let clusters = 1;
|
|
588
|
+
let curEnd = sorted[0][1];
|
|
589
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
590
|
+
const [s, e] = sorted[i];
|
|
591
|
+
if (s - curEnd < W) {
|
|
592
|
+
curEnd = Math.max(curEnd, e);
|
|
593
|
+
} else {
|
|
594
|
+
clusters++;
|
|
595
|
+
curEnd = e;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return clusters;
|
|
540
599
|
}
|
|
541
600
|
|
|
542
601
|
export function commitVotes(
|
|
@@ -545,6 +604,8 @@ export function commitVotes(
|
|
|
545
604
|
votes: Map<number, number>;
|
|
546
605
|
votesIdf: Map<number, number>;
|
|
547
606
|
support: Map<number, { start: number; end: number; w: number }>;
|
|
607
|
+
regionSupport: Map<number, number>;
|
|
608
|
+
regionSpans: Map<number, Array<[number, number]>>;
|
|
548
609
|
steps: DerivationStep[];
|
|
549
610
|
},
|
|
550
611
|
sat: SaturationInfo,
|
|
@@ -552,16 +613,32 @@ export function commitVotes(
|
|
|
552
613
|
regionVoter: ReadonlyArray<{ id: number; score: number; w: number } | null>,
|
|
553
614
|
N: number,
|
|
554
615
|
): AttentionRead {
|
|
555
|
-
const { votes, votesIdf, support, steps } =
|
|
616
|
+
const { votes, votesIdf, support, regionSupport, regionSpans, steps } =
|
|
617
|
+
pooled;
|
|
556
618
|
if (votes.size === 0) {
|
|
557
619
|
traceAttention(ctx, regions, regionVoter, [], steps);
|
|
558
620
|
return { roots: [], ranked: [] };
|
|
559
621
|
}
|
|
560
622
|
|
|
623
|
+
// SCALE-INVARIANT confidence — see Attention.breadth's doc. regions.length
|
|
624
|
+
// is the query's OWN full candidate count (most never vote at all), the
|
|
625
|
+
// same denominator the "N of M sub-regions voted" rationale text already
|
|
626
|
+
// reports; regionSupport is that same accounting read PER ANCHOR.
|
|
627
|
+
const totalRegions = Math.max(1, regions.length);
|
|
561
628
|
const ranked = [...votes.entries()]
|
|
562
629
|
.map(([anchor, vote]) => {
|
|
563
630
|
const s = support.get(anchor)!;
|
|
564
|
-
return {
|
|
631
|
+
return {
|
|
632
|
+
anchor,
|
|
633
|
+
vote,
|
|
634
|
+
start: s.start,
|
|
635
|
+
end: s.end,
|
|
636
|
+
breadth: (regionSupport.get(anchor) ?? 0) / totalRegions,
|
|
637
|
+
clusters: countClusters(
|
|
638
|
+
regionSpans.get(anchor) ?? [],
|
|
639
|
+
ctx.space.maxGroup,
|
|
640
|
+
),
|
|
641
|
+
};
|
|
565
642
|
})
|
|
566
643
|
.sort((a, b) => b.vote - a.vote);
|
|
567
644
|
|
|
@@ -1016,14 +1093,6 @@ async function crossRegionVotes(
|
|
|
1016
1093
|
spanStart = Math.min(spanStart, regions[ei].start);
|
|
1017
1094
|
spanEnd = Math.max(spanEnd, regions[ei].end);
|
|
1018
1095
|
}
|
|
1019
|
-
out.push({
|
|
1020
|
-
start: spanStart,
|
|
1021
|
-
end: spanEnd,
|
|
1022
|
-
canonicalFailed: false, // content-addressed: never saturation-masked
|
|
1023
|
-
roots: reach.roots,
|
|
1024
|
-
w,
|
|
1025
|
-
wFocus: w,
|
|
1026
|
-
});
|
|
1027
1096
|
consumed.add(cand[a]);
|
|
1028
1097
|
consumed.add(cand[b]);
|
|
1029
1098
|
for (const ei of bestExtras) consumed.add(ei);
|
|
@@ -1033,14 +1102,33 @@ async function crossRegionVotes(
|
|
|
1033
1102
|
// vote's bytes are literally part of the learnt whole), and FULL root
|
|
1034
1103
|
// disjointness is the disagreement test: a vote sharing even one root
|
|
1035
1104
|
// with the junction corroborates it and keeps its say elsewhere.
|
|
1105
|
+
// Counted BEFORE pushing the junction's own vote below: each ORIGINAL
|
|
1106
|
+
// region this ascent explains away is evidence the junction speaks
|
|
1107
|
+
// for, not evidence lost — `absorbed` (RegionVote's breadth-accounting
|
|
1108
|
+
// field) must credit the junction with all of it, not just the ONE
|
|
1109
|
+
// pooled axiom it collapses to.
|
|
1036
1110
|
const containerBytes = cachedRead(ctx, cache, best.id, cap);
|
|
1037
1111
|
const jointRoots = new Set(reach.roots);
|
|
1112
|
+
let explainedAway = 0;
|
|
1038
1113
|
for (const rv of rvs.votes) {
|
|
1039
1114
|
if (rv.roots.some((r) => jointRoots.has(r))) continue;
|
|
1040
1115
|
const bytes = query.subarray(rv.start, rv.end);
|
|
1041
|
-
if (indexOf(containerBytes, bytes, 0) >= 0
|
|
1116
|
+
if (indexOf(containerBytes, bytes, 0) >= 0 && !superseded.has(rv)) {
|
|
1117
|
+
superseded.add(rv);
|
|
1118
|
+
explainedAway++;
|
|
1119
|
+
}
|
|
1042
1120
|
}
|
|
1043
1121
|
|
|
1122
|
+
out.push({
|
|
1123
|
+
start: spanStart,
|
|
1124
|
+
end: spanEnd,
|
|
1125
|
+
canonicalFailed: false, // content-addressed: never saturation-masked
|
|
1126
|
+
roots: reach.roots,
|
|
1127
|
+
w,
|
|
1128
|
+
wFocus: w,
|
|
1129
|
+
absorbed: 1 + explainedAway,
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1044
1132
|
const label = [cand[a], cand[b], ...bestExtras]
|
|
1045
1133
|
.sort((x, y) => regions[x].start - regions[y].start)
|
|
1046
1134
|
.map((ri) => dec(query.subarray(regions[ri].start, regions[ri].end)))
|