react-state-basis 0.6.4 → 0.6.6

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.
@@ -24,33 +24,92 @@ __export(zustand_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(zustand_exports);
26
26
 
27
+ // src/core/constants.ts
28
+ var WINDOW_SIZE = 50;
29
+ var PAIR_RARITY_TARGET = 0.01;
30
+ var RELATIVE_OVERLAP_FLOOR = 0.65;
31
+ var LOOP_THRESHOLD = 150;
32
+ var VOLATILITY_THRESHOLD = 25;
33
+ var INSTANCE_SEP = "##";
34
+
27
35
  // src/core/math.ts
28
- var calculateSimilarityCircular = (bufferA, headA, bufferB, headB, offset) => {
36
+ var choose = (n, k) => {
37
+ if (k < 0 || k > n) return 0;
38
+ k = Math.min(k, n - k);
39
+ let result = 1;
40
+ for (let i = 0; i < k; i++) {
41
+ result = result * (n - i) / (i + 1);
42
+ }
43
+ return result;
44
+ };
45
+ var hypergeomPMF = (k, n1, m1, N) => {
46
+ const total = choose(N, n1);
47
+ if (total === 0) return 0;
48
+ return choose(m1, k) * choose(N - m1, n1 - k) / total;
49
+ };
50
+ var hypergeomUpperTailP = (kStart, n1, m1, N) => {
51
+ const kMax = Math.min(n1, m1);
52
+ let p = 0;
53
+ for (let k = Math.max(0, kStart); k <= kMax; k++) {
54
+ p += hypergeomPMF(k, n1, m1, N);
55
+ }
56
+ return p;
57
+ };
58
+ var minOverlapForRarity = (n1, m1, N, targetP) => {
59
+ const kMax = Math.min(n1, m1);
60
+ for (let k = 0; k <= kMax; k++) {
61
+ if (hypergeomUpperTailP(k, n1, m1, N) <= targetP) return k;
62
+ }
63
+ return kMax + 1;
64
+ };
65
+ var overlapThresholdCache = /* @__PURE__ */ new Map();
66
+ var getMinOverlap = (densityA, densityB, windowSize) => {
67
+ const lo = Math.min(densityA, densityB) | 0;
68
+ const hi = Math.max(densityA, densityB) | 0;
69
+ const key = `${lo}_${hi}_${windowSize}`;
70
+ let cached = overlapThresholdCache.get(key);
71
+ if (cached === void 0) {
72
+ const rare = minOverlapForRarity(lo, hi, windowSize, PAIR_RARITY_TARGET);
73
+ const floor = Math.ceil(RELATIVE_OVERLAP_FLOOR * lo);
74
+ cached = Math.max(rare, floor);
75
+ overlapThresholdCache.set(key, cached);
76
+ }
77
+ return cached;
78
+ };
79
+ var isSignificantOverlap = (overlap, densityA, densityB, windowSize) => {
80
+ if (densityA < 2 || densityB < 2) return false;
81
+ return overlap >= getMinOverlap(densityA, densityB, windowSize);
82
+ };
83
+ var countOverlapsCircular = (bufferA, headA, bufferB, headB) => {
29
84
  const L = bufferA.length;
30
- let dot = 0, magA = 0, magB = 0;
31
- const baseOffset = ((headB - headA + offset) % L + L) % L;
85
+ const offSync = ((headB - headA) % L + L) % L;
86
+ const offALeadsB = ((headB - headA + 1) % L + L) % L;
87
+ const offBLeadsA = ((headB - headA - 1) % L + L) % L;
88
+ let kSync = 0;
89
+ let kALeadsB = 0;
90
+ let kBLeadsA = 0;
91
+ let densityA = 0;
92
+ let densityB = 0;
32
93
  for (let i = 0; i < L; i++) {
33
- const valA = bufferA[i];
34
- let iB = i + baseOffset;
35
- if (iB >= L) {
36
- iB -= L;
37
- }
38
- const valB = bufferB[iB];
39
- dot += valA * valB;
40
- magA += valA * valA;
41
- magB += valB * valB;
94
+ const a = bufferA[i] ? 1 : 0;
95
+ const b = bufferB[i] ? 1 : 0;
96
+ densityA += a;
97
+ densityB += b;
98
+ let iSync = i + offSync;
99
+ if (iSync >= L) iSync -= L;
100
+ let iALeadsB = i + offALeadsB;
101
+ if (iALeadsB >= L) iALeadsB -= L;
102
+ let iBLeadsA = i + offBLeadsA;
103
+ if (iBLeadsA >= L) iBLeadsA -= L;
104
+ if (a && bufferB[iSync]) kSync++;
105
+ if (a && bufferB[iALeadsB]) kALeadsB++;
106
+ if (a && bufferB[iBLeadsA]) kBLeadsA++;
42
107
  }
43
- if (magA === 0 || magB === 0) return 0;
44
- return dot / (Math.sqrt(magA) * Math.sqrt(magB));
108
+ return { kSync, kALeadsB, kBLeadsA, densityA, densityB };
45
109
  };
46
- var calculateCosineSimilarity = (A, B) => {
47
- let dot = 0, magA = 0, magB = 0;
48
- for (let i = 0; i < A.length; i++) {
49
- dot += A[i] * B[i];
50
- magA += A[i] * A[i];
51
- magB += B[i] * B[i];
52
- }
53
- return magA === 0 || magB === 0 ? 0 : dot / (Math.sqrt(magA) * Math.sqrt(magB));
110
+ var cosineFromOverlap = (overlap, densityA, densityB) => {
111
+ if (densityA <= 0 || densityB <= 0) return 0;
112
+ return overlap / Math.sqrt(densityA * densityB);
54
113
  };
55
114
 
56
115
  // src/core/graph.ts
@@ -87,13 +146,27 @@ var calculateSpectralInfluence = (graph, maxIterations = 20, tolerance = 1e-3) =
87
146
  }
88
147
  return scores;
89
148
  };
90
-
91
- // src/core/constants.ts
92
- var WINDOW_SIZE = 50;
93
- var SIMILARITY_THRESHOLD = 0.88;
94
- var LOOP_THRESHOLD = 150;
95
- var VOLATILITY_THRESHOLD = 25;
96
- var INSTANCE_SEP = "##";
149
+ var groupEventSources = (nodes, edges) => {
150
+ const outgoing = /* @__PURE__ */ new Map();
151
+ edges.forEach((e) => {
152
+ if (!outgoing.has(e.source)) outgoing.set(e.source, []);
153
+ outgoing.get(e.source).push(e);
154
+ });
155
+ const eventSourceIds = nodes.filter((n) => n.role === "event").map((n) => n.id);
156
+ const signatureOf = (sourceEdges) => sourceEdges.map((e) => `${e.target}@${e.weight}`).sort().join("|");
157
+ const buckets = /* @__PURE__ */ new Map();
158
+ eventSourceIds.forEach((id) => {
159
+ const sig = signatureOf(outgoing.get(id) || []);
160
+ if (!buckets.has(sig)) buckets.set(sig, []);
161
+ buckets.get(sig).push(id);
162
+ });
163
+ const groups = Array.from(buckets.values()).map((sourceIds) => ({
164
+ sourceIds,
165
+ occurrences: sourceIds.length,
166
+ edges: (outgoing.get(sourceIds[0]) || []).slice()
167
+ }));
168
+ return groups.sort((a, b) => b.edges.length - a.edges.length || b.occurrences - a.occurrences);
169
+ };
97
170
 
98
171
  // src/core/label.ts
99
172
  var stripInstance = (label) => {
@@ -106,6 +179,7 @@ var parseLabel = (label) => {
106
179
  return { file: parts[0] || "Unknown", name: parts[1] || base };
107
180
  };
108
181
  var isSameField = (labelA, labelB) => stripInstance(labelA) === stripInstance(labelB);
182
+ var isEffectLabel = (name) => /^effect_L\d+(:\d+)?$/.test(name) || name === "anonymous_effect" || name === "anonymous_layout_effect";
109
183
 
110
184
  // src/core/ranker.ts
111
185
  var identifyTopIssues = (graph, history2, redundantLabels2, violationMap) => {
@@ -207,32 +281,22 @@ var LAST_LOG_TIMES = /* @__PURE__ */ new Map();
207
281
  var LOG_COOLDOWN = 3e3;
208
282
  var THEME = {
209
283
  identity: "#6C5CE7",
210
- // Purple (Brand)
211
284
  problem: "#D63031",
212
- // Red (Bugs)
213
285
  solution: "#FBC531",
214
- // Yellow (Fixes)
215
286
  context: "#0984E3",
216
- // Blue (Locations)
217
287
  muted: "#9AA0A6",
218
- // Gray (Metadata)
219
288
  border: "#2E2E35",
220
289
  success: "#00b894"
221
- // Green (Good Score)
222
290
  };
223
291
  var STYLES = {
224
- // Structure
225
292
  basis: `background: ${THEME.identity}; color: white; font-weight: bold; padding: 2px 6px; border-radius: 3px;`,
226
293
  headerIdentity: `background: ${THEME.identity}; color: white; font-weight: bold; padding: 4px 8px; border-radius: 4px;`,
227
294
  headerProblem: `background: ${THEME.problem}; color: white; font-weight: bold; padding: 4px 8px; border-radius: 4px;`,
228
295
  version: `background: #a29bfe; color: #2d3436; padding: 2px 6px; border-radius: 3px; margin-left: -4px;`,
229
- // Actions
230
296
  actionLabel: `color: ${THEME.solution}; font-weight: bold;`,
231
297
  actionPill: `color: ${THEME.solution}; font-weight: bold; border: 1px solid ${THEME.solution}; padding: 0 4px; border-radius: 3px;`,
232
- // Context
233
298
  impactLabel: `color: ${THEME.context}; font-weight: bold;`,
234
299
  location: `color: ${THEME.context}; font-family: monospace; font-weight: bold;`,
235
- // Text
236
300
  subText: `color: ${THEME.muted}; font-size: 11px;`,
237
301
  bold: "font-weight: bold;",
238
302
  label: "background: #dfe6e9; color: #2d3436; padding: 0 4px; border-radius: 3px; font-family: monospace; font-weight: bold; border: 1px solid #b2bec3;"
@@ -247,6 +311,15 @@ var shouldLog = (key) => {
247
311
  return false;
248
312
  };
249
313
  var isBooleanLike = (name) => /^(is|has|can|should|did|will|show|hide)(?=[A-Z_])/.test(name);
314
+ var areSyncSignificant = (metaA, metaB) => {
315
+ const { kSync, densityA, densityB } = countOverlapsCircular(
316
+ metaA.buffer,
317
+ metaA.head,
318
+ metaB.buffer,
319
+ metaB.head
320
+ );
321
+ return isSignificantOverlap(kSync, densityA, densityB, metaA.buffer.length);
322
+ };
250
323
  var getSuggestedFix = (issue, info) => {
251
324
  if (issue.label.includes("Global Event")) {
252
325
  return `These variables update together but live in different hooks/files. Consolidate them into a single %cuseReducer%c or atomic store update.`;
@@ -276,7 +349,7 @@ var getSuggestedFix = (issue, info) => {
276
349
  }
277
350
  return `Check the dependency chain of ${info.name}.`;
278
351
  };
279
- var displayHealthReport = (history2, threshold, violationMap) => {
352
+ var displayHealthReport = (history2, violationMap) => {
280
353
  if (!isWeb) return;
281
354
  const entries = Array.from(history2.entries());
282
355
  if (entries.length === 0) return;
@@ -352,11 +425,10 @@ var displayHealthReport = (history2, threshold, violationMap) => {
352
425
  processed.add(labelA);
353
426
  entries.forEach(([labelB, metaB]) => {
354
427
  if (labelA === labelB || processed.has(labelB)) return;
355
- if (calculateCosineSimilarity(metaA.buffer, metaB.buffer) > threshold) {
356
- if (metaA.role === "context" /* CONTEXT */ && metaB.role === "context" /* CONTEXT */) return;
357
- currentCluster.push(labelB);
358
- processed.add(labelB);
359
- }
428
+ if (!areSyncSignificant(metaA, metaB)) return;
429
+ if (metaA.role === "context" /* CONTEXT */ && metaB.role === "context" /* CONTEXT */) return;
430
+ currentCluster.push(labelB);
431
+ processed.add(labelB);
360
432
  });
361
433
  if (currentCluster.length > 1) clusters.push(currentCluster);
362
434
  else independentCount++;
@@ -432,7 +504,7 @@ var displayRedundancyAlert = (labelA, metaA, labelB, metaB, sim) => {
432
504
  const alertType = isContextMirror ? "CONTEXT MIRRORING" : isStoreMirror ? "STORE MIRRORING" : "DUPLICATE STATE";
433
505
  console.group(`%c \u264A BASIS | ${alertType} `, STYLES.headerProblem);
434
506
  console.log(`%c\u{1F4CD} Location: %c${infoA.file}`, STYLES.bold, STYLES.location);
435
- console.log(`%cIssue:%c ${infoA.name} and ${infoB.name} are synchronized (${(sim * 100).toFixed(0)}%).`, STYLES.bold, "");
507
+ console.log(`%cIssue:%c ${infoA.name} and ${infoB.name} overlapped on ${(sim * 100).toFixed(0)}% of aligned updates.`, STYLES.bold, "");
436
508
  if (isContextMirror || isStoreMirror) {
437
509
  const sourceType = isStoreMirror ? "External Store" : "Global Context";
438
510
  console.log(
@@ -501,6 +573,99 @@ var displayCausalHint = (targetLabel, targetMeta, sourceLabel, sourceMeta) => {
501
573
  }
502
574
  console.groupEnd();
503
575
  };
576
+ var splitHookLine = (raw) => {
577
+ const m = raw.match(/^(.*):(\d+)$/);
578
+ if (!m) return { hook: raw };
579
+ return { hook: m[1], line: Number(m[2]) };
580
+ };
581
+ var formatHook = (raw) => {
582
+ const { hook } = splitHookLine(raw);
583
+ if (!isEffectLabel(hook)) return hook;
584
+ const lineMatch = hook.match(/L(\d+)$/);
585
+ return lineMatch ? `effect @ L${lineMatch[1]}` : "effect (anonymous)";
586
+ };
587
+ var formatNode = (node, fallbackId = "?") => {
588
+ if (!node) return fallbackId;
589
+ if (node.role === "event") return "Event";
590
+ const hook = formatHook(node.name || node.id);
591
+ if (node.file && hook) return `${node.file} \u2192 ${hook}`;
592
+ return hook || node.id;
593
+ };
594
+ var displayGraphReport = (graph) => {
595
+ if (!isWeb) return;
596
+ if (graph.nodes.length === 0) {
597
+ console.log(
598
+ `%c \u{1F4CA} BASIS | CAUSAL GRAPH %c(no data yet)`,
599
+ STYLES.headerIdentity,
600
+ `color: ${THEME.muted}; font-style: italic;`
601
+ );
602
+ return;
603
+ }
604
+ const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));
605
+ const outgoing = /* @__PURE__ */ new Map();
606
+ graph.edges.forEach((e) => {
607
+ if (!outgoing.has(e.source)) outgoing.set(e.source, []);
608
+ outgoing.get(e.source).push(e);
609
+ });
610
+ const eventGroups = graph.eventGroups.map((g) => ({
611
+ sourceIds: g.sourceIds,
612
+ sourceNode: nodeById.get(g.sourceIds[0]),
613
+ edges: g.edges,
614
+ occurrences: g.occurrences
615
+ }));
616
+ const groupedSourceIds = new Set(graph.eventGroups.flatMap((g) => g.sourceIds));
617
+ const nonEventGroups = Array.from(outgoing.keys()).filter((id) => !groupedSourceIds.has(id)).map((id) => ({ sourceIds: [id], sourceNode: nodeById.get(id), edges: outgoing.get(id), occurrences: 1 }));
618
+ const groups = [...eventGroups, ...nonEventGroups].sort(
619
+ (a, b) => b.edges.length - a.edges.length || b.occurrences - a.occurrences
620
+ );
621
+ console.group(
622
+ `%c \u{1F4CA} BASIS | CAUSAL GRAPH %c${graph.nodes.length} nodes \xB7 ${graph.edges.length} edges \xB7 ${groups.length} sources \xB7 buffer window ${graph.bufferWindowSize}`,
623
+ STYLES.headerIdentity,
624
+ `color: ${THEME.muted}; font-weight: normal; font-style: italic;`
625
+ );
626
+ console.log(
627
+ `%cparent \u2192 child = observed cause \u2192 update. (\xD7N) = times in this window. Event groups with the same fan-out are collapsed.`,
628
+ STYLES.subText
629
+ );
630
+ groups.forEach((group) => {
631
+ const isEvent = group.sourceNode?.role === "event";
632
+ const isCtx = group.sourceNode?.role === "context" /* CONTEXT */;
633
+ const isFx = group.sourceNode?.role === "effect";
634
+ const isUnknown = group.sourceNode?.role === "unknown";
635
+ const icon = isEvent ? "\u26A1" : isCtx ? "\u03A9" : isFx ? "\u21AF" : isUnknown ? "?" : "\u25CF";
636
+ const color = isEvent ? THEME.solution : isCtx ? THEME.context : THEME.identity;
637
+ const fanout = group.edges.length;
638
+ const hits = group.occurrences;
639
+ const hitLabel = hits > 1 ? ` \xB7 \xD7${hits}` : "";
640
+ const title = isEvent ? `Event \xB7 ${fanout} target${fanout === 1 ? "" : "s"}${hitLabel}` : formatNode(group.sourceNode, group.sourceIds[0]);
641
+ console.groupCollapsed(
642
+ `%c${icon} %c${title}`,
643
+ `color: ${color};`,
644
+ "font-family: monospace; font-weight: 600;"
645
+ );
646
+ group.edges.slice().sort((a, b) => b.weight - a.weight).forEach((edge) => {
647
+ const target = nodeById.get(edge.target);
648
+ const label = formatNode(target, edge.target);
649
+ const weight = edge.weight > 1 ? ` (\xD7${edge.weight})` : "";
650
+ if (target?.redundant) {
651
+ console.log(
652
+ `%c ${label}%c${weight} %credundant`,
653
+ `color: ${THEME.muted}; font-family: monospace;`,
654
+ `color: ${THEME.muted}; font-style: italic;`,
655
+ `color: ${THEME.problem}; font-weight: bold;`
656
+ );
657
+ } else {
658
+ console.log(
659
+ `%c ${label}%c${weight}`,
660
+ `color: ${THEME.muted}; font-family: monospace;`,
661
+ `color: ${THEME.muted}; font-style: italic;`
662
+ );
663
+ }
664
+ });
665
+ console.groupEnd();
666
+ });
667
+ console.groupEnd();
668
+ };
504
669
  var displayViolentBreaker = (label, count, threshold) => {
505
670
  if (!isWeb) return;
506
671
  const { name } = parseLabel(label);
@@ -513,7 +678,6 @@ Frequency: ${count} updates/sec`);
513
678
  };
514
679
 
515
680
  // src/core/analysis.ts
516
- var CAUSAL_MARGIN = 0.05;
517
681
  var isEventDriven = (label, graph) => {
518
682
  for (const [parent, targets] of graph.entries()) {
519
683
  if (parent.startsWith("Event_Tick_") && targets.has(label)) {
@@ -523,28 +687,33 @@ var isEventDriven = (label, graph) => {
523
687
  return false;
524
688
  };
525
689
  var calculateAllSimilarities = (entryA, entryB) => {
526
- const sync = calculateSimilarityCircular(
527
- entryA.meta.buffer,
528
- entryA.meta.head,
529
- entryB.meta.buffer,
530
- entryB.meta.head,
531
- 0
532
- );
533
- const bA = calculateSimilarityCircular(
690
+ const { kSync, kALeadsB, kBLeadsA, densityA, densityB } = countOverlapsCircular(
534
691
  entryA.meta.buffer,
535
692
  entryA.meta.head,
536
693
  entryB.meta.buffer,
537
- entryB.meta.head,
538
- 1
694
+ entryB.meta.head
539
695
  );
540
- const aB = calculateSimilarityCircular(
541
- entryA.meta.buffer,
542
- entryA.meta.head,
543
- entryB.meta.buffer,
544
- entryB.meta.head,
545
- -1
546
- );
547
- return { sync, bA, aB, max: Math.max(sync, bA, aB) };
696
+ const sync = cosineFromOverlap(kSync, densityA, densityB);
697
+ const bA = cosineFromOverlap(kALeadsB, densityA, densityB);
698
+ const aB = cosineFromOverlap(kBLeadsA, densityA, densityB);
699
+ const max = Math.max(sync, bA, aB);
700
+ const windowSize = entryA.meta.buffer.length;
701
+ const significantSync = isSignificantOverlap(kSync, densityA, densityB, windowSize);
702
+ const kLead = Math.max(kALeadsB, kBLeadsA);
703
+ const significantLead = isSignificantOverlap(kLead, densityA, densityB, windowSize) && kLead >= kSync + 1;
704
+ return {
705
+ sync,
706
+ bA,
707
+ aB,
708
+ max,
709
+ kSync,
710
+ kALeadsB,
711
+ kBLeadsA,
712
+ densityA,
713
+ densityB,
714
+ significantSync,
715
+ significantLead
716
+ };
548
717
  };
549
718
  var shouldSkipComparison = (entryA, entryB, dirtyLabels2) => {
550
719
  if (entryA.label === entryB.label) return true;
@@ -569,52 +738,51 @@ var detectRedundancy = (entryA, entryB, similarities, redundantSet, violationMap
569
738
  const roleA = entryA.meta.role;
570
739
  const roleB = entryB.meta.role;
571
740
  if (isGlobalSource(roleA) && isGlobalSource(roleB)) return;
572
- if (entryA.meta.density < 2 || entryB.meta.density < 2) return;
741
+ if (similarities.densityA < 2 || similarities.densityB < 2) return;
742
+ const score = similarities.sync;
573
743
  if (roleA === "local" /* LOCAL */ && isGlobalSource(roleB)) {
574
744
  redundantSet.add(entryA.label);
575
- pushViolation(violationMap, entryB.label, { type: "context_mirror", target: entryA.label, similarity: similarities.max });
576
- displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, similarities.max);
745
+ pushViolation(violationMap, entryB.label, { type: "context_mirror", target: entryA.label, similarity: score });
746
+ displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, score);
577
747
  } else if (isGlobalSource(roleA) && roleB === "local" /* LOCAL */) {
578
748
  redundantSet.add(entryB.label);
579
- pushViolation(violationMap, entryA.label, { type: "context_mirror", target: entryB.label, similarity: similarities.max });
580
- displayRedundancyAlert(entryB.label, entryB.meta, entryA.label, entryA.meta, similarities.max);
749
+ pushViolation(violationMap, entryA.label, { type: "context_mirror", target: entryB.label, similarity: score });
750
+ displayRedundancyAlert(entryB.label, entryB.meta, entryA.label, entryA.meta, score);
581
751
  } else if (roleA === "local" /* LOCAL */ && roleB === "local" /* LOCAL */) {
582
752
  redundantSet.add(entryA.label);
583
753
  redundantSet.add(entryB.label);
584
- pushViolation(violationMap, entryA.label, { type: "duplicate_state", target: entryB.label, similarity: similarities.max });
585
- pushViolation(violationMap, entryB.label, { type: "duplicate_state", target: entryA.label, similarity: similarities.max });
586
- displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, similarities.max);
754
+ pushViolation(violationMap, entryA.label, { type: "duplicate_state", target: entryB.label, similarity: score });
755
+ pushViolation(violationMap, entryB.label, { type: "duplicate_state", target: entryA.label, similarity: score });
756
+ displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, score);
587
757
  }
588
758
  };
589
759
  var detectCausalLeak = (entryA, entryB, similarities, violationMap, graph) => {
590
760
  if (entryA.isVolatile || entryB.isVolatile) return;
591
- if (similarities.max - similarities.sync < CAUSAL_MARGIN) return;
592
761
  const addLeak = (source, target) => {
593
762
  if (isEventDriven(target, graph)) return;
594
- if (!violationMap.has(source)) {
595
- violationMap.set(source, []);
596
- }
597
- violationMap.get(source).push({ type: "causal_leak", target });
763
+ pushViolation(violationMap, source, { type: "causal_leak", target });
598
764
  const sourceEntry = source === entryA.label ? entryA : entryB;
599
765
  const targetEntry = source === entryA.label ? entryB : entryA;
600
766
  displayCausalHint(target, targetEntry.meta, source, sourceEntry.meta);
601
767
  };
602
- if (similarities.bA === similarities.max) {
768
+ if (similarities.kALeadsB >= similarities.kBLeadsA) {
603
769
  addLeak(entryA.label, entryB.label);
604
- } else if (similarities.aB === similarities.max) {
770
+ } else {
605
771
  addLeak(entryB.label, entryA.label);
606
772
  }
607
773
  };
608
774
  var detectSubspaceOverlap = (dirtyEntries, allEntries, redundantSet, dirtyLabels2, graph) => {
609
- let compCount = 0;
610
775
  const violationMap = /* @__PURE__ */ new Map();
776
+ let compCount = 0;
611
777
  for (const entryA of dirtyEntries) {
612
778
  for (const entryB of allEntries) {
613
779
  if (shouldSkipComparison(entryA, entryB, dirtyLabels2)) continue;
614
780
  compCount++;
615
781
  const similarities = calculateAllSimilarities(entryA, entryB);
616
- if (similarities.max > SIMILARITY_THRESHOLD) {
782
+ if (similarities.significantSync) {
617
783
  detectRedundancy(entryA, entryB, similarities, redundantSet, violationMap);
784
+ }
785
+ if (similarities.significantLead) {
618
786
  detectCausalLeak(entryA, entryB, similarities, violationMap, graph);
619
787
  }
620
788
  }
@@ -847,9 +1015,9 @@ var registerVariable = (l, o = {}) => {
847
1015
  });
848
1016
  }
849
1017
  };
850
- var printBasisHealthReport = (threshold = 0.5) => {
1018
+ var printBasisHealthReport = () => {
851
1019
  if (!instance.config.debug) return;
852
- displayHealthReport(instance.history, threshold, instance.violationMap);
1020
+ displayHealthReport(instance.history, instance.violationMap);
853
1021
  };
854
1022
  var getBasisMetrics = () => ({
855
1023
  engine: "v0.6.x",
@@ -857,9 +1025,53 @@ var getBasisMetrics = () => ({
857
1025
  analysis_ms: instance.metrics.lastAnalysisTimeMs.toFixed(3),
858
1026
  entropy: instance.metrics.systemEntropy.toFixed(3)
859
1027
  });
1028
+ var getBasisGraph = () => {
1029
+ const nodeIds = /* @__PURE__ */ new Set();
1030
+ const edges = [];
1031
+ instance.graph.forEach((targets, source) => {
1032
+ nodeIds.add(source);
1033
+ targets.forEach((weight, target) => {
1034
+ nodeIds.add(target);
1035
+ edges.push({ source, target, weight });
1036
+ });
1037
+ });
1038
+ const nodes = Array.from(nodeIds).map((id) => {
1039
+ if (id.startsWith("Event_Tick_")) {
1040
+ return { id, name: "Event", file: "(shared trigger)", role: "event", density: null, redundant: false };
1041
+ }
1042
+ const meta = instance.history.get(id);
1043
+ const { file, name } = parseLabel(id);
1044
+ if (!meta) {
1045
+ const role = isEffectLabel(name) ? "effect" : "unknown";
1046
+ return { id, name, file, role, density: null, redundant: false };
1047
+ }
1048
+ return {
1049
+ id,
1050
+ name,
1051
+ file,
1052
+ role: meta.role,
1053
+ density: meta.density,
1054
+ redundant: instance.redundantLabels.has(id)
1055
+ };
1056
+ });
1057
+ return {
1058
+ generatedAt: Date.now(),
1059
+ bufferWindowSize: WINDOW_SIZE,
1060
+ eventTtlMs: EVENT_TTL,
1061
+ nodes,
1062
+ edges,
1063
+ eventGroups: groupEventSources(nodes, edges)
1064
+ };
1065
+ };
1066
+ var printBasisGraph = () => {
1067
+ if (!instance.config.debug) return;
1068
+ displayGraphReport(getBasisGraph());
1069
+ };
860
1070
  if (typeof window !== "undefined") {
861
1071
  window.printBasisReport = printBasisHealthReport;
862
1072
  window.getBasisMetrics = getBasisMetrics;
1073
+ window.getBasisGraph = getBasisGraph;
1074
+ window.printBasisGraph = printBasisGraph;
863
1075
  }
864
1076
 
865
1077
  // src/integrations/zustand.ts