agentic-qe 3.7.21 → 3.8.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/.claude/helpers/brain-checkpoint.cjs +4 -1
- package/.claude/helpers/statusline-v3.cjs +3 -1
- package/.claude/skills/skills-manifest.json +1 -1
- package/CHANGELOG.md +45 -0
- package/README.md +2 -14
- package/assets/helpers/statusline-v3.cjs +3 -1
- package/dist/cli/brain-commands.js +6 -10
- package/dist/cli/bundle.js +7441 -4327
- package/dist/cli/commands/audit.d.ts +43 -0
- package/dist/cli/commands/audit.js +125 -0
- package/dist/cli/commands/hooks.js +29 -6
- package/dist/cli/commands/init.js +1 -73
- package/dist/cli/commands/learning.js +270 -13
- package/dist/cli/commands/ruvector-commands.d.ts +15 -0
- package/dist/cli/commands/ruvector-commands.js +271 -0
- package/dist/cli/handlers/init-handler.d.ts +0 -1
- package/dist/cli/handlers/init-handler.js +0 -6
- package/dist/cli/index.js +4 -2
- package/dist/context/sources/defect-source.js +2 -2
- package/dist/context/sources/memory-source.js +2 -2
- package/dist/context/sources/requirements-source.js +2 -2
- package/dist/coordination/behavior-tree/decorators.d.ts +108 -0
- package/dist/coordination/behavior-tree/decorators.js +251 -0
- package/dist/coordination/behavior-tree/index.d.ts +12 -0
- package/dist/coordination/behavior-tree/index.js +15 -0
- package/dist/coordination/behavior-tree/nodes.d.ts +165 -0
- package/dist/coordination/behavior-tree/nodes.js +338 -0
- package/dist/coordination/behavior-tree/qe-trees.d.ts +105 -0
- package/dist/coordination/behavior-tree/qe-trees.js +181 -0
- package/dist/coordination/coherence-action-gate.d.ts +284 -0
- package/dist/coordination/coherence-action-gate.js +512 -0
- package/dist/coordination/index.d.ts +4 -0
- package/dist/coordination/index.js +8 -0
- package/dist/coordination/reasoning-qec.d.ts +315 -0
- package/dist/coordination/reasoning-qec.js +585 -0
- package/dist/coordination/task-executor.d.ts +16 -0
- package/dist/coordination/task-executor.js +99 -0
- package/dist/coordination/workflow-orchestrator.d.ts +29 -0
- package/dist/coordination/workflow-orchestrator.js +42 -0
- package/dist/domains/visual-accessibility/cnn-visual-regression.d.ts +135 -0
- package/dist/domains/visual-accessibility/cnn-visual-regression.js +327 -0
- package/dist/domains/visual-accessibility/index.d.ts +1 -0
- package/dist/domains/visual-accessibility/index.js +4 -0
- package/dist/governance/coherence-validator.d.ts +112 -0
- package/dist/governance/coherence-validator.js +180 -0
- package/dist/governance/index.d.ts +1 -0
- package/dist/governance/index.js +2 -0
- package/dist/governance/witness-chain.d.ts +311 -0
- package/dist/governance/witness-chain.js +509 -0
- package/dist/init/index.d.ts +0 -2
- package/dist/init/index.js +0 -1
- package/dist/init/init-wizard-steps.d.ts +10 -0
- package/dist/init/init-wizard-steps.js +87 -1
- package/dist/init/init-wizard.d.ts +1 -9
- package/dist/init/init-wizard.js +3 -69
- package/dist/init/orchestrator.js +0 -1
- package/dist/init/phases/01-detection.js +0 -27
- package/dist/init/phases/07-hooks.js +6 -4
- package/dist/init/phases/phase-interface.d.ts +0 -1
- package/dist/init/settings-merge.js +1 -1
- package/dist/integrations/browser/qe-dashboard/clustering.d.ts +48 -0
- package/dist/integrations/browser/qe-dashboard/clustering.js +183 -0
- package/dist/integrations/browser/qe-dashboard/index.d.ts +12 -0
- package/dist/integrations/browser/qe-dashboard/index.js +15 -0
- package/dist/integrations/browser/qe-dashboard/pattern-explorer.d.ts +165 -0
- package/dist/integrations/browser/qe-dashboard/pattern-explorer.js +260 -0
- package/dist/integrations/browser/qe-dashboard/wasm-vector-store.d.ts +144 -0
- package/dist/integrations/browser/qe-dashboard/wasm-vector-store.js +277 -0
- package/dist/integrations/ruvector/cognitive-container-codec.d.ts +51 -0
- package/dist/integrations/ruvector/cognitive-container-codec.js +180 -0
- package/dist/integrations/ruvector/cognitive-container.d.ts +125 -0
- package/dist/integrations/ruvector/cognitive-container.js +306 -0
- package/dist/integrations/ruvector/coherence-gate.d.ts +309 -0
- package/dist/integrations/ruvector/coherence-gate.js +631 -0
- package/dist/integrations/ruvector/compressed-hnsw-integration.d.ts +176 -0
- package/dist/integrations/ruvector/compressed-hnsw-integration.js +301 -0
- package/dist/integrations/ruvector/dither-adapter.d.ts +122 -0
- package/dist/integrations/ruvector/dither-adapter.js +295 -0
- package/dist/integrations/ruvector/domain-transfer.d.ts +129 -0
- package/dist/integrations/ruvector/domain-transfer.js +220 -0
- package/dist/integrations/ruvector/feature-flags.d.ts +214 -2
- package/dist/integrations/ruvector/feature-flags.js +167 -2
- package/dist/integrations/ruvector/filter-adapter.d.ts +71 -0
- package/dist/integrations/ruvector/filter-adapter.js +285 -0
- package/dist/integrations/ruvector/gnn-wrapper.d.ts +20 -0
- package/dist/integrations/ruvector/gnn-wrapper.js +40 -0
- package/dist/integrations/ruvector/hnsw-health-monitor.d.ts +237 -0
- package/dist/integrations/ruvector/hnsw-health-monitor.js +394 -0
- package/dist/integrations/ruvector/index.d.ts +8 -2
- package/dist/integrations/ruvector/index.js +18 -2
- package/dist/integrations/ruvector/interfaces.d.ts +40 -0
- package/dist/integrations/ruvector/sona-persistence.d.ts +54 -0
- package/dist/integrations/ruvector/sona-persistence.js +162 -0
- package/dist/integrations/ruvector/sona-three-loop.d.ts +392 -0
- package/dist/integrations/ruvector/sona-three-loop.js +814 -0
- package/dist/integrations/ruvector/sona-wrapper.d.ts +97 -0
- package/dist/integrations/ruvector/sona-wrapper.js +147 -3
- package/dist/integrations/ruvector/spectral-math.d.ts +101 -0
- package/dist/integrations/ruvector/spectral-math.js +254 -0
- package/dist/integrations/ruvector/temporal-compression.d.ts +163 -0
- package/dist/integrations/ruvector/temporal-compression.js +318 -0
- package/dist/integrations/ruvector/thompson-sampler.d.ts +61 -0
- package/dist/integrations/ruvector/thompson-sampler.js +118 -0
- package/dist/integrations/ruvector/transfer-coherence-stub.d.ts +80 -0
- package/dist/integrations/ruvector/transfer-coherence-stub.js +63 -0
- package/dist/integrations/ruvector/transfer-verification.d.ts +119 -0
- package/dist/integrations/ruvector/transfer-verification.js +115 -0
- package/dist/kernel/hnsw-adapter.d.ts +52 -1
- package/dist/kernel/hnsw-adapter.js +139 -4
- package/dist/kernel/hnsw-index-provider.d.ts +5 -0
- package/dist/kernel/native-hnsw-backend.d.ts +110 -0
- package/dist/kernel/native-hnsw-backend.js +408 -0
- package/dist/kernel/unified-memory.js +5 -6
- package/dist/learning/aqe-learning-engine.d.ts +2 -0
- package/dist/learning/aqe-learning-engine.js +65 -0
- package/dist/learning/experience-capture-middleware.js +20 -0
- package/dist/learning/experience-capture.d.ts +10 -0
- package/dist/learning/experience-capture.js +34 -0
- package/dist/learning/index.d.ts +2 -2
- package/dist/learning/index.js +4 -4
- package/dist/learning/metrics-tracker.d.ts +11 -0
- package/dist/learning/metrics-tracker.js +29 -13
- package/dist/learning/pattern-lifecycle.d.ts +30 -1
- package/dist/learning/pattern-lifecycle.js +92 -20
- package/dist/learning/pattern-store.d.ts +8 -0
- package/dist/learning/pattern-store.js +8 -2
- package/dist/learning/qe-unified-memory.js +1 -28
- package/dist/learning/regret-tracker.d.ts +201 -0
- package/dist/learning/regret-tracker.js +361 -0
- package/dist/mcp/bundle.js +5915 -474
- package/dist/routing/index.d.ts +4 -2
- package/dist/routing/index.js +3 -1
- package/dist/routing/neural-tiny-dancer-router.d.ts +268 -0
- package/dist/routing/neural-tiny-dancer-router.js +514 -0
- package/dist/routing/queen-integration.js +5 -5
- package/dist/routing/routing-config.d.ts +6 -0
- package/dist/routing/routing-config.js +1 -0
- package/dist/routing/simple-neural-router.d.ts +76 -0
- package/dist/routing/simple-neural-router.js +202 -0
- package/dist/routing/tiny-dancer-router.d.ts +20 -1
- package/dist/routing/tiny-dancer-router.js +21 -2
- package/dist/test-scheduling/dag-attention-scheduler.d.ts +81 -0
- package/dist/test-scheduling/dag-attention-scheduler.js +358 -0
- package/dist/test-scheduling/dag-attention-types.d.ts +81 -0
- package/dist/test-scheduling/dag-attention-types.js +10 -0
- package/dist/test-scheduling/index.d.ts +1 -0
- package/dist/test-scheduling/index.js +4 -0
- package/dist/test-scheduling/pipeline.d.ts +8 -0
- package/dist/test-scheduling/pipeline.js +28 -0
- package/package.json +6 -2
- package/dist/cli/commands/migrate.d.ts +0 -9
- package/dist/cli/commands/migrate.js +0 -566
- package/dist/init/init-wizard-migration.d.ts +0 -52
- package/dist/init/init-wizard-migration.js +0 -345
- package/dist/init/migration/config-migrator.d.ts +0 -31
- package/dist/init/migration/config-migrator.js +0 -149
- package/dist/init/migration/data-migrator.d.ts +0 -72
- package/dist/init/migration/data-migrator.js +0 -232
- package/dist/init/migration/detector.d.ts +0 -44
- package/dist/init/migration/detector.js +0 -105
- package/dist/init/migration/index.d.ts +0 -8
- package/dist/init/migration/index.js +0 -8
- package/dist/learning/v2-to-v3-migration.d.ts +0 -86
- package/dist/learning/v2-to-v3-migration.js +0 -529
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @module integrations/ruvector/sona-wrapper
|
|
9
9
|
*/
|
|
10
10
|
import type { RLState, RLAction, DomainName } from '../rl-suite/interfaces.js';
|
|
11
|
+
import { SONAThreeLoopEngine, type ThreeLoopConfig, type AdaptationResult, type ConsolidationResult, type PeerState, type EWCMetrics } from './sona-three-loop.js';
|
|
11
12
|
/**
|
|
12
13
|
* Pattern types supported by QE SONA (extends @ruvector/sona with QE specifics)
|
|
13
14
|
*/
|
|
@@ -136,6 +137,9 @@ export declare class QESONA {
|
|
|
136
137
|
private adaptationTimes;
|
|
137
138
|
private cacheHits;
|
|
138
139
|
private totalAdaptations;
|
|
140
|
+
private threeLoopEngine;
|
|
141
|
+
private fisherPersistFn;
|
|
142
|
+
private fisherDomain;
|
|
139
143
|
constructor(config?: Partial<QESONAConfig>);
|
|
140
144
|
/**
|
|
141
145
|
* Adapt pattern based on context - leverages @ruvector/sona's fast pattern matching
|
|
@@ -259,6 +263,98 @@ export declare class QESONA {
|
|
|
259
263
|
* Get maximum adaptation time
|
|
260
264
|
*/
|
|
261
265
|
private maxAdaptationTime;
|
|
266
|
+
/**
|
|
267
|
+
* Initialize the three-loop coordination engine.
|
|
268
|
+
*
|
|
269
|
+
* The three-loop engine adds:
|
|
270
|
+
* - Instant MicroLoRA adaptation per request (<100us)
|
|
271
|
+
* - Background EWC++ consolidation to prevent forgetting
|
|
272
|
+
* - Cross-agent state synchronization
|
|
273
|
+
*
|
|
274
|
+
* When available, delegates MicroLoRA and background learning to the
|
|
275
|
+
* native @ruvector/sona engine for true rank-1 LoRA operations.
|
|
276
|
+
*
|
|
277
|
+
* This is an additive enhancement; all existing QESONA methods continue
|
|
278
|
+
* to work the same way with or without the three-loop engine.
|
|
279
|
+
*
|
|
280
|
+
* @param config - Optional three-loop configuration overrides
|
|
281
|
+
*/
|
|
282
|
+
initThreeLoopEngine(config?: Partial<ThreeLoopConfig>): void;
|
|
283
|
+
/**
|
|
284
|
+
* Get the three-loop engine instance.
|
|
285
|
+
* Returns null if not initialized via initThreeLoopEngine().
|
|
286
|
+
*/
|
|
287
|
+
getThreeLoopEngine(): SONAThreeLoopEngine | null;
|
|
288
|
+
/**
|
|
289
|
+
* Perform instant MicroLoRA adaptation for a request.
|
|
290
|
+
* Delegates to the three-loop engine's instant loop.
|
|
291
|
+
*
|
|
292
|
+
* @param requestFeatures - Feature vector for the current request
|
|
293
|
+
* @returns Adaptation result, or null if three-loop engine not initialized
|
|
294
|
+
*/
|
|
295
|
+
instantAdapt(requestFeatures: number[]): AdaptationResult | null;
|
|
296
|
+
/**
|
|
297
|
+
* Record the outcome of a request for REINFORCE-style gradient estimation.
|
|
298
|
+
* Delegates to the three-loop engine's recordOutcome().
|
|
299
|
+
*
|
|
300
|
+
* Must be called after instantAdapt() with the reward signal.
|
|
301
|
+
*
|
|
302
|
+
* @param reward - Scalar reward (e.g., 1.0 for success, -1.0 for failure, 0.0 for neutral)
|
|
303
|
+
* @param requestIndex - Optional requestIndex from AdaptationResult for matching
|
|
304
|
+
*/
|
|
305
|
+
recordOutcome(reward: number, requestIndex?: number): void;
|
|
306
|
+
/**
|
|
307
|
+
* Run background consolidation cycle.
|
|
308
|
+
* Delegates to the three-loop engine's background loop.
|
|
309
|
+
*
|
|
310
|
+
* **Important**: This method performs synchronous SQLite writes (Fisher matrix
|
|
311
|
+
* persistence via better-sqlite3) when the persistence callback is set.
|
|
312
|
+
* Call from a background timer or idle callback, NOT from the request hot path.
|
|
313
|
+
*
|
|
314
|
+
* @returns Consolidation result, or null if three-loop engine not initialized
|
|
315
|
+
*/
|
|
316
|
+
backgroundConsolidate(): ConsolidationResult | null;
|
|
317
|
+
/**
|
|
318
|
+
* Set the Fisher matrix persistence callback and domain.
|
|
319
|
+
*
|
|
320
|
+
* When set, `backgroundConsolidate()` automatically persists Fisher state
|
|
321
|
+
* to SQLite after each consolidation or task boundary detection.
|
|
322
|
+
*
|
|
323
|
+
* @param persistFn - Callback that writes Fisher data to SQLite
|
|
324
|
+
* (typically PersistentSONAEngine.saveFisherMatrix.bind(engine))
|
|
325
|
+
* @param domain - Domain identifier for the Fisher state
|
|
326
|
+
*/
|
|
327
|
+
setFisherPersistence(persistFn: (domain: string, fisher: Float32Array, optimal: Float32Array, base: Float32Array, meta: {
|
|
328
|
+
taskBoundaries: number;
|
|
329
|
+
consolidationCycles: number;
|
|
330
|
+
requestCount: number;
|
|
331
|
+
ewcLambda: number;
|
|
332
|
+
}) => void, domain: string): void;
|
|
333
|
+
/**
|
|
334
|
+
* Synchronize with peer agents.
|
|
335
|
+
* Delegates to the three-loop engine's coordination loop.
|
|
336
|
+
*
|
|
337
|
+
* @param peerStates - Peer states to synchronize with
|
|
338
|
+
*/
|
|
339
|
+
syncWithPeers(peerStates: PeerState[]): void;
|
|
340
|
+
/**
|
|
341
|
+
* Get EWC++ metrics for monitoring.
|
|
342
|
+
*
|
|
343
|
+
* @returns EWC metrics, or null if three-loop engine not initialized
|
|
344
|
+
*/
|
|
345
|
+
getEWCMetrics(): EWCMetrics | null;
|
|
346
|
+
/**
|
|
347
|
+
* Check if background consolidation is due.
|
|
348
|
+
*/
|
|
349
|
+
shouldConsolidate(): boolean;
|
|
350
|
+
/**
|
|
351
|
+
* Get local peer state for sharing with other agents.
|
|
352
|
+
*/
|
|
353
|
+
getLocalPeerState(peerId: string, domain: string): PeerState | null;
|
|
354
|
+
/**
|
|
355
|
+
* Check if the three-loop engine is initialized.
|
|
356
|
+
*/
|
|
357
|
+
isThreeLoopEnabled(): boolean;
|
|
262
358
|
/**
|
|
263
359
|
* Verify performance target
|
|
264
360
|
*/
|
|
@@ -273,6 +369,7 @@ export declare class QESONA {
|
|
|
273
369
|
}>;
|
|
274
370
|
}>;
|
|
275
371
|
}
|
|
372
|
+
export type { AdaptationResult, ConsolidationResult, PeerState, EWCMetrics, ThreeLoopConfig, } from './sona-three-loop.js';
|
|
276
373
|
/**
|
|
277
374
|
* Create a QE SONA instance with default configuration
|
|
278
375
|
*/
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { randomUUID } from 'crypto';
|
|
11
11
|
import { SonaEngine } from '@ruvector/sona';
|
|
12
12
|
import { secureRandom } from '../../shared/utils/crypto-random.js';
|
|
13
|
+
import { SONAThreeLoopEngine, } from './sona-three-loop.js';
|
|
13
14
|
// ============================================================================
|
|
14
15
|
// Default Configuration
|
|
15
16
|
// ============================================================================
|
|
@@ -137,6 +138,9 @@ export class QESONA {
|
|
|
137
138
|
adaptationTimes = [];
|
|
138
139
|
cacheHits = 0;
|
|
139
140
|
totalAdaptations = 0;
|
|
141
|
+
threeLoopEngine = null;
|
|
142
|
+
fisherPersistFn = null;
|
|
143
|
+
fisherDomain = 'default';
|
|
140
144
|
constructor(config = {}) {
|
|
141
145
|
this.config = { ...DEFAULT_QE_SONA_CONFIG, ...config };
|
|
142
146
|
// Initialize @ruvector/sona engine with config
|
|
@@ -577,6 +581,149 @@ export class QESONA {
|
|
|
577
581
|
return 0;
|
|
578
582
|
return Math.max(...this.adaptationTimes);
|
|
579
583
|
}
|
|
584
|
+
// ========================================================================
|
|
585
|
+
// Three-Loop Engine (MicroLoRA + EWC++ + Coordination)
|
|
586
|
+
// ========================================================================
|
|
587
|
+
/**
|
|
588
|
+
* Initialize the three-loop coordination engine.
|
|
589
|
+
*
|
|
590
|
+
* The three-loop engine adds:
|
|
591
|
+
* - Instant MicroLoRA adaptation per request (<100us)
|
|
592
|
+
* - Background EWC++ consolidation to prevent forgetting
|
|
593
|
+
* - Cross-agent state synchronization
|
|
594
|
+
*
|
|
595
|
+
* When available, delegates MicroLoRA and background learning to the
|
|
596
|
+
* native @ruvector/sona engine for true rank-1 LoRA operations.
|
|
597
|
+
*
|
|
598
|
+
* This is an additive enhancement; all existing QESONA methods continue
|
|
599
|
+
* to work the same way with or without the three-loop engine.
|
|
600
|
+
*
|
|
601
|
+
* @param config - Optional three-loop configuration overrides
|
|
602
|
+
*/
|
|
603
|
+
initThreeLoopEngine(config) {
|
|
604
|
+
this.threeLoopEngine = new SONAThreeLoopEngine({
|
|
605
|
+
dimension: this.config.embeddingDim ?? 384,
|
|
606
|
+
microLoraLr: this.config.microLoraLr ?? 0.001,
|
|
607
|
+
ewcLambda: this.config.ewcLambda ?? 1000.0,
|
|
608
|
+
...config,
|
|
609
|
+
}, this.engine);
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Get the three-loop engine instance.
|
|
613
|
+
* Returns null if not initialized via initThreeLoopEngine().
|
|
614
|
+
*/
|
|
615
|
+
getThreeLoopEngine() {
|
|
616
|
+
return this.threeLoopEngine;
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Perform instant MicroLoRA adaptation for a request.
|
|
620
|
+
* Delegates to the three-loop engine's instant loop.
|
|
621
|
+
*
|
|
622
|
+
* @param requestFeatures - Feature vector for the current request
|
|
623
|
+
* @returns Adaptation result, or null if three-loop engine not initialized
|
|
624
|
+
*/
|
|
625
|
+
instantAdapt(requestFeatures) {
|
|
626
|
+
if (!this.threeLoopEngine)
|
|
627
|
+
return null;
|
|
628
|
+
return this.threeLoopEngine.instantAdapt(requestFeatures);
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Record the outcome of a request for REINFORCE-style gradient estimation.
|
|
632
|
+
* Delegates to the three-loop engine's recordOutcome().
|
|
633
|
+
*
|
|
634
|
+
* Must be called after instantAdapt() with the reward signal.
|
|
635
|
+
*
|
|
636
|
+
* @param reward - Scalar reward (e.g., 1.0 for success, -1.0 for failure, 0.0 for neutral)
|
|
637
|
+
* @param requestIndex - Optional requestIndex from AdaptationResult for matching
|
|
638
|
+
*/
|
|
639
|
+
recordOutcome(reward, requestIndex) {
|
|
640
|
+
if (!this.threeLoopEngine)
|
|
641
|
+
return;
|
|
642
|
+
this.threeLoopEngine.recordOutcome(reward, requestIndex);
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Run background consolidation cycle.
|
|
646
|
+
* Delegates to the three-loop engine's background loop.
|
|
647
|
+
*
|
|
648
|
+
* **Important**: This method performs synchronous SQLite writes (Fisher matrix
|
|
649
|
+
* persistence via better-sqlite3) when the persistence callback is set.
|
|
650
|
+
* Call from a background timer or idle callback, NOT from the request hot path.
|
|
651
|
+
*
|
|
652
|
+
* @returns Consolidation result, or null if three-loop engine not initialized
|
|
653
|
+
*/
|
|
654
|
+
backgroundConsolidate() {
|
|
655
|
+
if (!this.threeLoopEngine)
|
|
656
|
+
return null;
|
|
657
|
+
const result = this.threeLoopEngine.backgroundConsolidate();
|
|
658
|
+
// Persist Fisher matrix after consolidation when a persistence callback is set
|
|
659
|
+
if (result && (result.consolidated || result.taskBoundaryDetected) && this.fisherPersistFn) {
|
|
660
|
+
try {
|
|
661
|
+
this.threeLoopEngine.persistFisher(this.fisherPersistFn, this.fisherDomain);
|
|
662
|
+
}
|
|
663
|
+
catch (err) {
|
|
664
|
+
// Persistence failures must not break the consolidation path
|
|
665
|
+
console.warn('[QESONA] Fisher persistence failed:', err instanceof Error ? err.message : err);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return result;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Set the Fisher matrix persistence callback and domain.
|
|
672
|
+
*
|
|
673
|
+
* When set, `backgroundConsolidate()` automatically persists Fisher state
|
|
674
|
+
* to SQLite after each consolidation or task boundary detection.
|
|
675
|
+
*
|
|
676
|
+
* @param persistFn - Callback that writes Fisher data to SQLite
|
|
677
|
+
* (typically PersistentSONAEngine.saveFisherMatrix.bind(engine))
|
|
678
|
+
* @param domain - Domain identifier for the Fisher state
|
|
679
|
+
*/
|
|
680
|
+
setFisherPersistence(persistFn, domain) {
|
|
681
|
+
this.fisherPersistFn = persistFn;
|
|
682
|
+
this.fisherDomain = domain;
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Synchronize with peer agents.
|
|
686
|
+
* Delegates to the three-loop engine's coordination loop.
|
|
687
|
+
*
|
|
688
|
+
* @param peerStates - Peer states to synchronize with
|
|
689
|
+
*/
|
|
690
|
+
syncWithPeers(peerStates) {
|
|
691
|
+
if (!this.threeLoopEngine)
|
|
692
|
+
return;
|
|
693
|
+
this.threeLoopEngine.syncWithPeers(peerStates);
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Get EWC++ metrics for monitoring.
|
|
697
|
+
*
|
|
698
|
+
* @returns EWC metrics, or null if three-loop engine not initialized
|
|
699
|
+
*/
|
|
700
|
+
getEWCMetrics() {
|
|
701
|
+
if (!this.threeLoopEngine)
|
|
702
|
+
return null;
|
|
703
|
+
return this.threeLoopEngine.getEWCMetrics();
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Check if background consolidation is due.
|
|
707
|
+
*/
|
|
708
|
+
shouldConsolidate() {
|
|
709
|
+
if (!this.threeLoopEngine)
|
|
710
|
+
return false;
|
|
711
|
+
return this.threeLoopEngine.shouldConsolidate();
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Get local peer state for sharing with other agents.
|
|
715
|
+
*/
|
|
716
|
+
getLocalPeerState(peerId, domain) {
|
|
717
|
+
if (!this.threeLoopEngine)
|
|
718
|
+
return null;
|
|
719
|
+
return this.threeLoopEngine.getLocalPeerState(peerId, domain);
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Check if the three-loop engine is initialized.
|
|
723
|
+
*/
|
|
724
|
+
isThreeLoopEnabled() {
|
|
725
|
+
return this.threeLoopEngine !== null;
|
|
726
|
+
}
|
|
580
727
|
/**
|
|
581
728
|
* Verify performance target
|
|
582
729
|
*/
|
|
@@ -608,9 +755,6 @@ export class QESONA {
|
|
|
608
755
|
};
|
|
609
756
|
}
|
|
610
757
|
}
|
|
611
|
-
// ============================================================================
|
|
612
|
-
// Factory Functions
|
|
613
|
-
// ============================================================================
|
|
614
758
|
/**
|
|
615
759
|
* Create a QE SONA instance with default configuration
|
|
616
760
|
*/
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spectral Graph Theory Utilities
|
|
3
|
+
*
|
|
4
|
+
* Mathematical primitives for spectral analysis of graph structures.
|
|
5
|
+
* Provides Laplacian construction, power-iteration eigenvalue approximation,
|
|
6
|
+
* effective resistance estimation, and coherence scoring.
|
|
7
|
+
*
|
|
8
|
+
* These utilities are used by the HNSW Health Monitor to assess the
|
|
9
|
+
* structural health of HNSW index graphs without requiring native
|
|
10
|
+
* ruvector-coherence.
|
|
11
|
+
*
|
|
12
|
+
* @module integrations/ruvector/spectral-math
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Build the graph Laplacian from an adjacency list.
|
|
16
|
+
* L = D - A where D is the degree matrix and A is the adjacency matrix.
|
|
17
|
+
*
|
|
18
|
+
* Returns the Laplacian as a dense matrix (suitable for small graphs).
|
|
19
|
+
* For large graphs, we use the adjacency list directly with power iteration.
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildLaplacian(adjacency: number[][], n: number): Float64Array[];
|
|
22
|
+
/**
|
|
23
|
+
* Multiply Laplacian (in adjacency list form) by a vector.
|
|
24
|
+
* L * v = D*v - A*v
|
|
25
|
+
* This avoids materializing the full Laplacian matrix.
|
|
26
|
+
*/
|
|
27
|
+
export declare function laplacianMultiply(adjacency: number[][], v: Float64Array): Float64Array;
|
|
28
|
+
/**
|
|
29
|
+
* Compute the L2 norm of a vector.
|
|
30
|
+
*/
|
|
31
|
+
export declare function vectorNorm(v: Float64Array): number;
|
|
32
|
+
/**
|
|
33
|
+
* Normalize a vector in-place.
|
|
34
|
+
*/
|
|
35
|
+
export declare function normalizeInPlace(v: Float64Array): void;
|
|
36
|
+
/**
|
|
37
|
+
* Remove the component of v along the direction of u (projection).
|
|
38
|
+
* v = v - (v . u) * u
|
|
39
|
+
* Assumes u is normalized.
|
|
40
|
+
*/
|
|
41
|
+
export declare function deflateVector(v: Float64Array, u: Float64Array): void;
|
|
42
|
+
/**
|
|
43
|
+
* Approximate the Fiedler value (second smallest eigenvalue of L)
|
|
44
|
+
* using power iteration on L with deflation of the trivial eigenvector.
|
|
45
|
+
*
|
|
46
|
+
* The trivial eigenvector of L is the all-ones vector (eigenvalue = 0).
|
|
47
|
+
* We deflate it out and use power iteration to find the smallest
|
|
48
|
+
* non-trivial eigenvalue.
|
|
49
|
+
*
|
|
50
|
+
* Since power iteration finds the LARGEST eigenvalue, we use the shift-
|
|
51
|
+
* invert approach: we run power iteration on (lambda_max * I - L) to
|
|
52
|
+
* find the second smallest eigenvalue of L.
|
|
53
|
+
*
|
|
54
|
+
* @param adjacency - Adjacency list
|
|
55
|
+
* @param n - Number of nodes
|
|
56
|
+
* @param maxIter - Maximum iterations
|
|
57
|
+
* @param tol - Convergence tolerance
|
|
58
|
+
* @returns Approximate Fiedler value
|
|
59
|
+
*/
|
|
60
|
+
export declare function approximateFiedlerValue(adjacency: number[][], n: number, maxIter?: number, tol?: number): number;
|
|
61
|
+
/**
|
|
62
|
+
* Estimate the spectral gap: difference between the second smallest
|
|
63
|
+
* and smallest eigenvalues of the Laplacian.
|
|
64
|
+
*
|
|
65
|
+
* For a connected graph, the smallest eigenvalue is 0, so the spectral
|
|
66
|
+
* gap equals the Fiedler value.
|
|
67
|
+
*/
|
|
68
|
+
export declare function approximateSpectralGap(adjacency: number[][], n: number, maxIter?: number, tol?: number): number;
|
|
69
|
+
/**
|
|
70
|
+
* Estimate the average effective resistance between sampled node pairs.
|
|
71
|
+
*
|
|
72
|
+
* Effective resistance between nodes i and j is:
|
|
73
|
+
* R_ij = (e_i - e_j)^T * L^+ * (e_i - e_j)
|
|
74
|
+
* where L^+ is the pseudo-inverse of the Laplacian.
|
|
75
|
+
*
|
|
76
|
+
* We approximate this by using the spectral decomposition:
|
|
77
|
+
* R_ij = sum_{k>=2} (1/lambda_k) * (v_k[i] - v_k[j])^2
|
|
78
|
+
*
|
|
79
|
+
* For a lightweight check, we approximate using only the Fiedler eigenvector
|
|
80
|
+
* and value, which gives the dominant contribution.
|
|
81
|
+
*
|
|
82
|
+
* @param adjacency - Adjacency list
|
|
83
|
+
* @param n - Number of nodes
|
|
84
|
+
* @param sampleSize - Number of pairs to sample
|
|
85
|
+
* @param fiedlerValue - Pre-computed Fiedler value (pass to avoid recomputation)
|
|
86
|
+
* @returns Average effective resistance estimate
|
|
87
|
+
*/
|
|
88
|
+
export declare function estimateEffectiveResistance(adjacency: number[][], n: number, sampleSize?: number, fiedlerValue?: number): number;
|
|
89
|
+
/**
|
|
90
|
+
* Compute a combined coherence score from spectral metrics.
|
|
91
|
+
*
|
|
92
|
+
* Combines Fiedler value, spectral gap, and effective resistance into
|
|
93
|
+
* a single 0-1 score where 1 = perfectly healthy and 0 = critically unhealthy.
|
|
94
|
+
*
|
|
95
|
+
* @param fiedler - Fiedler value
|
|
96
|
+
* @param spectralGap - Spectral gap
|
|
97
|
+
* @param resistance - Average effective resistance
|
|
98
|
+
* @returns Coherence score in [0, 1]
|
|
99
|
+
*/
|
|
100
|
+
export declare function computeCoherenceScore(fiedler: number, spectralGap: number, resistance: number): number;
|
|
101
|
+
//# sourceMappingURL=spectral-math.d.ts.map
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spectral Graph Theory Utilities
|
|
3
|
+
*
|
|
4
|
+
* Mathematical primitives for spectral analysis of graph structures.
|
|
5
|
+
* Provides Laplacian construction, power-iteration eigenvalue approximation,
|
|
6
|
+
* effective resistance estimation, and coherence scoring.
|
|
7
|
+
*
|
|
8
|
+
* These utilities are used by the HNSW Health Monitor to assess the
|
|
9
|
+
* structural health of HNSW index graphs without requiring native
|
|
10
|
+
* ruvector-coherence.
|
|
11
|
+
*
|
|
12
|
+
* @module integrations/ruvector/spectral-math
|
|
13
|
+
*/
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Laplacian Construction
|
|
16
|
+
// ============================================================================
|
|
17
|
+
/**
|
|
18
|
+
* Build the graph Laplacian from an adjacency list.
|
|
19
|
+
* L = D - A where D is the degree matrix and A is the adjacency matrix.
|
|
20
|
+
*
|
|
21
|
+
* Returns the Laplacian as a dense matrix (suitable for small graphs).
|
|
22
|
+
* For large graphs, we use the adjacency list directly with power iteration.
|
|
23
|
+
*/
|
|
24
|
+
export function buildLaplacian(adjacency, n) {
|
|
25
|
+
const L = Array.from({ length: n }, () => new Float64Array(n));
|
|
26
|
+
for (let i = 0; i < n; i++) {
|
|
27
|
+
const degree = adjacency[i].length;
|
|
28
|
+
L[i][i] = degree; // Diagonal = degree
|
|
29
|
+
for (const j of adjacency[i]) {
|
|
30
|
+
L[i][j] = -1; // Off-diagonal = -1 for each edge
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return L;
|
|
34
|
+
}
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Linear Algebra Helpers
|
|
37
|
+
// ============================================================================
|
|
38
|
+
/**
|
|
39
|
+
* Multiply Laplacian (in adjacency list form) by a vector.
|
|
40
|
+
* L * v = D*v - A*v
|
|
41
|
+
* This avoids materializing the full Laplacian matrix.
|
|
42
|
+
*/
|
|
43
|
+
export function laplacianMultiply(adjacency, v) {
|
|
44
|
+
const n = v.length;
|
|
45
|
+
const result = new Float64Array(n);
|
|
46
|
+
for (let i = 0; i < n; i++) {
|
|
47
|
+
const degree = adjacency[i].length;
|
|
48
|
+
result[i] = degree * v[i]; // D * v
|
|
49
|
+
for (const j of adjacency[i]) {
|
|
50
|
+
result[i] -= v[j]; // -A * v
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Compute the L2 norm of a vector.
|
|
57
|
+
*/
|
|
58
|
+
export function vectorNorm(v) {
|
|
59
|
+
let sum = 0;
|
|
60
|
+
for (let i = 0; i < v.length; i++)
|
|
61
|
+
sum += v[i] * v[i];
|
|
62
|
+
return Math.sqrt(sum);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Normalize a vector in-place.
|
|
66
|
+
*/
|
|
67
|
+
export function normalizeInPlace(v) {
|
|
68
|
+
const norm = vectorNorm(v);
|
|
69
|
+
if (norm > 0) {
|
|
70
|
+
for (let i = 0; i < v.length; i++)
|
|
71
|
+
v[i] /= norm;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Remove the component of v along the direction of u (projection).
|
|
76
|
+
* v = v - (v . u) * u
|
|
77
|
+
* Assumes u is normalized.
|
|
78
|
+
*/
|
|
79
|
+
export function deflateVector(v, u) {
|
|
80
|
+
let dot = 0;
|
|
81
|
+
for (let i = 0; i < v.length; i++)
|
|
82
|
+
dot += v[i] * u[i];
|
|
83
|
+
for (let i = 0; i < v.length; i++)
|
|
84
|
+
v[i] -= dot * u[i];
|
|
85
|
+
}
|
|
86
|
+
// ============================================================================
|
|
87
|
+
// Eigenvalue Approximation
|
|
88
|
+
// ============================================================================
|
|
89
|
+
/**
|
|
90
|
+
* Approximate the Fiedler value (second smallest eigenvalue of L)
|
|
91
|
+
* using power iteration on L with deflation of the trivial eigenvector.
|
|
92
|
+
*
|
|
93
|
+
* The trivial eigenvector of L is the all-ones vector (eigenvalue = 0).
|
|
94
|
+
* We deflate it out and use power iteration to find the smallest
|
|
95
|
+
* non-trivial eigenvalue.
|
|
96
|
+
*
|
|
97
|
+
* Since power iteration finds the LARGEST eigenvalue, we use the shift-
|
|
98
|
+
* invert approach: we run power iteration on (lambda_max * I - L) to
|
|
99
|
+
* find the second smallest eigenvalue of L.
|
|
100
|
+
*
|
|
101
|
+
* @param adjacency - Adjacency list
|
|
102
|
+
* @param n - Number of nodes
|
|
103
|
+
* @param maxIter - Maximum iterations
|
|
104
|
+
* @param tol - Convergence tolerance
|
|
105
|
+
* @returns Approximate Fiedler value
|
|
106
|
+
*/
|
|
107
|
+
export function approximateFiedlerValue(adjacency, n, maxIter = 100, tol = 1e-6) {
|
|
108
|
+
if (n <= 1)
|
|
109
|
+
return 0;
|
|
110
|
+
if (n === 2) {
|
|
111
|
+
// For 2 nodes: Fiedler = number of edges between them
|
|
112
|
+
return adjacency[0].includes(1) ? 2 : 0;
|
|
113
|
+
}
|
|
114
|
+
// The trivial eigenvector of L: normalized all-ones
|
|
115
|
+
const trivial = new Float64Array(n).fill(1 / Math.sqrt(n));
|
|
116
|
+
// First, estimate lambda_max using a few power iterations on L
|
|
117
|
+
let maxVec = new Float64Array(n);
|
|
118
|
+
for (let i = 0; i < n; i++)
|
|
119
|
+
maxVec[i] = Math.random() - 0.5;
|
|
120
|
+
deflateVector(maxVec, trivial);
|
|
121
|
+
normalizeInPlace(maxVec);
|
|
122
|
+
let lambdaMax = 0;
|
|
123
|
+
for (let iter = 0; iter < 30; iter++) {
|
|
124
|
+
const Lv = laplacianMultiply(adjacency, maxVec);
|
|
125
|
+
deflateVector(Lv, trivial);
|
|
126
|
+
lambdaMax = vectorNorm(Lv);
|
|
127
|
+
if (lambdaMax > 0) {
|
|
128
|
+
for (let i = 0; i < n; i++)
|
|
129
|
+
maxVec[i] = Lv[i] / lambdaMax;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (lambdaMax < tol)
|
|
133
|
+
return 0; // Disconnected or trivial graph
|
|
134
|
+
// Now use power iteration on (lambdaMax * I - L) to find the
|
|
135
|
+
// eigenvector corresponding to the LARGEST eigenvalue of (lambdaMax*I - L),
|
|
136
|
+
// which corresponds to the SMALLEST non-trivial eigenvalue of L.
|
|
137
|
+
let v = new Float64Array(n);
|
|
138
|
+
for (let i = 0; i < n; i++)
|
|
139
|
+
v[i] = Math.random() - 0.5;
|
|
140
|
+
deflateVector(v, trivial);
|
|
141
|
+
normalizeInPlace(v);
|
|
142
|
+
let eigenvalue = 0;
|
|
143
|
+
for (let iter = 0; iter < maxIter; iter++) {
|
|
144
|
+
// Compute (lambdaMax * I - L) * v = lambdaMax * v - L * v
|
|
145
|
+
const Lv = laplacianMultiply(adjacency, v);
|
|
146
|
+
const shifted = new Float64Array(n);
|
|
147
|
+
for (let i = 0; i < n; i++) {
|
|
148
|
+
shifted[i] = lambdaMax * v[i] - Lv[i];
|
|
149
|
+
}
|
|
150
|
+
// Deflate the trivial eigenvector
|
|
151
|
+
deflateVector(shifted, trivial);
|
|
152
|
+
const norm = vectorNorm(shifted);
|
|
153
|
+
if (norm < tol)
|
|
154
|
+
break;
|
|
155
|
+
const newEigenvalue = norm;
|
|
156
|
+
for (let i = 0; i < n; i++)
|
|
157
|
+
v[i] = shifted[i] / norm;
|
|
158
|
+
if (Math.abs(newEigenvalue - eigenvalue) < tol)
|
|
159
|
+
break;
|
|
160
|
+
eigenvalue = newEigenvalue;
|
|
161
|
+
}
|
|
162
|
+
// The Fiedler value is lambdaMax - eigenvalue of (lambdaMax*I - L)
|
|
163
|
+
const fiedler = lambdaMax - eigenvalue;
|
|
164
|
+
return Math.max(0, fiedler);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Estimate the spectral gap: difference between the second smallest
|
|
168
|
+
* and smallest eigenvalues of the Laplacian.
|
|
169
|
+
*
|
|
170
|
+
* For a connected graph, the smallest eigenvalue is 0, so the spectral
|
|
171
|
+
* gap equals the Fiedler value.
|
|
172
|
+
*/
|
|
173
|
+
export function approximateSpectralGap(adjacency, n, maxIter = 100, tol = 1e-6) {
|
|
174
|
+
// For the Laplacian, lambda_1 = 0 always (connected or not)
|
|
175
|
+
// So spectral gap = lambda_2 - lambda_1 = Fiedler value
|
|
176
|
+
return approximateFiedlerValue(adjacency, n, maxIter, tol);
|
|
177
|
+
}
|
|
178
|
+
// ============================================================================
|
|
179
|
+
// Effective Resistance
|
|
180
|
+
// ============================================================================
|
|
181
|
+
/**
|
|
182
|
+
* Estimate the average effective resistance between sampled node pairs.
|
|
183
|
+
*
|
|
184
|
+
* Effective resistance between nodes i and j is:
|
|
185
|
+
* R_ij = (e_i - e_j)^T * L^+ * (e_i - e_j)
|
|
186
|
+
* where L^+ is the pseudo-inverse of the Laplacian.
|
|
187
|
+
*
|
|
188
|
+
* We approximate this by using the spectral decomposition:
|
|
189
|
+
* R_ij = sum_{k>=2} (1/lambda_k) * (v_k[i] - v_k[j])^2
|
|
190
|
+
*
|
|
191
|
+
* For a lightweight check, we approximate using only the Fiedler eigenvector
|
|
192
|
+
* and value, which gives the dominant contribution.
|
|
193
|
+
*
|
|
194
|
+
* @param adjacency - Adjacency list
|
|
195
|
+
* @param n - Number of nodes
|
|
196
|
+
* @param sampleSize - Number of pairs to sample
|
|
197
|
+
* @param fiedlerValue - Pre-computed Fiedler value (pass to avoid recomputation)
|
|
198
|
+
* @returns Average effective resistance estimate
|
|
199
|
+
*/
|
|
200
|
+
export function estimateEffectiveResistance(adjacency, n, sampleSize = 50, fiedlerValue) {
|
|
201
|
+
if (n <= 1)
|
|
202
|
+
return 0;
|
|
203
|
+
const fiedler = fiedlerValue ?? approximateFiedlerValue(adjacency, n);
|
|
204
|
+
if (fiedler < 1e-10)
|
|
205
|
+
return Infinity; // Disconnected graph
|
|
206
|
+
// For a well-connected graph with Fiedler value lambda_2,
|
|
207
|
+
// the average effective resistance is approximately n / (n * lambda_2)
|
|
208
|
+
// = 1 / lambda_2 for a regular graph.
|
|
209
|
+
// For a more accurate estimate, we use the trace formula:
|
|
210
|
+
// sum of all R_ij = n * sum_{k>=2} 1/lambda_k
|
|
211
|
+
// We approximate by assuming eigenvalues are distributed uniformly
|
|
212
|
+
// between lambda_2 and lambda_max ~ 2 * max_degree.
|
|
213
|
+
const maxDeg = Math.max(...adjacency.map(a => a.length), 1);
|
|
214
|
+
const lambdaMax = 2 * maxDeg;
|
|
215
|
+
// Approximate: eigenvalues roughly uniform in [fiedler, lambdaMax]
|
|
216
|
+
// Average 1/lambda over this range:
|
|
217
|
+
// integral from fiedler to lambdaMax of (1/x) dx / (lambdaMax - fiedler)
|
|
218
|
+
// = ln(lambdaMax/fiedler) / (lambdaMax - fiedler)
|
|
219
|
+
const avgInvLambda = Math.log(lambdaMax / fiedler) / (lambdaMax - fiedler);
|
|
220
|
+
// Average resistance over random pairs is approximately:
|
|
221
|
+
// 2 * avgInvLambda (two endpoints contribute)
|
|
222
|
+
const avgResistance = 2 * avgInvLambda;
|
|
223
|
+
return Math.max(0, avgResistance);
|
|
224
|
+
}
|
|
225
|
+
// ============================================================================
|
|
226
|
+
// Coherence Score
|
|
227
|
+
// ============================================================================
|
|
228
|
+
/**
|
|
229
|
+
* Compute a combined coherence score from spectral metrics.
|
|
230
|
+
*
|
|
231
|
+
* Combines Fiedler value, spectral gap, and effective resistance into
|
|
232
|
+
* a single 0-1 score where 1 = perfectly healthy and 0 = critically unhealthy.
|
|
233
|
+
*
|
|
234
|
+
* @param fiedler - Fiedler value
|
|
235
|
+
* @param spectralGap - Spectral gap
|
|
236
|
+
* @param resistance - Average effective resistance
|
|
237
|
+
* @returns Coherence score in [0, 1]
|
|
238
|
+
*/
|
|
239
|
+
export function computeCoherenceScore(fiedler, spectralGap, resistance) {
|
|
240
|
+
// Fiedler component: sigmoid-like scaling
|
|
241
|
+
// Score approaches 1 when fiedler >> threshold, 0 when fiedler << threshold
|
|
242
|
+
const fiedlerScore = 1 - Math.exp(-fiedler / 0.05);
|
|
243
|
+
// Spectral gap component
|
|
244
|
+
const gapScore = 1 - Math.exp(-spectralGap / 0.5);
|
|
245
|
+
// Resistance component: lower resistance = healthier
|
|
246
|
+
// Use inverse scaling: score = 1 / (1 + resistance/threshold)
|
|
247
|
+
const resistanceScore = resistance === Infinity
|
|
248
|
+
? 0
|
|
249
|
+
: 1 / (1 + resistance / 5.0);
|
|
250
|
+
// Weighted combination
|
|
251
|
+
const score = 0.4 * fiedlerScore + 0.3 * gapScore + 0.3 * resistanceScore;
|
|
252
|
+
return Math.max(0, Math.min(1, score));
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=spectral-math.js.map
|