@kernel.chat/kbot 3.12.0 → 3.13.0
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/agents/replit.d.ts +3 -0
- package/dist/agents/replit.d.ts.map +1 -0
- package/dist/agents/replit.js +68 -0
- package/dist/agents/replit.js.map +1 -0
- package/dist/integrations/openclaw/plugin.d.ts +22 -0
- package/dist/integrations/openclaw/plugin.d.ts.map +1 -0
- package/dist/integrations/openclaw/plugin.js +132 -0
- package/dist/integrations/openclaw/plugin.js.map +1 -0
- package/dist/interference.d.ts +208 -0
- package/dist/interference.d.ts.map +1 -0
- package/dist/interference.js +845 -0
- package/dist/interference.js.map +1 -0
- package/dist/matrix.d.ts.map +1 -1
- package/dist/matrix.js +7 -0
- package/dist/matrix.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
// kbot Cognitive Module Interference Tracker
|
|
2
|
+
//
|
|
3
|
+
// When multiple cognitive modules fire simultaneously, their signals can
|
|
4
|
+
// interfere — constructively (amplifying each other) or destructively
|
|
5
|
+
// (contradicting each other). This module tracks those interference events,
|
|
6
|
+
// maps known tensions and synergies, and provides aggregate statistics
|
|
7
|
+
// for the daemon to surface in diagnostic reports.
|
|
8
|
+
//
|
|
9
|
+
// Why this matters:
|
|
10
|
+
// - free-energy wants to converge on a low-surprise model, but
|
|
11
|
+
// quality-diversity wants to explore novel solutions. Which wins?
|
|
12
|
+
// - prompt-evolution rewrites prompts for better scores, but
|
|
13
|
+
// memory-synthesis preserves historical context. Evolving into forgetting.
|
|
14
|
+
// - reflection critiques past failures (backward-looking), but
|
|
15
|
+
// tree-planner searches forward for the best next action.
|
|
16
|
+
//
|
|
17
|
+
// By tracking which module "wins" and whether the outcome was good,
|
|
18
|
+
// we can learn which interferences to lean into and which to dampen.
|
|
19
|
+
//
|
|
20
|
+
// Storage: ~/.kbot/memory/interference.json (max 1000 events)
|
|
21
|
+
// All heuristic — no LLM calls.
|
|
22
|
+
import { homedir } from 'node:os';
|
|
23
|
+
import { join } from 'node:path';
|
|
24
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
25
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
26
|
+
// Constants & Paths
|
|
27
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
28
|
+
const MEMORY_DIR = join(homedir(), '.kbot', 'memory');
|
|
29
|
+
const INTERFERENCE_FILE = join(MEMORY_DIR, 'interference.json');
|
|
30
|
+
const MAX_EVENTS = 1000;
|
|
31
|
+
/** All cognitive module identifiers for validation */
|
|
32
|
+
export const ALL_MODULES = [
|
|
33
|
+
'free-energy',
|
|
34
|
+
'predictive-processing',
|
|
35
|
+
'strange-loops',
|
|
36
|
+
'integrated-information',
|
|
37
|
+
'autopoiesis',
|
|
38
|
+
'quality-diversity',
|
|
39
|
+
'skill-rating',
|
|
40
|
+
'tree-planner',
|
|
41
|
+
'prompt-evolution',
|
|
42
|
+
'memory-synthesis',
|
|
43
|
+
'reflection',
|
|
44
|
+
];
|
|
45
|
+
/** Threshold below which a signal is considered "near zero" */
|
|
46
|
+
const NEAR_ZERO_THRESHOLD = 0.05;
|
|
47
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
48
|
+
// Known Tensions — module pairs that structurally conflict
|
|
49
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
50
|
+
export const KNOWN_TENSIONS = [
|
|
51
|
+
{
|
|
52
|
+
moduleA: 'free-energy',
|
|
53
|
+
moduleB: 'quality-diversity',
|
|
54
|
+
description: 'convergence vs exploration',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
moduleA: 'prompt-evolution',
|
|
58
|
+
moduleB: 'memory-synthesis',
|
|
59
|
+
description: 'evolving into forgetting',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
moduleA: 'reflection',
|
|
63
|
+
moduleB: 'tree-planner',
|
|
64
|
+
description: 'backward critique vs forward search',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
moduleA: 'skill-rating',
|
|
68
|
+
moduleB: 'free-energy',
|
|
69
|
+
description: 'infrastructure becoming intelligence',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
moduleA: 'skill-rating',
|
|
73
|
+
moduleB: 'predictive-processing',
|
|
74
|
+
description: 'infrastructure becoming intelligence',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
moduleA: 'skill-rating',
|
|
78
|
+
moduleB: 'strange-loops',
|
|
79
|
+
description: 'infrastructure becoming intelligence',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
moduleA: 'skill-rating',
|
|
83
|
+
moduleB: 'integrated-information',
|
|
84
|
+
description: 'infrastructure becoming intelligence',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
moduleA: 'skill-rating',
|
|
88
|
+
moduleB: 'autopoiesis',
|
|
89
|
+
description: 'infrastructure becoming intelligence',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
moduleA: 'skill-rating',
|
|
93
|
+
moduleB: 'quality-diversity',
|
|
94
|
+
description: 'infrastructure becoming intelligence',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
moduleA: 'skill-rating',
|
|
98
|
+
moduleB: 'tree-planner',
|
|
99
|
+
description: 'infrastructure becoming intelligence',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
moduleA: 'skill-rating',
|
|
103
|
+
moduleB: 'prompt-evolution',
|
|
104
|
+
description: 'infrastructure becoming intelligence',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
moduleA: 'skill-rating',
|
|
108
|
+
moduleB: 'memory-synthesis',
|
|
109
|
+
description: 'infrastructure becoming intelligence',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
moduleA: 'skill-rating',
|
|
113
|
+
moduleB: 'reflection',
|
|
114
|
+
description: 'infrastructure becoming intelligence',
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
118
|
+
// Known Synergies — module pairs that amplify each other
|
|
119
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
120
|
+
export const KNOWN_SYNERGIES = [
|
|
121
|
+
{
|
|
122
|
+
moduleA: 'memory-synthesis',
|
|
123
|
+
moduleB: 'reflection',
|
|
124
|
+
description: 'reflections feed synthesis',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
moduleA: 'predictive-processing',
|
|
128
|
+
moduleB: 'skill-rating',
|
|
129
|
+
description: 'anticipation improves routing',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
moduleA: 'strange-loops',
|
|
133
|
+
moduleB: 'autopoiesis',
|
|
134
|
+
description: 'self-reference enables self-maintenance',
|
|
135
|
+
},
|
|
136
|
+
];
|
|
137
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
138
|
+
// Storage — load / save / ensure
|
|
139
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
140
|
+
function ensureDir() {
|
|
141
|
+
if (!existsSync(MEMORY_DIR))
|
|
142
|
+
mkdirSync(MEMORY_DIR, { recursive: true });
|
|
143
|
+
}
|
|
144
|
+
function loadState() {
|
|
145
|
+
ensureDir();
|
|
146
|
+
if (!existsSync(INTERFERENCE_FILE)) {
|
|
147
|
+
return { events: [], lastUpdated: new Date().toISOString() };
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const raw = readFileSync(INTERFERENCE_FILE, 'utf-8');
|
|
151
|
+
const parsed = JSON.parse(raw);
|
|
152
|
+
// Validate shape
|
|
153
|
+
if (!Array.isArray(parsed.events)) {
|
|
154
|
+
return { events: [], lastUpdated: new Date().toISOString() };
|
|
155
|
+
}
|
|
156
|
+
return parsed;
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return { events: [], lastUpdated: new Date().toISOString() };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function saveState(state) {
|
|
163
|
+
ensureDir();
|
|
164
|
+
// Trim to max events — keep the most recent
|
|
165
|
+
if (state.events.length > MAX_EVENTS) {
|
|
166
|
+
state.events = state.events.slice(-MAX_EVENTS);
|
|
167
|
+
}
|
|
168
|
+
state.lastUpdated = new Date().toISOString();
|
|
169
|
+
writeFileSync(INTERFERENCE_FILE, JSON.stringify(state, null, 2));
|
|
170
|
+
}
|
|
171
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
172
|
+
// Classification Helper
|
|
173
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
174
|
+
/**
|
|
175
|
+
* Classify how two module signals interfere based on their output values.
|
|
176
|
+
*
|
|
177
|
+
* - Same sign (both positive or both negative) = CONSTRUCTIVE
|
|
178
|
+
* (both modules agree on direction)
|
|
179
|
+
* - Opposite signs = DESTRUCTIVE
|
|
180
|
+
* (modules disagree — one says go, the other says stop)
|
|
181
|
+
* - Either signal near zero = NEUTRAL
|
|
182
|
+
* (one module is inactive or indifferent)
|
|
183
|
+
*
|
|
184
|
+
* @param signalA - Numeric output from module A (positive = activate, negative = inhibit)
|
|
185
|
+
* @param signalB - Numeric output from module B
|
|
186
|
+
* @returns The interference type
|
|
187
|
+
*/
|
|
188
|
+
export function classifyInterference(signalA, signalB) {
|
|
189
|
+
const absA = Math.abs(signalA);
|
|
190
|
+
const absB = Math.abs(signalB);
|
|
191
|
+
// If either signal is near zero, the module is essentially silent
|
|
192
|
+
if (absA < NEAR_ZERO_THRESHOLD || absB < NEAR_ZERO_THRESHOLD) {
|
|
193
|
+
return 'NEUTRAL';
|
|
194
|
+
}
|
|
195
|
+
// Same sign = both modules agree on direction
|
|
196
|
+
if ((signalA > 0 && signalB > 0) || (signalA < 0 && signalB < 0)) {
|
|
197
|
+
return 'CONSTRUCTIVE';
|
|
198
|
+
}
|
|
199
|
+
// Opposite signs = disagreement
|
|
200
|
+
return 'DESTRUCTIVE';
|
|
201
|
+
}
|
|
202
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
203
|
+
// Event Recording
|
|
204
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
205
|
+
/**
|
|
206
|
+
* Generate a unique event ID.
|
|
207
|
+
*/
|
|
208
|
+
function generateEventId() {
|
|
209
|
+
return `intf_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Validate that a string is a valid CognitiveModule.
|
|
213
|
+
*/
|
|
214
|
+
export function isValidModule(id) {
|
|
215
|
+
return ALL_MODULES.includes(id);
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Record an interference event between two cognitive modules.
|
|
219
|
+
*
|
|
220
|
+
* @param moduleA - First module in the interference
|
|
221
|
+
* @param moduleB - Second module (order doesn't matter for stats)
|
|
222
|
+
* @param type - CONSTRUCTIVE, DESTRUCTIVE, or NEUTRAL
|
|
223
|
+
* @param context - Free-form description of what triggered this
|
|
224
|
+
* @param resolution - Which module was ultimately followed
|
|
225
|
+
* @param outcome - Whether following that module led to success
|
|
226
|
+
* @returns The recorded event
|
|
227
|
+
*/
|
|
228
|
+
export function recordInterference(moduleA, moduleB, type, context, resolution, outcome = 'pending') {
|
|
229
|
+
const event = {
|
|
230
|
+
id: generateEventId(),
|
|
231
|
+
timestamp: new Date().toISOString(),
|
|
232
|
+
moduleA,
|
|
233
|
+
moduleB,
|
|
234
|
+
type,
|
|
235
|
+
context: context.slice(0, 500), // Cap context length
|
|
236
|
+
resolution,
|
|
237
|
+
outcome,
|
|
238
|
+
};
|
|
239
|
+
const state = loadState();
|
|
240
|
+
state.events.push(event);
|
|
241
|
+
saveState(state);
|
|
242
|
+
return event;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Record an interference by classifying signals automatically.
|
|
246
|
+
* Convenience wrapper around classifyInterference + recordInterference.
|
|
247
|
+
*/
|
|
248
|
+
export function recordSignalInterference(moduleA, signalA, moduleB, signalB, context, resolution, outcome = 'pending') {
|
|
249
|
+
const type = classifyInterference(signalA, signalB);
|
|
250
|
+
return recordInterference(moduleA, moduleB, type, context, resolution, outcome);
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Update the outcome of a pending interference event.
|
|
254
|
+
* Called after the resolution's result is known.
|
|
255
|
+
*
|
|
256
|
+
* @param eventId - The event to update
|
|
257
|
+
* @param outcome - The actual outcome
|
|
258
|
+
* @returns true if the event was found and updated, false otherwise
|
|
259
|
+
*/
|
|
260
|
+
export function resolveInterference(eventId, outcome) {
|
|
261
|
+
const state = loadState();
|
|
262
|
+
const event = state.events.find(e => e.id === eventId);
|
|
263
|
+
if (!event)
|
|
264
|
+
return false;
|
|
265
|
+
event.outcome = outcome;
|
|
266
|
+
saveState(state);
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
270
|
+
// Known Interaction Lookups
|
|
271
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
272
|
+
/**
|
|
273
|
+
* Normalize a module pair to a canonical order for consistent lookups.
|
|
274
|
+
* Alphabetical by module name.
|
|
275
|
+
*/
|
|
276
|
+
function normalizePair(a, b) {
|
|
277
|
+
return a <= b ? [a, b] : [b, a];
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Check if two modules have a known tension.
|
|
281
|
+
* Returns the tension description or null.
|
|
282
|
+
*/
|
|
283
|
+
export function getKnownTension(moduleA, moduleB) {
|
|
284
|
+
for (const tension of KNOWN_TENSIONS) {
|
|
285
|
+
if ((tension.moduleA === moduleA && tension.moduleB === moduleB) ||
|
|
286
|
+
(tension.moduleA === moduleB && tension.moduleB === moduleA)) {
|
|
287
|
+
return tension.description;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Check if two modules have a known synergy.
|
|
294
|
+
* Returns the synergy description or null.
|
|
295
|
+
*/
|
|
296
|
+
export function getKnownSynergy(moduleA, moduleB) {
|
|
297
|
+
for (const synergy of KNOWN_SYNERGIES) {
|
|
298
|
+
if ((synergy.moduleA === moduleA && synergy.moduleB === moduleB) ||
|
|
299
|
+
(synergy.moduleA === moduleB && synergy.moduleB === moduleA)) {
|
|
300
|
+
return synergy.description;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Get all known tensions for a specific module.
|
|
307
|
+
*/
|
|
308
|
+
export function getTensionsFor(module) {
|
|
309
|
+
return KNOWN_TENSIONS.filter(t => t.moduleA === module || t.moduleB === module);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get all known synergies for a specific module.
|
|
313
|
+
*/
|
|
314
|
+
export function getSynergiesFor(module) {
|
|
315
|
+
return KNOWN_SYNERGIES.filter(s => s.moduleA === module || s.moduleB === module);
|
|
316
|
+
}
|
|
317
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
318
|
+
// Aggregate Statistics
|
|
319
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
320
|
+
/**
|
|
321
|
+
* Get the conflict rate (destructive / total) for a specific module pair.
|
|
322
|
+
* Returns 0 if no events exist for the pair.
|
|
323
|
+
*/
|
|
324
|
+
export function getConflictRate(moduleA, moduleB) {
|
|
325
|
+
const state = loadState();
|
|
326
|
+
const pairEvents = state.events.filter(e => (e.moduleA === moduleA && e.moduleB === moduleB) ||
|
|
327
|
+
(e.moduleA === moduleB && e.moduleB === moduleA));
|
|
328
|
+
if (pairEvents.length === 0)
|
|
329
|
+
return 0;
|
|
330
|
+
const destructive = pairEvents.filter(e => e.type === 'DESTRUCTIVE').length;
|
|
331
|
+
return destructive / pairEvents.length;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Compute detailed statistics for a specific module pair.
|
|
335
|
+
*/
|
|
336
|
+
function computePairStats(moduleA, moduleB, events) {
|
|
337
|
+
const pairEvents = events.filter(e => (e.moduleA === moduleA && e.moduleB === moduleB) ||
|
|
338
|
+
(e.moduleA === moduleB && e.moduleB === moduleA));
|
|
339
|
+
const total = pairEvents.length;
|
|
340
|
+
const constructive = pairEvents.filter(e => e.type === 'CONSTRUCTIVE').length;
|
|
341
|
+
const destructive = pairEvents.filter(e => e.type === 'DESTRUCTIVE').length;
|
|
342
|
+
const neutral = pairEvents.filter(e => e.type === 'NEUTRAL').length;
|
|
343
|
+
// Resolution stats
|
|
344
|
+
const aWinEvents = pairEvents.filter(e => e.resolution === moduleA);
|
|
345
|
+
const bWinEvents = pairEvents.filter(e => e.resolution === moduleB);
|
|
346
|
+
const aWins = aWinEvents.length;
|
|
347
|
+
const bWins = bWinEvents.length;
|
|
348
|
+
// Success rates (only count resolved events, not pending)
|
|
349
|
+
const aResolved = aWinEvents.filter(e => e.outcome !== 'pending');
|
|
350
|
+
const bResolved = bWinEvents.filter(e => e.outcome !== 'pending');
|
|
351
|
+
const aSuccesses = aResolved.filter(e => e.outcome === 'success').length;
|
|
352
|
+
const bSuccesses = bResolved.filter(e => e.outcome === 'success').length;
|
|
353
|
+
const aWinSuccessRate = aResolved.length > 0 ? aSuccesses / aResolved.length : 0;
|
|
354
|
+
const bWinSuccessRate = bResolved.length > 0 ? bSuccesses / bResolved.length : 0;
|
|
355
|
+
const conflictRate = total > 0 ? destructive / total : 0;
|
|
356
|
+
return {
|
|
357
|
+
moduleA,
|
|
358
|
+
moduleB,
|
|
359
|
+
total,
|
|
360
|
+
constructive,
|
|
361
|
+
destructive,
|
|
362
|
+
neutral,
|
|
363
|
+
aWins,
|
|
364
|
+
bWins,
|
|
365
|
+
aWinSuccessRate,
|
|
366
|
+
bWinSuccessRate,
|
|
367
|
+
conflictRate,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Get interference statistics for all module pairs that have events.
|
|
372
|
+
* Returns an array of PairStats, sorted by total events descending.
|
|
373
|
+
*/
|
|
374
|
+
export function getInterferenceStats() {
|
|
375
|
+
const state = loadState();
|
|
376
|
+
if (state.events.length === 0)
|
|
377
|
+
return [];
|
|
378
|
+
// Discover all unique pairs that appear in events
|
|
379
|
+
const pairSet = new Set();
|
|
380
|
+
for (const event of state.events) {
|
|
381
|
+
const [a, b] = normalizePair(event.moduleA, event.moduleB);
|
|
382
|
+
pairSet.add(`${a}|${b}`);
|
|
383
|
+
}
|
|
384
|
+
const stats = [];
|
|
385
|
+
for (const pairKey of pairSet) {
|
|
386
|
+
const [a, b] = pairKey.split('|');
|
|
387
|
+
stats.push(computePairStats(a, b, state.events));
|
|
388
|
+
}
|
|
389
|
+
// Sort by total events descending
|
|
390
|
+
stats.sort((x, y) => y.total - x.total);
|
|
391
|
+
return stats;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Get statistics for a single module — how often it is involved
|
|
395
|
+
* in interference, and its win/success rates.
|
|
396
|
+
*/
|
|
397
|
+
export function getModuleStats(module) {
|
|
398
|
+
const state = loadState();
|
|
399
|
+
const involved = state.events.filter(e => e.moduleA === module || e.moduleB === module);
|
|
400
|
+
if (involved.length === 0) {
|
|
401
|
+
return {
|
|
402
|
+
totalEvents: 0,
|
|
403
|
+
asModuleA: 0,
|
|
404
|
+
asModuleB: 0,
|
|
405
|
+
timesWon: 0,
|
|
406
|
+
winSuccessRate: 0,
|
|
407
|
+
topPartner: null,
|
|
408
|
+
dominantType: 'NEUTRAL',
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
const asModuleA = involved.filter(e => e.moduleA === module).length;
|
|
412
|
+
const asModuleB = involved.filter(e => e.moduleB === module).length;
|
|
413
|
+
const wonEvents = involved.filter(e => e.resolution === module);
|
|
414
|
+
const timesWon = wonEvents.length;
|
|
415
|
+
const resolvedWins = wonEvents.filter(e => e.outcome !== 'pending');
|
|
416
|
+
const winSuccesses = resolvedWins.filter(e => e.outcome === 'success').length;
|
|
417
|
+
const winSuccessRate = resolvedWins.length > 0 ? winSuccesses / resolvedWins.length : 0;
|
|
418
|
+
// Find top partner (the module it interferes with most)
|
|
419
|
+
const partnerCounts = new Map();
|
|
420
|
+
for (const e of involved) {
|
|
421
|
+
const partner = e.moduleA === module ? e.moduleB : e.moduleA;
|
|
422
|
+
partnerCounts.set(partner, (partnerCounts.get(partner) || 0) + 1);
|
|
423
|
+
}
|
|
424
|
+
let topPartner = null;
|
|
425
|
+
let maxCount = 0;
|
|
426
|
+
for (const [partner, count] of partnerCounts) {
|
|
427
|
+
if (count > maxCount) {
|
|
428
|
+
topPartner = partner;
|
|
429
|
+
maxCount = count;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// Dominant type
|
|
433
|
+
const constructive = involved.filter(e => e.type === 'CONSTRUCTIVE').length;
|
|
434
|
+
const destructive = involved.filter(e => e.type === 'DESTRUCTIVE').length;
|
|
435
|
+
const neutral = involved.filter(e => e.type === 'NEUTRAL').length;
|
|
436
|
+
let dominantType = 'NEUTRAL';
|
|
437
|
+
if (constructive >= destructive && constructive >= neutral)
|
|
438
|
+
dominantType = 'CONSTRUCTIVE';
|
|
439
|
+
else if (destructive >= constructive && destructive >= neutral)
|
|
440
|
+
dominantType = 'DESTRUCTIVE';
|
|
441
|
+
return {
|
|
442
|
+
totalEvents: involved.length,
|
|
443
|
+
asModuleA,
|
|
444
|
+
asModuleB,
|
|
445
|
+
timesWon,
|
|
446
|
+
winSuccessRate,
|
|
447
|
+
topPartner,
|
|
448
|
+
dominantType,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
452
|
+
// Reporting
|
|
453
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
454
|
+
/**
|
|
455
|
+
* Generate a formatted interference report string for the daemon.
|
|
456
|
+
* Includes:
|
|
457
|
+
* - Total event count and time range
|
|
458
|
+
* - Top conflicting pairs
|
|
459
|
+
* - Top synergistic pairs
|
|
460
|
+
* - Known tensions with observed conflict rates
|
|
461
|
+
* - Recommendations based on resolution success rates
|
|
462
|
+
*/
|
|
463
|
+
export function getInterferenceReport() {
|
|
464
|
+
const state = loadState();
|
|
465
|
+
const events = state.events;
|
|
466
|
+
const lines = [];
|
|
467
|
+
lines.push('═══ Cognitive Module Interference Report ═══');
|
|
468
|
+
lines.push('');
|
|
469
|
+
if (events.length === 0) {
|
|
470
|
+
lines.push('No interference events recorded yet.');
|
|
471
|
+
lines.push('Events are recorded when multiple cognitive modules fire simultaneously');
|
|
472
|
+
lines.push('with conflicting or reinforcing signals.');
|
|
473
|
+
return lines.join('\n');
|
|
474
|
+
}
|
|
475
|
+
// ── Summary ──
|
|
476
|
+
const oldest = events[0].timestamp.split('T')[0];
|
|
477
|
+
const newest = events[events.length - 1].timestamp.split('T')[0];
|
|
478
|
+
const constructiveCount = events.filter(e => e.type === 'CONSTRUCTIVE').length;
|
|
479
|
+
const destructiveCount = events.filter(e => e.type === 'DESTRUCTIVE').length;
|
|
480
|
+
const neutralCount = events.filter(e => e.type === 'NEUTRAL').length;
|
|
481
|
+
const resolvedCount = events.filter(e => e.outcome !== 'pending').length;
|
|
482
|
+
const successCount = events.filter(e => e.outcome === 'success').length;
|
|
483
|
+
const failureCount = events.filter(e => e.outcome === 'failure').length;
|
|
484
|
+
lines.push(`Total events: ${events.length} (${oldest} to ${newest})`);
|
|
485
|
+
lines.push(` Constructive: ${constructiveCount} (${pct(constructiveCount, events.length)})`);
|
|
486
|
+
lines.push(` Destructive: ${destructiveCount} (${pct(destructiveCount, events.length)})`);
|
|
487
|
+
lines.push(` Neutral: ${neutralCount} (${pct(neutralCount, events.length)})`);
|
|
488
|
+
lines.push(` Resolved: ${resolvedCount} — ${successCount} success, ${failureCount} failure`);
|
|
489
|
+
lines.push('');
|
|
490
|
+
// ── Top Conflicting Pairs ──
|
|
491
|
+
const stats = getInterferenceStats();
|
|
492
|
+
const conflicting = stats
|
|
493
|
+
.filter(s => s.destructive > 0)
|
|
494
|
+
.sort((a, b) => b.conflictRate - a.conflictRate)
|
|
495
|
+
.slice(0, 5);
|
|
496
|
+
if (conflicting.length > 0) {
|
|
497
|
+
lines.push('── Top Conflicting Pairs ──');
|
|
498
|
+
for (const pair of conflicting) {
|
|
499
|
+
const tension = getKnownTension(pair.moduleA, pair.moduleB);
|
|
500
|
+
const tensionLabel = tension ? ` [${tension}]` : '';
|
|
501
|
+
lines.push(` ${pair.moduleA} vs ${pair.moduleB}: ` +
|
|
502
|
+
`${pct(pair.destructive, pair.total)} conflict rate ` +
|
|
503
|
+
`(${pair.destructive}/${pair.total})${tensionLabel}`);
|
|
504
|
+
}
|
|
505
|
+
lines.push('');
|
|
506
|
+
}
|
|
507
|
+
// ── Top Synergistic Pairs ──
|
|
508
|
+
const synergistic = stats
|
|
509
|
+
.filter(s => s.constructive > 0)
|
|
510
|
+
.sort((a, b) => (b.constructive / b.total) - (a.constructive / a.total))
|
|
511
|
+
.slice(0, 5);
|
|
512
|
+
if (synergistic.length > 0) {
|
|
513
|
+
lines.push('── Top Synergistic Pairs ──');
|
|
514
|
+
for (const pair of synergistic) {
|
|
515
|
+
const synergy = getKnownSynergy(pair.moduleA, pair.moduleB);
|
|
516
|
+
const synergyLabel = synergy ? ` [${synergy}]` : '';
|
|
517
|
+
const synergyRate = pair.constructive / pair.total;
|
|
518
|
+
lines.push(` ${pair.moduleA} + ${pair.moduleB}: ` +
|
|
519
|
+
`${pct(pair.constructive, pair.total)} synergy rate ` +
|
|
520
|
+
`(${pair.constructive}/${pair.total})${synergyLabel}`);
|
|
521
|
+
}
|
|
522
|
+
lines.push('');
|
|
523
|
+
}
|
|
524
|
+
// ── Known Tensions vs Observed ──
|
|
525
|
+
lines.push('── Known Tensions (Observed) ──');
|
|
526
|
+
for (const tension of KNOWN_TENSIONS) {
|
|
527
|
+
const rate = getConflictRate(tension.moduleA, tension.moduleB);
|
|
528
|
+
const pairEvts = events.filter(e => (e.moduleA === tension.moduleA && e.moduleB === tension.moduleB) ||
|
|
529
|
+
(e.moduleA === tension.moduleB && e.moduleB === tension.moduleA));
|
|
530
|
+
if (pairEvts.length > 0) {
|
|
531
|
+
lines.push(` ${tension.moduleA} vs ${tension.moduleB}: ` +
|
|
532
|
+
`"${tension.description}" — ${(rate * 100).toFixed(0)}% conflict (${pairEvts.length} events)`);
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
lines.push(` ${tension.moduleA} vs ${tension.moduleB}: ` +
|
|
536
|
+
`"${tension.description}" — no events yet`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
lines.push('');
|
|
540
|
+
// ── Known Synergies vs Observed ──
|
|
541
|
+
lines.push('── Known Synergies (Observed) ──');
|
|
542
|
+
for (const synergy of KNOWN_SYNERGIES) {
|
|
543
|
+
const pairEvts = events.filter(e => (e.moduleA === synergy.moduleA && e.moduleB === synergy.moduleB) ||
|
|
544
|
+
(e.moduleA === synergy.moduleB && e.moduleB === synergy.moduleA));
|
|
545
|
+
if (pairEvts.length > 0) {
|
|
546
|
+
const constructive = pairEvts.filter(e => e.type === 'CONSTRUCTIVE').length;
|
|
547
|
+
lines.push(` ${synergy.moduleA} + ${synergy.moduleB}: ` +
|
|
548
|
+
`"${synergy.description}" — ${pct(constructive, pairEvts.length)} constructive (${pairEvts.length} events)`);
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
lines.push(` ${synergy.moduleA} + ${synergy.moduleB}: ` +
|
|
552
|
+
`"${synergy.description}" — no events yet`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
lines.push('');
|
|
556
|
+
// ── Recommendations ──
|
|
557
|
+
const recommendations = generateRecommendations(stats, events);
|
|
558
|
+
if (recommendations.length > 0) {
|
|
559
|
+
lines.push('── Recommendations ──');
|
|
560
|
+
for (const rec of recommendations) {
|
|
561
|
+
lines.push(` • ${rec}`);
|
|
562
|
+
}
|
|
563
|
+
lines.push('');
|
|
564
|
+
}
|
|
565
|
+
lines.push(`Last updated: ${state.lastUpdated}`);
|
|
566
|
+
return lines.join('\n');
|
|
567
|
+
}
|
|
568
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
569
|
+
// Recommendation Engine
|
|
570
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
571
|
+
/**
|
|
572
|
+
* Generate actionable recommendations based on observed interference patterns.
|
|
573
|
+
*/
|
|
574
|
+
function generateRecommendations(stats, events) {
|
|
575
|
+
const recs = [];
|
|
576
|
+
// Recommendation 1: High conflict pairs where one module consistently wins
|
|
577
|
+
// and that winning leads to failures — suggest flipping the resolution
|
|
578
|
+
for (const pair of stats) {
|
|
579
|
+
if (pair.total < 5 || pair.conflictRate < 0.5)
|
|
580
|
+
continue;
|
|
581
|
+
// Check if module A dominates but fails
|
|
582
|
+
if (pair.aWins > pair.bWins * 2 && pair.aWinSuccessRate < 0.4 && pair.aWins >= 3) {
|
|
583
|
+
recs.push(`${pair.moduleA} dominates ${pair.moduleB} but has ${(pair.aWinSuccessRate * 100).toFixed(0)}% success rate. ` +
|
|
584
|
+
`Consider deferring to ${pair.moduleB} more often.`);
|
|
585
|
+
}
|
|
586
|
+
// Check the reverse
|
|
587
|
+
if (pair.bWins > pair.aWins * 2 && pair.bWinSuccessRate < 0.4 && pair.bWins >= 3) {
|
|
588
|
+
recs.push(`${pair.moduleB} dominates ${pair.moduleA} but has ${(pair.bWinSuccessRate * 100).toFixed(0)}% success rate. ` +
|
|
589
|
+
`Consider deferring to ${pair.moduleA} more often.`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
// Recommendation 2: Known synergies that are actually conflicting
|
|
593
|
+
for (const synergy of KNOWN_SYNERGIES) {
|
|
594
|
+
const rate = getConflictRate(synergy.moduleA, synergy.moduleB);
|
|
595
|
+
const pairEvts = events.filter(e => (e.moduleA === synergy.moduleA && e.moduleB === synergy.moduleB) ||
|
|
596
|
+
(e.moduleA === synergy.moduleB && e.moduleB === synergy.moduleA));
|
|
597
|
+
if (pairEvts.length >= 3 && rate > 0.5) {
|
|
598
|
+
recs.push(`Expected synergy "${synergy.description}" between ${synergy.moduleA} and ${synergy.moduleB} ` +
|
|
599
|
+
`is actually conflicting (${(rate * 100).toFixed(0)}% conflict rate). Investigate module integration.`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
// Recommendation 3: Known tensions with unexpectedly low conflict
|
|
603
|
+
for (const tension of KNOWN_TENSIONS) {
|
|
604
|
+
const rate = getConflictRate(tension.moduleA, tension.moduleB);
|
|
605
|
+
const pairEvts = events.filter(e => (e.moduleA === tension.moduleA && e.moduleB === tension.moduleB) ||
|
|
606
|
+
(e.moduleA === tension.moduleB && e.moduleB === tension.moduleA));
|
|
607
|
+
if (pairEvts.length >= 5 && rate < 0.2) {
|
|
608
|
+
recs.push(`Expected tension "${tension.description}" between ${tension.moduleA} and ${tension.moduleB} ` +
|
|
609
|
+
`has only ${(rate * 100).toFixed(0)}% conflict rate. The modules may have naturally aligned.`);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
// Recommendation 4: Modules with many pending outcomes — need feedback loop
|
|
613
|
+
const pendingCount = events.filter(e => e.outcome === 'pending').length;
|
|
614
|
+
if (pendingCount > events.length * 0.5 && events.length >= 10) {
|
|
615
|
+
recs.push(`${pendingCount}/${events.length} events (${pct(pendingCount, events.length)}) are still pending. ` +
|
|
616
|
+
`Wire up outcome resolution for better interference learning.`);
|
|
617
|
+
}
|
|
618
|
+
// Recommendation 5: Module that participates in many destructive events
|
|
619
|
+
const moduleCounts = new Map();
|
|
620
|
+
for (const event of events) {
|
|
621
|
+
for (const mod of [event.moduleA, event.moduleB]) {
|
|
622
|
+
const current = moduleCounts.get(mod) || { destructive: 0, total: 0 };
|
|
623
|
+
current.total++;
|
|
624
|
+
if (event.type === 'DESTRUCTIVE')
|
|
625
|
+
current.destructive++;
|
|
626
|
+
moduleCounts.set(mod, current);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
for (const [mod, counts] of moduleCounts) {
|
|
630
|
+
if (counts.total >= 10 && counts.destructive / counts.total > 0.6) {
|
|
631
|
+
recs.push(`${mod} is involved in destructive interference ${(counts.destructive / counts.total * 100).toFixed(0)}% of the time ` +
|
|
632
|
+
`(${counts.destructive}/${counts.total}). Consider dampening its signal or improving its integration.`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return recs;
|
|
636
|
+
}
|
|
637
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
638
|
+
// Query Helpers
|
|
639
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
640
|
+
/**
|
|
641
|
+
* Get all interference events, optionally filtered.
|
|
642
|
+
*/
|
|
643
|
+
export function getEvents(filter) {
|
|
644
|
+
const state = loadState();
|
|
645
|
+
let events = state.events;
|
|
646
|
+
if (filter) {
|
|
647
|
+
if (filter.module) {
|
|
648
|
+
const mod = filter.module;
|
|
649
|
+
events = events.filter(e => e.moduleA === mod || e.moduleB === mod);
|
|
650
|
+
}
|
|
651
|
+
if (filter.type) {
|
|
652
|
+
const t = filter.type;
|
|
653
|
+
events = events.filter(e => e.type === t);
|
|
654
|
+
}
|
|
655
|
+
if (filter.outcome) {
|
|
656
|
+
const o = filter.outcome;
|
|
657
|
+
events = events.filter(e => e.outcome === o);
|
|
658
|
+
}
|
|
659
|
+
if (filter.since) {
|
|
660
|
+
const sinceTime = new Date(filter.since).getTime();
|
|
661
|
+
events = events.filter(e => new Date(e.timestamp).getTime() >= sinceTime);
|
|
662
|
+
}
|
|
663
|
+
if (filter.limit && filter.limit > 0) {
|
|
664
|
+
events = events.slice(-filter.limit);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return events;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Get the most recent interference events (for dashboard display).
|
|
671
|
+
*/
|
|
672
|
+
export function getRecentEvents(count = 10) {
|
|
673
|
+
const state = loadState();
|
|
674
|
+
return state.events.slice(-count);
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Count total recorded interference events.
|
|
678
|
+
*/
|
|
679
|
+
export function getEventCount() {
|
|
680
|
+
const state = loadState();
|
|
681
|
+
return state.events.length;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Clear all interference events. Use with caution — this is destructive.
|
|
685
|
+
* Returns the number of events that were cleared.
|
|
686
|
+
*/
|
|
687
|
+
export function clearEvents() {
|
|
688
|
+
const state = loadState();
|
|
689
|
+
const count = state.events.length;
|
|
690
|
+
state.events = [];
|
|
691
|
+
saveState(state);
|
|
692
|
+
return count;
|
|
693
|
+
}
|
|
694
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
695
|
+
// Predictive Helpers
|
|
696
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
697
|
+
/**
|
|
698
|
+
* Predict the likely interference type for a module pair based on
|
|
699
|
+
* historical data and known interactions.
|
|
700
|
+
*
|
|
701
|
+
* Returns a prediction with confidence, or null if insufficient data.
|
|
702
|
+
*/
|
|
703
|
+
export function predictInterference(moduleA, moduleB) {
|
|
704
|
+
const state = loadState();
|
|
705
|
+
const pairEvents = state.events.filter(e => (e.moduleA === moduleA && e.moduleB === moduleB) ||
|
|
706
|
+
(e.moduleA === moduleB && e.moduleB === moduleA));
|
|
707
|
+
// Check known interactions first
|
|
708
|
+
const tension = getKnownTension(moduleA, moduleB);
|
|
709
|
+
const synergy = getKnownSynergy(moduleA, moduleB);
|
|
710
|
+
// If we have historical data, use it
|
|
711
|
+
if (pairEvents.length >= 3) {
|
|
712
|
+
const constructive = pairEvents.filter(e => e.type === 'CONSTRUCTIVE').length;
|
|
713
|
+
const destructive = pairEvents.filter(e => e.type === 'DESTRUCTIVE').length;
|
|
714
|
+
const neutral = pairEvents.filter(e => e.type === 'NEUTRAL').length;
|
|
715
|
+
const total = pairEvents.length;
|
|
716
|
+
let predicted;
|
|
717
|
+
let maxCount;
|
|
718
|
+
if (constructive >= destructive && constructive >= neutral) {
|
|
719
|
+
predicted = 'CONSTRUCTIVE';
|
|
720
|
+
maxCount = constructive;
|
|
721
|
+
}
|
|
722
|
+
else if (destructive >= constructive && destructive >= neutral) {
|
|
723
|
+
predicted = 'DESTRUCTIVE';
|
|
724
|
+
maxCount = destructive;
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
predicted = 'NEUTRAL';
|
|
728
|
+
maxCount = neutral;
|
|
729
|
+
}
|
|
730
|
+
// Confidence is the proportion of the dominant type, scaled by sample size
|
|
731
|
+
const rawConfidence = maxCount / total;
|
|
732
|
+
// Small sample penalty: sqrt(n/20) capped at 1
|
|
733
|
+
const sampleFactor = Math.min(1, Math.sqrt(total / 20));
|
|
734
|
+
const confidence = rawConfidence * sampleFactor;
|
|
735
|
+
return {
|
|
736
|
+
predicted,
|
|
737
|
+
confidence: Math.round(confidence * 100) / 100,
|
|
738
|
+
basis: `historical (${total} events: ${constructive}C/${destructive}D/${neutral}N)`,
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
// Fall back to known interactions
|
|
742
|
+
if (tension) {
|
|
743
|
+
return {
|
|
744
|
+
predicted: 'DESTRUCTIVE',
|
|
745
|
+
confidence: 0.6,
|
|
746
|
+
basis: `known tension: "${tension}"`,
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
if (synergy) {
|
|
750
|
+
return {
|
|
751
|
+
predicted: 'CONSTRUCTIVE',
|
|
752
|
+
confidence: 0.6,
|
|
753
|
+
basis: `known synergy: "${synergy}"`,
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
// Not enough data
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Suggest which module to follow when two modules conflict,
|
|
761
|
+
* based on historical win/success rates.
|
|
762
|
+
*
|
|
763
|
+
* Returns the recommended module and the basis for the recommendation,
|
|
764
|
+
* or null if insufficient data.
|
|
765
|
+
*/
|
|
766
|
+
export function suggestResolution(moduleA, moduleB) {
|
|
767
|
+
const state = loadState();
|
|
768
|
+
const pairEvents = state.events.filter(e => (e.moduleA === moduleA && e.moduleB === moduleB) ||
|
|
769
|
+
(e.moduleA === moduleB && e.moduleB === moduleA));
|
|
770
|
+
// Need enough resolved events to make a recommendation
|
|
771
|
+
const resolved = pairEvents.filter(e => e.outcome !== 'pending');
|
|
772
|
+
if (resolved.length < 3)
|
|
773
|
+
return null;
|
|
774
|
+
const aWins = resolved.filter(e => e.resolution === moduleA);
|
|
775
|
+
const bWins = resolved.filter(e => e.resolution === moduleB);
|
|
776
|
+
const aSuccesses = aWins.filter(e => e.outcome === 'success').length;
|
|
777
|
+
const bSuccesses = bWins.filter(e => e.outcome === 'success').length;
|
|
778
|
+
const aRate = aWins.length > 0 ? aSuccesses / aWins.length : 0;
|
|
779
|
+
const bRate = bWins.length > 0 ? bSuccesses / bWins.length : 0;
|
|
780
|
+
// If one module has a clearly better success rate
|
|
781
|
+
if (Math.abs(aRate - bRate) > 0.15 && (aWins.length >= 2 || bWins.length >= 2)) {
|
|
782
|
+
if (aRate > bRate) {
|
|
783
|
+
return {
|
|
784
|
+
recommended: moduleA,
|
|
785
|
+
confidence: Math.round(aRate * 100) / 100,
|
|
786
|
+
reason: `${moduleA} has ${(aRate * 100).toFixed(0)}% success rate vs ${(bRate * 100).toFixed(0)}% for ${moduleB} (${resolved.length} resolved events)`,
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
else {
|
|
790
|
+
return {
|
|
791
|
+
recommended: moduleB,
|
|
792
|
+
confidence: Math.round(bRate * 100) / 100,
|
|
793
|
+
reason: `${moduleB} has ${(bRate * 100).toFixed(0)}% success rate vs ${(aRate * 100).toFixed(0)}% for ${moduleA} (${resolved.length} resolved events)`,
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
// Too close to call
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
800
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
801
|
+
// Compact Summary (for system prompt injection)
|
|
802
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
803
|
+
/**
|
|
804
|
+
* Generate a compact interference summary suitable for system prompt injection.
|
|
805
|
+
* Returns empty string if no meaningful patterns exist.
|
|
806
|
+
*/
|
|
807
|
+
export function getInterferenceSummary() {
|
|
808
|
+
const state = loadState();
|
|
809
|
+
if (state.events.length < 5)
|
|
810
|
+
return '';
|
|
811
|
+
const stats = getInterferenceStats();
|
|
812
|
+
if (stats.length === 0)
|
|
813
|
+
return '';
|
|
814
|
+
const lines = [];
|
|
815
|
+
lines.push('[Cognitive Module Interference]');
|
|
816
|
+
// Only include pairs with significant data
|
|
817
|
+
const significant = stats.filter(s => s.total >= 3);
|
|
818
|
+
if (significant.length === 0)
|
|
819
|
+
return '';
|
|
820
|
+
for (const pair of significant.slice(0, 5)) {
|
|
821
|
+
const tension = getKnownTension(pair.moduleA, pair.moduleB);
|
|
822
|
+
const synergy = getKnownSynergy(pair.moduleA, pair.moduleB);
|
|
823
|
+
const label = tension ? `tension: ${tension}` : synergy ? `synergy: ${synergy}` : 'observed';
|
|
824
|
+
// Determine which module to favor
|
|
825
|
+
let favor = '';
|
|
826
|
+
if (pair.aWinSuccessRate > pair.bWinSuccessRate + 0.2) {
|
|
827
|
+
favor = ` → favor ${pair.moduleA}`;
|
|
828
|
+
}
|
|
829
|
+
else if (pair.bWinSuccessRate > pair.aWinSuccessRate + 0.2) {
|
|
830
|
+
favor = ` → favor ${pair.moduleB}`;
|
|
831
|
+
}
|
|
832
|
+
lines.push(`- ${pair.moduleA}/${pair.moduleB}: ${pair.conflictRate > 0.5 ? 'high conflict' : pair.conflictRate > 0.2 ? 'moderate' : 'low conflict'} (${label})${favor}`);
|
|
833
|
+
}
|
|
834
|
+
return lines.join('\n');
|
|
835
|
+
}
|
|
836
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
837
|
+
// Helpers
|
|
838
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
839
|
+
/** Format a fraction as a percentage string */
|
|
840
|
+
function pct(numerator, denominator) {
|
|
841
|
+
if (denominator === 0)
|
|
842
|
+
return '0%';
|
|
843
|
+
return `${((numerator / denominator) * 100).toFixed(0)}%`;
|
|
844
|
+
}
|
|
845
|
+
//# sourceMappingURL=interference.js.map
|