musubi-sdd 3.10.0 → 5.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.
Files changed (44) hide show
  1. package/README.md +24 -19
  2. package/package.json +1 -1
  3. package/src/agents/agent-loop.js +532 -0
  4. package/src/agents/agentic/code-generator.js +767 -0
  5. package/src/agents/agentic/code-reviewer.js +698 -0
  6. package/src/agents/agentic/index.js +43 -0
  7. package/src/agents/function-tool.js +432 -0
  8. package/src/agents/index.js +45 -0
  9. package/src/agents/schema-generator.js +514 -0
  10. package/src/analyzers/ast-extractor.js +870 -0
  11. package/src/analyzers/context-optimizer.js +681 -0
  12. package/src/analyzers/repository-map.js +692 -0
  13. package/src/integrations/index.js +7 -1
  14. package/src/integrations/mcp/index.js +175 -0
  15. package/src/integrations/mcp/mcp-context-provider.js +472 -0
  16. package/src/integrations/mcp/mcp-discovery.js +436 -0
  17. package/src/integrations/mcp/mcp-tool-registry.js +467 -0
  18. package/src/integrations/mcp-connector.js +818 -0
  19. package/src/integrations/tool-discovery.js +589 -0
  20. package/src/managers/index.js +7 -0
  21. package/src/managers/skill-tools.js +565 -0
  22. package/src/monitoring/cost-tracker.js +7 -0
  23. package/src/monitoring/incident-manager.js +10 -0
  24. package/src/monitoring/observability.js +10 -0
  25. package/src/monitoring/quality-dashboard.js +491 -0
  26. package/src/monitoring/release-manager.js +10 -0
  27. package/src/orchestration/agent-skill-binding.js +655 -0
  28. package/src/orchestration/error-handler.js +827 -0
  29. package/src/orchestration/index.js +235 -1
  30. package/src/orchestration/mcp-tool-adapters.js +896 -0
  31. package/src/orchestration/reasoning/index.js +58 -0
  32. package/src/orchestration/reasoning/planning-engine.js +831 -0
  33. package/src/orchestration/reasoning/reasoning-engine.js +710 -0
  34. package/src/orchestration/reasoning/self-correction.js +751 -0
  35. package/src/orchestration/skill-executor.js +665 -0
  36. package/src/orchestration/skill-registry.js +650 -0
  37. package/src/orchestration/workflow-examples.js +1072 -0
  38. package/src/orchestration/workflow-executor.js +779 -0
  39. package/src/phase4-integration.js +248 -0
  40. package/src/phase5-integration.js +402 -0
  41. package/src/steering/steering-auto-update.js +572 -0
  42. package/src/steering/steering-validator.js +547 -0
  43. package/src/templates/template-constraints.js +646 -0
  44. package/src/validators/advanced-validation.js +580 -0
