musubi-sdd 3.5.1 → 3.6.1

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 (29) hide show
  1. package/README.md +25 -3
  2. package/bin/musubi-orchestrate.js +309 -0
  3. package/package.json +1 -1
  4. package/src/llm-providers/anthropic-provider.js +175 -0
  5. package/src/llm-providers/base-provider.js +221 -0
  6. package/src/llm-providers/copilot-provider.js +262 -0
  7. package/src/llm-providers/index.js +214 -0
  8. package/src/llm-providers/openai-provider.js +205 -0
  9. package/src/orchestration/index.js +25 -0
  10. package/src/orchestration/patterns/swarm.js +111 -4
  11. package/src/orchestration/replanning/adaptive-goal-modifier.js +1150 -0
  12. package/src/orchestration/replanning/alternative-generator.js +508 -0
  13. package/src/orchestration/replanning/config.js +378 -0
  14. package/src/orchestration/replanning/goal-progress-tracker.js +727 -0
  15. package/src/orchestration/replanning/index.js +82 -0
  16. package/src/orchestration/replanning/plan-evaluator.js +455 -0
  17. package/src/orchestration/replanning/plan-monitor.js +379 -0
  18. package/src/orchestration/replanning/proactive-path-optimizer.js +972 -0
  19. package/src/orchestration/replanning/replan-history.js +402 -0
  20. package/src/orchestration/replanning/replanning-engine.js +706 -0
  21. package/src/templates/agents/claude-code/CLAUDE.md +45 -0
  22. package/src/templates/agents/claude-code/skills/orchestrator/SKILL.md +20 -0
  23. package/src/templates/agents/claude-code/skills/orchestrator/patterns.md +89 -0
  24. package/src/templates/agents/codex/AGENTS.md +13 -0
  25. package/src/templates/agents/cursor/AGENTS.md +13 -0
  26. package/src/templates/agents/gemini-cli/GEMINI.md +13 -0
  27. package/src/templates/agents/github-copilot/AGENTS.md +13 -0
  28. package/src/templates/agents/qwen-code/QWEN.md +13 -0
  29. package/src/templates/agents/windsurf/AGENTS.md +13 -0
