codeflow-hook 1.4.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,622 @@
1
+ import {
2
+ PipelineConfig,
3
+ StageConfig,
4
+ SimulationResult,
5
+ SimulationContext,
6
+ StageExecution,
7
+ StageStatus,
8
+ StageMetrics,
9
+ PipelineMetrics,
10
+ ErrorInfo,
11
+ SimulationMode
12
+ } from './types.js';
13
+
14
+ /**
15
+ * Advanced Simulation Engine for configurable CI/CD pipelines
16
+ * Provides realistic simulation behaviors with probabilistic outcomes,
17
+ * resource usage simulation, and comprehensive error handling.
18
+ */
19
+ export class SimulationEngine {
20
+ private context: SimulationContext | null = null;
21
+
22
+ /**
23
+ * Execute a pipeline configuration with realistic simulation
24
+ */
25
+ async executePipeline(config: PipelineConfig): Promise<SimulationResult> {
26
+ const executionId = this.generateExecutionId();
27
+ const startTime = Date.now();
28
+
29
+ this.context = {
30
+ pipelineId: config.id,
31
+ executionId,
32
+ startTime,
33
+ config,
34
+ variables: { ...config.environment },
35
+ artifacts: new Map()
36
+ };
37
+
38
+ const logs: string[] = [];
39
+ const stageExecutions: StageExecution[] = [];
40
+
41
+ try {
42
+ logs.push(`[${new Date().toISOString()}] 🚀 Starting pipeline: ${config.name} (v${config.version})`);
43
+ logs.push(`[${new Date().toISOString()}] 📋 Execution ID: ${executionId}`);
44
+ logs.push(`[${new Date().toISOString()}] 🎯 Mode: ${config.settings.mode}`);
45
+
46
+ // Build dependency graph
47
+ const stages: StageConfig[] = config.stages || [];
48
+ const dependencyGraph = this.buildDependencyGraph(stages);
49
+ const executionOrder = this.resolveExecutionOrder(dependencyGraph, stages);
50
+
51
+ // Execute stages respecting dependencies and concurrency limits
52
+ const results = await this.executeStagesWithDependencies(
53
+ executionOrder,
54
+ config,
55
+ logs
56
+ );
57
+
58
+ stageExecutions.push(...results);
59
+
60
+ // Calculate pipeline metrics
61
+ const metrics = this.calculatePipelineMetrics(stageExecutions, config);
62
+
63
+ // Determine overall status
64
+ const failedStages = stageExecutions.filter(s => s.status === StageStatus.Failed);
65
+
66
+ let status: SimulationResult['status'] = 'success';
67
+ if (failedStages.length > 0) {
68
+ status = config.settings.failFast ? 'failed' : 'partial';
69
+ }
70
+
71
+ const endTime = Date.now();
72
+ logs.push(`[${new Date().toISOString()}] ✅ Pipeline completed in ${(endTime - startTime) / 1000}s`);
73
+
74
+ return {
75
+ id: this.generateResultId(),
76
+ pipelineId: config.id,
77
+ executionId,
78
+ startTime: new Date(startTime),
79
+ endTime: new Date(endTime),
80
+ status,
81
+ stages: stageExecutions,
82
+ metrics,
83
+ artifacts: Array.from(this.context.artifacts.values()),
84
+ config,
85
+ logs
86
+ };
87
+
88
+ } catch (error) {
89
+ const errorMessage = error instanceof Error ? error.message : String(error);
90
+ logs.push(`[${new Date().toISOString()}] ❌ Pipeline failed: ${errorMessage}`);
91
+
92
+ return {
93
+ id: this.generateResultId(),
94
+ pipelineId: config.id,
95
+ executionId,
96
+ startTime: new Date(startTime),
97
+ endTime: new Date(),
98
+ status: 'failed',
99
+ stages: stageExecutions,
100
+ metrics: this.calculatePipelineMetrics(stageExecutions, config),
101
+ artifacts: Array.from(this.context.artifacts.values()),
102
+ config,
103
+ logs
104
+ };
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Execute stages respecting dependencies and concurrency limits
110
+ */
111
+ private async executeStagesWithDependencies(
112
+ executionOrder: StageConfig[][],
113
+ config: PipelineConfig,
114
+ logs: string[]
115
+ ): Promise<StageExecution[]> {
116
+ const results: StageExecution[] = [];
117
+ const completedStages = new Set<string>();
118
+ const runningStages = new Set<string>();
119
+
120
+ for (const level of executionOrder) {
121
+ // Execute stages in this level concurrently, respecting maxConcurrency
122
+ const levelPromises = level
123
+ .filter(stage => this.canExecuteStage(stage, completedStages))
124
+ .slice(0, config.settings.maxConcurrency)
125
+ .map(stage => this.executeStage(stage, config, logs));
126
+
127
+ const levelResults = await Promise.all(levelPromises);
128
+ results.push(...levelResults);
129
+
130
+ // Update completed stages
131
+ levelResults.forEach(result => {
132
+ if (result.status === StageStatus.Success || result.status === StageStatus.Failed) {
133
+ completedStages.add(result.id);
134
+ }
135
+ runningStages.delete(result.id);
136
+ });
137
+
138
+ // Handle fail-fast behavior
139
+ if (config.settings.failFast) {
140
+ const failedInLevel = levelResults.filter((r: StageExecution) => r.status === StageStatus.Failed);
141
+ if (failedInLevel.length > 0) {
142
+ // Mark remaining stages as skipped
143
+ const remainingStages = executionOrder
144
+ .slice(executionOrder.indexOf(level) + 1)
145
+ .flat()
146
+ .filter(stage => !completedStages.has(stage.id));
147
+
148
+ for (const stage of remainingStages) {
149
+ results.push({
150
+ id: stage.id,
151
+ status: StageStatus.Skipped,
152
+ logs: [`[${new Date().toISOString()}] ⏭️ Skipped due to previous failure`],
153
+ duration: 0
154
+ });
155
+ }
156
+ break;
157
+ }
158
+ }
159
+ }
160
+
161
+ return results;
162
+ }
163
+
164
+ /**
165
+ * Execute a single stage with realistic simulation
166
+ */
167
+ private async executeStage(
168
+ stageConfig: StageConfig,
169
+ pipelineConfig: PipelineConfig,
170
+ logs: string[]
171
+ ): Promise<StageExecution> {
172
+ const startTime = Date.now();
173
+ const stageLogs: string[] = [];
174
+ const errors: ErrorInfo[] = [];
175
+
176
+ stageLogs.push(`[${new Date().toISOString()}] 🎬 Starting stage: ${stageConfig.name}`);
177
+
178
+ try {
179
+ // Simulate stage execution based on configuration
180
+ const result = await this.simulateStageExecution(stageConfig, pipelineConfig);
181
+
182
+ // Update logs and metrics
183
+ stageLogs.push(...result.logs);
184
+
185
+ if (result.errors) {
186
+ errors.push(...result.errors);
187
+ }
188
+
189
+ const duration = Date.now() - startTime;
190
+ const status = result.success ? StageStatus.Success : StageStatus.Failed;
191
+
192
+ stageLogs.push(`[${new Date().toISOString()}] ${result.success ? '✅' : '❌'} Stage ${stageConfig.name} completed in ${duration}ms`);
193
+
194
+ const executionResult: StageExecution = {
195
+ id: stageConfig.id,
196
+ status,
197
+ logs: stageLogs,
198
+ duration,
199
+ metrics: result.metrics
200
+ };
201
+
202
+ if (errors.length > 0) {
203
+ executionResult.errors = errors;
204
+ }
205
+
206
+ return executionResult;
207
+
208
+ } catch (error) {
209
+ const duration = Date.now() - startTime;
210
+ const errorMessage = error instanceof Error ? error.message : String(error);
211
+ stageLogs.push(`[${new Date().toISOString()}] 💥 Stage ${stageConfig.name} failed: ${errorMessage}`);
212
+
213
+ errors.push({
214
+ type: 'execution_error',
215
+ message: errorMessage,
216
+ timestamp: Date.now(),
217
+ recoverable: false,
218
+ context: { stageId: stageConfig.id }
219
+ });
220
+
221
+ return {
222
+ id: stageConfig.id,
223
+ status: StageStatus.Failed,
224
+ logs: stageLogs,
225
+ duration,
226
+ errors
227
+ };
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Simulate realistic stage execution with probabilistic outcomes
233
+ */
234
+ private async simulateStageExecution(
235
+ stageConfig: StageConfig,
236
+ pipelineConfig: PipelineConfig
237
+ ): Promise<{
238
+ success: boolean;
239
+ logs: string[];
240
+ metrics: StageMetrics;
241
+ errors?: ErrorInfo[];
242
+ }> {
243
+ // Calculate realistic duration based on simulation mode
244
+ const duration = this.calculateStageDuration(stageConfig, pipelineConfig);
245
+
246
+ // Simulate resource usage
247
+ const metrics = this.simulateResourceUsage(stageConfig, duration);
248
+
249
+ // Determine if stage should succeed based on success rate
250
+ const successRoll = Math.random();
251
+ const shouldSucceed = successRoll <= stageConfig.successRate;
252
+
253
+ // Simulate the stage behavior
254
+ switch (stageConfig.type) {
255
+ case 'trigger':
256
+ return this.simulateTriggerStage(stageConfig, shouldSucceed, duration, metrics);
257
+
258
+ case 'ai-review':
259
+ return this.simulateAiReviewStage(stageConfig, shouldSucceed, duration, metrics);
260
+
261
+ case 'docker-build':
262
+ return this.simulateDockerBuildStage(stageConfig, shouldSucceed, duration, metrics);
263
+
264
+ case 'unit-tests':
265
+ return this.simulateUnitTestStage(stageConfig, shouldSucceed, duration, metrics);
266
+
267
+ case 'deploy':
268
+ return this.simulateDeployStage(stageConfig, shouldSucceed, duration, metrics);
269
+
270
+ default:
271
+ return this.simulateGenericStage(stageConfig, shouldSucceed, duration, metrics);
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Calculate realistic stage duration based on configuration and mode
277
+ */
278
+ private calculateStageDuration(stageConfig: StageConfig, pipelineConfig: PipelineConfig): number {
279
+ const { min, max, baseMultiplier } = stageConfig.durationRange;
280
+
281
+ // Base duration calculation
282
+ let baseDuration = (min + max) / 2;
283
+
284
+ // Apply simulation mode multipliers
285
+ switch (pipelineConfig.settings.mode) {
286
+ case SimulationMode.Fast:
287
+ baseDuration *= 0.3;
288
+ break;
289
+ case SimulationMode.Realistic:
290
+ // Add some randomness
291
+ baseDuration *= (0.8 + Math.random() * 0.4);
292
+ break;
293
+ case SimulationMode.Chaotic:
294
+ // High variability
295
+ baseDuration *= (0.2 + Math.random() * 2.0);
296
+ break;
297
+ case SimulationMode.Deterministic:
298
+ // Consistent timing
299
+ break;
300
+ }
301
+
302
+ // Apply base multiplier (could be based on codebase size)
303
+ baseDuration *= baseMultiplier;
304
+
305
+ // Ensure within bounds
306
+ return Math.max(min, Math.min(max, baseDuration));
307
+ }
308
+
309
+ /**
310
+ * Simulate resource usage for a stage
311
+ */
312
+ private simulateResourceUsage(stageConfig: StageConfig, _duration: number): StageMetrics {
313
+ // Simulate realistic resource usage patterns
314
+ const baseCpu = 20 + Math.random() * 60; // 20-80% CPU
315
+ const baseMemory = 100 + Math.random() * 400; // 100-500MB
316
+
317
+ return {
318
+ cpuUsage: Math.round(baseCpu),
319
+ memoryUsage: Math.round(baseMemory),
320
+ networkIO: Math.round(_duration * (0.1 + Math.random() * 0.9)), // KB/s
321
+ diskIO: Math.round(_duration * (0.05 + Math.random() * 0.15)), // KB/s
322
+ duration: Math.round(_duration),
323
+ success: true // Will be overridden
324
+ };
325
+ }
326
+
327
+ /**
328
+ * Simulate trigger stage (git push detection)
329
+ */
330
+ private simulateTriggerStage(
331
+ stageConfig: StageConfig,
332
+ success: boolean,
333
+ duration: number,
334
+ metrics: StageMetrics
335
+ ): { success: boolean; logs: string[]; metrics: StageMetrics; errors?: ErrorInfo[] } {
336
+ const logs: string[] = [];
337
+
338
+ if (success) {
339
+ logs.push(`[${new Date().toISOString()}] 📡 Git push detected on branch \`${stageConfig.config.branch || 'main'}\``);
340
+ logs.push(`[${new Date().toISOString()}] 👤 Commit \`${stageConfig.config.commitMessage || 'feat: update codebase'}\` by \`${stageConfig.config.author || 'developer@example.com'}\``);
341
+ logs.push(`[${new Date().toISOString()}] ✅ Workflow triggered successfully`);
342
+ } else {
343
+ logs.push(`[${new Date().toISOString()}] ❌ Failed to detect git push - invalid webhook payload`);
344
+ }
345
+
346
+ return { success, logs, metrics };
347
+ }
348
+
349
+ /**
350
+ * Simulate AI review stage
351
+ */
352
+ private simulateAiReviewStage(
353
+ _stageConfig: StageConfig,
354
+ success: boolean,
355
+ _duration: number,
356
+ metrics: StageMetrics
357
+ ): { success: boolean; logs: string[]; metrics: StageMetrics; errors?: ErrorInfo[] } {
358
+ const logs: string[] = [];
359
+
360
+ logs.push(`[${new Date().toISOString()}] 🧠 Analyzing code changes...`);
361
+ logs.push(`[${new Date().toISOString()}] 📊 Processing ${_stageConfig.config.fileCount || 5} files`);
362
+
363
+ if (success) {
364
+ logs.push(`[${new Date().toISOString()}] ✅ Analysis complete - ${_stageConfig.config.issueCount || 0} issues found`);
365
+ logs.push(`[${new Date().toISOString()}] 📈 Code quality score: ${85 + Math.random() * 10}/100`);
366
+ } else {
367
+ logs.push(`[${new Date().toISOString()}] ❌ Analysis failed - API timeout`);
368
+ }
369
+
370
+ return { success, logs, metrics };
371
+ }
372
+
373
+ /**
374
+ * Simulate Docker build stage
375
+ */
376
+ private simulateDockerBuildStage(
377
+ stageConfig: StageConfig,
378
+ success: boolean,
379
+ _duration: number,
380
+ metrics: StageMetrics
381
+ ): { success: boolean; logs: string[]; metrics: StageMetrics; errors?: ErrorInfo[] } {
382
+ const logs: string[] = [];
383
+
384
+ logs.push(`[${new Date().toISOString()}] 🐳 Building Docker image \`${stageConfig.config.imageName || 'app:latest'}\``);
385
+ logs.push(`[${new Date().toISOString()}] 📦 Step 1/8 : FROM ${stageConfig.config.baseImage || 'node:18-alpine'}`);
386
+
387
+ if (success) {
388
+ logs.push(`[${new Date().toISOString()}] 📦 Step 8/8 : CMD ["npm", "start"]`);
389
+ logs.push(`[${new Date().toISOString()}] ✅ Successfully built ${stageConfig.config.imageId || 'a1b2c3d4e5f6'}`);
390
+ logs.push(`[${new Date().toISOString()}] 🔍 Image size: ${50 + Math.random() * 200}MB`);
391
+
392
+ // Create artifact
393
+ if (this.context) {
394
+ this.context.artifacts.set(`${stageConfig.id}-image`, {
395
+ name: `${stageConfig.config.imageName || 'app'}.tar.gz`,
396
+ type: 'docker-image',
397
+ size: Math.round(50 + Math.random() * 200) * 1024 * 1024,
398
+ path: `/artifacts/${stageConfig.id}`,
399
+ metadata: { imageId: stageConfig.config.imageId || 'a1b2c3d4e5f6' }
400
+ });
401
+ }
402
+ } else {
403
+ logs.push(`[${new Date().toISOString()}] ❌ Build failed at step ${Math.floor(Math.random() * 8) + 1}`);
404
+ }
405
+
406
+ return { success, logs, metrics };
407
+ }
408
+
409
+ /**
410
+ * Simulate unit test stage
411
+ */
412
+ private simulateUnitTestStage(
413
+ stageConfig: StageConfig,
414
+ success: boolean,
415
+ _duration: number,
416
+ metrics: StageMetrics
417
+ ): { success: boolean; logs: string[]; metrics: StageMetrics; errors?: ErrorInfo[] } {
418
+ const logs: string[] = [];
419
+
420
+ const testCount = stageConfig.config.testCount || 50;
421
+ logs.push(`[${new Date().toISOString()}] 🧪 Running ${testCount} unit tests`);
422
+
423
+ if (success) {
424
+ logs.push(`[${new Date().toISOString()}] ✅ Tests passed: ${testCount}/${testCount}`);
425
+ logs.push(`[${new Date().toISOString()}] 📊 Coverage: ${85 + Math.random() * 10}%`);
426
+ } else {
427
+ const failed = Math.floor(Math.random() * 5) + 1;
428
+ logs.push(`[${new Date().toISOString()}] ❌ Tests failed: ${failed}/${testCount}`);
429
+ logs.push(`[${new Date().toISOString()}] 🔍 Failed tests: ${Array.from({length: failed}, (_, i) => `test_${i + 1}`).join(', ')}`);
430
+ }
431
+
432
+ return { success, logs, metrics };
433
+ }
434
+
435
+ /**
436
+ * Simulate deploy stage
437
+ */
438
+ private simulateDeployStage(
439
+ stageConfig: StageConfig,
440
+ success: boolean,
441
+ _duration: number,
442
+ metrics: StageMetrics
443
+ ): { success: boolean; logs: string[]; metrics: StageMetrics; errors?: ErrorInfo[] } {
444
+ const logs: string[] = [];
445
+
446
+ logs.push(`[${new Date().toISOString()}] 🚀 Deploying to ${stageConfig.config.environment || 'staging'}`);
447
+ logs.push(`[${new Date().toISOString()}] ☸️ Applying Kubernetes deployment`);
448
+
449
+ if (success) {
450
+ logs.push(`[${new Date().toISOString()}] ✅ Deployment successful`);
451
+ logs.push(`[${new Date().toISOString()}] 🌐 Service available at ${stageConfig.config.url || 'https://staging.example.com'}`);
452
+ } else {
453
+ logs.push(`[${new Date().toISOString()}] ❌ Deployment failed - pod readiness timeout`);
454
+ }
455
+
456
+ return { success, logs, metrics };
457
+ }
458
+
459
+ /**
460
+ * Simulate generic stage
461
+ */
462
+ private simulateGenericStage(
463
+ stageConfig: StageConfig,
464
+ success: boolean,
465
+ _duration: number,
466
+ metrics: StageMetrics
467
+ ): { success: boolean; logs: string[]; metrics: StageMetrics; errors?: ErrorInfo[] } {
468
+ const logs: string[] = [];
469
+
470
+ logs.push(`[${new Date().toISOString()}] 🔧 Executing ${stageConfig.type} stage`);
471
+
472
+ if (success) {
473
+ logs.push(`[${new Date().toISOString()}] ✅ Stage completed successfully`);
474
+ } else {
475
+ logs.push(`[${new Date().toISOString()}] ❌ Stage failed`);
476
+ }
477
+
478
+ return { success, logs, metrics };
479
+ }
480
+
481
+ /**
482
+ * Build dependency graph from stage configurations
483
+ */
484
+ private buildDependencyGraph(stages: StageConfig[]): Map<string, string[]> {
485
+ const graph = new Map<string, string[]>();
486
+
487
+ for (const stage of stages) {
488
+ graph.set(stage.id, stage.dependencies || []);
489
+ }
490
+
491
+ return graph;
492
+ }
493
+
494
+ /**
495
+ * Resolve execution order respecting dependencies
496
+ */
497
+ private resolveExecutionOrder(dependencyGraph: Map<string, string[]>, stages: StageConfig[]): StageConfig[][] {
498
+ const visited = new Set<string>();
499
+ const visiting = new Set<string>();
500
+ const stageMap = new Map<string, StageConfig>();
501
+
502
+ // Build stage map for quick lookup
503
+ for (const stage of stages) {
504
+ stageMap.set(stage.id, stage);
505
+ }
506
+
507
+ // This is a simplified topological sort - in production you'd want a more robust implementation
508
+ const sorted: string[] = [];
509
+ const processStage = (stageId: string) => {
510
+ if (visited.has(stageId)) return;
511
+ if (visiting.has(stageId)) throw new Error(`Circular dependency detected: ${stageId}`);
512
+
513
+ visiting.add(stageId);
514
+
515
+ const dependencies = dependencyGraph.get(stageId) || [];
516
+ for (const dep of dependencies) {
517
+ processStage(dep);
518
+ }
519
+
520
+ visiting.delete(stageId);
521
+ visited.add(stageId);
522
+ sorted.push(stageId);
523
+ };
524
+
525
+ // Process all stages
526
+ for (const stageId of Array.from(dependencyGraph.keys())) {
527
+ if (!visited.has(stageId)) {
528
+ processStage(stageId);
529
+ }
530
+ }
531
+
532
+ // Group into levels (simplified - no parallel execution consideration)
533
+ const result: StageConfig[][] = [];
534
+ for (const stageId of sorted) {
535
+ const stage = stageMap.get(stageId);
536
+ if (stage) {
537
+ if (result.length === 0 || this.hasDependencyInPreviousLevel(stage, result[result.length - 1]!)) {
538
+ result.push([stage]);
539
+ } else {
540
+ result[result.length - 1]!.push(stage);
541
+ }
542
+ }
543
+ }
544
+
545
+ return result;
546
+ }
547
+
548
+ /**
549
+ * Check if stage can be executed (all dependencies completed)
550
+ */
551
+ private canExecuteStage(stage: StageConfig, completedStages: Set<string>): boolean {
552
+ return stage.dependencies.every(dep => completedStages.has(dep));
553
+ }
554
+
555
+ /**
556
+ * Check if stage has dependencies in the previous level
557
+ */
558
+ private hasDependencyInPreviousLevel(stage: StageConfig, previousLevel: StageConfig[]): boolean {
559
+ return stage.dependencies.some(dep => previousLevel.some(s => s.id === dep));
560
+ }
561
+
562
+ /**
563
+ * Calculate comprehensive pipeline metrics
564
+ */
565
+ private calculatePipelineMetrics(stages: StageExecution[], _config: PipelineConfig): PipelineMetrics {
566
+ const successful = stages.filter(s => s.status === StageStatus.Success);
567
+ const failed = stages.filter(s => s.status === StageStatus.Failed);
568
+ const skipped = stages.filter(s => s.status === StageStatus.Skipped);
569
+
570
+ const totalDuration = stages.reduce((sum, s) => sum + (s.duration || 0), 0);
571
+ const avgStageDuration = stages.length > 0 ? totalDuration / stages.length : 0;
572
+
573
+ // Find bottleneck (longest running stage)
574
+ const bottleneck = stages.reduce((max, s) =>
575
+ (s.duration || 0) > (max?.duration || 0) ? s : max
576
+ );
577
+
578
+ // Calculate resource utilization
579
+ const metricsWithData = stages.filter(s => s.metrics);
580
+ const avgCpu = metricsWithData.length > 0
581
+ ? metricsWithData.reduce((sum, s) => sum + s.metrics!.cpuUsage, 0) / metricsWithData.length
582
+ : 0;
583
+ const avgMemory = metricsWithData.length > 0
584
+ ? metricsWithData.reduce((sum, s) => sum + s.metrics!.memoryUsage, 0) / metricsWithData.length
585
+ : 0;
586
+ const peakCpu = Math.max(...metricsWithData.map(s => s.metrics!.cpuUsage), 0);
587
+ const peakMemory = Math.max(...metricsWithData.map(s => s.metrics!.memoryUsage), 0);
588
+
589
+ return {
590
+ totalDuration,
591
+ stageCount: stages.length,
592
+ successCount: successful.length,
593
+ failureCount: failed.length,
594
+ skippedCount: skipped.length,
595
+ averageStageDuration: avgStageDuration,
596
+ bottleneckStage: bottleneck?.id,
597
+ resourceUtilization: {
598
+ avgCpu: Math.round(avgCpu),
599
+ avgMemory: Math.round(avgMemory),
600
+ peakCpu,
601
+ peakMemory
602
+ }
603
+ };
604
+ }
605
+
606
+ /**
607
+ * Generate unique execution ID
608
+ */
609
+ private generateExecutionId(): string {
610
+ return `exec_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
611
+ }
612
+
613
+ /**
614
+ * Generate unique result ID
615
+ */
616
+ private generateResultId(): string {
617
+ return `result_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
618
+ }
619
+ }
620
+
621
+ // Export singleton instance
622
+ export const simulationEngine = new SimulationEngine();