@hviana/sema 0.2.3 → 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 (45) hide show
  1. package/dist/src/mind/attention.d.ts +14 -2
  2. package/dist/src/mind/attention.js +54 -8
  3. package/dist/src/mind/bridge.d.ts +30 -0
  4. package/dist/src/mind/bridge.js +569 -0
  5. package/dist/src/mind/match.d.ts +15 -2
  6. package/dist/src/mind/match.js +3 -8
  7. package/dist/src/mind/mechanisms/cast.d.ts +54 -0
  8. package/dist/src/mind/mechanisms/cast.js +268 -37
  9. package/dist/src/mind/mechanisms/cover.js +16 -31
  10. package/dist/src/mind/mechanisms/recall.js +66 -0
  11. package/dist/src/mind/reasoning.d.ts +11 -0
  12. package/dist/src/mind/reasoning.js +58 -2
  13. package/dist/src/mind/recognition.js +127 -7
  14. package/dist/src/mind/traverse.js +16 -15
  15. package/dist/src/mind/types.d.ts +39 -2
  16. package/dist/src/mind/types.js +38 -7
  17. package/dist/src/store.d.ts +12 -3
  18. package/dist/src/store.js +9 -3
  19. package/package.json +1 -1
  20. package/src/mind/attention.ts +65 -7
  21. package/src/mind/bridge.ts +596 -0
  22. package/src/mind/match.ts +19 -5
  23. package/src/mind/mechanisms/cast.ts +290 -38
  24. package/src/mind/mechanisms/cover.ts +23 -36
  25. package/src/mind/mechanisms/recall.ts +79 -0
  26. package/src/mind/reasoning.ts +71 -2
  27. package/src/mind/recognition.ts +132 -7
  28. package/src/mind/traverse.ts +16 -17
  29. package/src/mind/types.ts +70 -6
  30. package/src/store.ts +19 -5
  31. package/test/36-already-answered-fusion.test.mjs +128 -0
  32. package/test/37-cluster-dispersion-fusion.test.mjs +190 -0
  33. package/test/38-reason-restate-guard.test.mjs +94 -0
  34. package/test/39-cast-restate-guard.test.mjs +102 -0
  35. package/test/40-choosenext-scale-guard.test.mjs +75 -0
  36. package/test/41-seatofnode-direction.test.mjs +85 -0
  37. package/test/42-recognise-trace-idempotence.test.mjs +106 -0
  38. package/test/43-cast-analog-seat.test.mjs +244 -0
  39. package/test/44-recognise-edge-whitespace.test.mjs +63 -0
  40. package/test/45-liftanswer-restated-trim.test.mjs +60 -0
  41. package/test/46-recognise-multibyte-edge.test.mjs +85 -0
  42. package/test/47-cast-comparison-coverage.test.mjs +134 -0
  43. package/test/48-recognise-turn-connective.test.mjs +125 -0
  44. package/test/49-natural-units-synonym-bridge.test.mjs +175 -0
  45. 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;
@@ -420,6 +438,8 @@ export function poolVotes(
420
438
  /** Per-anchor SCALE-INVARIANT support: Σ RegionVote.absorbed over the
421
439
  * distinct contributing regions — see Attention.breadth. */
422
440
  regionSupport: Map<number, number>;
441
+ /** Per-anchor contributing region spans — see Attention.clusters. */
442
+ regionSpans: Map<number, Array<[number, number]>>;
423
443
  steps: DerivationStep[];
424
444
  } {
425
445
  const eligible: number[] = [];
@@ -500,6 +520,7 @@ export function poolVotes(
500
520
  { start: number; end: number; w: number }
501
521
  >();
502
522
  const regionSupport = new Map<number, number>();
523
+ const regionSpans = new Map<number, Array<[number, number]>>();
503
524
  const steps: DerivationStep[] = [];
504
525
  let order = 0;
505
526
  for (const pc of pool.values()) {
@@ -508,6 +529,7 @@ export function poolVotes(
508
529
  const premises: DerivationItem[] = [];
509
530
  const seenRi = new Set<number>();
510
531
  let breadthSum = 0;
532
+ const spans: Array<[number, number]> = [];
511
533
  for (const c of pc.contributions) {
512
534
  const p0 = c.premises[0].item;
513
535
  if (p0.kind !== "region" || seenRi.has(p0.ri)) continue;
@@ -515,8 +537,10 @@ export function poolVotes(
515
537
  const rv = regionVotes[p0.ri];
516
538
  breadthSum += rv.absorbed ?? 1;
517
539
  premises.push({ kind: "form", span: [rv.start, rv.end] });
540
+ spans.push([rv.start, rv.end]);
518
541
  }
519
542
  regionSupport.set(pc.item.id, breadthSum);
543
+ regionSpans.set(pc.item.id, spans);
520
544
  steps.push({
521
545
  order: order++,
522
546
  move: "pool-vote",
@@ -543,7 +567,35 @@ export function poolVotes(
543
567
  }
544
568
  }
545
569
  }
546
- return { votes, votesIdf, support, regionSupport, 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;
547
599
  }
548
600
 
549
601
  export function commitVotes(
@@ -553,6 +605,7 @@ export function commitVotes(
553
605
  votesIdf: Map<number, number>;
554
606
  support: Map<number, { start: number; end: number; w: number }>;
555
607
  regionSupport: Map<number, number>;
608
+ regionSpans: Map<number, Array<[number, number]>>;
556
609
  steps: DerivationStep[];
557
610
  },
558
611
  sat: SaturationInfo,
@@ -560,7 +613,8 @@ export function commitVotes(
560
613
  regionVoter: ReadonlyArray<{ id: number; score: number; w: number } | null>,
561
614
  N: number,
562
615
  ): AttentionRead {
563
- const { votes, votesIdf, support, regionSupport, steps } = pooled;
616
+ const { votes, votesIdf, support, regionSupport, regionSpans, steps } =
617
+ pooled;
564
618
  if (votes.size === 0) {
565
619
  traceAttention(ctx, regions, regionVoter, [], steps);
566
620
  return { roots: [], ranked: [] };
@@ -580,6 +634,10 @@ export function commitVotes(
580
634
  start: s.start,
581
635
  end: s.end,
582
636
  breadth: (regionSupport.get(anchor) ?? 0) / totalRegions,
637
+ clusters: countClusters(
638
+ regionSpans.get(anchor) ?? [],
639
+ ctx.space.maxGroup,
640
+ ),
583
641
  };
584
642
  })
585
643
  .sort((a, b) => b.vote - a.vote);