@@ -0,0 +1,972 @@
1
+ /**
2
+ * @fileoverview Proactive Path Optimizer for MUSUBI Replanning Engine
3
+ * Continuously evaluates and optimizes execution paths, even on success
4
+ * @module orchestration/replanning/proactive-path-optimizer
5
+ * @version 1.0.0
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const EventEmitter = require('events');
11
+
12
+ /**
13
+ * Default configuration for ProactivePathOptimizer
14
+ */
15
+ const DEFAULT_CONFIG = {
16
+ // Enable proactive optimization
17
+ enabled: true,
18
+
19
+ // Evaluation frequency (every N successful tasks)
20
+ evaluateEvery: 3,
21
+
22
+ // Minimum improvement threshold to trigger re-routing (percentage)
23
+ minImprovementThreshold: 0.15,
24
+
25
+ // Maximum time to spend on optimization (ms)
26
+ optimizationTimeout: 5000,
27
+
28
+ // Consider parallel execution opportunities
29
+ considerParallelization: true,
30
+
31
+ // Consider task merging opportunities
32
+ considerMerging: true,
33
+
34
+ // Consider task reordering for better dependency resolution
35
+ considerReordering: true,
36
+
37
+ // Consider skipping optional tasks when ahead of schedule
38
+ considerSkipping: false,
39
+
40
+ // Learning from past executions
41
+ learningEnabled: true,
42
+
43
+ // Maximum optimization history to keep
44
+ maxHistorySize: 100
45
+ };
46
+
47
+ /**
48
+ * Path metrics for comparison
49
+ */
50
+ class PathMetrics {
51
+ constructor(data = {}) {
52
+ this.estimatedTime = data.estimatedTime || 0;
53
+ this.estimatedCost = data.estimatedCost || 0;
54
+ this.parallelizationFactor = data.parallelizationFactor || 1.0;
55
+ this.riskScore = data.riskScore || 0;
56
+ this.dependencyComplexity = data.dependencyComplexity || 0;
57
+ this.resourceUtilization = data.resourceUtilization || 0;
58
+ }
59
+
60
+ /**
61
+ * Calculate overall score (lower is better)
62
+ * @returns {number} Overall score
63
+ */
64
+ getScore() {
65
+ return (
66
+ this.estimatedTime * 0.4 +
67
+ this.estimatedCost * 0.2 +
68
+ (1 - this.parallelizationFactor) * 0.15 +
69
+ this.riskScore * 0.15 +
70
+ this.dependencyComplexity * 0.1
71
+ );
72
+ }
73
+
74
+ /**
75
+ * Compare with another metrics object
76
+ * @param {PathMetrics} other - Other metrics
77
+ * @returns {number} Improvement ratio (positive = better)
78
+ */
79
+ compareWith(other) {
80
+ const thisScore = this.getScore();
81
+ const otherScore = other.getScore();
82
+
83
+ if (otherScore === 0) return 0;
84
+ return (otherScore - thisScore) / otherScore;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Optimization opportunity
90
+ */
91
+ class OptimizationOpportunity {
92
+ constructor(data = {}) {
93
+ this.type = data.type; // 'parallelize', 'merge', 'reorder', 'skip', 'substitute'
94
+ this.description = data.description || '';
95
+ this.affectedTasks = data.affectedTasks || [];
96
+ this.estimatedImprovement = data.estimatedImprovement || 0;
97
+ this.confidence = data.confidence || 0.5;
98
+ this.newPath = data.newPath || null;
99
+ this.reasoning = data.reasoning || '';
100
+ }
101
+
102
+ /**
103
+ * Get weighted score for ranking
104
+ * @returns {number} Weighted score
105
+ */
106
+ getWeightedScore() {
107
+ return this.estimatedImprovement * this.confidence;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Proactive Path Optimizer
113
+ * Continuously analyzes and optimizes execution paths
114
+ */
115
+ class ProactivePathOptimizer extends EventEmitter {
116
+ /**
117
+ * Create a proactive path optimizer
118
+ * @param {Object} llmProvider - LLM provider for intelligent optimization
119
+ * @param {Object} [options={}] - Optimizer options
120
+ */
121
+ constructor(llmProvider, options = {}) {
122
+ super();
123
+
124
+ this.llm = llmProvider;
125
+ this.config = { ...DEFAULT_CONFIG, ...options.config };
126
+
127
+ // State
128
+ this.successCount = 0;
129
+ this.optimizationHistory = [];
130
+ this.learningData = new Map(); // Task patterns -> performance data
131
+ this.currentMetrics = null;
132
+
133
+ // Analyzers
134
+ this.parallelizationAnalyzer = new ParallelizationAnalyzer();
135
+ this.mergingAnalyzer = new MergingAnalyzer();
136
+ this.reorderingAnalyzer = new ReorderingAnalyzer();
137
+ }
138
+
139
+ /**
140
+ * Notify optimizer of successful task completion
141
+ * @param {Object} task - Completed task
142
+ * @param {Object} context - Execution context
143
+ * @param {Object} result - Task result
144
+ * @returns {Promise<OptimizationResult|null>} Optimization result if triggered
145
+ */
146
+ async onTaskSuccess(task, context, result) {
147
+ if (!this.config.enabled) return null;
148
+
149
+ this.successCount++;
150
+
151
+ // Record performance data for learning
152
+ if (this.config.learningEnabled) {
153
+ this.recordPerformance(task, result);
154
+ }
155
+
156
+ // Check if we should evaluate
157
+ if (this.successCount % this.config.evaluateEvery !== 0) {
158
+ return null;
159
+ }
160
+
161
+ // Perform proactive optimization
162
+ return this.optimize(context);
163
+ }
164
+
165
+ /**
166
+ * Perform proactive path optimization
167
+ * @param {Object} context - Execution context
168
+ * @returns {Promise<OptimizationResult>} Optimization result
169
+ */
170
+ async optimize(context) {
171
+ const startTime = Date.now();
172
+
173
+ // Calculate current path metrics
174
+ this.currentMetrics = this.calculatePathMetrics(context);
175
+
176
+ // Find optimization opportunities
177
+ const opportunities = await this.findOpportunities(context);
178
+
179
+ if (opportunities.length === 0) {
180
+ return {
181
+ optimized: false,
182
+ reason: 'No optimization opportunities found',
183
+ currentMetrics: this.currentMetrics
184
+ };
185
+ }
186
+
187
+ // Rank opportunities
188
+ const ranked = this.rankOpportunities(opportunities);
189
+ const best = ranked[0];
190
+
191
+ // Check if improvement meets threshold
192
+ if (best.estimatedImprovement < this.config.minImprovementThreshold) {
193
+ return {
194
+ optimized: false,
195
+ reason: `Best improvement (${(best.estimatedImprovement * 100).toFixed(1)}%) below threshold`,
196
+ opportunities: ranked.slice(0, 3),
197
+ currentMetrics: this.currentMetrics
198
+ };
199
+ }
200
+
201
+ // Validate the optimization
202
+ const validation = await this.validateOptimization(best, context);
203
+
204
+ if (!validation.valid) {
205
+ return {
206
+ optimized: false,
207
+ reason: validation.reason,
208
+ opportunities: ranked.slice(0, 3),
209
+ currentMetrics: this.currentMetrics
210
+ };
211
+ }
212
+
213
+ // Record optimization
214
+ this.recordOptimization(best, context);
215
+
216
+ // Emit optimization event
217
+ this.emit('optimization', {
218
+ type: best.type,
219
+ improvement: best.estimatedImprovement,
220
+ affectedTasks: best.affectedTasks,
221
+ newPath: best.newPath
222
+ });
223
+
224
+ return {
225
+ optimized: true,
226
+ optimization: best,
227
+ newPath: best.newPath,
228
+ estimatedImprovement: best.estimatedImprovement,
229
+ newMetrics: this.calculatePathMetrics({ ...context, pending: best.newPath }),
230
+ duration: Date.now() - startTime
231
+ };
232
+ }
233
+
234
+ /**
235
+ * Calculate metrics for current execution path
236
+ * @param {Object} context - Execution context
237
+ * @returns {PathMetrics} Path metrics
238
+ */
239
+ calculatePathMetrics(context) {
240
+ const pending = context.pending || [];
241
+
242
+ // Estimate total time
243
+ let estimatedTime = 0;
244
+ pending.forEach(task => {
245
+ const historical = this.getHistoricalDuration(task);
246
+ estimatedTime += historical || task.estimatedDuration || 30000;
247
+ });
248
+
249
+ // Calculate parallelization factor
250
+ const parallelizable = this.countParallelizable(pending);
251
+ const parallelizationFactor = pending.length > 0
252
+ ? parallelizable / pending.length
253
+ : 1.0;
254
+
255
+ // Calculate risk score
256
+ const riskScore = this.calculateRiskScore(pending, context);
257
+
258
+ // Calculate dependency complexity
259
+ const dependencyComplexity = this.calculateDependencyComplexity(pending);
260
+
261
+ return new PathMetrics({
262
+ estimatedTime,
263
+ parallelizationFactor,
264
+ riskScore,
265
+ dependencyComplexity
266
+ });
267
+ }
268
+
269
+ /**
270
+ * Find optimization opportunities
271
+ * @param {Object} context - Execution context
272
+ * @returns {Promise<OptimizationOpportunity[]>} Found opportunities
273
+ */
274
+ async findOpportunities(context) {
275
+ const opportunities = [];
276
+ const pending = context.pending || [];
277
+
278
+ if (pending.length < 2) return opportunities;
279
+
280
+ // Check parallelization opportunities
281
+ if (this.config.considerParallelization) {
282
+ const parallelOps = this.parallelizationAnalyzer.analyze(pending, context);
283
+ opportunities.push(...parallelOps);
284
+ }
285
+
286
+ // Check merging opportunities
287
+ if (this.config.considerMerging) {
288
+ const mergeOps = this.mergingAnalyzer.analyze(pending, context);
289
+ opportunities.push(...mergeOps);
290
+ }
291
+
292
+ // Check reordering opportunities
293
+ if (this.config.considerReordering) {
294
+ const reorderOps = this.reorderingAnalyzer.analyze(pending, context);
295
+ opportunities.push(...reorderOps);
296
+ }
297
+
298
+ // Use LLM for additional insights if available
299
+ if (this.llm && pending.length >= 3) {
300
+ const llmOps = await this.getLLMOptimizations(pending, context);
301
+ opportunities.push(...llmOps);
302
+ }
303
+
304
+ return opportunities;
305
+ }
306
+
307
+ /**
308
+ * Get LLM-based optimization suggestions
309
+ * @param {Object[]} pending - Pending tasks
310
+ * @param {Object} context - Execution context
311
+ * @returns {Promise<OptimizationOpportunity[]>} LLM suggestions
312
+ */
313
+ async getLLMOptimizations(pending, context) {
314
+ try {
315
+ const prompt = this.buildOptimizationPrompt(pending, context);
316
+
317
+ const response = await Promise.race([
318
+ this.llm.completeJSON(prompt, this.getOptimizationSchema()),
319
+ new Promise((_, reject) =>
320
+ setTimeout(() => reject(new Error('Timeout')), this.config.optimizationTimeout)
321
+ )
322
+ ]);
323
+
324
+ return this.processLLMResponse(response, pending);
325
+ } catch (error) {
326
+ // LLM optimization is optional, don't fail on error
327
+ return [];
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Build optimization prompt for LLM
333
+ * @param {Object[]} pending - Pending tasks
334
+ * @param {Object} context - Execution context
335
+ * @returns {string} Prompt
336
+ */
337
+ buildOptimizationPrompt(pending, context) {
338
+ const completedSummary = (context.completed || []).map(t => ({
339
+ name: t.name || t.skill,
340
+ duration: t.duration
341
+ }));
342
+
343
+ const pendingSummary = pending.map(t => ({
344
+ id: t.id,
345
+ name: t.name || t.skill,
346
+ dependencies: t.dependencies || [],
347
+ estimatedDuration: t.estimatedDuration
348
+ }));
349
+
350
+ return `Analyze this execution plan and suggest optimizations:
351
+
352
+ COMPLETED TASKS:
353
+ ${JSON.stringify(completedSummary, null, 2)}
354
+
355
+ PENDING TASKS:
356
+ ${JSON.stringify(pendingSummary, null, 2)}
357
+
358
+ Consider:
359
+ 1. Tasks that can run in parallel (no dependencies between them)
360
+ 2. Tasks that could be merged into one
361
+ 3. Better ordering based on dependencies
362
+ 4. Tasks that could be skipped or simplified
363
+
364
+ Return optimization suggestions with estimated improvement percentages.`;
365
+ }
366
+
367
+ /**
368
+ * Get JSON schema for LLM response
369
+ * @returns {Object} JSON schema
370
+ */
371
+ getOptimizationSchema() {
372
+ return {
373
+ type: 'object',
374
+ properties: {
375
+ optimizations: {
376
+ type: 'array',
377
+ items: {
378
+ type: 'object',
379
+ properties: {
380
+ type: { type: 'string', enum: ['parallelize', 'merge', 'reorder', 'skip', 'substitute'] },
381
+ description: { type: 'string' },
382
+ affectedTaskIds: { type: 'array', items: { type: 'string' } },
383
+ estimatedImprovement: { type: 'number' },
384
+ newOrder: { type: 'array', items: { type: 'string' } },
385
+ reasoning: { type: 'string' }
386
+ }
387
+ }
388
+ }
389
+ }
390
+ };
391
+ }
392
+
393
+ /**
394
+ * Process LLM response into optimization opportunities
395
+ * @param {Object} response - LLM response
396
+ * @param {Object[]} pending - Pending tasks
397
+ * @returns {OptimizationOpportunity[]} Processed opportunities
398
+ */
399
+ processLLMResponse(response, pending) {
400
+ if (!response || !response.optimizations) return [];
401
+
402
+ return response.optimizations.map(opt => {
403
+ const affectedTasks = (opt.affectedTaskIds || [])
404
+ .map(id => pending.find(t => t.id === id))
405
+ .filter(Boolean);
406
+
407
+ let newPath = null;
408
+ if (opt.newOrder) {
409
+ newPath = opt.newOrder
410
+ .map(id => pending.find(t => t.id === id))
411
+ .filter(Boolean);
412
+
413
+ // Add any tasks not in newOrder at the end
414
+ const inOrder = new Set(opt.newOrder);
415
+ pending.forEach(t => {
416
+ if (!inOrder.has(t.id)) {
417
+ newPath.push(t);
418
+ }
419
+ });
420
+ }
421
+
422
+ return new OptimizationOpportunity({
423
+ type: opt.type,
424
+ description: opt.description,
425
+ affectedTasks,
426
+ estimatedImprovement: opt.estimatedImprovement || 0.1,
427
+ confidence: 0.6, // LLM suggestions get moderate confidence
428
+ newPath,
429
+ reasoning: opt.reasoning
430
+ });
431
+ });
432
+ }
433
+
434
+ /**
435
+ * Rank opportunities by weighted score
436
+ * @param {OptimizationOpportunity[]} opportunities - Opportunities
437
+ * @returns {OptimizationOpportunity[]} Ranked opportunities
438
+ */
439
+ rankOpportunities(opportunities) {
440
+ return [...opportunities].sort((a, b) =>
441
+ b.getWeightedScore() - a.getWeightedScore()
442
+ );
443
+ }
444
+
445
+ /**
446
+ * Validate an optimization before applying
447
+ * @param {OptimizationOpportunity} optimization - Optimization to validate
448
+ * @param {Object} context - Execution context
449
+ * @returns {Promise<{valid: boolean, reason?: string}>} Validation result
450
+ */
451
+ async validateOptimization(optimization, context) {
452
+ // Check dependency constraints
453
+ if (optimization.newPath) {
454
+ const valid = this.validateDependencies(optimization.newPath);
455
+ if (!valid) {
456
+ return { valid: false, reason: 'New path violates dependency constraints' };
457
+ }
458
+ }
459
+
460
+ // Check resource constraints
461
+ if (optimization.type === 'parallelize') {
462
+ const canParallelize = this.checkResourceCapacity(
463
+ optimization.affectedTasks.length
464
+ );
465
+ if (!canParallelize) {
466
+ return { valid: false, reason: 'Insufficient resources for parallelization' };
467
+ }
468
+ }
469
+
470
+ return { valid: true };
471
+ }
472
+
473
+ /**
474
+ * Validate dependencies in a path
475
+ * @param {Object[]} path - Task path
476
+ * @returns {boolean} Whether dependencies are satisfied
477
+ */
478
+ validateDependencies(path) {
479
+ const completed = new Set();
480
+
481
+ for (const task of path) {
482
+ const deps = task.dependencies || [];
483
+ for (const dep of deps) {
484
+ if (!completed.has(dep)) {
485
+ // Check if dependency is in remaining path
486
+ const depIndex = path.findIndex(t => t.id === dep);
487
+ const taskIndex = path.indexOf(task);
488
+ if (depIndex === -1 || depIndex > taskIndex) {
489
+ return false;
490
+ }
491
+ }
492
+ }
493
+ completed.add(task.id);
494
+ }
495
+
496
+ return true;
497
+ }
498
+
499
+ /**
500
+ * Check if resources allow parallelization
501
+ * @param {number} parallelCount - Number of parallel tasks
502
+ * @returns {boolean} Whether resources are available
503
+ */
504
+ checkResourceCapacity(parallelCount) {
505
+ // Default implementation assumes we can handle up to 4 parallel tasks
506
+ return parallelCount <= 4;
507
+ }
508
+
509
+ /**
510
+ * Record performance data for learning
511
+ * @param {Object} task - Completed task
512
+ * @param {Object} result - Task result
513
+ */
514
+ recordPerformance(task, result) {
515
+ const key = task.skill || task.name;
516
+
517
+ if (!this.learningData.has(key)) {
518
+ this.learningData.set(key, {
519
+ durations: [],
520
+ successRate: 0,
521
+ totalCount: 0
522
+ });
523
+ }
524
+
525
+ const data = this.learningData.get(key);
526
+ data.durations.push(result.duration || 0);
527
+ data.totalCount++;
528
+ data.successRate = (data.successRate * (data.totalCount - 1) + 1) / data.totalCount;
529
+
530
+ // Keep only last 50 durations
531
+ if (data.durations.length > 50) {
532
+ data.durations.shift();
533
+ }
534
+ }
535
+
536
+ /**
537
+ * Get historical duration for a task type
538
+ * @param {Object} task - Task
539
+ * @returns {number|null} Historical average duration or null
540
+ */
541
+ getHistoricalDuration(task) {
542
+ const key = task.skill || task.name;
543
+ const data = this.learningData.get(key);
544
+
545
+ if (!data || data.durations.length === 0) return null;
546
+
547
+ return data.durations.reduce((a, b) => a + b, 0) / data.durations.length;
548
+ }
549
+
550
+ /**
551
+ * Count parallelizable tasks
552
+ * @param {Object[]} tasks - Tasks
553
+ * @returns {number} Count of parallelizable tasks
554
+ */
555
+ countParallelizable(tasks) {
556
+ let count = 0;
557
+ const completed = new Set();
558
+
559
+ for (const task of tasks) {
560
+ const deps = task.dependencies || [];
561
+ if (deps.length === 0 || deps.every(d => completed.has(d))) {
562
+ count++;
563
+ }
564
+ completed.add(task.id);
565
+ }
566
+
567
+ return count;
568
+ }
569
+
570
+ /**
571
+ * Calculate risk score for pending tasks
572
+ * @param {Object[]} pending - Pending tasks
573
+ * @param {Object} context - Execution context
574
+ * @returns {number} Risk score (0-1)
575
+ */
576
+ calculateRiskScore(pending, context) {
577
+ if (pending.length === 0) return 0;
578
+
579
+ let riskSum = 0;
580
+
581
+ for (const task of pending) {
582
+ const key = task.skill || task.name;
583
+ const data = this.learningData.get(key);
584
+
585
+ if (data) {
586
+ // Use failure rate as risk indicator
587
+ riskSum += (1 - data.successRate);
588
+ } else {
589
+ // Unknown tasks get moderate risk
590
+ riskSum += 0.3;
591
+ }
592
+ }
593
+
594
+ return riskSum / pending.length;
595
+ }
596
+
597
+ /**
598
+ * Calculate dependency complexity
599
+ * @param {Object[]} tasks - Tasks
600
+ * @returns {number} Complexity score (0-1)
601
+ */
602
+ calculateDependencyComplexity(tasks) {
603
+ if (tasks.length === 0) return 0;
604
+
605
+ let totalDeps = 0;
606
+ let maxDeps = 0;
607
+
608
+ for (const task of tasks) {
609
+ const deps = (task.dependencies || []).length;
610
+ totalDeps += deps;
611
+ maxDeps = Math.max(maxDeps, deps);
612
+ }
613
+
614
+ // Normalize: average deps / max possible deps
615
+ const avgDeps = totalDeps / tasks.length;
616
+ const normalized = Math.min(avgDeps / 5, 1); // Assume 5+ deps is max complexity
617
+
618
+ return normalized;
619
+ }
620
+
621
+ /**
622
+ * Record optimization for history
623
+ * @param {OptimizationOpportunity} optimization - Applied optimization
624
+ * @param {Object} context - Execution context
625
+ */
626
+ recordOptimization(optimization, context) {
627
+ this.optimizationHistory.push({
628
+ timestamp: Date.now(),
629
+ type: optimization.type,
630
+ improvement: optimization.estimatedImprovement,
631
+ tasksAffected: optimization.affectedTasks.length,
632
+ contextSnapshot: {
633
+ completedCount: context.completed?.length || 0,
634
+ pendingCount: context.pending?.length || 0
635
+ }
636
+ });
637
+
638
+ // Limit history size
639
+ while (this.optimizationHistory.length > this.config.maxHistorySize) {
640
+ this.optimizationHistory.shift();
641
+ }
642
+ }
643
+
644
+ /**
645
+ * Get optimization statistics
646
+ * @returns {Object} Statistics
647
+ */
648
+ getStatistics() {
649
+ const history = this.optimizationHistory;
650
+
651
+ if (history.length === 0) {
652
+ return {
653
+ totalOptimizations: 0,
654
+ averageImprovement: 0,
655
+ byType: {}
656
+ };
657
+ }
658
+
659
+ const byType = {};
660
+ let totalImprovement = 0;
661
+
662
+ for (const opt of history) {
663
+ byType[opt.type] = (byType[opt.type] || 0) + 1;
664
+ totalImprovement += opt.improvement;
665
+ }
666
+
667
+ return {
668
+ totalOptimizations: history.length,
669
+ averageImprovement: totalImprovement / history.length,
670
+ byType,
671
+ learningDataSize: this.learningData.size
672
+ };
673
+ }
674
+
675
+ /**
676
+ * Reset optimizer state
677
+ */
678
+ reset() {
679
+ this.successCount = 0;
680
+ this.currentMetrics = null;
681
+ }
682
+
683
+ /**
684
+ * Clear all learning data
685
+ */
686
+ clearLearningData() {
687
+ this.learningData.clear();
688
+ this.optimizationHistory = [];
689
+ }
690
+ }
691
+
692
+ /**
693
+ * Parallelization Analyzer
694
+ * Finds opportunities to run tasks in parallel
695
+ */
696
+ class ParallelizationAnalyzer {
697
+ /**
698
+ * Analyze tasks for parallelization opportunities
699
+ * @param {Object[]} tasks - Tasks
700
+ * @param {Object} context - Context
701
+ * @returns {OptimizationOpportunity[]} Opportunities
702
+ */
703
+ analyze(tasks, context) {
704
+ const opportunities = [];
705
+ const groups = this.findParallelGroups(tasks);
706
+
707
+ for (const group of groups) {
708
+ if (group.length >= 2) {
709
+ const sequentialTime = group.reduce((sum, t) =>
710
+ sum + (t.estimatedDuration || 30000), 0
711
+ );
712
+ const parallelTime = Math.max(...group.map(t =>
713
+ t.estimatedDuration || 30000
714
+ ));
715
+ const improvement = (sequentialTime - parallelTime) / sequentialTime;
716
+
717
+ if (improvement > 0.1) {
718
+ opportunities.push(new OptimizationOpportunity({
719
+ type: 'parallelize',
720
+ description: `Run ${group.length} tasks in parallel`,
721
+ affectedTasks: group,
722
+ estimatedImprovement: improvement,
723
+ confidence: 0.8,
724
+ reasoning: 'Tasks have no interdependencies'
725
+ }));
726
+ }
727
+ }
728
+ }
729
+
730
+ return opportunities;
731
+ }
732
+
733
+ /**
734
+ * Find groups of tasks that can run in parallel
735
+ * @param {Object[]} tasks - Tasks
736
+ * @returns {Object[][]} Groups
737
+ */
738
+ findParallelGroups(tasks) {
739
+ const groups = [];
740
+ const used = new Set();
741
+ const completed = new Set();
742
+
743
+ for (let i = 0; i < tasks.length; i++) {
744
+ if (used.has(i)) continue;
745
+
746
+ const task = tasks[i];
747
+ const deps = task.dependencies || [];
748
+
749
+ // Check if dependencies are satisfied
750
+ if (!deps.every(d => completed.has(d))) continue;
751
+
752
+ // Find other tasks that can run in parallel
753
+ const group = [task];
754
+ used.add(i);
755
+
756
+ for (let j = i + 1; j < tasks.length; j++) {
757
+ if (used.has(j)) continue;
758
+
759
+ const other = tasks[j];
760
+ const otherDeps = other.dependencies || [];
761
+
762
+ // Can run in parallel if:
763
+ // 1. Its dependencies are satisfied
764
+ // 2. It doesn't depend on any task in the current group
765
+ if (otherDeps.every(d => completed.has(d)) &&
766
+ !otherDeps.some(d => group.some(g => g.id === d))) {
767
+ group.push(other);
768
+ used.add(j);
769
+ }
770
+ }
771
+
772
+ groups.push(group);
773
+ group.forEach(t => completed.add(t.id));
774
+ }
775
+
776
+ return groups;
777
+ }
778
+ }
779
+
780
+ /**
781
+ * Merging Analyzer
782
+ * Finds opportunities to merge similar tasks
783
+ */
784
+ class MergingAnalyzer {
785
+ /**
786
+ * Analyze tasks for merging opportunities
787
+ * @param {Object[]} tasks - Tasks
788
+ * @param {Object} context - Context
789
+ * @returns {OptimizationOpportunity[]} Opportunities
790
+ */
791
+ analyze(tasks, context) {
792
+ const opportunities = [];
793
+ const groups = this.findMergeableGroups(tasks);
794
+
795
+ for (const group of groups) {
796
+ if (group.length >= 2) {
797
+ const overhead = 5000; // Assumed overhead per task
798
+ const savingsTime = overhead * (group.length - 1);
799
+ const totalTime = group.reduce((sum, t) =>
800
+ sum + (t.estimatedDuration || 30000), 0
801
+ );
802
+ const improvement = savingsTime / totalTime;
803
+
804
+ if (improvement > 0.05) {
805
+ opportunities.push(new OptimizationOpportunity({
806
+ type: 'merge',
807
+ description: `Merge ${group.length} similar ${group[0].skill || group[0].name} tasks`,
808
+ affectedTasks: group,
809
+ estimatedImprovement: improvement,
810
+ confidence: 0.7,
811
+ reasoning: 'Tasks operate on similar targets'
812
+ }));
813
+ }
814
+ }
815
+ }
816
+
817
+ return opportunities;
818
+ }
819
+
820
+ /**
821
+ * Find groups of tasks that could be merged
822
+ * @param {Object[]} tasks - Tasks
823
+ * @returns {Object[][]} Mergeable groups
824
+ */
825
+ findMergeableGroups(tasks) {
826
+ const bySkill = new Map();
827
+
828
+ for (const task of tasks) {
829
+ const skill = task.skill || task.name;
830
+ if (!bySkill.has(skill)) {
831
+ bySkill.set(skill, []);
832
+ }
833
+ bySkill.get(skill).push(task);
834
+ }
835
+
836
+ // Return groups with 2+ tasks of the same skill
837
+ return Array.from(bySkill.values()).filter(g => g.length >= 2);
838
+ }
839
+ }
840
+
841
+ /**
842
+ * Reordering Analyzer
843
+ * Finds better task orderings based on dependencies
844
+ */
845
+ class ReorderingAnalyzer {
846
+ /**
847
+ * Analyze tasks for reordering opportunities
848
+ * @param {Object[]} tasks - Tasks
849
+ * @param {Object} context - Context
850
+ * @returns {OptimizationOpportunity[]} Opportunities
851
+ */
852
+ analyze(tasks, context) {
853
+ const opportunities = [];
854
+
855
+ // Check for dependency-based improvements
856
+ const optimalOrder = this.topologicalSort(tasks);
857
+
858
+ if (!this.arraysEqual(tasks, optimalOrder)) {
859
+ const improvement = this.estimateReorderImprovement(tasks, optimalOrder);
860
+
861
+ if (improvement > 0.05) {
862
+ opportunities.push(new OptimizationOpportunity({
863
+ type: 'reorder',
864
+ description: 'Optimize task order for better dependency resolution',
865
+ affectedTasks: tasks,
866
+ estimatedImprovement: improvement,
867
+ confidence: 0.9,
868
+ newPath: optimalOrder,
869
+ reasoning: 'Current order causes unnecessary waiting'
870
+ }));
871
+ }
872
+ }
873
+
874
+ return opportunities;
875
+ }
876
+
877
+ /**
878
+ * Topological sort of tasks based on dependencies
879
+ * @param {Object[]} tasks - Tasks
880
+ * @returns {Object[]} Sorted tasks
881
+ */
882
+ topologicalSort(tasks) {
883
+ const taskMap = new Map(tasks.map(t => [t.id, t]));
884
+ const inDegree = new Map();
885
+ const result = [];
886
+
887
+ // Initialize in-degrees
888
+ for (const task of tasks) {
889
+ inDegree.set(task.id, 0);
890
+ }
891
+
892
+ // Calculate in-degrees
893
+ for (const task of tasks) {
894
+ for (const dep of (task.dependencies || [])) {
895
+ if (taskMap.has(dep)) {
896
+ inDegree.set(task.id, inDegree.get(task.id) + 1);
897
+ }
898
+ }
899
+ }
900
+
901
+ // Queue tasks with no dependencies
902
+ const queue = tasks.filter(t => inDegree.get(t.id) === 0);
903
+
904
+ while (queue.length > 0) {
905
+ const task = queue.shift();
906
+ result.push(task);
907
+
908
+ // Reduce in-degree for dependent tasks
909
+ for (const other of tasks) {
910
+ if ((other.dependencies || []).includes(task.id)) {
911
+ const newDegree = inDegree.get(other.id) - 1;
912
+ inDegree.set(other.id, newDegree);
913
+ if (newDegree === 0) {
914
+ queue.push(other);
915
+ }
916
+ }
917
+ }
918
+ }
919
+
920
+ // Add remaining tasks (circular dependencies)
921
+ for (const task of tasks) {
922
+ if (!result.includes(task)) {
923
+ result.push(task);
924
+ }
925
+ }
926
+
927
+ return result;
928
+ }
929
+
930
+ /**
931
+ * Check if two arrays have same elements in same order
932
+ * @param {Object[]} a - First array
933
+ * @param {Object[]} b - Second array
934
+ * @returns {boolean} Whether equal
935
+ */
936
+ arraysEqual(a, b) {
937
+ if (a.length !== b.length) return false;
938
+ for (let i = 0; i < a.length; i++) {
939
+ if (a[i].id !== b[i].id) return false;
940
+ }
941
+ return true;
942
+ }
943
+
944
+ /**
945
+ * Estimate improvement from reordering
946
+ * @param {Object[]} current - Current order
947
+ * @param {Object[]} optimal - Optimal order
948
+ * @returns {number} Estimated improvement (0-1)
949
+ */
950
+ estimateReorderImprovement(current, optimal) {
951
+ // Simple heuristic: count how many tasks are out of place
952
+ let outOfPlace = 0;
953
+ for (let i = 0; i < current.length; i++) {
954
+ if (current[i].id !== optimal[i].id) {
955
+ outOfPlace++;
956
+ }
957
+ }
958
+
959
+ // More out of place = more potential improvement
960
+ return (outOfPlace / current.length) * 0.3;
961
+ }
962
+ }
963
+
964
+ module.exports = {
965
+ ProactivePathOptimizer,
966
+ PathMetrics,
967
+ OptimizationOpportunity,
968
+ ParallelizationAnalyzer,
969
+ MergingAnalyzer,
970
+ ReorderingAnalyzer,
971
+ DEFAULT_CONFIG
972
+ };