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.
Files changed (164) hide show
  1. package/.claude/helpers/brain-checkpoint.cjs +4 -1
  2. package/.claude/helpers/statusline-v3.cjs +3 -1
  3. package/.claude/skills/skills-manifest.json +1 -1
  4. package/CHANGELOG.md +45 -0
  5. package/README.md +2 -14
  6. package/assets/helpers/statusline-v3.cjs +3 -1
  7. package/dist/cli/brain-commands.js +6 -10
  8. package/dist/cli/bundle.js +7441 -4327
  9. package/dist/cli/commands/audit.d.ts +43 -0
  10. package/dist/cli/commands/audit.js +125 -0
  11. package/dist/cli/commands/hooks.js +29 -6
  12. package/dist/cli/commands/init.js +1 -73
  13. package/dist/cli/commands/learning.js +270 -13
  14. package/dist/cli/commands/ruvector-commands.d.ts +15 -0
  15. package/dist/cli/commands/ruvector-commands.js +271 -0
  16. package/dist/cli/handlers/init-handler.d.ts +0 -1
  17. package/dist/cli/handlers/init-handler.js +0 -6
  18. package/dist/cli/index.js +4 -2
  19. package/dist/context/sources/defect-source.js +2 -2
  20. package/dist/context/sources/memory-source.js +2 -2
  21. package/dist/context/sources/requirements-source.js +2 -2
  22. package/dist/coordination/behavior-tree/decorators.d.ts +108 -0
  23. package/dist/coordination/behavior-tree/decorators.js +251 -0
  24. package/dist/coordination/behavior-tree/index.d.ts +12 -0
  25. package/dist/coordination/behavior-tree/index.js +15 -0
  26. package/dist/coordination/behavior-tree/nodes.d.ts +165 -0
  27. package/dist/coordination/behavior-tree/nodes.js +338 -0
  28. package/dist/coordination/behavior-tree/qe-trees.d.ts +105 -0
  29. package/dist/coordination/behavior-tree/qe-trees.js +181 -0
  30. package/dist/coordination/coherence-action-gate.d.ts +284 -0
  31. package/dist/coordination/coherence-action-gate.js +512 -0
  32. package/dist/coordination/index.d.ts +4 -0
  33. package/dist/coordination/index.js +8 -0
  34. package/dist/coordination/reasoning-qec.d.ts +315 -0
  35. package/dist/coordination/reasoning-qec.js +585 -0
  36. package/dist/coordination/task-executor.d.ts +16 -0
  37. package/dist/coordination/task-executor.js +99 -0
  38. package/dist/coordination/workflow-orchestrator.d.ts +29 -0
  39. package/dist/coordination/workflow-orchestrator.js +42 -0
  40. package/dist/domains/visual-accessibility/cnn-visual-regression.d.ts +135 -0
  41. package/dist/domains/visual-accessibility/cnn-visual-regression.js +327 -0
  42. package/dist/domains/visual-accessibility/index.d.ts +1 -0
  43. package/dist/domains/visual-accessibility/index.js +4 -0
  44. package/dist/governance/coherence-validator.d.ts +112 -0
  45. package/dist/governance/coherence-validator.js +180 -0
  46. package/dist/governance/index.d.ts +1 -0
  47. package/dist/governance/index.js +2 -0
  48. package/dist/governance/witness-chain.d.ts +311 -0
  49. package/dist/governance/witness-chain.js +509 -0
  50. package/dist/init/index.d.ts +0 -2
  51. package/dist/init/index.js +0 -1
  52. package/dist/init/init-wizard-steps.d.ts +10 -0
  53. package/dist/init/init-wizard-steps.js +87 -1
  54. package/dist/init/init-wizard.d.ts +1 -9
  55. package/dist/init/init-wizard.js +3 -69
  56. package/dist/init/orchestrator.js +0 -1
  57. package/dist/init/phases/01-detection.js +0 -27
  58. package/dist/init/phases/07-hooks.js +6 -4
  59. package/dist/init/phases/phase-interface.d.ts +0 -1
  60. package/dist/init/settings-merge.js +1 -1
  61. package/dist/integrations/browser/qe-dashboard/clustering.d.ts +48 -0
  62. package/dist/integrations/browser/qe-dashboard/clustering.js +183 -0
  63. package/dist/integrations/browser/qe-dashboard/index.d.ts +12 -0
  64. package/dist/integrations/browser/qe-dashboard/index.js +15 -0
  65. package/dist/integrations/browser/qe-dashboard/pattern-explorer.d.ts +165 -0
  66. package/dist/integrations/browser/qe-dashboard/pattern-explorer.js +260 -0
  67. package/dist/integrations/browser/qe-dashboard/wasm-vector-store.d.ts +144 -0
  68. package/dist/integrations/browser/qe-dashboard/wasm-vector-store.js +277 -0
  69. package/dist/integrations/ruvector/cognitive-container-codec.d.ts +51 -0
  70. package/dist/integrations/ruvector/cognitive-container-codec.js +180 -0
  71. package/dist/integrations/ruvector/cognitive-container.d.ts +125 -0
  72. package/dist/integrations/ruvector/cognitive-container.js +306 -0
  73. package/dist/integrations/ruvector/coherence-gate.d.ts +309 -0
  74. package/dist/integrations/ruvector/coherence-gate.js +631 -0
  75. package/dist/integrations/ruvector/compressed-hnsw-integration.d.ts +176 -0
  76. package/dist/integrations/ruvector/compressed-hnsw-integration.js +301 -0
  77. package/dist/integrations/ruvector/dither-adapter.d.ts +122 -0
  78. package/dist/integrations/ruvector/dither-adapter.js +295 -0
  79. package/dist/integrations/ruvector/domain-transfer.d.ts +129 -0
  80. package/dist/integrations/ruvector/domain-transfer.js +220 -0
  81. package/dist/integrations/ruvector/feature-flags.d.ts +214 -2
  82. package/dist/integrations/ruvector/feature-flags.js +167 -2
  83. package/dist/integrations/ruvector/filter-adapter.d.ts +71 -0
  84. package/dist/integrations/ruvector/filter-adapter.js +285 -0
  85. package/dist/integrations/ruvector/gnn-wrapper.d.ts +20 -0
  86. package/dist/integrations/ruvector/gnn-wrapper.js +40 -0
  87. package/dist/integrations/ruvector/hnsw-health-monitor.d.ts +237 -0
  88. package/dist/integrations/ruvector/hnsw-health-monitor.js +394 -0
  89. package/dist/integrations/ruvector/index.d.ts +8 -2
  90. package/dist/integrations/ruvector/index.js +18 -2
  91. package/dist/integrations/ruvector/interfaces.d.ts +40 -0
  92. package/dist/integrations/ruvector/sona-persistence.d.ts +54 -0
  93. package/dist/integrations/ruvector/sona-persistence.js +162 -0
  94. package/dist/integrations/ruvector/sona-three-loop.d.ts +392 -0
  95. package/dist/integrations/ruvector/sona-three-loop.js +814 -0
  96. package/dist/integrations/ruvector/sona-wrapper.d.ts +97 -0
  97. package/dist/integrations/ruvector/sona-wrapper.js +147 -3
  98. package/dist/integrations/ruvector/spectral-math.d.ts +101 -0
  99. package/dist/integrations/ruvector/spectral-math.js +254 -0
  100. package/dist/integrations/ruvector/temporal-compression.d.ts +163 -0
  101. package/dist/integrations/ruvector/temporal-compression.js +318 -0
  102. package/dist/integrations/ruvector/thompson-sampler.d.ts +61 -0
  103. package/dist/integrations/ruvector/thompson-sampler.js +118 -0
  104. package/dist/integrations/ruvector/transfer-coherence-stub.d.ts +80 -0
  105. package/dist/integrations/ruvector/transfer-coherence-stub.js +63 -0
  106. package/dist/integrations/ruvector/transfer-verification.d.ts +119 -0
  107. package/dist/integrations/ruvector/transfer-verification.js +115 -0
  108. package/dist/kernel/hnsw-adapter.d.ts +52 -1
  109. package/dist/kernel/hnsw-adapter.js +139 -4
  110. package/dist/kernel/hnsw-index-provider.d.ts +5 -0
  111. package/dist/kernel/native-hnsw-backend.d.ts +110 -0
  112. package/dist/kernel/native-hnsw-backend.js +408 -0
  113. package/dist/kernel/unified-memory.js +5 -6
  114. package/dist/learning/aqe-learning-engine.d.ts +2 -0
  115. package/dist/learning/aqe-learning-engine.js +65 -0
  116. package/dist/learning/experience-capture-middleware.js +20 -0
  117. package/dist/learning/experience-capture.d.ts +10 -0
  118. package/dist/learning/experience-capture.js +34 -0
  119. package/dist/learning/index.d.ts +2 -2
  120. package/dist/learning/index.js +4 -4
  121. package/dist/learning/metrics-tracker.d.ts +11 -0
  122. package/dist/learning/metrics-tracker.js +29 -13
  123. package/dist/learning/pattern-lifecycle.d.ts +30 -1
  124. package/dist/learning/pattern-lifecycle.js +92 -20
  125. package/dist/learning/pattern-store.d.ts +8 -0
  126. package/dist/learning/pattern-store.js +8 -2
  127. package/dist/learning/qe-unified-memory.js +1 -28
  128. package/dist/learning/regret-tracker.d.ts +201 -0
  129. package/dist/learning/regret-tracker.js +361 -0
  130. package/dist/mcp/bundle.js +5915 -474
  131. package/dist/routing/index.d.ts +4 -2
  132. package/dist/routing/index.js +3 -1
  133. package/dist/routing/neural-tiny-dancer-router.d.ts +268 -0
  134. package/dist/routing/neural-tiny-dancer-router.js +514 -0
  135. package/dist/routing/queen-integration.js +5 -5
  136. package/dist/routing/routing-config.d.ts +6 -0
  137. package/dist/routing/routing-config.js +1 -0
  138. package/dist/routing/simple-neural-router.d.ts +76 -0
  139. package/dist/routing/simple-neural-router.js +202 -0
  140. package/dist/routing/tiny-dancer-router.d.ts +20 -1
  141. package/dist/routing/tiny-dancer-router.js +21 -2
  142. package/dist/test-scheduling/dag-attention-scheduler.d.ts +81 -0
  143. package/dist/test-scheduling/dag-attention-scheduler.js +358 -0
  144. package/dist/test-scheduling/dag-attention-types.d.ts +81 -0
  145. package/dist/test-scheduling/dag-attention-types.js +10 -0
  146. package/dist/test-scheduling/index.d.ts +1 -0
  147. package/dist/test-scheduling/index.js +4 -0
  148. package/dist/test-scheduling/pipeline.d.ts +8 -0
  149. package/dist/test-scheduling/pipeline.js +28 -0
  150. package/package.json +6 -2
  151. package/dist/cli/commands/migrate.d.ts +0 -9
  152. package/dist/cli/commands/migrate.js +0 -566
  153. package/dist/init/init-wizard-migration.d.ts +0 -52
  154. package/dist/init/init-wizard-migration.js +0 -345
  155. package/dist/init/migration/config-migrator.d.ts +0 -31
  156. package/dist/init/migration/config-migrator.js +0 -149
  157. package/dist/init/migration/data-migrator.d.ts +0 -72
  158. package/dist/init/migration/data-migrator.js +0 -232
  159. package/dist/init/migration/detector.d.ts +0 -44
  160. package/dist/init/migration/detector.js +0 -105
  161. package/dist/init/migration/index.d.ts +0 -8
  162. package/dist/init/migration/index.js +0 -8
  163. package/dist/learning/v2-to-v3-migration.d.ts +0 -86
  164. 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