@@ -0,0 +1,831 @@
1
+ /**
2
+ * @file planning-engine.js
3
+ * @description Agentic planning engine for task decomposition and execution planning
4
+ * @version 1.0.0
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { EventEmitter } = require('events');
10
+
11
+ /**
12
+ * Plan status types
13
+ * @enum {string}
14
+ */
15
+ const PLAN_STATUS = {
16
+ DRAFT: 'draft',
17
+ READY: 'ready',
18
+ EXECUTING: 'executing',
19
+ PAUSED: 'paused',
20
+ COMPLETED: 'completed',
21
+ FAILED: 'failed',
22
+ CANCELLED: 'cancelled'
23
+ };
24
+
25
+ /**
26
+ * Task status types
27
+ * @enum {string}
28
+ */
29
+ const TASK_STATUS = {
30
+ PENDING: 'pending',
31
+ READY: 'ready',
32
+ IN_PROGRESS: 'in-progress',
33
+ BLOCKED: 'blocked',
34
+ COMPLETED: 'completed',
35
+ FAILED: 'failed',
36
+ SKIPPED: 'skipped'
37
+ };
38
+
39
+ /**
40
+ * Task priority levels
41
+ * @enum {number}
42
+ */
43
+ const PRIORITY = {
44
+ CRITICAL: 0,
45
+ HIGH: 1,
46
+ MEDIUM: 2,
47
+ LOW: 3,
48
+ OPTIONAL: 4
49
+ };
50
+
51
+ /**
52
+ * @typedef {Object} Task
53
+ * @property {string} id - Task identifier
54
+ * @property {string} name - Task name
55
+ * @property {string} description - Task description
56
+ * @property {string} status - Task status
57
+ * @property {number} priority - Priority level
58
+ * @property {string[]} dependencies - Task IDs this depends on
59
+ * @property {string[]} outputs - Expected outputs
60
+ * @property {number} estimatedTime - Estimated time in ms
61
+ * @property {number} [actualTime] - Actual time taken
62
+ * @property {Object} [result] - Task result
63
+ * @property {Object} [metadata] - Additional metadata
64
+ */
65
+
66
+ /**
67
+ * @typedef {Object} Plan
68
+ * @property {string} id - Plan identifier
69
+ * @property {string} goal - High-level goal
70
+ * @property {string} status - Plan status
71
+ * @property {Task[]} tasks - Ordered list of tasks
72
+ * @property {Object} context - Planning context
73
+ * @property {number} createdAt - Creation timestamp
74
+ * @property {number} [startedAt] - Execution start timestamp
75
+ * @property {number} [completedAt] - Completion timestamp
76
+ * @property {Object} metrics - Execution metrics
77
+ */
78
+
79
+ /**
80
+ * @typedef {Object} PlanningOptions
81
+ * @property {number} [maxTasks=50] - Maximum tasks in a plan
82
+ * @property {boolean} [parallelExecution=true] - Allow parallel task execution
83
+ * @property {number} [maxParallel=4] - Maximum parallel tasks
84
+ * @property {boolean} [adaptivePlanning=true] - Enable adaptive replanning
85
+ * @property {number} [replanThreshold=0.3] - Threshold for triggering replan
86
+ */
87
+
88
+ /**
89
+ * Planning Engine class for task decomposition and execution planning
90
+ * @extends EventEmitter
91
+ */
92
+ class PlanningEngine extends EventEmitter {
93
+ /**
94
+ * Create planning engine
95
+ * @param {PlanningOptions} [options={}] - Engine options
96
+ */
97
+ constructor(options = {}) {
98
+ super();
99
+
100
+ this.maxTasks = options.maxTasks ?? 50;
101
+ this.parallelExecution = options.parallelExecution ?? true;
102
+ this.maxParallel = options.maxParallel ?? 4;
103
+ this.adaptivePlanning = options.adaptivePlanning ?? true;
104
+ this.replanThreshold = options.replanThreshold ?? 0.3;
105
+
106
+ // State
107
+ this.plans = new Map();
108
+ this.activePlan = null;
109
+ this.taskCounter = 0;
110
+ }
111
+
112
+ /**
113
+ * Create a plan from a goal
114
+ * @param {string} goal - High-level goal description
115
+ * @param {Object} [context={}] - Additional context
116
+ * @returns {Promise<Plan>}
117
+ */
118
+ async createPlan(goal, context = {}) {
119
+ const planId = this.generateId('plan');
120
+
121
+ const plan = {
122
+ id: planId,
123
+ goal,
124
+ status: PLAN_STATUS.DRAFT,
125
+ tasks: [],
126
+ context,
127
+ createdAt: Date.now(),
128
+ startedAt: null,
129
+ completedAt: null,
130
+ metrics: {
131
+ totalTasks: 0,
132
+ completedTasks: 0,
133
+ failedTasks: 0,
134
+ skippedTasks: 0,
135
+ totalEstimatedTime: 0,
136
+ actualTime: 0
137
+ }
138
+ };
139
+
140
+ this.emit('plan:creating', { planId, goal });
141
+
142
+ // Decompose goal into tasks
143
+ const tasks = await this.decomposeGoal(goal, context);
144
+ plan.tasks = tasks;
145
+ plan.metrics.totalTasks = tasks.length;
146
+ plan.metrics.totalEstimatedTime = tasks.reduce((sum, t) => sum + t.estimatedTime, 0);
147
+
148
+ // Optimize task order
149
+ this.optimizeTaskOrder(plan);
150
+
151
+ // Update status
152
+ plan.status = PLAN_STATUS.READY;
153
+
154
+ this.plans.set(planId, plan);
155
+ this.emit('plan:created', { plan });
156
+
157
+ return plan;
158
+ }
159
+
160
+ /**
161
+ * Decompose goal into tasks
162
+ * @private
163
+ */
164
+ async decomposeGoal(goal, context) {
165
+ const tasks = [];
166
+
167
+ // Analyze goal for key components
168
+ const components = this.analyzeGoal(goal);
169
+
170
+ for (let i = 0; i < components.length; i++) {
171
+ const component = components[i];
172
+
173
+ const task = {
174
+ id: this.generateId('task'),
175
+ name: component.name,
176
+ description: component.description,
177
+ status: TASK_STATUS.PENDING,
178
+ priority: component.priority ?? PRIORITY.MEDIUM,
179
+ dependencies: [],
180
+ outputs: component.outputs || [],
181
+ estimatedTime: component.estimatedTime || 5000,
182
+ actualTime: null,
183
+ result: null,
184
+ metadata: {
185
+ component: component.type,
186
+ index: i
187
+ }
188
+ };
189
+
190
+ // Add dependencies from previous tasks if sequential
191
+ if (i > 0 && component.dependsOnPrevious !== false) {
192
+ task.dependencies.push(tasks[i - 1].id);
193
+ }
194
+
195
+ tasks.push(task);
196
+ }
197
+
198
+ return tasks;
199
+ }
200
+
201
+ /**
202
+ * Analyze goal to extract components
203
+ * @private
204
+ */
205
+ analyzeGoal(goal) {
206
+ const components = [];
207
+ const goalLower = goal.toLowerCase();
208
+
209
+ // Pattern matching for common goal structures
210
+ const patterns = [
211
+ { keyword: 'create', type: 'creation', priority: PRIORITY.HIGH },
212
+ { keyword: 'build', type: 'construction', priority: PRIORITY.HIGH },
213
+ { keyword: 'implement', type: 'implementation', priority: PRIORITY.HIGH },
214
+ { keyword: 'fix', type: 'bugfix', priority: PRIORITY.CRITICAL },
215
+ { keyword: 'test', type: 'testing', priority: PRIORITY.MEDIUM },
216
+ { keyword: 'deploy', type: 'deployment', priority: PRIORITY.HIGH },
217
+ { keyword: 'refactor', type: 'refactoring', priority: PRIORITY.MEDIUM },
218
+ { keyword: 'document', type: 'documentation', priority: PRIORITY.LOW },
219
+ { keyword: 'analyze', type: 'analysis', priority: PRIORITY.MEDIUM },
220
+ { keyword: 'review', type: 'review', priority: PRIORITY.MEDIUM }
221
+ ];
222
+
223
+ // Match patterns
224
+ for (const pattern of patterns) {
225
+ if (goalLower.includes(pattern.keyword)) {
226
+ components.push({
227
+ name: `${pattern.keyword.charAt(0).toUpperCase() + pattern.keyword.slice(1)} phase`,
228
+ description: `${pattern.type} task for: ${goal.substring(0, 50)}`,
229
+ type: pattern.type,
230
+ priority: pattern.priority,
231
+ estimatedTime: this.estimateTime(pattern.type)
232
+ });
233
+ }
234
+ }
235
+
236
+ // If no patterns matched, create generic decomposition
237
+ if (components.length === 0) {
238
+ components.push(
239
+ {
240
+ name: 'Analysis',
241
+ description: 'Analyze requirements and context',
242
+ type: 'analysis',
243
+ priority: PRIORITY.HIGH,
244
+ estimatedTime: 3000
245
+ },
246
+ {
247
+ name: 'Planning',
248
+ description: 'Create detailed implementation plan',
249
+ type: 'planning',
250
+ priority: PRIORITY.HIGH,
251
+ estimatedTime: 2000
252
+ },
253
+ {
254
+ name: 'Execution',
255
+ description: 'Execute the main task',
256
+ type: 'execution',
257
+ priority: PRIORITY.HIGH,
258
+ estimatedTime: 10000
259
+ },
260
+ {
261
+ name: 'Validation',
262
+ description: 'Validate results and quality',
263
+ type: 'validation',
264
+ priority: PRIORITY.MEDIUM,
265
+ estimatedTime: 3000
266
+ }
267
+ );
268
+ }
269
+
270
+ return components;
271
+ }
272
+
273
+ /**
274
+ * Estimate time for task type
275
+ * @private
276
+ */
277
+ estimateTime(type) {
278
+ const estimates = {
279
+ creation: 15000,
280
+ construction: 20000,
281
+ implementation: 25000,
282
+ bugfix: 10000,
283
+ testing: 8000,
284
+ deployment: 12000,
285
+ refactoring: 15000,
286
+ documentation: 5000,
287
+ analysis: 5000,
288
+ review: 6000
289
+ };
290
+
291
+ return estimates[type] || 10000;
292
+ }
293
+
294
+ /**
295
+ * Optimize task order based on dependencies and priorities
296
+ * @private
297
+ */
298
+ optimizeTaskOrder(plan) {
299
+ // Topological sort with priority consideration
300
+ const sorted = [];
301
+ const visited = new Set();
302
+ const visiting = new Set();
303
+
304
+ const visit = (taskId) => {
305
+ if (visited.has(taskId)) return;
306
+ if (visiting.has(taskId)) {
307
+ throw new Error(`Circular dependency detected for task ${taskId}`);
308
+ }
309
+
310
+ visiting.add(taskId);
311
+
312
+ const task = plan.tasks.find(t => t.id === taskId);
313
+ if (task) {
314
+ for (const depId of task.dependencies) {
315
+ visit(depId);
316
+ }
317
+ visited.add(taskId);
318
+ sorted.push(task);
319
+ }
320
+
321
+ visiting.delete(taskId);
322
+ };
323
+
324
+ // Sort by priority first, then visit
325
+ const byPriority = [...plan.tasks].sort((a, b) => a.priority - b.priority);
326
+ for (const task of byPriority) {
327
+ visit(task.id);
328
+ }
329
+
330
+ plan.tasks = sorted;
331
+
332
+ // Update ready status for tasks with no dependencies
333
+ for (const task of plan.tasks) {
334
+ if (task.dependencies.length === 0) {
335
+ task.status = TASK_STATUS.READY;
336
+ }
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Execute a plan
342
+ * @param {string} planId - Plan identifier
343
+ * @param {Function} executor - Task executor function (task) => Promise<result>
344
+ * @returns {Promise<Plan>}
345
+ */
346
+ async executePlan(planId, executor) {
347
+ const plan = this.plans.get(planId);
348
+ if (!plan) throw new Error(`Plan ${planId} not found`);
349
+ if (plan.status === PLAN_STATUS.EXECUTING) {
350
+ throw new Error('Plan is already executing');
351
+ }
352
+
353
+ plan.status = PLAN_STATUS.EXECUTING;
354
+ plan.startedAt = Date.now();
355
+ this.activePlan = plan;
356
+
357
+ this.emit('plan:executing', { planId });
358
+
359
+ try {
360
+ if (this.parallelExecution) {
361
+ await this.executeParallel(plan, executor);
362
+ } else {
363
+ await this.executeSequential(plan, executor);
364
+ }
365
+
366
+ // Determine final status
367
+ const hasFailures = plan.tasks.some(t => t.status === TASK_STATUS.FAILED);
368
+ plan.status = hasFailures ? PLAN_STATUS.FAILED : PLAN_STATUS.COMPLETED;
369
+
370
+ } catch (error) {
371
+ plan.status = PLAN_STATUS.FAILED;
372
+ this.emit('plan:error', { planId, error: error.message });
373
+ }
374
+
375
+ plan.completedAt = Date.now();
376
+ plan.metrics.actualTime = plan.completedAt - plan.startedAt;
377
+ this.activePlan = null;
378
+
379
+ this.emit('plan:completed', { plan });
380
+
381
+ return plan;
382
+ }
383
+
384
+ /**
385
+ * Execute tasks sequentially
386
+ * @private
387
+ */
388
+ async executeSequential(plan, executor) {
389
+ for (const task of plan.tasks) {
390
+ if (task.status === TASK_STATUS.SKIPPED) continue;
391
+
392
+ // Check dependencies
393
+ const blocked = task.dependencies.some(depId => {
394
+ const dep = plan.tasks.find(t => t.id === depId);
395
+ return dep && dep.status === TASK_STATUS.FAILED;
396
+ });
397
+
398
+ if (blocked) {
399
+ task.status = TASK_STATUS.SKIPPED;
400
+ plan.metrics.skippedTasks++;
401
+ continue;
402
+ }
403
+
404
+ await this.executeTask(task, executor, plan);
405
+ }
406
+ }
407
+
408
+ /**
409
+ * Execute tasks in parallel where possible
410
+ * @private
411
+ */
412
+ async executeParallel(plan, executor) {
413
+ const completed = new Set();
414
+
415
+ while (true) {
416
+ // Find tasks that are ready to execute
417
+ const ready = plan.tasks.filter(task => {
418
+ if (task.status !== TASK_STATUS.READY && task.status !== TASK_STATUS.PENDING) {
419
+ return false;
420
+ }
421
+ // Check all dependencies are completed
422
+ return task.dependencies.every(depId => completed.has(depId));
423
+ });
424
+
425
+ if (ready.length === 0) {
426
+ // No more tasks ready - check if we're done
427
+ const remaining = plan.tasks.filter(t =>
428
+ t.status === TASK_STATUS.PENDING ||
429
+ t.status === TASK_STATUS.READY ||
430
+ t.status === TASK_STATUS.IN_PROGRESS
431
+ );
432
+ if (remaining.length === 0) break;
433
+
434
+ // Check for blocked tasks (dependencies failed)
435
+ for (const task of remaining) {
436
+ const hasFailed = task.dependencies.some(depId => {
437
+ const dep = plan.tasks.find(t => t.id === depId);
438
+ return dep && dep.status === TASK_STATUS.FAILED;
439
+ });
440
+ if (hasFailed) {
441
+ task.status = TASK_STATUS.SKIPPED;
442
+ plan.metrics.skippedTasks++;
443
+ }
444
+ }
445
+ break;
446
+ }
447
+
448
+ // Execute batch of ready tasks
449
+ const batch = ready.slice(0, this.maxParallel);
450
+ await Promise.all(batch.map(async (task) => {
451
+ await this.executeTask(task, executor, plan);
452
+ completed.add(task.id);
453
+ }));
454
+
455
+ // Mark newly ready tasks
456
+ for (const task of plan.tasks) {
457
+ if (task.status === TASK_STATUS.PENDING) {
458
+ const allDepsComplete = task.dependencies.every(depId => completed.has(depId));
459
+ if (allDepsComplete) {
460
+ task.status = TASK_STATUS.READY;
461
+ }
462
+ }
463
+ }
464
+ }
465
+ }
466
+
467
+ /**
468
+ * Execute a single task
469
+ * @private
470
+ */
471
+ async executeTask(task, executor, plan) {
472
+ task.status = TASK_STATUS.IN_PROGRESS;
473
+ const startTime = Date.now();
474
+
475
+ this.emit('task:start', { taskId: task.id, planId: plan.id, task });
476
+
477
+ try {
478
+ const result = await executor(task);
479
+ task.result = result;
480
+ task.status = TASK_STATUS.COMPLETED;
481
+ plan.metrics.completedTasks++;
482
+
483
+ this.emit('task:complete', { taskId: task.id, planId: plan.id, result });
484
+
485
+ // Check for replanning
486
+ if (this.adaptivePlanning) {
487
+ await this.checkReplan(plan, task);
488
+ }
489
+
490
+ } catch (error) {
491
+ task.result = { error: error.message };
492
+ task.status = TASK_STATUS.FAILED;
493
+ plan.metrics.failedTasks++;
494
+
495
+ this.emit('task:error', { taskId: task.id, planId: plan.id, error: error.message });
496
+ }
497
+
498
+ task.actualTime = Date.now() - startTime;
499
+ }
500
+
501
+ /**
502
+ * Check if replanning is needed
503
+ * @private
504
+ */
505
+ async checkReplan(plan, completedTask) {
506
+ // Calculate deviation from estimates
507
+ if (!completedTask.actualTime || !completedTask.estimatedTime) return;
508
+
509
+ const deviation = Math.abs(completedTask.actualTime - completedTask.estimatedTime) / completedTask.estimatedTime;
510
+
511
+ if (deviation > this.replanThreshold) {
512
+ this.emit('plan:replan-trigger', {
513
+ planId: plan.id,
514
+ taskId: completedTask.id,
515
+ deviation
516
+ });
517
+
518
+ // Adjust estimates for remaining tasks
519
+ const remaining = plan.tasks.filter(t =>
520
+ t.status === TASK_STATUS.PENDING || t.status === TASK_STATUS.READY
521
+ );
522
+
523
+ for (const task of remaining) {
524
+ if (task.metadata.component === completedTask.metadata.component) {
525
+ // Adjust based on actual vs estimated ratio
526
+ const ratio = completedTask.actualTime / completedTask.estimatedTime;
527
+ task.estimatedTime = Math.round(task.estimatedTime * ratio);
528
+ }
529
+ }
530
+ }
531
+ }
532
+
533
+ /**
534
+ * Pause plan execution
535
+ * @param {string} planId - Plan identifier
536
+ */
537
+ pausePlan(planId) {
538
+ const plan = this.plans.get(planId);
539
+ if (!plan) throw new Error(`Plan ${planId} not found`);
540
+
541
+ if (plan.status === PLAN_STATUS.EXECUTING) {
542
+ plan.status = PLAN_STATUS.PAUSED;
543
+ this.emit('plan:paused', { planId });
544
+ }
545
+ }
546
+
547
+ /**
548
+ * Resume plan execution
549
+ * @param {string} planId - Plan identifier
550
+ * @param {Function} executor - Task executor function
551
+ * @returns {Promise<Plan>}
552
+ */
553
+ async resumePlan(planId, executor) {
554
+ const plan = this.plans.get(planId);
555
+ if (!plan) throw new Error(`Plan ${planId} not found`);
556
+
557
+ if (plan.status !== PLAN_STATUS.PAUSED) {
558
+ throw new Error('Plan is not paused');
559
+ }
560
+
561
+ this.emit('plan:resuming', { planId });
562
+ return this.executePlan(planId, executor);
563
+ }
564
+
565
+ /**
566
+ * Cancel plan execution
567
+ * @param {string} planId - Plan identifier
568
+ */
569
+ cancelPlan(planId) {
570
+ const plan = this.plans.get(planId);
571
+ if (!plan) throw new Error(`Plan ${planId} not found`);
572
+
573
+ plan.status = PLAN_STATUS.CANCELLED;
574
+ plan.completedAt = Date.now();
575
+
576
+ // Mark pending tasks as skipped
577
+ for (const task of plan.tasks) {
578
+ if (task.status === TASK_STATUS.PENDING || task.status === TASK_STATUS.READY) {
579
+ task.status = TASK_STATUS.SKIPPED;
580
+ plan.metrics.skippedTasks++;
581
+ }
582
+ }
583
+
584
+ this.emit('plan:cancelled', { planId });
585
+ }
586
+
587
+ /**
588
+ * Add task to existing plan
589
+ * @param {string} planId - Plan identifier
590
+ * @param {Object} taskDef - Task definition
591
+ * @returns {Task}
592
+ */
593
+ addTask(planId, taskDef) {
594
+ const plan = this.plans.get(planId);
595
+ if (!plan) throw new Error(`Plan ${planId} not found`);
596
+
597
+ const task = {
598
+ id: this.generateId('task'),
599
+ name: taskDef.name,
600
+ description: taskDef.description || '',
601
+ status: TASK_STATUS.PENDING,
602
+ priority: taskDef.priority ?? PRIORITY.MEDIUM,
603
+ dependencies: taskDef.dependencies || [],
604
+ outputs: taskDef.outputs || [],
605
+ estimatedTime: taskDef.estimatedTime || 5000,
606
+ actualTime: null,
607
+ result: null,
608
+ metadata: taskDef.metadata || {}
609
+ };
610
+
611
+ // Insert at appropriate position based on dependencies
612
+ if (taskDef.after) {
613
+ const afterIndex = plan.tasks.findIndex(t => t.id === taskDef.after);
614
+ if (afterIndex >= 0) {
615
+ task.dependencies.push(taskDef.after);
616
+ plan.tasks.splice(afterIndex + 1, 0, task);
617
+ } else {
618
+ plan.tasks.push(task);
619
+ }
620
+ } else {
621
+ plan.tasks.push(task);
622
+ }
623
+
624
+ plan.metrics.totalTasks++;
625
+ plan.metrics.totalEstimatedTime += task.estimatedTime;
626
+
627
+ this.emit('task:added', { planId, task });
628
+
629
+ return task;
630
+ }
631
+
632
+ /**
633
+ * Remove task from plan
634
+ * @param {string} planId - Plan identifier
635
+ * @param {string} taskId - Task identifier
636
+ */
637
+ removeTask(planId, taskId) {
638
+ const plan = this.plans.get(planId);
639
+ if (!plan) throw new Error(`Plan ${planId} not found`);
640
+
641
+ const index = plan.tasks.findIndex(t => t.id === taskId);
642
+ if (index < 0) throw new Error(`Task ${taskId} not found`);
643
+
644
+ const task = plan.tasks[index];
645
+
646
+ // Remove from other tasks' dependencies
647
+ for (const t of plan.tasks) {
648
+ t.dependencies = t.dependencies.filter(d => d !== taskId);
649
+ }
650
+
651
+ plan.tasks.splice(index, 1);
652
+ plan.metrics.totalTasks--;
653
+ plan.metrics.totalEstimatedTime -= task.estimatedTime;
654
+
655
+ this.emit('task:removed', { planId, taskId });
656
+ }
657
+
658
+ /**
659
+ * Get plan by ID
660
+ * @param {string} planId - Plan identifier
661
+ * @returns {Plan|null}
662
+ */
663
+ getPlan(planId) {
664
+ return this.plans.get(planId) || null;
665
+ }
666
+
667
+ /**
668
+ * Get all plans
669
+ * @returns {Plan[]}
670
+ */
671
+ getAllPlans() {
672
+ return Array.from(this.plans.values());
673
+ }
674
+
675
+ /**
676
+ * Get plan progress
677
+ * @param {string} planId - Plan identifier
678
+ * @returns {Object}
679
+ */
680
+ getProgress(planId) {
681
+ const plan = this.plans.get(planId);
682
+ if (!plan) return null;
683
+
684
+ const total = plan.metrics.totalTasks;
685
+ const completed = plan.metrics.completedTasks;
686
+ const failed = plan.metrics.failedTasks;
687
+ const skipped = plan.metrics.skippedTasks;
688
+ const inProgress = plan.tasks.filter(t => t.status === TASK_STATUS.IN_PROGRESS).length;
689
+ const pending = total - completed - failed - skipped - inProgress;
690
+
691
+ return {
692
+ total,
693
+ completed,
694
+ failed,
695
+ skipped,
696
+ inProgress,
697
+ pending,
698
+ percentage: total > 0 ? Math.round((completed / total) * 100) : 0,
699
+ estimatedRemaining: plan.tasks
700
+ .filter(t => t.status === TASK_STATUS.PENDING || t.status === TASK_STATUS.READY)
701
+ .reduce((sum, t) => sum + t.estimatedTime, 0)
702
+ };
703
+ }
704
+
705
+ /**
706
+ * Get dependency graph
707
+ * @param {string} planId - Plan identifier
708
+ * @returns {Object}
709
+ */
710
+ getDependencyGraph(planId) {
711
+ const plan = this.plans.get(planId);
712
+ if (!plan) return null;
713
+
714
+ const nodes = plan.tasks.map(t => ({
715
+ id: t.id,
716
+ name: t.name,
717
+ status: t.status,
718
+ priority: t.priority
719
+ }));
720
+
721
+ const edges = [];
722
+ for (const task of plan.tasks) {
723
+ for (const dep of task.dependencies) {
724
+ edges.push({ from: dep, to: task.id });
725
+ }
726
+ }
727
+
728
+ return { nodes, edges };
729
+ }
730
+
731
+ /**
732
+ * Generate unique ID
733
+ * @private
734
+ */
735
+ generateId(prefix) {
736
+ return `${prefix}-${++this.taskCounter}-${Date.now().toString(36)}`;
737
+ }
738
+
739
+ /**
740
+ * Clear all plans
741
+ */
742
+ clearPlans() {
743
+ this.plans.clear();
744
+ this.activePlan = null;
745
+ this.taskCounter = 0;
746
+ }
747
+
748
+ /**
749
+ * Export plan to readable format
750
+ * @param {string} planId - Plan identifier
751
+ * @returns {string}
752
+ */
753
+ exportPlan(planId) {
754
+ const plan = this.plans.get(planId);
755
+ if (!plan) return '';
756
+
757
+ let output = `# Plan: ${plan.goal}\n\n`;
758
+ output += `**ID:** ${plan.id}\n`;
759
+ output += `**Status:** ${plan.status}\n`;
760
+ output += `**Created:** ${new Date(plan.createdAt).toISOString()}\n\n`;
761
+
762
+ output += `## Tasks (${plan.metrics.totalTasks})\n\n`;
763
+
764
+ for (let i = 0; i < plan.tasks.length; i++) {
765
+ const task = plan.tasks[i];
766
+ const statusIcon = this.getStatusIcon(task.status);
767
+ output += `${i + 1}. ${statusIcon} **${task.name}** [${task.status}]\n`;
768
+ output += ` - ${task.description}\n`;
769
+ if (task.dependencies.length > 0) {
770
+ output += ` - Depends on: ${task.dependencies.join(', ')}\n`;
771
+ }
772
+ output += `\n`;
773
+ }
774
+
775
+ output += `## Metrics\n\n`;
776
+ output += `- Completed: ${plan.metrics.completedTasks}\n`;
777
+ output += `- Failed: ${plan.metrics.failedTasks}\n`;
778
+ output += `- Skipped: ${plan.metrics.skippedTasks}\n`;
779
+ if (plan.metrics.actualTime) {
780
+ output += `- Total Time: ${plan.metrics.actualTime}ms\n`;
781
+ }
782
+
783
+ return output;
784
+ }
785
+
786
+ /**
787
+ * Get status icon
788
+ * @private
789
+ */
790
+ getStatusIcon(status) {
791
+ const icons = {
792
+ [TASK_STATUS.PENDING]: '⏳',
793
+ [TASK_STATUS.READY]: '📋',
794
+ [TASK_STATUS.IN_PROGRESS]: '🔄',
795
+ [TASK_STATUS.BLOCKED]: '🚫',
796
+ [TASK_STATUS.COMPLETED]: '✅',
797
+ [TASK_STATUS.FAILED]: '❌',
798
+ [TASK_STATUS.SKIPPED]: '⏭️'
799
+ };
800
+ return icons[status] || '•';
801
+ }
802
+ }
803
+
804
+ /**
805
+ * Create planning engine
806
+ * @param {PlanningOptions} [options={}] - Engine options
807
+ * @returns {PlanningEngine}
808
+ */
809
+ function createPlanningEngine(options = {}) {
810
+ return new PlanningEngine(options);
811
+ }
812
+
813
+ /**
814
+ * Create plan from goal
815
+ * @param {string} goal - Goal description
816
+ * @param {Object} [options={}] - Planning options
817
+ * @returns {Promise<Plan>}
818
+ */
819
+ async function createPlan(goal, options = {}) {
820
+ const engine = createPlanningEngine(options);
821
+ return engine.createPlan(goal, options.context || {});
822
+ }
823
+
824
+ module.exports = {
825
+ PlanningEngine,
826
+ createPlanningEngine,
827
+ createPlan,
828
+ PLAN_STATUS,
829
+ TASK_STATUS,
830
+ PRIORITY
831
+ };