agentic-qe 3.8.5 → 3.8.7

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 (67) hide show
  1. package/.claude/skills/skills-manifest.json +1 -1
  2. package/CHANGELOG.md +26 -0
  3. package/assets/governance/constitution.md +1 -1
  4. package/assets/governance/shards/chaos-resilience.shard.md +1 -1
  5. package/assets/governance/shards/code-intelligence.shard.md +1 -1
  6. package/assets/governance/shards/contract-testing.shard.md +1 -1
  7. package/assets/governance/shards/coverage-analysis.shard.md +1 -1
  8. package/assets/governance/shards/defect-intelligence.shard.md +1 -1
  9. package/assets/governance/shards/learning-optimization.shard.md +1 -1
  10. package/assets/governance/shards/quality-assessment.shard.md +1 -1
  11. package/assets/governance/shards/requirements-validation.shard.md +1 -1
  12. package/assets/governance/shards/security-compliance.shard.md +1 -1
  13. package/assets/governance/shards/test-execution.shard.md +1 -1
  14. package/assets/governance/shards/test-generation.shard.md +1 -1
  15. package/assets/governance/shards/visual-accessibility.shard.md +1 -1
  16. package/dist/cli/bundle.js +715 -643
  17. package/dist/cli/command-registry.js +3 -1
  18. package/dist/cli/completions/index.d.ts +17 -0
  19. package/dist/cli/completions/index.js +49 -1
  20. package/dist/cli/handlers/hypergraph-handler.d.ts +27 -0
  21. package/dist/cli/handlers/hypergraph-handler.js +248 -0
  22. package/dist/cli/handlers/index.d.ts +1 -0
  23. package/dist/cli/handlers/index.js +1 -0
  24. package/dist/coordination/mincut/phase-executor.d.ts +27 -0
  25. package/dist/coordination/mincut/phase-executor.js +70 -0
  26. package/dist/coordination/mincut/time-crystal-analysis.d.ts +35 -0
  27. package/dist/coordination/mincut/time-crystal-analysis.js +237 -0
  28. package/dist/coordination/mincut/time-crystal-persistence.d.ts +35 -0
  29. package/dist/coordination/mincut/time-crystal-persistence.js +81 -0
  30. package/dist/coordination/mincut/time-crystal-scheduling.d.ts +34 -0
  31. package/dist/coordination/mincut/time-crystal-scheduling.js +213 -0
  32. package/dist/coordination/mincut/time-crystal-types.d.ts +278 -0
  33. package/dist/coordination/mincut/time-crystal-types.js +67 -0
  34. package/dist/coordination/mincut/time-crystal.d.ts +8 -438
  35. package/dist/coordination/mincut/time-crystal.js +87 -905
  36. package/dist/coordination/protocols/code-intelligence-index.js +2 -11
  37. package/dist/domains/code-intelligence/coordinator-hypergraph.js +1 -1
  38. package/dist/domains/code-intelligence/coordinator.d.ts +5 -0
  39. package/dist/domains/code-intelligence/coordinator.js +35 -3
  40. package/dist/init/phases/06-code-intelligence.d.ts +8 -3
  41. package/dist/init/phases/06-code-intelligence.js +70 -32
  42. package/dist/learning/agent-routing.d.ts +53 -0
  43. package/dist/learning/agent-routing.js +142 -0
  44. package/dist/learning/embedding-utils.d.ts +34 -0
  45. package/dist/learning/embedding-utils.js +95 -0
  46. package/dist/learning/pattern-promotion.d.ts +63 -0
  47. package/dist/learning/pattern-promotion.js +187 -0
  48. package/dist/learning/pretrained-patterns.d.ts +14 -0
  49. package/dist/learning/pretrained-patterns.js +726 -0
  50. package/dist/learning/qe-reasoning-bank-types.d.ts +174 -0
  51. package/dist/learning/qe-reasoning-bank-types.js +24 -0
  52. package/dist/learning/qe-reasoning-bank.d.ts +9 -192
  53. package/dist/learning/qe-reasoning-bank.js +48 -1093
  54. package/dist/mcp/bundle.js +1084 -1083
  55. package/dist/mcp/handlers/hypergraph-handler.d.ts +27 -0
  56. package/dist/mcp/handlers/hypergraph-handler.js +140 -0
  57. package/dist/mcp/handlers/index.d.ts +1 -0
  58. package/dist/mcp/handlers/index.js +2 -0
  59. package/dist/mcp/server.js +19 -0
  60. package/dist/mcp/tool-scoping.js +5 -0
  61. package/dist/shared/code-index-extractor.d.ts +23 -0
  62. package/dist/shared/code-index-extractor.js +101 -0
  63. package/dist/shared/security/command-validator.js +2 -2
  64. package/dist/shared/security/input-sanitizer.js +1 -1
  65. package/dist/shared/security/path-traversal-validator.js +1 -1
  66. package/dist/shared/security/regex-safety-validator.js +7 -7
  67. package/package.json +1 -1
@@ -1,108 +1,18 @@
1
1
  /**
2
- * Agentic QE v3 - Time Crystal CI/CD Coordination
2
+ * Agentic QE v3 - Time Crystal CI/CD Coordination Controller
3
3
  * ADR-047: MinCut Self-Organizing QE Integration - Phase 4
4
- * ADR-032: Kuramoto CPG oscillators for self-sustaining scheduling (INTEGRATED)
5
- *
6
- * Implements temporal patterns for CI/CD optimization using time crystal concepts.
7
- * The system identifies periodic patterns in build/test execution, predicts optimal
8
- * execution windows, and coordinates scheduling for maximum throughput.
9
- *
10
- * Key Concepts:
11
- * - TemporalAttractor: Stable states the system evolves toward (stable, degraded, chaotic)
12
- * - TimeCrystalPhase: Periodic patterns in CI/CD execution
13
- * - CrystalLattice: Network of temporal dependencies between tests
14
- * - TimeCrystalController: Orchestrates temporal coordination
15
- * - KuramotoCPG: Self-sustaining oscillator-based scheduler (from kuramoto-cpg.ts)
16
- *
17
- * Integration Points:
18
- * - StrangeLoopController: Feedback for self-healing decisions
19
- * - MinCutHealthMonitor: Health metrics for stability assessment
20
- * - TestFailureCausalGraph: Failure prediction and prevention
21
- * - KuramotoCPG: Self-sustaining phase transitions without external timers
22
- *
23
- * Reference: RuVector Time Crystal Pattern (time_crystal.rs)
4
+ * ADR-032: Kuramoto CPG oscillators for self-sustaining scheduling
24
5
  */
25
6
  import { v4 as uuidv4 } from 'uuid';
26
- import { getUnifiedMemory } from '../../kernel/unified-memory.js';
27
- import { toErrorMessage } from '../../shared/error-utils.js';
28
- import { secureRandom, secureRandomInt, secureRandomFloat } from '../../shared/utils/crypto-random.js';
29
7
  import { createKuramotoCPG, DEFAULT_CPG_CONFIG, } from './kuramoto-cpg';
