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.
- package/README.md +59 -1
- package/dist/{chunk-6GBZSOB3.mjs → chunk-WMHY2C6D.mjs} +300 -86
- package/dist/chunk-WMHY2C6D.mjs.map +1 -0
- package/dist/index.d.mts +37 -2
- package/dist/index.d.ts +37 -2
- package/dist/index.js +300 -84
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5 -1
- package/dist/index.mjs.map +1 -1
- package/dist/integrations/zustand.js +296 -84
- package/dist/integrations/zustand.js.map +1 -1
- package/dist/integrations/zustand.mjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-6GBZSOB3.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -34,7 +34,9 @@ __export(index_exports, {
|
|
|
34
34
|
basis: () => basis,
|
|
35
35
|
configureBasis: () => configureBasis,
|
|
36
36
|
createContext: () => createContext,
|
|
37
|
+
getBasisGraph: () => getBasisGraph,
|
|
37
38
|
getBasisMetrics: () => getBasisMetrics,
|
|
39
|
+
printBasisGraph: () => printBasisGraph,
|
|
38
40
|
printBasisHealthReport: () => printBasisHealthReport,
|
|
39
41
|
use: () => use,
|
|
40
42
|
useActionState: () => useActionState,
|
|
@@ -62,33 +64,92 @@ module.exports = __toCommonJS(index_exports);
|
|
|
62
64
|
var React = __toESM(require("react"));
|
|
63
65
|
var import_react = require("react");
|
|
64
66
|
|
|
67
|
+
// src/core/constants.ts
|
|
68
|
+
var WINDOW_SIZE = 50;
|
|
69
|
+
var PAIR_RARITY_TARGET = 0.01;
|
|
70
|
+
var RELATIVE_OVERLAP_FLOOR = 0.65;
|
|
71
|
+
var LOOP_THRESHOLD = 150;
|
|
72
|
+
var VOLATILITY_THRESHOLD = 25;
|
|
73
|
+
var INSTANCE_SEP = "##";
|
|
74
|
+
|
|
65
75
|
// src/core/math.ts
|
|
66
|
-
var
|
|
76
|
+
var choose = (n, k) => {
|
|
77
|
+
if (k < 0 || k > n) return 0;
|
|
78
|
+
k = Math.min(k, n - k);
|
|
79
|
+
let result = 1;
|
|
80
|
+
for (let i = 0; i < k; i++) {
|
|
81
|
+
result = result * (n - i) / (i + 1);
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
};
|
|
85
|
+
var hypergeomPMF = (k, n1, m1, N) => {
|
|
86
|
+
const total = choose(N, n1);
|
|
87
|
+
if (total === 0) return 0;
|
|
88
|
+
return choose(m1, k) * choose(N - m1, n1 - k) / total;
|
|
89
|
+
};
|
|
90
|
+
var hypergeomUpperTailP = (kStart, n1, m1, N) => {
|
|
91
|
+
const kMax = Math.min(n1, m1);
|
|
92
|
+
let p = 0;
|
|
93
|
+
for (let k = Math.max(0, kStart); k <= kMax; k++) {
|
|
94
|
+
p += hypergeomPMF(k, n1, m1, N);
|
|
95
|
+
}
|
|
96
|
+
return p;
|
|
97
|
+
};
|
|
98
|
+
var minOverlapForRarity = (n1, m1, N, targetP) => {
|
|
99
|
+
const kMax = Math.min(n1, m1);
|
|
100
|
+
for (let k = 0; k <= kMax; k++) {
|
|
101
|
+
if (hypergeomUpperTailP(k, n1, m1, N) <= targetP) return k;
|
|
102
|
+
}
|
|
103
|
+
return kMax + 1;
|
|
104
|
+
};
|
|
105
|
+
var overlapThresholdCache = /* @__PURE__ */ new Map();
|
|
106
|
+
var getMinOverlap = (densityA, densityB, windowSize) => {
|
|
107
|
+
const lo = Math.min(densityA, densityB) | 0;
|
|
108
|
+
const hi = Math.max(densityA, densityB) | 0;
|
|
109
|
+
const key = `${lo}_${hi}_${windowSize}`;
|
|
110
|
+
let cached = overlapThresholdCache.get(key);
|
|
111
|
+
if (cached === void 0) {
|
|
112
|
+
const rare = minOverlapForRarity(lo, hi, windowSize, PAIR_RARITY_TARGET);
|
|
113
|
+
const floor = Math.ceil(RELATIVE_OVERLAP_FLOOR * lo);
|
|
114
|
+
cached = Math.max(rare, floor);
|
|
115
|
+
overlapThresholdCache.set(key, cached);
|
|
116
|
+
}
|
|
117
|
+
return cached;
|
|
118
|
+
};
|
|
119
|
+
var isSignificantOverlap = (overlap, densityA, densityB, windowSize) => {
|
|
120
|
+
if (densityA < 2 || densityB < 2) return false;
|
|
121
|
+
return overlap >= getMinOverlap(densityA, densityB, windowSize);
|
|
122
|
+
};
|
|
123
|
+
var countOverlapsCircular = (bufferA, headA, bufferB, headB) => {
|
|
67
124
|
const L = bufferA.length;
|
|
68
|
-
|
|
69
|
-
const
|
|
125
|
+
const offSync = ((headB - headA) % L + L) % L;
|
|
126
|
+
const offALeadsB = ((headB - headA + 1) % L + L) % L;
|
|
127
|
+
const offBLeadsA = ((headB - headA - 1) % L + L) % L;
|
|
128
|
+
let kSync = 0;
|
|
129
|
+
let kALeadsB = 0;
|
|
130
|
+
let kBLeadsA = 0;
|
|
131
|
+
let densityA = 0;
|
|
132
|
+
let densityB = 0;
|
|
70
133
|
for (let i = 0; i < L; i++) {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
134
|
+
const a = bufferA[i] ? 1 : 0;
|
|
135
|
+
const b = bufferB[i] ? 1 : 0;
|
|
136
|
+
densityA += a;
|
|
137
|
+
densityB += b;
|
|
138
|
+
let iSync = i + offSync;
|
|
139
|
+
if (iSync >= L) iSync -= L;
|
|
140
|
+
let iALeadsB = i + offALeadsB;
|
|
141
|
+
if (iALeadsB >= L) iALeadsB -= L;
|
|
142
|
+
let iBLeadsA = i + offBLeadsA;
|
|
143
|
+
if (iBLeadsA >= L) iBLeadsA -= L;
|
|
144
|
+
if (a && bufferB[iSync]) kSync++;
|
|
145
|
+
if (a && bufferB[iALeadsB]) kALeadsB++;
|
|
146
|
+
if (a && bufferB[iBLeadsA]) kBLeadsA++;
|
|
80
147
|
}
|
|
81
|
-
|
|
82
|
-
return dot / (Math.sqrt(magA) * Math.sqrt(magB));
|
|
148
|
+
return { kSync, kALeadsB, kBLeadsA, densityA, densityB };
|
|
83
149
|
};
|
|
84
|
-
var
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
dot += A[i] * B[i];
|
|
88
|
-
magA += A[i] * A[i];
|
|
89
|
-
magB += B[i] * B[i];
|
|
90
|
-
}
|
|
91
|
-
return magA === 0 || magB === 0 ? 0 : dot / (Math.sqrt(magA) * Math.sqrt(magB));
|
|
150
|
+
var cosineFromOverlap = (overlap, densityA, densityB) => {
|
|
151
|
+
if (densityA <= 0 || densityB <= 0) return 0;
|
|
152
|
+
return overlap / Math.sqrt(densityA * densityB);
|
|
92
153
|
};
|
|
93
154
|
|
|
94
155
|
// src/core/graph.ts
|
|
@@ -125,13 +186,27 @@ var calculateSpectralInfluence = (graph, maxIterations = 20, tolerance = 1e-3) =
|
|
|
125
186
|
}
|
|
126
187
|
return scores;
|
|
127
188
|
};
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
189
|
+
var groupEventSources = (nodes, edges) => {
|
|
190
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
191
|
+
edges.forEach((e) => {
|
|
192
|
+
if (!outgoing.has(e.source)) outgoing.set(e.source, []);
|
|
193
|
+
outgoing.get(e.source).push(e);
|
|
194
|
+
});
|
|
195
|
+
const eventSourceIds = nodes.filter((n) => n.role === "event").map((n) => n.id);
|
|
196
|
+
const signatureOf = (sourceEdges) => sourceEdges.map((e) => `${e.target}@${e.weight}`).sort().join("|");
|
|
197
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
198
|
+
eventSourceIds.forEach((id) => {
|
|
199
|
+
const sig = signatureOf(outgoing.get(id) || []);
|
|
200
|
+
if (!buckets.has(sig)) buckets.set(sig, []);
|
|
201
|
+
buckets.get(sig).push(id);
|
|
202
|
+
});
|
|
203
|
+
const groups = Array.from(buckets.values()).map((sourceIds) => ({
|
|
204
|
+
sourceIds,
|
|
205
|
+
occurrences: sourceIds.length,
|
|
206
|
+
edges: (outgoing.get(sourceIds[0]) || []).slice()
|
|
207
|
+
}));
|
|
208
|
+
return groups.sort((a, b) => b.edges.length - a.edges.length || b.occurrences - a.occurrences);
|
|
209
|
+
};
|
|
135
210
|
|
|
136
211
|
// src/core/label.ts
|
|
137
212
|
var stripInstance = (label) => {
|
|
@@ -144,6 +219,7 @@ var parseLabel = (label) => {
|
|
|
144
219
|
return { file: parts[0] || "Unknown", name: parts[1] || base };
|
|
145
220
|
};
|
|
146
221
|
var isSameField = (labelA, labelB) => stripInstance(labelA) === stripInstance(labelB);
|
|
222
|
+
var isEffectLabel = (name) => /^effect_L\d+(:\d+)?$/.test(name) || name === "anonymous_effect" || name === "anonymous_layout_effect";
|
|
147
223
|
|
|
148
224
|
// src/core/ranker.ts
|
|
149
225
|
var identifyTopIssues = (graph, history2, redundantLabels2, violationMap) => {
|
|
@@ -245,32 +321,22 @@ var LAST_LOG_TIMES = /* @__PURE__ */ new Map();
|
|
|
245
321
|
var LOG_COOLDOWN = 3e3;
|
|
246
322
|
var THEME = {
|
|
247
323
|
identity: "#6C5CE7",
|
|
248
|
-
// Purple (Brand)
|
|
249
324
|
problem: "#D63031",
|
|
250
|
-
// Red (Bugs)
|
|
251
325
|
solution: "#FBC531",
|
|
252
|
-
// Yellow (Fixes)
|
|
253
326
|
context: "#0984E3",
|
|
254
|
-
// Blue (Locations)
|
|
255
327
|
muted: "#9AA0A6",
|
|
256
|
-
// Gray (Metadata)
|
|
257
328
|
border: "#2E2E35",
|
|
258
329
|
success: "#00b894"
|
|
259
|
-
// Green (Good Score)
|
|
260
330
|
};
|
|
261
331
|
var STYLES = {
|
|
262
|
-
// Structure
|
|
263
332
|
basis: `background: ${THEME.identity}; color: white; font-weight: bold; padding: 2px 6px; border-radius: 3px;`,
|
|
264
333
|
headerIdentity: `background: ${THEME.identity}; color: white; font-weight: bold; padding: 4px 8px; border-radius: 4px;`,
|
|
265
334
|
headerProblem: `background: ${THEME.problem}; color: white; font-weight: bold; padding: 4px 8px; border-radius: 4px;`,
|
|
266
335
|
version: `background: #a29bfe; color: #2d3436; padding: 2px 6px; border-radius: 3px; margin-left: -4px;`,
|
|
267
|
-
// Actions
|
|
268
336
|
actionLabel: `color: ${THEME.solution}; font-weight: bold;`,
|
|
269
337
|
actionPill: `color: ${THEME.solution}; font-weight: bold; border: 1px solid ${THEME.solution}; padding: 0 4px; border-radius: 3px;`,
|
|
270
|
-
// Context
|
|
271
338
|
impactLabel: `color: ${THEME.context}; font-weight: bold;`,
|
|
272
339
|
location: `color: ${THEME.context}; font-family: monospace; font-weight: bold;`,
|
|
273
|
-
// Text
|
|
274
340
|
subText: `color: ${THEME.muted}; font-size: 11px;`,
|
|
275
341
|
bold: "font-weight: bold;",
|
|
276
342
|
label: "background: #dfe6e9; color: #2d3436; padding: 0 4px; border-radius: 3px; font-family: monospace; font-weight: bold; border: 1px solid #b2bec3;"
|
|
@@ -285,6 +351,15 @@ var shouldLog = (key) => {
|
|
|
285
351
|
return false;
|
|
286
352
|
};
|
|
287
353
|
var isBooleanLike = (name) => /^(is|has|can|should|did|will|show|hide)(?=[A-Z_])/.test(name);
|
|
354
|
+
var areSyncSignificant = (metaA, metaB) => {
|
|
355
|
+
const { kSync, densityA, densityB } = countOverlapsCircular(
|
|
356
|
+
metaA.buffer,
|
|
357
|
+
metaA.head,
|
|
358
|
+
metaB.buffer,
|
|
359
|
+
metaB.head
|
|
360
|
+
);
|
|
361
|
+
return isSignificantOverlap(kSync, densityA, densityB, metaA.buffer.length);
|
|
362
|
+
};
|
|
288
363
|
var getSuggestedFix = (issue, info) => {
|
|
289
364
|
if (issue.label.includes("Global Event")) {
|
|
290
365
|
return `These variables update together but live in different hooks/files. Consolidate them into a single %cuseReducer%c or atomic store update.`;
|
|
@@ -314,7 +389,7 @@ var getSuggestedFix = (issue, info) => {
|
|
|
314
389
|
}
|
|
315
390
|
return `Check the dependency chain of ${info.name}.`;
|
|
316
391
|
};
|
|
317
|
-
var displayHealthReport = (history2,
|
|
392
|
+
var displayHealthReport = (history2, violationMap) => {
|
|
318
393
|
if (!isWeb) return;
|
|
319
394
|
const entries = Array.from(history2.entries());
|
|
320
395
|
if (entries.length === 0) return;
|
|
@@ -390,11 +465,10 @@ var displayHealthReport = (history2, threshold, violationMap) => {
|
|
|
390
465
|
processed.add(labelA);
|
|
391
466
|
entries.forEach(([labelB, metaB]) => {
|
|
392
467
|
if (labelA === labelB || processed.has(labelB)) return;
|
|
393
|
-
if (
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
}
|
|
468
|
+
if (!areSyncSignificant(metaA, metaB)) return;
|
|
469
|
+
if (metaA.role === "context" /* CONTEXT */ && metaB.role === "context" /* CONTEXT */) return;
|
|
470
|
+
currentCluster.push(labelB);
|
|
471
|
+
processed.add(labelB);
|
|
398
472
|
});
|
|
399
473
|
if (currentCluster.length > 1) clusters.push(currentCluster);
|
|
400
474
|
else independentCount++;
|
|
@@ -470,7 +544,7 @@ var displayRedundancyAlert = (labelA, metaA, labelB, metaB, sim) => {
|
|
|
470
544
|
const alertType = isContextMirror ? "CONTEXT MIRRORING" : isStoreMirror ? "STORE MIRRORING" : "DUPLICATE STATE";
|
|
471
545
|
console.group(`%c \u264A BASIS | ${alertType} `, STYLES.headerProblem);
|
|
472
546
|
console.log(`%c\u{1F4CD} Location: %c${infoA.file}`, STYLES.bold, STYLES.location);
|
|
473
|
-
console.log(`%cIssue:%c ${infoA.name} and ${infoB.name}
|
|
547
|
+
console.log(`%cIssue:%c ${infoA.name} and ${infoB.name} overlapped on ${(sim * 100).toFixed(0)}% of aligned updates.`, STYLES.bold, "");
|
|
474
548
|
if (isContextMirror || isStoreMirror) {
|
|
475
549
|
const sourceType = isStoreMirror ? "External Store" : "Global Context";
|
|
476
550
|
console.log(
|
|
@@ -539,6 +613,99 @@ var displayCausalHint = (targetLabel, targetMeta, sourceLabel, sourceMeta) => {
|
|
|
539
613
|
}
|
|
540
614
|
console.groupEnd();
|
|
541
615
|
};
|
|
616
|
+
var splitHookLine = (raw) => {
|
|
617
|
+
const m = raw.match(/^(.*):(\d+)$/);
|
|
618
|
+
if (!m) return { hook: raw };
|
|
619
|
+
return { hook: m[1], line: Number(m[2]) };
|
|
620
|
+
};
|
|
621
|
+
var formatHook = (raw) => {
|
|
622
|
+
const { hook } = splitHookLine(raw);
|
|
623
|
+
if (!isEffectLabel(hook)) return hook;
|
|
624
|
+
const lineMatch = hook.match(/L(\d+)$/);
|
|
625
|
+
return lineMatch ? `effect @ L${lineMatch[1]}` : "effect (anonymous)";
|
|
626
|
+
};
|
|
627
|
+
var formatNode = (node, fallbackId = "?") => {
|
|
628
|
+
if (!node) return fallbackId;
|
|
629
|
+
if (node.role === "event") return "Event";
|
|
630
|
+
const hook = formatHook(node.name || node.id);
|
|
631
|
+
if (node.file && hook) return `${node.file} \u2192 ${hook}`;
|
|
632
|
+
return hook || node.id;
|
|
633
|
+
};
|
|
634
|
+
var displayGraphReport = (graph) => {
|
|
635
|
+
if (!isWeb) return;
|
|
636
|
+
if (graph.nodes.length === 0) {
|
|
637
|
+
console.log(
|
|
638
|
+
`%c \u{1F4CA} BASIS | CAUSAL GRAPH %c(no data yet)`,
|
|
639
|
+
STYLES.headerIdentity,
|
|
640
|
+
`color: ${THEME.muted}; font-style: italic;`
|
|
641
|
+
);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
const nodeById = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
645
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
646
|
+
graph.edges.forEach((e) => {
|
|
647
|
+
if (!outgoing.has(e.source)) outgoing.set(e.source, []);
|
|
648
|
+
outgoing.get(e.source).push(e);
|
|
649
|
+
});
|
|
650
|
+
const eventGroups = graph.eventGroups.map((g) => ({
|
|
651
|
+
sourceIds: g.sourceIds,
|
|
652
|
+
sourceNode: nodeById.get(g.sourceIds[0]),
|
|
653
|
+
edges: g.edges,
|
|
654
|
+
occurrences: g.occurrences
|
|
655
|
+
}));
|
|
656
|
+
const groupedSourceIds = new Set(graph.eventGroups.flatMap((g) => g.sourceIds));
|
|
657
|
+
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 }));
|
|
658
|
+
const groups = [...eventGroups, ...nonEventGroups].sort(
|
|
659
|
+
(a, b) => b.edges.length - a.edges.length || b.occurrences - a.occurrences
|
|
660
|
+
);
|
|
661
|
+
console.group(
|
|
662
|
+
`%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}`,
|
|
663
|
+
STYLES.headerIdentity,
|
|
664
|
+
`color: ${THEME.muted}; font-weight: normal; font-style: italic;`
|
|
665
|
+
);
|
|
666
|
+
console.log(
|
|
667
|
+
`%cparent \u2192 child = observed cause \u2192 update. (\xD7N) = times in this window. Event groups with the same fan-out are collapsed.`,
|
|
668
|
+
STYLES.subText
|
|
669
|
+
);
|
|
670
|
+
groups.forEach((group) => {
|
|
671
|
+
const isEvent = group.sourceNode?.role === "event";
|
|
672
|
+
const isCtx = group.sourceNode?.role === "context" /* CONTEXT */;
|
|
673
|
+
const isFx = group.sourceNode?.role === "effect";
|
|
674
|
+
const isUnknown = group.sourceNode?.role === "unknown";
|
|
675
|
+
const icon = isEvent ? "\u26A1" : isCtx ? "\u03A9" : isFx ? "\u21AF" : isUnknown ? "?" : "\u25CF";
|
|
676
|
+
const color = isEvent ? THEME.solution : isCtx ? THEME.context : THEME.identity;
|
|
677
|
+
const fanout = group.edges.length;
|
|
678
|
+
const hits = group.occurrences;
|
|
679
|
+
const hitLabel = hits > 1 ? ` \xB7 \xD7${hits}` : "";
|
|
680
|
+
const title = isEvent ? `Event \xB7 ${fanout} target${fanout === 1 ? "" : "s"}${hitLabel}` : formatNode(group.sourceNode, group.sourceIds[0]);
|
|
681
|
+
console.groupCollapsed(
|
|
682
|
+
`%c${icon} %c${title}`,
|
|
683
|
+
`color: ${color};`,
|
|
684
|
+
"font-family: monospace; font-weight: 600;"
|
|
685
|
+
);
|
|
686
|
+
group.edges.slice().sort((a, b) => b.weight - a.weight).forEach((edge) => {
|
|
687
|
+
const target = nodeById.get(edge.target);
|
|
688
|
+
const label = formatNode(target, edge.target);
|
|
689
|
+
const weight = edge.weight > 1 ? ` (\xD7${edge.weight})` : "";
|
|
690
|
+
if (target?.redundant) {
|
|
691
|
+
console.log(
|
|
692
|
+
`%c ${label}%c${weight} %credundant`,
|
|
693
|
+
`color: ${THEME.muted}; font-family: monospace;`,
|
|
694
|
+
`color: ${THEME.muted}; font-style: italic;`,
|
|
695
|
+
`color: ${THEME.problem}; font-weight: bold;`
|
|
696
|
+
);
|
|
697
|
+
} else {
|
|
698
|
+
console.log(
|
|
699
|
+
`%c ${label}%c${weight}`,
|
|
700
|
+
`color: ${THEME.muted}; font-family: monospace;`,
|
|
701
|
+
`color: ${THEME.muted}; font-style: italic;`
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
});
|
|
705
|
+
console.groupEnd();
|
|
706
|
+
});
|
|
707
|
+
console.groupEnd();
|
|
708
|
+
};
|
|
542
709
|
var displayViolentBreaker = (label, count, threshold) => {
|
|
543
710
|
if (!isWeb) return;
|
|
544
711
|
const { name } = parseLabel(label);
|
|
@@ -555,7 +722,6 @@ var displayBootLog = (windowSize) => {
|
|
|
555
722
|
};
|
|
556
723
|
|
|
557
724
|
// src/core/analysis.ts
|
|
558
|
-
var CAUSAL_MARGIN = 0.05;
|
|
559
725
|
var isEventDriven = (label, graph) => {
|
|
560
726
|
for (const [parent, targets] of graph.entries()) {
|
|
561
727
|
if (parent.startsWith("Event_Tick_") && targets.has(label)) {
|
|
@@ -565,28 +731,33 @@ var isEventDriven = (label, graph) => {
|
|
|
565
731
|
return false;
|
|
566
732
|
};
|
|
567
733
|
var calculateAllSimilarities = (entryA, entryB) => {
|
|
568
|
-
const
|
|
569
|
-
entryA.meta.buffer,
|
|
570
|
-
entryA.meta.head,
|
|
571
|
-
entryB.meta.buffer,
|
|
572
|
-
entryB.meta.head,
|
|
573
|
-
0
|
|
574
|
-
);
|
|
575
|
-
const bA = calculateSimilarityCircular(
|
|
576
|
-
entryA.meta.buffer,
|
|
577
|
-
entryA.meta.head,
|
|
578
|
-
entryB.meta.buffer,
|
|
579
|
-
entryB.meta.head,
|
|
580
|
-
1
|
|
581
|
-
);
|
|
582
|
-
const aB = calculateSimilarityCircular(
|
|
734
|
+
const { kSync, kALeadsB, kBLeadsA, densityA, densityB } = countOverlapsCircular(
|
|
583
735
|
entryA.meta.buffer,
|
|
584
736
|
entryA.meta.head,
|
|
585
737
|
entryB.meta.buffer,
|
|
586
|
-
entryB.meta.head
|
|
587
|
-
-1
|
|
738
|
+
entryB.meta.head
|
|
588
739
|
);
|
|
589
|
-
|
|
740
|
+
const sync = cosineFromOverlap(kSync, densityA, densityB);
|
|
741
|
+
const bA = cosineFromOverlap(kALeadsB, densityA, densityB);
|
|
742
|
+
const aB = cosineFromOverlap(kBLeadsA, densityA, densityB);
|
|
743
|
+
const max = Math.max(sync, bA, aB);
|
|
744
|
+
const windowSize = entryA.meta.buffer.length;
|
|
745
|
+
const significantSync = isSignificantOverlap(kSync, densityA, densityB, windowSize);
|
|
746
|
+
const kLead = Math.max(kALeadsB, kBLeadsA);
|
|
747
|
+
const significantLead = isSignificantOverlap(kLead, densityA, densityB, windowSize) && kLead >= kSync + 1;
|
|
748
|
+
return {
|
|
749
|
+
sync,
|
|
750
|
+
bA,
|
|
751
|
+
aB,
|
|
752
|
+
max,
|
|
753
|
+
kSync,
|
|
754
|
+
kALeadsB,
|
|
755
|
+
kBLeadsA,
|
|
756
|
+
densityA,
|
|
757
|
+
densityB,
|
|
758
|
+
significantSync,
|
|
759
|
+
significantLead
|
|
760
|
+
};
|
|
590
761
|
};
|
|
591
762
|
var shouldSkipComparison = (entryA, entryB, dirtyLabels2) => {
|
|
592
763
|
if (entryA.label === entryB.label) return true;
|
|
@@ -611,52 +782,51 @@ var detectRedundancy = (entryA, entryB, similarities, redundantSet, violationMap
|
|
|
611
782
|
const roleA = entryA.meta.role;
|
|
612
783
|
const roleB = entryB.meta.role;
|
|
613
784
|
if (isGlobalSource(roleA) && isGlobalSource(roleB)) return;
|
|
614
|
-
if (
|
|
785
|
+
if (similarities.densityA < 2 || similarities.densityB < 2) return;
|
|
786
|
+
const score = similarities.sync;
|
|
615
787
|
if (roleA === "local" /* LOCAL */ && isGlobalSource(roleB)) {
|
|
616
788
|
redundantSet.add(entryA.label);
|
|
617
|
-
pushViolation(violationMap, entryB.label, { type: "context_mirror", target: entryA.label, similarity:
|
|
618
|
-
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta,
|
|
789
|
+
pushViolation(violationMap, entryB.label, { type: "context_mirror", target: entryA.label, similarity: score });
|
|
790
|
+
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, score);
|
|
619
791
|
} else if (isGlobalSource(roleA) && roleB === "local" /* LOCAL */) {
|
|
620
792
|
redundantSet.add(entryB.label);
|
|
621
|
-
pushViolation(violationMap, entryA.label, { type: "context_mirror", target: entryB.label, similarity:
|
|
622
|
-
displayRedundancyAlert(entryB.label, entryB.meta, entryA.label, entryA.meta,
|
|
793
|
+
pushViolation(violationMap, entryA.label, { type: "context_mirror", target: entryB.label, similarity: score });
|
|
794
|
+
displayRedundancyAlert(entryB.label, entryB.meta, entryA.label, entryA.meta, score);
|
|
623
795
|
} else if (roleA === "local" /* LOCAL */ && roleB === "local" /* LOCAL */) {
|
|
624
796
|
redundantSet.add(entryA.label);
|
|
625
797
|
redundantSet.add(entryB.label);
|
|
626
|
-
pushViolation(violationMap, entryA.label, { type: "duplicate_state", target: entryB.label, similarity:
|
|
627
|
-
pushViolation(violationMap, entryB.label, { type: "duplicate_state", target: entryA.label, similarity:
|
|
628
|
-
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta,
|
|
798
|
+
pushViolation(violationMap, entryA.label, { type: "duplicate_state", target: entryB.label, similarity: score });
|
|
799
|
+
pushViolation(violationMap, entryB.label, { type: "duplicate_state", target: entryA.label, similarity: score });
|
|
800
|
+
displayRedundancyAlert(entryA.label, entryA.meta, entryB.label, entryB.meta, score);
|
|
629
801
|
}
|
|
630
802
|
};
|
|
631
803
|
var detectCausalLeak = (entryA, entryB, similarities, violationMap, graph) => {
|
|
632
804
|
if (entryA.isVolatile || entryB.isVolatile) return;
|
|
633
|
-
if (similarities.max - similarities.sync < CAUSAL_MARGIN) return;
|
|
634
805
|
const addLeak = (source, target) => {
|
|
635
806
|
if (isEventDriven(target, graph)) return;
|
|
636
|
-
|
|
637
|
-
violationMap.set(source, []);
|
|
638
|
-
}
|
|
639
|
-
violationMap.get(source).push({ type: "causal_leak", target });
|
|
807
|
+
pushViolation(violationMap, source, { type: "causal_leak", target });
|
|
640
808
|
const sourceEntry = source === entryA.label ? entryA : entryB;
|
|
641
809
|
const targetEntry = source === entryA.label ? entryB : entryA;
|
|
642
810
|
displayCausalHint(target, targetEntry.meta, source, sourceEntry.meta);
|
|
643
811
|
};
|
|
644
|
-
if (similarities.
|
|
812
|
+
if (similarities.kALeadsB >= similarities.kBLeadsA) {
|
|
645
813
|
addLeak(entryA.label, entryB.label);
|
|
646
|
-
} else
|
|
814
|
+
} else {
|
|
647
815
|
addLeak(entryB.label, entryA.label);
|
|
648
816
|
}
|
|
649
817
|
};
|
|
650
818
|
var detectSubspaceOverlap = (dirtyEntries, allEntries, redundantSet, dirtyLabels2, graph) => {
|
|
651
|
-
let compCount = 0;
|
|
652
819
|
const violationMap = /* @__PURE__ */ new Map();
|
|
820
|
+
let compCount = 0;
|
|
653
821
|
for (const entryA of dirtyEntries) {
|
|
654
822
|
for (const entryB of allEntries) {
|
|
655
823
|
if (shouldSkipComparison(entryA, entryB, dirtyLabels2)) continue;
|
|
656
824
|
compCount++;
|
|
657
825
|
const similarities = calculateAllSimilarities(entryA, entryB);
|
|
658
|
-
if (similarities.
|
|
826
|
+
if (similarities.significantSync) {
|
|
659
827
|
detectRedundancy(entryA, entryB, similarities, redundantSet, violationMap);
|
|
828
|
+
}
|
|
829
|
+
if (similarities.significantLead) {
|
|
660
830
|
detectCausalLeak(entryA, entryB, similarities, violationMap, graph);
|
|
661
831
|
}
|
|
662
832
|
}
|
|
@@ -915,9 +1085,9 @@ var beginEffectTracking = (l) => {
|
|
|
915
1085
|
var endEffectTracking = () => {
|
|
916
1086
|
instance.currentEffectSource = null;
|
|
917
1087
|
};
|
|
918
|
-
var printBasisHealthReport = (
|
|
1088
|
+
var printBasisHealthReport = () => {
|
|
919
1089
|
if (!instance.config.debug) return;
|
|
920
|
-
displayHealthReport(instance.history,
|
|
1090
|
+
displayHealthReport(instance.history, instance.violationMap);
|
|
921
1091
|
};
|
|
922
1092
|
var getBasisMetrics = () => ({
|
|
923
1093
|
engine: "v0.6.x",
|
|
@@ -925,9 +1095,53 @@ var getBasisMetrics = () => ({
|
|
|
925
1095
|
analysis_ms: instance.metrics.lastAnalysisTimeMs.toFixed(3),
|
|
926
1096
|
entropy: instance.metrics.systemEntropy.toFixed(3)
|
|
927
1097
|
});
|
|
1098
|
+
var getBasisGraph = () => {
|
|
1099
|
+
const nodeIds = /* @__PURE__ */ new Set();
|
|
1100
|
+
const edges = [];
|
|
1101
|
+
instance.graph.forEach((targets, source) => {
|
|
1102
|
+
nodeIds.add(source);
|
|
1103
|
+
targets.forEach((weight, target) => {
|
|
1104
|
+
nodeIds.add(target);
|
|
1105
|
+
edges.push({ source, target, weight });
|
|
1106
|
+
});
|
|
1107
|
+
});
|
|
1108
|
+
const nodes = Array.from(nodeIds).map((id) => {
|
|
1109
|
+
if (id.startsWith("Event_Tick_")) {
|
|
1110
|
+
return { id, name: "Event", file: "(shared trigger)", role: "event", density: null, redundant: false };
|
|
1111
|
+
}
|
|
1112
|
+
const meta = instance.history.get(id);
|
|
1113
|
+
const { file, name } = parseLabel(id);
|
|
1114
|
+
if (!meta) {
|
|
1115
|
+
const role = isEffectLabel(name) ? "effect" : "unknown";
|
|
1116
|
+
return { id, name, file, role, density: null, redundant: false };
|
|
1117
|
+
}
|
|
1118
|
+
return {
|
|
1119
|
+
id,
|
|
1120
|
+
name,
|
|
1121
|
+
file,
|
|
1122
|
+
role: meta.role,
|
|
1123
|
+
density: meta.density,
|
|
1124
|
+
redundant: instance.redundantLabels.has(id)
|
|
1125
|
+
};
|
|
1126
|
+
});
|
|
1127
|
+
return {
|
|
1128
|
+
generatedAt: Date.now(),
|
|
1129
|
+
bufferWindowSize: WINDOW_SIZE,
|
|
1130
|
+
eventTtlMs: EVENT_TTL,
|
|
1131
|
+
nodes,
|
|
1132
|
+
edges,
|
|
1133
|
+
eventGroups: groupEventSources(nodes, edges)
|
|
1134
|
+
};
|
|
1135
|
+
};
|
|
1136
|
+
var printBasisGraph = () => {
|
|
1137
|
+
if (!instance.config.debug) return;
|
|
1138
|
+
displayGraphReport(getBasisGraph());
|
|
1139
|
+
};
|
|
928
1140
|
if (typeof window !== "undefined") {
|
|
929
1141
|
window.printBasisReport = printBasisHealthReport;
|
|
930
1142
|
window.getBasisMetrics = getBasisMetrics;
|
|
1143
|
+
window.getBasisGraph = getBasisGraph;
|
|
1144
|
+
window.printBasisGraph = printBasisGraph;
|
|
931
1145
|
}
|
|
932
1146
|
|
|
933
1147
|
// src/hooks.ts
|
|
@@ -1330,7 +1544,9 @@ function basis() {
|
|
|
1330
1544
|
basis,
|
|
1331
1545
|
configureBasis,
|
|
1332
1546
|
createContext,
|
|
1547
|
+
getBasisGraph,
|
|
1333
1548
|
getBasisMetrics,
|
|
1549
|
+
printBasisGraph,
|
|
1334
1550
|
printBasisHealthReport,
|
|
1335
1551
|
use,
|
|
1336
1552
|
useActionState,
|