@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.
Files changed (60) hide show
  1. package/dist/src/mind/articulation.js +1 -1
  2. package/dist/src/mind/attention.d.ts +18 -2
  3. package/dist/src/mind/attention.js +88 -18
  4. package/dist/src/mind/bridge.d.ts +30 -0
  5. package/dist/src/mind/bridge.js +569 -0
  6. package/dist/src/mind/graph-search.d.ts +16 -1
  7. package/dist/src/mind/graph-search.js +34 -5
  8. package/dist/src/mind/match.d.ts +15 -2
  9. package/dist/src/mind/match.js +3 -8
  10. package/dist/src/mind/mechanisms/alu.js +8 -1
  11. package/dist/src/mind/mechanisms/cast.d.ts +54 -0
  12. package/dist/src/mind/mechanisms/cast.js +303 -48
  13. package/dist/src/mind/mechanisms/cover.js +24 -32
  14. package/dist/src/mind/mechanisms/extraction.js +75 -30
  15. package/dist/src/mind/mechanisms/recall.js +66 -0
  16. package/dist/src/mind/mind.d.ts +1 -0
  17. package/dist/src/mind/mind.js +6 -1
  18. package/dist/src/mind/pipeline.js +34 -2
  19. package/dist/src/mind/reasoning.d.ts +20 -1
  20. package/dist/src/mind/reasoning.js +84 -6
  21. package/dist/src/mind/recognition.js +157 -13
  22. package/dist/src/mind/traverse.js +16 -0
  23. package/dist/src/mind/types.d.ts +65 -2
  24. package/dist/src/mind/types.js +53 -7
  25. package/dist/src/store.d.ts +12 -3
  26. package/dist/src/store.js +9 -3
  27. package/package.json +1 -1
  28. package/src/mind/articulation.ts +1 -0
  29. package/src/mind/attention.ts +105 -17
  30. package/src/mind/bridge.ts +596 -0
  31. package/src/mind/graph-search.ts +59 -2
  32. package/src/mind/match.ts +19 -5
  33. package/src/mind/mechanisms/alu.ts +8 -1
  34. package/src/mind/mechanisms/cast.ts +336 -46
  35. package/src/mind/mechanisms/cover.ts +31 -36
  36. package/src/mind/mechanisms/extraction.ts +101 -40
  37. package/src/mind/mechanisms/recall.ts +79 -0
  38. package/src/mind/mind.ts +7 -1
  39. package/src/mind/pipeline.ts +37 -2
  40. package/src/mind/reasoning.ts +97 -5
  41. package/src/mind/recognition.ts +160 -12
  42. package/src/mind/traverse.ts +17 -0
  43. package/src/mind/types.ts +110 -6
  44. package/src/store.ts +19 -5
  45. package/test/35-attention-confidence.test.mjs +139 -0
  46. package/test/36-already-answered-fusion.test.mjs +128 -0
  47. package/test/37-cluster-dispersion-fusion.test.mjs +190 -0
  48. package/test/38-reason-restate-guard.test.mjs +94 -0
  49. package/test/39-cast-restate-guard.test.mjs +102 -0
  50. package/test/40-choosenext-scale-guard.test.mjs +75 -0
  51. package/test/41-seatofnode-direction.test.mjs +85 -0
  52. package/test/42-recognise-trace-idempotence.test.mjs +106 -0
  53. package/test/43-cast-analog-seat.test.mjs +244 -0
  54. package/test/44-recognise-edge-whitespace.test.mjs +63 -0
  55. package/test/45-liftanswer-restated-trim.test.mjs +60 -0
  56. package/test/46-recognise-multibyte-edge.test.mjs +85 -0
  57. package/test/47-cast-comparison-coverage.test.mjs +134 -0
  58. package/test/48-recognise-turn-connective.test.mjs +125 -0
  59. package/test/49-natural-units-synonym-bridge.test.mjs +175 -0
  60. package/test/50-cast-analog-consensus-floor.test.mjs +179 -0
@@ -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 when
61
- * ctx.trace is null. */
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(). Skipped while tracing.
70
- if (ctx.climbMemo && !ctx.trace) {
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) return hit;
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 } = pooled;
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 { anchor, vote, start: s.start, end: s.end };
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) superseded.add(rv);
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)))