30
- /** Domain name for time crystal events */
31
- const TIME_CRYSTAL_SOURCE = 'coordination';
32
- /**
33
- * Default phase executor that simulates test execution
34
- * Replace with real test runner integration in production
35
- */
36
- export class DefaultPhaseExecutor {
37
- name;
38
- ready = true;
39
- constructor(name = 'default-executor') {
40
- this.name = name;
41
- }
42
- async execute(phase) {
43
- const startTime = Date.now();
44
- // Simulate test execution with some variance
45
- const basePassRate = phase.qualityThresholds.minPassRate;
46
- const variance = (secureRandom() - 0.5) * 0.1; // ±5% variance
47
- const actualPassRate = Math.min(1, Math.max(0, basePassRate + variance));
48
- const testsRun = secureRandomInt(50, 150);
49
- const testsPassed = Math.floor(testsRun * actualPassRate);
50
- const testsFailed = testsRun - testsPassed;
51
- // Simulate execution time with variance
52
- const expectedDuration = phase.expectedDuration;
53
- const durationVariance = (secureRandom() - 0.5) * 0.3; // ±15% variance
54
- const actualDuration = Math.floor(expectedDuration * (1 + durationVariance));
55
- // Simulate wait (scaled down for testing - use 1% of expected duration)
56
- const simulatedWait = Math.min(100, actualDuration * 0.01);
57
- await new Promise((resolve) => setTimeout(resolve, simulatedWait));
58
- const flakyRatio = secureRandom() * phase.qualityThresholds.maxFlakyRatio;
59
- const coverage = secureRandomFloat(phase.qualityThresholds.minCoverage, phase.qualityThresholds.minCoverage + 0.1);
60
- const qualityMet = actualPassRate >= phase.qualityThresholds.minPassRate &&
61
- flakyRatio <= phase.qualityThresholds.maxFlakyRatio &&
62
- coverage >= phase.qualityThresholds.minCoverage;
63
- return {
64
- phaseId: phase.id,
65
- phaseName: phase.name,
66
- passRate: actualPassRate,
67
- flakyRatio,
68
- coverage: Math.min(1, coverage),
69
- duration: Date.now() - startTime,
70
- testsRun,
71
- testsPassed,
72
- testsFailed,
73
- testsSkipped: 0,
74
- qualityMet,
75
- };
76
- }
77
- isReady() {
78
- return this.ready;
79
- }
80
- getName() {
81
- return this.name;
82
- }
83
- setReady(ready) {
84
- this.ready = ready;
85
- }
86
- }
87
- /**
88
- * Default time crystal configuration
89
- */
90
- export const DEFAULT_TIME_CRYSTAL_CONFIG = {
91
- enabled: true,
92
- observationIntervalMs: 30000, // 30 seconds
93
- phaseDetectionWindowMs: 3600000, // 1 hour
94
- minObservationsForPattern: 10,
95
- anomalySensitivity: 0.7,
96
- stabilityThreshold: 0.8,
97
- maxParallelGroups: 8,
98
- predictionHorizonMs: 600000, // 10 minutes
99
- autoOptimize: true,
100
- autoStabilize: true,
101
- useCPGScheduling: true, // Enable CPG by default
102
- };
103
- // ============================================================================
104
- // Time Crystal Controller Implementation
105
- // ============================================================================
8
+ // Re-export everything from sub-modules so barrel imports remain unchanged
9
+ export { TIME_CRYSTAL_SOURCE, DEFAULT_TIME_CRYSTAL_CONFIG, } from './time-crystal-types';
10
+ export { DefaultPhaseExecutor, createDefaultPhaseExecutor } from './phase-executor';
11
+ import { TIME_CRYSTAL_SOURCE, DEFAULT_TIME_CRYSTAL_CONFIG, DEFAULT_CRYSTAL_PHASES, } from './time-crystal-types';
12
+ import { DefaultPhaseExecutor } from './phase-executor';
13
+ import { initializeTimeCrystalDb, persistTimeCrystalToKv, loadTimeCrystalFromKv, PERSIST_INTERVAL, } from './time-crystal-persistence';
14
+ import { collectMetrics, detectAttractor, detectAnomalies, predictPhase, determineStabilization, } from './time-crystal-analysis';
15
+ import { determineOptimization, rebuildLatticeFromCausalGraph, } from './time-crystal-scheduling';
106
16
  /**
107
17
  * Time Crystal Controller - Orchestrates temporal CI/CD coordination
108
18
  *
@@ -115,18 +25,11 @@ export class TimeCrystalController {
115
25
  strangeLoop;
116
26
  healthMonitor;
117
27
  causalGraph;
118
- // Kuramoto CPG integration (ADR-032)
119
28
  kuramotoCPG;
120
29
  phaseExecutor;
121
30
  cpgStartPromise = null;
122
- // kv_store persistence
123
31
  db = null;
124
32
  persistCount = 0;
125
- static KV_NAMESPACE = 'time-crystal-metrics';
126
- static KV_KEY = 'time-crystal-snapshot';
127
- static KV_TTL = 86400; // 24 hours
128
- static PERSIST_INTERVAL = 20; // every 20 state changes
129
- // State
130
33
  running = false;
131
34
  observationTimer = null;
132
35
  observations = [];
@@ -134,7 +37,6 @@ export class TimeCrystalController {
134
37
  lattice;
135
38
  currentAttractor = 'stable';
136
39
  metricsHistory = [];
137
- // Statistics
138
40
  stats = {
139
41
  totalObservations: 0,
140
42
  totalOptimizations: 0,
@@ -152,15 +54,11 @@ export class TimeCrystalController {
152
54
  this.strangeLoop = strangeLoop;
153
55
  this.healthMonitor = healthMonitor;
154
56
  this.causalGraph = causalGraph;
155
- // Initialize phase executor (use provided or create default)
156
57
  this.phaseExecutor = phaseExecutor ?? new DefaultPhaseExecutor('time-crystal-executor');
157
- // Initialize Kuramoto CPG with config
158
58
  this.kuramotoCPG = createKuramotoCPG(this.config.cpgPhases, this.config.cpgConfig ? { ...DEFAULT_CPG_CONFIG, ...this.config.cpgConfig } : undefined);
159
- // Wire up CPG transition handler to execute phases and feed results back
160
59
  this.kuramotoCPG.setTransitionHandler(async (transition) => {
161
60
  await this.handleCPGTransition(transition);
162
61
  });
163
- // Wire up tick handler for observation sync
164
62
  this.kuramotoCPG.setTickHandler((state) => {
165
63
  this.emitEvent('crystal.cpg.tick', {
166
64
  time: state.time,
@@ -168,7 +66,6 @@ export class TimeCrystalController {
168
66
  orderParameter: state.orderParameter,
169
67
  });
170
68
  });
171
- // Initialize empty lattice
172
69
  this.lattice = {
173
70
  nodes: new Map(),
174
71
  dependencies: [],
@@ -176,13 +73,8 @@ export class TimeCrystalController {
176
73
  parallelGroups: [],
177
74
  lastOptimized: new Date(),
178
75
  };
179
- // Initialize default phases
180
76
  this.initializeDefaultPhases();
181
77
  }
182
- /**
183
- * Handle CPG phase transition - execute tests and feed results back
184
- * This is the REAL integration point between oscillators and test execution
185
- */
186
78
  async handleCPGTransition(transition) {
187
79
  this.stats.cpgTransitions++;
188
80
  await this.emitEvent('crystal.cpg.transition', {
@@ -193,17 +85,12 @@ export class TimeCrystalController {
193
85
  orderParameter: transition.orderParameter,
194
86
  timestamp: transition.timestamp,
195
87
  });
196
- // Execute the new phase's tests
197
88
  if (this.phaseExecutor.isReady()) {
198
89
  this.stats.cpgPhasesExecuted++;
199
90
  const result = await this.phaseExecutor.execute(transition.toPhase);
200
- // Feed quality result back to CPG for adaptive scheduling
201
91
  this.kuramotoCPG.recordPhaseResult(result);
202
- // Track quality failures
203
- if (!result.qualityMet) {
92
+ if (!result.qualityMet)
204
93
  this.stats.cpgQualityFailures++;
205
- }
206
- // Update time crystal phase stats
207
94
  const crystalPhase = this.phases.get(transition.toPhase.name.toLowerCase() + '-tests');
208
95
  if (crystalPhase) {
209
96
  crystalPhase.executionCount++;
@@ -215,29 +102,17 @@ export class TimeCrystalController {
215
102
  crystalPhase.executionCount;
216
103
  }
217
104
  await this.emitEvent('crystal.phase.completed', {
218
- phaseId: result.phaseId,
219
- phaseName: result.phaseName,
220
- passRate: result.passRate,
221
- qualityMet: result.qualityMet,
222
- duration: result.duration,
223
- testsRun: result.testsRun,
105
+ phaseId: result.phaseId, phaseName: result.phaseName,
106
+ passRate: result.passRate, qualityMet: result.qualityMet,
107
+ duration: result.duration, testsRun: result.testsRun,
224
108
  });
225
109
  }
226
110
  }
227
- // ==========================================================================
228
- // Lifecycle
229
- // ==========================================================================
230
- /**
231
- * Start the time crystal controller
232
- *
233
- * When useCPGScheduling is enabled, the Kuramoto CPG drives phase transitions
234
- * through coupled oscillator dynamics rather than external timers.
235
- */
111
+ // -- Lifecycle --------------------------------------------------------------
236
112
  start() {
237
113
  if (this.running || !this.config.enabled)
238
114
  return;
239
115
  this.running = true;
240
- // Start CPG-driven scheduling if enabled
241
116
  if (this.config.useCPGScheduling) {
242
117
  this.cpgStartPromise = this.kuramotoCPG.start();
243
118
  this.emitEvent('crystal.cpg.started', {
@@ -245,24 +120,14 @@ export class TimeCrystalController {
245
120
  phases: this.kuramotoCPG.getPhases().map((p) => p.name),
246
121
  });
247
122
  }
248
- // Also run observation cycle for metrics collection
249
- // (this runs alongside CPG, not instead of it)
250
123
  this.observationTimer = setInterval(() => this.runCycle(), this.config.observationIntervalMs);
251
- // Run initial cycle
252
124
  this.runCycle();
253
125
  }
254
- /**
255
- * Stop the time crystal controller
256
- */
257
126
  stop() {
258
- // Stop CPG
259
127
  if (this.config.useCPGScheduling && this.kuramotoCPG.isRunning()) {
260
128
  this.kuramotoCPG.stop();
261
- this.emitEvent('crystal.cpg.stopped', {
262
- stats: this.kuramotoCPG.getStats(),
263
- });
129
+ this.emitEvent('crystal.cpg.stopped', { stats: this.kuramotoCPG.getStats() });
264
130
  }
265
- // Stop observation timer
266
131
  if (this.observationTimer) {
267
132
  clearInterval(this.observationTimer);
268
133
  this.observationTimer = null;
@@ -270,58 +135,21 @@ export class TimeCrystalController {
270
135
  this.running = false;
271
136
  this.cpgStartPromise = null;
272
137
  }
273
- /**
274
- * Pause CPG scheduling (observation continues)
275
- */
276
- pauseCPG() {
277
- if (this.config.useCPGScheduling) {
278
- this.kuramotoCPG.pause();
279
- }
280
- }
281
- /**
282
- * Resume CPG scheduling
283
- */
284
- resumeCPG() {
285
- if (this.config.useCPGScheduling) {
286
- this.kuramotoCPG.resume();
287
- }
288
- }
289
- /**
290
- * Get the Kuramoto CPG instance for direct access
291
- */
292
- getCPG() {
293
- return this.kuramotoCPG;
294
- }
295
- /**
296
- * Get the phase executor instance
297
- */
298
- getPhaseExecutor() {
299
- return this.phaseExecutor;
300
- }
301
- /**
302
- * Check if running
303
- */
304
- isRunning() {
305
- return this.running;
306
- }
307
- // ==========================================================================
308
- // Main Cycle: Observe -> Predict -> Optimize -> Stabilize
309
- // ==========================================================================
310
- /**
311
- * Run one complete time crystal cycle
312
- */
138
+ pauseCPG() { if (this.config.useCPGScheduling)
139
+ this.kuramotoCPG.pause(); }
140
+ resumeCPG() { if (this.config.useCPGScheduling)
141
+ this.kuramotoCPG.resume(); }
142
+ getCPG() { return this.kuramotoCPG; }
143
+ getPhaseExecutor() { return this.phaseExecutor; }
144
+ isRunning() { return this.running; }
145
+ // -- Main Cycle -------------------------------------------------------------
313
146
  async runCycle() {
314
- // OBSERVE: Collect CI/CD timing metrics
315
147
  const observation = await this.observe();
316
148
  this.observations.push(observation);
317
149
  this.stats.totalObservations++;
318
- // Trim observations
319
- while (this.observations.length > 1000) {
150
+ while (this.observations.length > 1000)
320
151
  this.observations.shift();
321
- }
322
- // PREDICT: Forecast next phase based on history
323
152
  this.predictPhase();
324
- // OPTIMIZE: Adjust scheduling if auto-optimize is enabled
325
153
  if (this.config.autoOptimize) {
326
154
  const optimization = await this.optimize();
327
155
  if (optimization.type !== 'no_change') {
@@ -329,7 +157,6 @@ export class TimeCrystalController {
329
157
  await this.emitEvent('crystal.optimization.applied', { optimization });
330
158
  }
331
159
  }
332
- // STABILIZE: Move toward stable attractor if needed
333
160
  if (this.config.autoStabilize && observation.attractor !== 'stable') {
334
161
  const stabilization = await this.stabilize();
335
162
  if (stabilization.type !== 'no_action') {
@@ -338,504 +165,55 @@ export class TimeCrystalController {
338
165
  }
339
166
  }
340
167
  await this.emitEvent('crystal.observation', { observation });
341
- // Track state change for kv_store persistence
342
168
  this.maybePeristToKv();
343
169
  return observation;
344
170
  }
345
- // ==========================================================================
346
- // OBSERVE: Collect CI/CD Timing Metrics
347
- // ==========================================================================
348
- /**
349
- * OBSERVE phase: Collect current CI/CD execution metrics
350
- */
171
+ // -- Observe ----------------------------------------------------------------
351
172
  async observe() {
352
- const metrics = this.collectMetrics();
173
+ const metrics = collectMetrics(this.observations, this.config, this.healthMonitor, this.strangeLoop);
353
174
  this.metricsHistory.push(metrics);
354
- // Trim metrics history
355
- while (this.metricsHistory.length > 500) {
175
+ while (this.metricsHistory.length > 500)
356
176
  this.metricsHistory.shift();
357
- }
358
- // Detect current attractor state
359
- const attractor = this.detectAttractor(metrics);
360
- // Check for attractor transition
177
+ const attractor = detectAttractor(metrics, this.config, this.healthMonitor);
361
178
  if (attractor !== this.currentAttractor) {
362
- const previousAttractor = this.currentAttractor;
179
+ const prev = this.currentAttractor;
363
180
  this.currentAttractor = attractor;
364
181
  this.stats.attractorTransitions++;
365
- await this.emitEvent('crystal.attractor.changed', {
366
- from: previousAttractor,
367
- to: attractor,
368
- });
182
+ await this.emitEvent('crystal.attractor.changed', { from: prev, to: attractor });
369
183
  }
370
- // Identify active phases
371
184
  const activePhases = this.identifyActivePhases();
372
- // Detect anomalies
373
- const anomalies = this.detectAnomalies(metrics);
185
+ const anomalies = detectAnomalies(metrics, this.metricsHistory, this.phases, this.config, this.causalGraph);
374
186
  for (const anomaly of anomalies) {
375
187
  this.stats.anomaliesDetected++;
376
188
  await this.emitEvent('crystal.anomaly.detected', { anomaly });
377
189
  }
378
- // Predict next phase
379
- const { phase: predictedNextPhase, confidence: predictionConfidence } = this.predictPhase();
190
+ const { phase: predictedNextPhase, confidence: predictionConfidence } = predictPhase(this.observations, this.config.minObservationsForPattern);
380
191
  return {
381
- id: uuidv4(),
382
- timestamp: new Date(),
383
- attractor,
384
- metrics,
385
- activePhases,
386
- anomalies,
387
- predictedNextPhase,
388
- predictionConfidence,
192
+ id: uuidv4(), timestamp: new Date(), attractor, metrics, activePhases,
193
+ anomalies, predictedNextPhase, predictionConfidence,
389
194
  };
390
195
  }
391
- /**
392
- * Collect current execution metrics
393
- */
394
- collectMetrics() {
395
- // Get health metrics if available
396
- const health = this.healthMonitor?.getHealth();
397
- // Get Strange Loop stats if available
398
- const loopStats = this.strangeLoop?.getStats();
399
- // Calculate throughput from recent observations
400
- const recentObs = this.observations.slice(-10);
401
- const recentMetrics = recentObs.map(o => o.metrics);
402
- const avgTestCount = recentMetrics.length > 0
403
- ? recentMetrics.reduce((s, m) => s + m.testCount, 0) / recentMetrics.length
404
- : 0;
405
- const avgBuildDuration = recentMetrics.length > 0
406
- ? recentMetrics.reduce((s, m) => s + m.avgBuildDuration, 0) / recentMetrics.length
407
- : 0;
408
- // Estimate resource utilization from health status
409
- let resourceUtilization = 0.5;
410
- if (health) {
411
- resourceUtilization = health.status === 'healthy' ? 0.6 :
412
- health.status === 'warning' ? 0.8 : 0.95;
413
- }
414
- return {
415
- timestamp: new Date(),
416
- buildCount: loopStats?.totalCycles ?? 1,
417
- successfulBuilds: loopStats?.successfulActions ?? 1,
418
- testCount: Math.floor(avgTestCount * 1.1), // Slight increase for current window
419
- testsPassed: Math.floor(avgTestCount * 0.9),
420
- testsFailed: Math.floor(avgTestCount * 0.1),
421
- avgBuildDuration: avgBuildDuration || 30000,
422
- avgTestDuration: 5000,
423
- resourceUtilization,
424
- queueDepth: health?.weakVertexCount ?? 0,
425
- throughput: avgTestCount / (this.config.observationIntervalMs / 60000),
426
- };
427
- }
428
- /**
429
- * Detect the current attractor state
430
- */
431
- detectAttractor(metrics) {
432
- const passRate = metrics.testCount > 0
433
- ? metrics.testsPassed / metrics.testCount
434
- : 1;
435
- const buildSuccessRate = metrics.buildCount > 0
436
- ? metrics.successfulBuilds / metrics.buildCount
437
- : 1;
438
- // Get health info if available
439
- const health = this.healthMonitor?.getHealth();
440
- const healthFactor = health
441
- ? (health.status === 'healthy' ? 1 : health.status === 'warning' ? 0.7 : 0.3)
442
- : 0.8;
443
- // Combined stability score
444
- const stabilityScore = (passRate * 0.4 + buildSuccessRate * 0.3 + healthFactor * 0.3);
445
- if (stabilityScore >= this.config.stabilityThreshold) {
446
- return 'stable';
447
- }
448
- else if (stabilityScore >= this.config.stabilityThreshold * 0.5) {
449
- return 'degraded';
450
- }
451
- else {
452
- return 'chaotic';
453
- }
454
- }
455
- /**
456
- * Identify currently active phases
457
- */
458
196
  identifyActivePhases() {
459
- const activePhases = [];
460
- for (const [id, phase] of Array.from(this.phases.entries())) {
461
- if (phase.state === 'active' || phase.state === 'activating') {
462
- activePhases.push(id);
463
- }
464
- }
465
- return activePhases;
466
- }
467
- /**
468
- * Detect anomalies in the metrics
469
- */
470
- detectAnomalies(metrics) {
471
- const anomalies = [];
472
- // Check for throughput drop
473
- if (this.metricsHistory.length >= 5) {
474
- const recent = this.metricsHistory.slice(-5);
475
- const older = this.metricsHistory.slice(-10, -5);
476
- if (older.length >= 5) {
477
- const recentAvg = recent.reduce((s, m) => s + m.throughput, 0) / recent.length;
478
- const olderAvg = older.reduce((s, m) => s + m.throughput, 0) / older.length;
479
- if (recentAvg < olderAvg * (1 - this.config.anomalySensitivity * 0.5)) {
480
- anomalies.push({
481
- type: 'throughput-drop',
482
- severity: Math.min(1, (olderAvg - recentAvg) / olderAvg),
483
- affected: [],
484
- description: `Throughput dropped from ${olderAvg.toFixed(1)} to ${recentAvg.toFixed(1)} items/min`,
485
- suggestion: 'Consider increasing parallelism or checking for resource contention',
486
- });
487
- }
488
- }
489
- }
490
- // Check for resource contention
491
- if (metrics.resourceUtilization > 0.9) {
492
- anomalies.push({
493
- type: 'resource-contention',
494
- severity: metrics.resourceUtilization,
495
- affected: [],
496
- description: `High resource utilization: ${(metrics.resourceUtilization * 100).toFixed(0)}%`,
497
- suggestion: 'Reduce parallelism or scale up resources',
498
- });
499
- }
500
- // Check for cascade failures using causal graph
501
- if (this.causalGraph) {
502
- const recentFailures = this.causalGraph.getAllFailures()
503
- .filter(f => Date.now() - f.timestamp.getTime() < this.config.phaseDetectionWindowMs);
504
- if (recentFailures.length >= 5) {
505
- // Check if failures are cascading
506
- for (const failure of recentFailures.slice(0, 3)) {
507
- const effects = this.causalGraph.getEffects(failure.id);
508
- if (effects.length >= 3) {
509
- anomalies.push({
510
- type: 'cascade-failure',
511
- severity: Math.min(1, effects.length / 10),
512
- affected: [failure.testId, ...effects.map(e => e.testId)],
513
- description: `Test ${failure.testName} is causing ${effects.length} cascading failures`,
514
- suggestion: 'Isolate the root cause test and fix before continuing',
515
- });
516
- }
517
- }
518
- }
519
- }
520
- // Check for phase drift
521
- for (const [id, phase] of Array.from(this.phases.entries())) {
522
- if (phase.executionCount >= 5 && phase.avgActualDuration > phase.expectedDuration * 1.5) {
523
- anomalies.push({
524
- type: 'phase-drift',
525
- severity: Math.min(1, (phase.avgActualDuration - phase.expectedDuration) / phase.expectedDuration),
526
- affected: [id],
527
- description: `Phase ${phase.name} is taking ${((phase.avgActualDuration / phase.expectedDuration - 1) * 100).toFixed(0)}% longer than expected`,
528
- suggestion: 'Review tests in this phase for performance issues',
529
- });
530
- }
531
- }
532
- return anomalies;
533
- }
534
- // ==========================================================================
535
- // PREDICT: Forecast Next Phase
536
- // ==========================================================================
537
- /**
538
- * PREDICT phase: Forecast the next phase based on history
539
- */
540
- predictPhase() {
541
- if (this.observations.length < this.config.minObservationsForPattern) {
542
- return { phase: undefined, confidence: 0 };
543
- }
544
- // Analyze phase transition patterns
545
- const transitions = new Map();
546
- for (let i = 1; i < this.observations.length; i++) {
547
- const prev = this.observations[i - 1].activePhases;
548
- const curr = this.observations[i].activePhases;
549
- for (const prevPhase of prev) {
550
- for (const currPhase of curr) {
551
- if (!transitions.has(prevPhase)) {
552
- transitions.set(prevPhase, new Map());
553
- }
554
- const count = transitions.get(prevPhase).get(currPhase) || 0;
555
- transitions.get(prevPhase).set(currPhase, count + 1);
556
- }
557
- }
558
- }
559
- // Find most likely next phase
560
- const currentPhases = this.observations[this.observations.length - 1]?.activePhases || [];
561
- let bestNext;
562
- let bestCount = 0;
563
- let totalCount = 0;
564
- for (const currentPhase of currentPhases) {
565
- const nextTransitions = transitions.get(currentPhase);
566
- if (nextTransitions) {
567
- for (const [next, count] of Array.from(nextTransitions.entries())) {
568
- totalCount += count;
569
- if (count > bestCount) {
570
- bestCount = count;
571
- bestNext = next;
572
- }
573
- }
574
- }
575
- }
576
- const confidence = totalCount > 0 ? bestCount / totalCount : 0;
577
- return { phase: bestNext, confidence };
578
- }
579
- // ==========================================================================
580
- // OPTIMIZE: Adjust Scheduling
581
- // ==========================================================================
582
- /**
583
- * OPTIMIZE phase: Adjust scheduling for optimal throughput
584
- */
585
- async optimize() {
586
- // Get current lattice state
587
- const lattice = this.lattice;
588
- // Check if optimization is needed
589
- const lastObs = this.observations[this.observations.length - 1];
590
- if (!lastObs || lastObs.attractor === 'stable' && lastObs.anomalies.length === 0) {
591
- return { type: 'no_change', reason: 'System is stable with no anomalies' };
592
- }
593
- // Analyze execution order for optimization
594
- const optimizedOrder = this.computeOptimalOrder();
595
- if (this.ordersDiffer(optimizedOrder, lattice.executionOrder)) {
596
- return { type: 'reorder', newOrder: optimizedOrder };
597
- }
598
- // Analyze parallelization opportunities
599
- const parallelGroups = this.computeParallelGroups();
600
- if (parallelGroups.length > lattice.parallelGroups.length) {
601
- return { type: 'parallelize', groups: parallelGroups };
602
- }
603
- // Check for flaky tests to skip
604
- if (this.causalGraph) {
605
- const recentFailures = this.causalGraph.getAllFailures()
606
- .filter(f => Date.now() - f.timestamp.getTime() < 3600000); // Last hour
607
- const failureCounts = new Map();
608
- for (const failure of recentFailures) {
609
- const count = failureCounts.get(failure.testId) || 0;
610
- failureCounts.set(failure.testId, count + 1);
611
- }
612
- // Find repeatedly failing tests
613
- for (const [testId, count] of Array.from(failureCounts.entries())) {
614
- if (count >= 3) {
615
- return {
616
- type: 'skip',
617
- nodeId: testId,
618
- reason: `Test has failed ${count} times in the last hour`,
619
- };
620
- }
621
- }
622
- }
623
- return { type: 'no_change', reason: 'No optimization opportunities found' };
624
- }
625
- /**
626
- * Compute optimal execution order
627
- */
628
- computeOptimalOrder() {
629
- const nodes = Array.from(this.lattice.nodes.values());
630
- // Sort by priority (higher first), then by execution time (shorter first)
631
- nodes.sort((a, b) => {
632
- if (b.priority !== a.priority) {
633
- return b.priority - a.priority;
634
- }
635
- return a.avgExecutionTime - b.avgExecutionTime;
636
- });
637
- // Apply topological sort for dependencies
638
- return this.topologicalSort(nodes.map(n => n.id));
639
- }
640
- /**
641
- * Topological sort respecting dependencies
642
- */
643
- topologicalSort(nodeIds) {
644
- const inDegree = new Map();
645
- const adjacency = new Map();
646
- // Initialize
647
- for (const id of nodeIds) {
648
- inDegree.set(id, 0);
649
- adjacency.set(id, []);
650
- }
651
- // Build adjacency and in-degree from dependencies
652
- for (const dep of this.lattice.dependencies) {
653
- if (dep.type === 'must-precede' || dep.type === 'should-precede') {
654
- const targets = adjacency.get(dep.sourceId) || [];
655
- targets.push(dep.targetId);
656
- adjacency.set(dep.sourceId, targets);
657
- const degree = inDegree.get(dep.targetId) || 0;
658
- inDegree.set(dep.targetId, degree + 1);
659
- }
660
- }
661
- // Kahn's algorithm
662
- const queue = nodeIds.filter(id => (inDegree.get(id) || 0) === 0);
663
197
  const result = [];
664
- while (queue.length > 0) {
665
- const node = queue.shift();
666
- result.push(node);
667
- for (const neighbor of adjacency.get(node) || []) {
668
- const degree = (inDegree.get(neighbor) || 1) - 1;
669
- inDegree.set(neighbor, degree);
670
- if (degree === 0) {
671
- queue.push(neighbor);
672
- }
673
- }
674
- }
675
- // Add any remaining nodes (in case of cycles)
676
- for (const id of nodeIds) {
677
- if (!result.includes(id)) {
198
+ for (const [id, phase] of Array.from(this.phases.entries())) {
199
+ if (phase.state === 'active' || phase.state === 'activating')
678
200
  result.push(id);
679
- }
680
201
  }
681
202
  return result;
682
203
  }
683
- /**
684
- * Compute parallel execution groups
685
- */
686
- computeParallelGroups() {
687
- const groups = [];
688
- const scheduled = new Set();
689
- const nodes = Array.from(this.lattice.nodes.values());
690
- while (scheduled.size < nodes.length) {
691
- const group = [];
692
- for (const node of nodes) {
693
- if (scheduled.has(node.id))
694
- continue;
695
- // Check if all dependencies are satisfied
696
- const canSchedule = this.lattice.dependencies
697
- .filter(d => d.targetId === node.id && (d.type === 'must-precede' || d.type === 'should-precede'))
698
- .every(d => scheduled.has(d.sourceId));
699
- // Check for conflicts with current group
700
- const hasConflict = this.lattice.dependencies
701
- .filter(d => d.type === 'conflicts')
702
- .some(d => (d.sourceId === node.id && group.includes(d.targetId)) ||
703
- (d.targetId === node.id && group.includes(d.sourceId)));
704
- if (canSchedule && !hasConflict && group.length < this.config.maxParallelGroups) {
705
- group.push(node.id);
706
- }
707
- }
708
- if (group.length === 0) {
709
- // No progress - break to avoid infinite loop
710
- break;
711
- }
712
- groups.push(group);
713
- for (const id of group) {
714
- scheduled.add(id);
715
- }
716
- }
717
- return groups;
204
+ // -- Predict / Optimize / Stabilize -----------------------------------------
205
+ predictPhase() {
206
+ return predictPhase(this.observations, this.config.minObservationsForPattern);
718
207
  }
719
- /**
720
- * Check if two execution orders differ significantly
721
- */
722
- ordersDiffer(a, b) {
723
- if (a.length !== b.length)
724
- return true;
725
- let differences = 0;
726
- for (let i = 0; i < a.length; i++) {
727
- if (a[i] !== b[i]) {
728
- differences++;
729
- }
730
- }
731
- // Consider different if more than 20% of positions changed
732
- return differences > a.length * 0.2;
208
+ async optimize() {
209
+ return determineOptimization(this.lattice, this.observations, this.causalGraph, this.config.maxParallelGroups);
733
210
  }
734
- // ==========================================================================
735
- // STABILIZE: Move Toward Stable Attractor
736
- // ==========================================================================
737
- /**
738
- * STABILIZE phase: Apply actions to move toward stable attractor
739
- */
740
211
  async stabilize() {
741
- const lastObs = this.observations[this.observations.length - 1];
742
- if (!lastObs) {
743
- return { type: 'no_action', reason: 'No observations available' };
744
- }
745
- // Handle chaotic state
746
- if (lastObs.attractor === 'chaotic') {
747
- // Find flaky tests to isolate
748
- if (this.causalGraph) {
749
- const rootCauses = [];
750
- const failures = this.causalGraph.getAllFailures().slice(-20);
751
- for (const failure of failures) {
752
- const analyses = this.causalGraph.findRootCauses(failure.id);
753
- for (const analysis of analyses) {
754
- if (analysis.confidence > 0.7 && analysis.impact >= 3) {
755
- rootCauses.push(analysis.rootCauseTest);
756
- }
757
- }
758
- }
759
- if (rootCauses.length > 0) {
760
- return {
761
- type: 'isolate_flaky',
762
- testIds: Array.from(new Set(rootCauses)),
763
- };
764
- }
765
- }
766
- // Reduce parallelism to stabilize
767
- return { type: 'reduce_parallelism', by: 2 };
768
- }
769
- // Handle degraded state
770
- if (lastObs.attractor === 'degraded') {
771
- const metrics = lastObs.metrics;
772
- // Check for resource contention
773
- if (metrics.resourceUtilization > 0.85) {
774
- return { type: 'reduce_parallelism', by: 1 };
775
- }
776
- // Check for queue buildup
777
- if (metrics.queueDepth > 10) {
778
- return { type: 'throttle', durationMs: 5000 };
779
- }
780
- // Try warming caches
781
- return {
782
- type: 'warm_cache',
783
- cacheKeys: ['test-deps', 'build-artifacts', 'node-modules'],
784
- };
785
- }
786
- // System is stable - consider increasing parallelism if throughput is low
787
- const metrics = lastObs.metrics;
788
- if (metrics.resourceUtilization < 0.5 && metrics.throughput < 10) {
789
- return { type: 'increase_parallelism', by: 1 };
790
- }
791
- return { type: 'no_action', reason: 'System is stable' };
212
+ return determineStabilization(this.observations, this.causalGraph);
792
213
  }
793
- // ==========================================================================
794
- // Phase Management
795
- // ==========================================================================
796
- /**
797
- * Initialize default CI/CD phases
798
- */
214
+ // -- Phase Management -------------------------------------------------------
799
215
  initializeDefaultPhases() {
800
- const defaultPhases = [
801
- {
802
- id: 'unit-tests',
803
- name: 'Unit Tests',
804
- periodMs: 60000,
805
- offsetMs: 0,
806
- expectedDuration: 30000,
807
- optimalParallelism: 8,
808
- testTypes: ['unit'],
809
- },
810
- {
811
- id: 'integration-tests',
812
- name: 'Integration Tests',
813
- periodMs: 120000,
814
- offsetMs: 30000,
815
- expectedDuration: 60000,
816
- optimalParallelism: 4,
817
- testTypes: ['integration'],
818
- },
819
- {
820
- id: 'e2e-tests',
821
- name: 'End-to-End Tests',
822
- periodMs: 300000,
823
- offsetMs: 90000,
824
- expectedDuration: 180000,
825
- optimalParallelism: 2,
826
- testTypes: ['e2e', 'visual'],
827
- },
828
- {
829
- id: 'performance-tests',
830
- name: 'Performance Tests',
831
- periodMs: 600000,
832
- offsetMs: 270000,
833
- expectedDuration: 120000,
834
- optimalParallelism: 1,
835
- testTypes: ['performance', 'load'],
836
- },
837
- ];
838
- for (const phaseConfig of defaultPhases) {
216
+ for (const phaseConfig of DEFAULT_CRYSTAL_PHASES) {
839
217
  this.phases.set(phaseConfig.id, {
840
218
  ...phaseConfig,
841
219
  state: 'dormant',
@@ -845,9 +223,6 @@ export class TimeCrystalController {
845
223
  });
846
224
  }
847
225
  }
848
- /**
849
- * Activate a phase
850
- */
851
226
  activatePhase(phaseId) {
852
227
  const phase = this.phases.get(phaseId);
853
228
  if (!phase || phase.state !== 'dormant')
@@ -855,267 +230,85 @@ export class TimeCrystalController {
855
230
  phase.state = 'activating';
856
231
  phase.lastActivation = new Date();
857
232
  this.emitEvent('crystal.phase.activated', { phaseId, phase });
858
- // Transition to active after brief activation period
859
- setTimeout(() => {
860
- if (phase.state === 'activating') {
861
- phase.state = 'active';
862
- }
863
- }, 1000);
233
+ setTimeout(() => { if (phase.state === 'activating')
234
+ phase.state = 'active'; }, 1000);
864
235
  return true;
865
236
  }
866
- /**
867
- * Complete a phase
868
- */
869
237
  completePhase(phaseId, success, actualDuration) {
870
238
  const phase = this.phases.get(phaseId);
871
239
  if (!phase || phase.state !== 'active')
872
240
  return false;
873
241
  phase.state = 'completing';
874
242
  phase.executionCount++;
875
- // Update running averages
876
243
  phase.avgActualDuration = (phase.avgActualDuration * (phase.executionCount - 1) + actualDuration) / phase.executionCount;
877
244
  phase.successRate = (phase.successRate * (phase.executionCount - 1) + (success ? 1 : 0)) / phase.executionCount;
878
245
  this.stats.phasesCompleted++;
879
- this.emitEvent('crystal.phase.completed', {
880
- phaseId,
881
- success,
882
- actualDuration,
883
- successRate: phase.successRate,
884
- });
885
- // Transition to cooldown then dormant
246
+ this.emitEvent('crystal.phase.completed', { phaseId, success, actualDuration, successRate: phase.successRate });
886
247
  setTimeout(() => {
887
248
  if (phase.state === 'completing') {
888
249
  phase.state = 'cooldown';
889
- setTimeout(() => {
890
- if (phase.state === 'cooldown') {
891
- phase.state = 'dormant';
892
- }
893
- }, 5000);
250
+ setTimeout(() => { if (phase.state === 'cooldown')
251
+ phase.state = 'dormant'; }, 5000);
894
252
  }
895
253
  }, 1000);
896
254
  return true;
897
255
  }
898
- /**
899
- * Get a phase by ID
900
- */
901
- getPhase(phaseId) {
902
- return this.phases.get(phaseId);
903
- }
904
- /**
905
- * Get all phases
906
- */
907
- getAllPhases() {
908
- return Array.from(this.phases.values());
909
- }
910
- // ==========================================================================
911
- // Lattice Management
912
- // ==========================================================================
913
- /**
914
- * Add a node to the lattice
915
- */
916
- addLatticeNode(node) {
917
- this.lattice.nodes.set(node.id, node);
918
- }
919
- /**
920
- * Add a dependency to the lattice
921
- */
922
- addDependency(dependency) {
923
- this.lattice.dependencies.push(dependency);
924
- }
925
- /**
926
- * Get the current lattice
927
- */
928
- getLattice() {
929
- return this.lattice;
930
- }
931
- /**
932
- * Rebuild the lattice from causal graph
933
- */
256
+ getPhase(phaseId) { return this.phases.get(phaseId); }
257
+ getAllPhases() { return Array.from(this.phases.values()); }
258
+ // -- Lattice Management -----------------------------------------------------
259
+ addLatticeNode(node) { this.lattice.nodes.set(node.id, node); }
260
+ addDependency(dependency) { this.lattice.dependencies.push(dependency); }
261
+ getLattice() { return this.lattice; }
934
262
  rebuildLatticeFromTestFailureCausalGraph() {
935
263
  if (!this.causalGraph)
936
264
  return;
937
- const failures = this.causalGraph.getAllFailures();
938
- const testIds = new Set();
939
- for (const failure of failures) {
940
- testIds.add(failure.testId);
941
- }
942
- const testIdArray = Array.from(testIds);
943
- // Create nodes for each test
944
- for (const testId of testIdArray) {
945
- const testFailures = failures.filter(f => f.testId === testId);
946
- const avgDuration = 5000; // Default
947
- const failureRate = testFailures.length / Math.max(1, failures.length / testIds.size);
948
- this.lattice.nodes.set(testId, {
949
- id: testId,
950
- type: 'test',
951
- avgExecutionTime: avgDuration,
952
- failureProbability: Math.min(1, failureRate),
953
- priority: 1 - failureRate, // Lower priority for flaky tests
954
- resources: { cpu: 1, memory: 256, io: 1 },
955
- });
956
- }
957
- // Build dependencies from causal links
958
- for (const testId of testIdArray) {
959
- const testFailures = failures.filter(f => f.testId === testId);
960
- for (const failure of testFailures) {
961
- const effects = this.causalGraph.getEffects(failure.id);
962
- for (const effect of effects) {
963
- if (testId !== effect.testId) {
964
- // Check if dependency already exists
965
- const existing = this.lattice.dependencies.find(d => d.sourceId === testId && d.targetId === effect.testId);
966
- if (existing) {
967
- existing.observationCount++;
968
- }
969
- else {
970
- this.lattice.dependencies.push({
971
- sourceId: testId,
972
- targetId: effect.testId,
973
- type: 'should-precede',
974
- strength: 0.5,
975
- latencyMs: effect.timestamp.getTime() - failure.timestamp.getTime(),
976
- observationCount: 1,
977
- });
978
- }
979
- }
980
- }
981
- }
982
- }
983
- // Recompute execution order and parallel groups
984
- this.lattice.executionOrder = this.computeOptimalOrder();
985
- this.lattice.parallelGroups = this.computeParallelGroups();
986
- this.lattice.lastOptimized = new Date();
987
- }
988
- // ==========================================================================
989
- // Status & Statistics
990
- // ==========================================================================
991
- /**
992
- * Get current attractor state
993
- */
994
- getCurrentAttractor() {
995
- return this.currentAttractor;
996
- }
997
- /**
998
- * Get recent observations
999
- */
1000
- getObservations(limit = 10) {
1001
- return this.observations.slice(-limit);
1002
- }
1003
- /**
1004
- * Get statistics
1005
- */
1006
- getStats() {
1007
- return { ...this.stats };
1008
- }
1009
- /**
1010
- * Get configuration
1011
- */
1012
- getConfig() {
1013
- return { ...this.config };
1014
- }
1015
- // ==========================================================================
1016
- // kv_store Persistence
1017
- // ==========================================================================
1018
- /**
1019
- * Initialize kv_store persistence and restore snapshot if available
1020
- */
265
+ rebuildLatticeFromCausalGraph(this.lattice, this.causalGraph, this.config.maxParallelGroups);
266
+ }
267
+ // -- Status & Statistics ----------------------------------------------------
268
+ getCurrentAttractor() { return this.currentAttractor; }
269
+ getObservations(limit = 10) { return this.observations.slice(-limit); }
270
+ getStats() { return { ...this.stats }; }
271
+ getConfig() { return { ...this.config }; }
272
+ // -- kv_store Persistence ---------------------------------------------------
1021
273
  async initializeDb() {
1022
- try {
1023
- this.db = getUnifiedMemory();
1024
- if (!this.db.isInitialized())
1025
- await this.db.initialize();
1026
- await this.loadFromKv();
1027
- }
1028
- catch (error) {
1029
- console.warn('[TimeCrystalController] DB init failed, using memory-only:', toErrorMessage(error));
1030
- this.db = null;
274
+ this.db = await initializeTimeCrystalDb();
275
+ if (this.db) {
276
+ const snapshot = await loadTimeCrystalFromKv(this.db);
277
+ if (snapshot)
278
+ this.applySnapshot(snapshot);
1031
279
  }
1032
280
  }
1033
- /**
1034
- * Persist current state snapshot to kv_store
1035
- */
1036
- async persistToKv() {
1037
- if (!this.db)
1038
- return;
1039
- const snapshot = {
1040
- observations: this.observations.slice(-100),
1041
- phases: Array.from(this.phases.entries()),
1042
- metricsHistory: this.metricsHistory.slice(-100),
1043
- currentAttractor: this.currentAttractor,
1044
- stats: this.stats,
1045
- savedAt: Date.now(),
1046
- };
1047
- await this.db.kvSet(TimeCrystalController.KV_KEY, snapshot, TimeCrystalController.KV_NAMESPACE, TimeCrystalController.KV_TTL);
1048
- }
1049
- /**
1050
- * Load state snapshot from kv_store
1051
- */
1052
- async loadFromKv() {
1053
- if (!this.db)
1054
- return;
1055
- const snapshot = await this.db.kvGet(TimeCrystalController.KV_KEY, TimeCrystalController.KV_NAMESPACE);
1056
- if (!snapshot)
1057
- return;
1058
- // Restore observations (convert Date strings back to Date objects)
1059
- if (snapshot.observations?.length) {
1060
- this.observations = snapshot.observations.map(obs => ({
1061
- ...obs,
1062
- timestamp: new Date(obs.timestamp),
1063
- metrics: { ...obs.metrics, timestamp: new Date(obs.metrics.timestamp) },
1064
- }));
1065
- }
1066
- // Restore phases Map
281
+ applySnapshot(snapshot) {
282
+ if (snapshot.observations?.length)
283
+ this.observations = snapshot.observations;
1067
284
  if (snapshot.phases?.length) {
1068
- for (const [key, phase] of snapshot.phases) {
1069
- this.phases.set(key, {
1070
- ...phase,
1071
- lastActivation: phase.lastActivation ? new Date(phase.lastActivation) : undefined,
1072
- });
1073
- }
1074
- }
1075
- // Restore metrics history
1076
- if (snapshot.metricsHistory?.length) {
1077
- this.metricsHistory = snapshot.metricsHistory.map(m => ({
1078
- ...m,
1079
- timestamp: new Date(m.timestamp),
1080
- }));
285
+ for (const [key, phase] of snapshot.phases)
286
+ this.phases.set(key, phase);
1081
287
  }
1082
- // Restore attractor and stats
1083
- if (snapshot.currentAttractor) {
288
+ if (snapshot.metricsHistory?.length)
289
+ this.metricsHistory = snapshot.metricsHistory;
290
+ if (snapshot.currentAttractor)
1084
291
  this.currentAttractor = snapshot.currentAttractor;
1085
- }
1086
- if (snapshot.stats) {
292
+ if (snapshot.stats)
1087
293
  Object.assign(this.stats, snapshot.stats);
1088
- }
1089
294
  }
1090
- /**
1091
- * Track state changes and persist periodically
1092
- */
1093
295
  maybePeristToKv() {
1094
296
  this.persistCount++;
1095
- if (this.persistCount >= TimeCrystalController.PERSIST_INTERVAL) {
297
+ if (this.persistCount >= PERSIST_INTERVAL) {
1096
298
  this.persistCount = 0;
1097
- this.persistToKv().catch(() => { });
299
+ if (this.db) {
300
+ persistTimeCrystalToKv(this.db, this.observations, this.phases, this.metricsHistory, this.currentAttractor, this.stats).catch(() => { });
301
+ }
1098
302
  }
1099
303
  }
1100
- // ==========================================================================
1101
- // Event Emission
1102
- // ==========================================================================
1103
- /**
1104
- * Emit a time crystal event
1105
- */
304
+ // -- Event Emission ---------------------------------------------------------
1106
305
  async emitEvent(type, payload) {
1107
306
  if (!this.eventBus)
1108
307
  return;
1109
308
  const event = {
1110
- id: uuidv4(),
1111
- type,
1112
- source: TIME_CRYSTAL_SOURCE,
1113
- timestamp: new Date(),
1114
- correlationId: uuidv4(),
1115
- payload: {
1116
- attractor: this.currentAttractor,
1117
- ...payload,
1118
- },
309
+ id: uuidv4(), type, source: TIME_CRYSTAL_SOURCE,
310
+ timestamp: new Date(), correlationId: uuidv4(),
311
+ payload: { attractor: this.currentAttractor, ...payload },
1119
312
  };
1120
313
  try {
1121
314
  await this.eventBus.publish(event);
@@ -1125,19 +318,8 @@ export class TimeCrystalController {
1125
318
  }
1126
319
  }
1127
320
  }
1128
- // ============================================================================
1129
- // Factory Functions
1130
- // ============================================================================
1131
- /**
1132
- * Create a Time Crystal controller
1133
- */
321
+ /** Create a Time Crystal controller */
1134
322
  export function createTimeCrystalController(config, eventBus, strangeLoop, healthMonitor, causalGraph, phaseExecutor) {
1135
323
  return new TimeCrystalController(config, eventBus, strangeLoop, healthMonitor, causalGraph, phaseExecutor);
1136
324
  }
1137
- /**
1138
- * Create a default phase executor
1139
- */
1140
- export function createDefaultPhaseExecutor(name) {
1141
- return new DefaultPhaseExecutor(name);
1142
- }
1143
325
  //# sourceMappingURL=time-crystal.js.map