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.
- package/.claude/skills/skills-manifest.json +1 -1
- package/CHANGELOG.md +26 -0
- package/assets/governance/constitution.md +1 -1
- package/assets/governance/shards/chaos-resilience.shard.md +1 -1
- package/assets/governance/shards/code-intelligence.shard.md +1 -1
- package/assets/governance/shards/contract-testing.shard.md +1 -1
- package/assets/governance/shards/coverage-analysis.shard.md +1 -1
- package/assets/governance/shards/defect-intelligence.shard.md +1 -1
- package/assets/governance/shards/learning-optimization.shard.md +1 -1
- package/assets/governance/shards/quality-assessment.shard.md +1 -1
- package/assets/governance/shards/requirements-validation.shard.md +1 -1
- package/assets/governance/shards/security-compliance.shard.md +1 -1
- package/assets/governance/shards/test-execution.shard.md +1 -1
- package/assets/governance/shards/test-generation.shard.md +1 -1
- package/assets/governance/shards/visual-accessibility.shard.md +1 -1
- package/dist/cli/bundle.js +715 -643
- package/dist/cli/command-registry.js +3 -1
- package/dist/cli/completions/index.d.ts +17 -0
- package/dist/cli/completions/index.js +49 -1
- package/dist/cli/handlers/hypergraph-handler.d.ts +27 -0
- package/dist/cli/handlers/hypergraph-handler.js +248 -0
- package/dist/cli/handlers/index.d.ts +1 -0
- package/dist/cli/handlers/index.js +1 -0
- package/dist/coordination/mincut/phase-executor.d.ts +27 -0
- package/dist/coordination/mincut/phase-executor.js +70 -0
- package/dist/coordination/mincut/time-crystal-analysis.d.ts +35 -0
- package/dist/coordination/mincut/time-crystal-analysis.js +237 -0
- package/dist/coordination/mincut/time-crystal-persistence.d.ts +35 -0
- package/dist/coordination/mincut/time-crystal-persistence.js +81 -0
- package/dist/coordination/mincut/time-crystal-scheduling.d.ts +34 -0
- package/dist/coordination/mincut/time-crystal-scheduling.js +213 -0
- package/dist/coordination/mincut/time-crystal-types.d.ts +278 -0
- package/dist/coordination/mincut/time-crystal-types.js +67 -0
- package/dist/coordination/mincut/time-crystal.d.ts +8 -438
- package/dist/coordination/mincut/time-crystal.js +87 -905
- package/dist/coordination/protocols/code-intelligence-index.js +2 -11
- package/dist/domains/code-intelligence/coordinator-hypergraph.js +1 -1
- package/dist/domains/code-intelligence/coordinator.d.ts +5 -0
- package/dist/domains/code-intelligence/coordinator.js +35 -3
- package/dist/init/phases/06-code-intelligence.d.ts +8 -3
- package/dist/init/phases/06-code-intelligence.js +70 -32
- package/dist/learning/agent-routing.d.ts +53 -0
- package/dist/learning/agent-routing.js +142 -0
- package/dist/learning/embedding-utils.d.ts +34 -0
- package/dist/learning/embedding-utils.js +95 -0
- package/dist/learning/pattern-promotion.d.ts +63 -0
- package/dist/learning/pattern-promotion.js +187 -0
- package/dist/learning/pretrained-patterns.d.ts +14 -0
- package/dist/learning/pretrained-patterns.js +726 -0
- package/dist/learning/qe-reasoning-bank-types.d.ts +174 -0
- package/dist/learning/qe-reasoning-bank-types.js +24 -0
- package/dist/learning/qe-reasoning-bank.d.ts +9 -192
- package/dist/learning/qe-reasoning-bank.js +48 -1093
- package/dist/mcp/bundle.js +1084 -1083
- package/dist/mcp/handlers/hypergraph-handler.d.ts +27 -0
- package/dist/mcp/handlers/hypergraph-handler.js +140 -0
- package/dist/mcp/handlers/index.d.ts +1 -0
- package/dist/mcp/handlers/index.js +2 -0
- package/dist/mcp/server.js +19 -0
- package/dist/mcp/tool-scoping.js +5 -0
- package/dist/shared/code-index-extractor.d.ts +23 -0
- package/dist/shared/code-index-extractor.js +101 -0
- package/dist/shared/security/command-validator.js +2 -2
- package/dist/shared/security/input-sanitizer.js +1 -1
- package/dist/shared/security/path-traversal-validator.js +1 -1
- package/dist/shared/security/regex-safety-validator.js +7 -7
- 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
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
220
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
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.
|
|
173
|
+
const metrics = collectMetrics(this.observations, this.config, this.healthMonitor, this.strangeLoop);
|
|
353
174
|
this.metricsHistory.push(metrics);
|
|
354
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
665
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
859
|
-
|
|
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
|
-
|
|
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
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
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
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
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
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1083
|
-
|
|
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 >=
|
|
297
|
+
if (this.persistCount >= PERSIST_INTERVAL) {
|
|
1096
298
|
this.persistCount = 0;
|
|
1097
|
-
this.
|
|
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
|
-
|
|
1112
|
-
|
|
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
|