swarm-engine 1.38.0 → 1.41.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 (107) hide show
  1. package/dist/core/patterns.d.ts +7 -1
  2. package/dist/core/patterns.d.ts.map +1 -1
  3. package/dist/core/patterns.js +23 -0
  4. package/dist/core/patterns.js.map +1 -1
  5. package/dist/core/types.d.ts +9 -0
  6. package/dist/core/types.d.ts.map +1 -1
  7. package/dist/core/types.js.map +1 -1
  8. package/dist/index.d.ts +11 -2
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +8 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/memory/index.d.ts.map +1 -1
  13. package/dist/memory/index.js +3 -9
  14. package/dist/memory/index.js.map +1 -1
  15. package/dist/runtime/adaptive.d.ts +1 -0
  16. package/dist/runtime/adaptive.d.ts.map +1 -1
  17. package/dist/runtime/adaptive.js +18 -4
  18. package/dist/runtime/adaptive.js.map +1 -1
  19. package/dist/runtime/agent-runner.d.ts +8 -0
  20. package/dist/runtime/agent-runner.d.ts.map +1 -1
  21. package/dist/runtime/agent-runner.js +79 -4
  22. package/dist/runtime/agent-runner.js.map +1 -1
  23. package/dist/runtime/backends/claude-session.d.ts +49 -0
  24. package/dist/runtime/backends/claude-session.d.ts.map +1 -0
  25. package/dist/runtime/backends/claude-session.js +237 -0
  26. package/dist/runtime/backends/claude-session.js.map +1 -0
  27. package/dist/runtime/backends/claude.d.ts.map +1 -1
  28. package/dist/runtime/backends/claude.js +90 -4
  29. package/dist/runtime/backends/claude.js.map +1 -1
  30. package/dist/runtime/backends/types.d.ts +31 -1
  31. package/dist/runtime/backends/types.d.ts.map +1 -1
  32. package/dist/runtime/compaction.d.ts +7 -1
  33. package/dist/runtime/compaction.d.ts.map +1 -1
  34. package/dist/runtime/compaction.js +12 -2
  35. package/dist/runtime/compaction.js.map +1 -1
  36. package/dist/runtime/distiller.d.ts +1 -0
  37. package/dist/runtime/distiller.d.ts.map +1 -1
  38. package/dist/runtime/distiller.js +8 -2
  39. package/dist/runtime/distiller.js.map +1 -1
  40. package/dist/runtime/engine.d.ts +43 -15
  41. package/dist/runtime/engine.d.ts.map +1 -1
  42. package/dist/runtime/engine.js +355 -67
  43. package/dist/runtime/engine.js.map +1 -1
  44. package/dist/runtime/execution-graph.d.ts.map +1 -1
  45. package/dist/runtime/execution-graph.js.map +1 -1
  46. package/dist/runtime/executor.d.ts +7 -1
  47. package/dist/runtime/executor.d.ts.map +1 -1
  48. package/dist/runtime/executor.js +20 -0
  49. package/dist/runtime/executor.js.map +1 -1
  50. package/dist/runtime/graph-adversarial.d.ts.map +1 -1
  51. package/dist/runtime/graph-adversarial.js.map +1 -1
  52. package/dist/runtime/graph-analyzer.d.ts.map +1 -1
  53. package/dist/runtime/graph-analyzer.js.map +1 -1
  54. package/dist/runtime/graph-discovery.d.ts.map +1 -1
  55. package/dist/runtime/graph-discovery.js +1 -4
  56. package/dist/runtime/graph-discovery.js.map +1 -1
  57. package/dist/runtime/graph-dropout.d.ts.map +1 -1
  58. package/dist/runtime/graph-dropout.js +10 -2
  59. package/dist/runtime/graph-dropout.js.map +1 -1
  60. package/dist/runtime/graph-embeddings.d.ts.map +1 -1
  61. package/dist/runtime/graph-embeddings.js +1 -3
  62. package/dist/runtime/graph-embeddings.js.map +1 -1
  63. package/dist/runtime/graph-feedback.d.ts.map +1 -1
  64. package/dist/runtime/graph-feedback.js +1 -3
  65. package/dist/runtime/graph-feedback.js.map +1 -1
  66. package/dist/runtime/graph-gnn.d.ts.map +1 -1
  67. package/dist/runtime/graph-gnn.js.map +1 -1
  68. package/dist/runtime/graph-meta-adversarial.d.ts.map +1 -1
  69. package/dist/runtime/graph-meta-adversarial.js.map +1 -1
  70. package/dist/runtime/graph-meta.d.ts.map +1 -1
  71. package/dist/runtime/graph-meta.js +48 -20
  72. package/dist/runtime/graph-meta.js.map +1 -1
  73. package/dist/runtime/graph-self-evolve.d.ts.map +1 -1
  74. package/dist/runtime/graph-self-evolve.js.map +1 -1
  75. package/dist/runtime/graph-synthesis.d.ts.map +1 -1
  76. package/dist/runtime/graph-synthesis.js.map +1 -1
  77. package/dist/runtime/graph-trajectory.d.ts.map +1 -1
  78. package/dist/runtime/graph-trajectory.js +3 -8
  79. package/dist/runtime/graph-trajectory.js.map +1 -1
  80. package/dist/runtime/learning-engine.d.ts.map +1 -1
  81. package/dist/runtime/learning-engine.js +10 -4
  82. package/dist/runtime/learning-engine.js.map +1 -1
  83. package/dist/runtime/prompt-compressor.d.ts +16 -0
  84. package/dist/runtime/prompt-compressor.d.ts.map +1 -0
  85. package/dist/runtime/prompt-compressor.js +68 -0
  86. package/dist/runtime/prompt-compressor.js.map +1 -0
  87. package/dist/runtime/repo-map.d.ts +40 -0
  88. package/dist/runtime/repo-map.d.ts.map +1 -0
  89. package/dist/runtime/repo-map.js +358 -0
  90. package/dist/runtime/repo-map.js.map +1 -0
  91. package/dist/runtime/sdk-mcp-server.d.ts +44 -0
  92. package/dist/runtime/sdk-mcp-server.d.ts.map +1 -0
  93. package/dist/runtime/sdk-mcp-server.js +133 -0
  94. package/dist/runtime/sdk-mcp-server.js.map +1 -0
  95. package/dist/runtime/structured-handoff.d.ts +41 -0
  96. package/dist/runtime/structured-handoff.d.ts.map +1 -0
  97. package/dist/runtime/structured-handoff.js +279 -0
  98. package/dist/runtime/structured-handoff.js.map +1 -0
  99. package/dist/runtime/token-analytics.d.ts +38 -0
  100. package/dist/runtime/token-analytics.d.ts.map +1 -0
  101. package/dist/runtime/token-analytics.js +59 -0
  102. package/dist/runtime/token-analytics.js.map +1 -0
  103. package/dist/runtime/verifier.d.ts +10 -0
  104. package/dist/runtime/verifier.d.ts.map +1 -1
  105. package/dist/runtime/verifier.js +97 -1
  106. package/dist/runtime/verifier.js.map +1 -1
  107. package/package.json +1 -1
@@ -23,6 +23,9 @@ import { estimateCacheSavings } from './cache-optimizer.js';
23
23
  import { createLivingSpec, updateSpecFromPhase } from './living-spec.js';
24
24
  import { estimateTokens } from '../utils/tokens.js';
25
25
  import { unlinkSync } from 'fs';
26
+ import { execFile } from 'child_process';
27
+ import { promisify } from 'util';
28
+ const execFileAsync = promisify(execFile);
26
29
  import { AgentRunner } from './agent-runner.js';
27
30
  import { LearningEngine } from './learning-engine.js';
28
31
  import { ExecutionGraph } from './execution-graph.js';
@@ -43,6 +46,9 @@ import { MetaAdversarialTester } from './graph-meta-adversarial.js';
43
46
  import { RuleEvolver } from './graph-self-evolve.js';
44
47
  import { TaskDiscovery } from './graph-discovery.js';
45
48
  import { OrchestrationEmbedder } from './graph-embeddings.js';
49
+ import { HandoffParser } from './structured-handoff.js';
50
+ import { classifyTaskComplexity } from './cascade.js';
51
+ import { SwarmMcpServer } from './sdk-mcp-server.js';
46
52
  /**
47
53
  * The Swarm Orchestration Engine.
48
54
  *
@@ -81,19 +87,134 @@ export class SwarmEngine {
81
87
  executionGraph = null;
82
88
  graphAnalyzer = null;
83
89
  graphLearner = null;
84
- reviewFeedback = null;
85
- causalEngine = null;
86
- gnnPredictor = null;
87
- adversarialEvolver = null;
88
- metaSelector = null;
89
- predictiveDropout = null;
90
- gnnWeights = null;
91
- patternSynthesizer = null;
92
- trajectoryPredictor = null;
93
- metaAdversarial = null;
94
- ruleEvolver = null;
95
- taskDiscovery = null;
96
- orchestrationEmbedder = null;
90
+ // Lazy-initialized ML modules (only constructed on first access)
91
+ _reviewFeedback;
92
+ _causalEngine;
93
+ _gnnPredictor;
94
+ _adversarialEvolver;
95
+ _metaSelector;
96
+ _predictiveDropout;
97
+ _gnnWeights;
98
+ _patternSynthesizer;
99
+ _trajectoryPredictor;
100
+ _metaAdversarial;
101
+ _ruleEvolver;
102
+ _taskDiscovery;
103
+ _orchestrationEmbedder;
104
+ swarmMcpServer = null;
105
+ decisions = new Map();
106
+ get reviewFeedback() {
107
+ if (this._reviewFeedback === undefined) {
108
+ this._reviewFeedback = this.executionGraph
109
+ ? this.tryInit(() => new ReviewFeedbackRecorder(this.executionGraph), 'ReviewFeedbackRecorder')
110
+ : null;
111
+ }
112
+ return this._reviewFeedback;
113
+ }
114
+ get causalEngine() {
115
+ if (this._causalEngine === undefined) {
116
+ this._causalEngine = this.executionGraph
117
+ ? this.tryInit(() => new CausalGraphEngine(this.executionGraph), 'CausalGraphEngine')
118
+ : null;
119
+ }
120
+ return this._causalEngine;
121
+ }
122
+ get gnnPredictor() {
123
+ if (this._gnnPredictor === undefined) {
124
+ this._gnnPredictor = this.executionGraph
125
+ ? this.tryInit(() => new FailurePropagationPredictor(this.executionGraph), 'FailurePropagationPredictor')
126
+ : null;
127
+ }
128
+ return this._gnnPredictor;
129
+ }
130
+ get adversarialEvolver() {
131
+ if (this._adversarialEvolver === undefined) {
132
+ this._adversarialEvolver = this.executionGraph
133
+ ? this.tryInit(() => new AdversarialEvolver(this.executionGraph), 'AdversarialEvolver')
134
+ : null;
135
+ }
136
+ return this._adversarialEvolver;
137
+ }
138
+ get metaSelector() {
139
+ if (this._metaSelector === undefined) {
140
+ this._metaSelector = this.executionGraph
141
+ ? this.tryInit(() => new MetaPatternSelector(this.executionGraph), 'MetaPatternSelector')
142
+ : null;
143
+ }
144
+ return this._metaSelector;
145
+ }
146
+ get predictiveDropout() {
147
+ if (this._predictiveDropout === undefined) {
148
+ this._predictiveDropout = this.executionGraph
149
+ ? this.tryInit(() => new PredictiveDropout(this.executionGraph), 'PredictiveDropout')
150
+ : null;
151
+ }
152
+ return this._predictiveDropout;
153
+ }
154
+ get gnnWeights() {
155
+ if (this._gnnWeights === undefined) {
156
+ if (this.gnnPredictor) {
157
+ try {
158
+ this._gnnWeights = this.gnnPredictor.initWeights();
159
+ }
160
+ catch {
161
+ this._gnnWeights = null;
162
+ }
163
+ }
164
+ else {
165
+ this._gnnWeights = null;
166
+ }
167
+ }
168
+ return this._gnnWeights;
169
+ }
170
+ get patternSynthesizer() {
171
+ if (this._patternSynthesizer === undefined) {
172
+ this._patternSynthesizer = this.executionGraph
173
+ ? this.tryInit(() => new PatternSynthesizer(this.executionGraph), 'PatternSynthesizer')
174
+ : null;
175
+ }
176
+ return this._patternSynthesizer;
177
+ }
178
+ get trajectoryPredictor() {
179
+ if (this._trajectoryPredictor === undefined) {
180
+ this._trajectoryPredictor = this.executionGraph
181
+ ? this.tryInit(() => new TrajectoryPredictor(this.executionGraph), 'TrajectoryPredictor')
182
+ : null;
183
+ }
184
+ return this._trajectoryPredictor;
185
+ }
186
+ get metaAdversarial() {
187
+ if (this._metaAdversarial === undefined) {
188
+ this._metaAdversarial = this.executionGraph
189
+ ? this.tryInit(() => new MetaAdversarialTester(this.executionGraph), 'MetaAdversarialTester')
190
+ : null;
191
+ }
192
+ return this._metaAdversarial;
193
+ }
194
+ get ruleEvolver() {
195
+ if (this._ruleEvolver === undefined) {
196
+ this._ruleEvolver = this.executionGraph
197
+ ? this.tryInit(() => new RuleEvolver(this.executionGraph), 'RuleEvolver')
198
+ : null;
199
+ }
200
+ return this._ruleEvolver;
201
+ }
202
+ get taskDiscovery() {
203
+ if (this._taskDiscovery === undefined) {
204
+ this._taskDiscovery = this.executionGraph
205
+ ? this.tryInit(() => new TaskDiscovery(this.executionGraph), 'TaskDiscovery')
206
+ : null;
207
+ }
208
+ return this._taskDiscovery;
209
+ }
210
+ get orchestrationEmbedder() {
211
+ if (this._orchestrationEmbedder === undefined) {
212
+ this._orchestrationEmbedder = this.executionGraph
213
+ ? this.tryInit(() => new OrchestrationEmbedder(this.executionGraph), 'OrchestrationEmbedder')
214
+ : null;
215
+ }
216
+ return this._orchestrationEmbedder;
217
+ }
97
218
  constructor(options) {
98
219
  this.options = options;
99
220
  this.registry = options.registry;
@@ -127,46 +248,36 @@ export class SwarmEngine {
127
248
  this.compounder = this.tryInit(() => new KnowledgeCompounder(), 'KnowledgeCompounder');
128
249
  this.executionGraph = this.tryInit(() => new ExecutionGraph(), 'ExecutionGraph');
129
250
  this.executionGraph?.attachToEventBus(this.bus);
251
+ // Create SwarmMcpServer for SDK tool injection
252
+ try {
253
+ this.swarmMcpServer = new SwarmMcpServer({
254
+ phaseOutputs: this.phaseOutputs,
255
+ graph: this.executionGraph,
256
+ decisions: this.decisions,
257
+ });
258
+ }
259
+ catch {
260
+ this.swarmMcpServer = null;
261
+ }
130
262
  // Create graph analysis deps (reused by AgentRunner + LearningEngine)
131
- this.graphLearner = this.executionGraph ? this.tryInit(() => new GraphLearner(this.executionGraph), 'GraphLearner') : null;
132
- this.graphAnalyzer = this.executionGraph ? this.tryInit(() => new GraphAnalyzer(this.executionGraph), 'GraphAnalyzer') : null;
133
- this.reviewFeedback = this.executionGraph ? this.tryInit(() => new ReviewFeedbackRecorder(this.executionGraph), 'ReviewFeedbackRecorder') : null;
134
- // Cutting-edge ML features
135
- this.causalEngine = this.executionGraph
136
- ? this.tryInit(() => new CausalGraphEngine(this.executionGraph), 'CausalGraphEngine')
137
- : null;
138
- this.gnnPredictor = this.executionGraph
139
- ? this.tryInit(() => new FailurePropagationPredictor(this.executionGraph), 'FailurePropagationPredictor')
140
- : null;
141
- this.adversarialEvolver = this.executionGraph
142
- ? this.tryInit(() => new AdversarialEvolver(this.executionGraph), 'AdversarialEvolver')
263
+ this.graphLearner = this.executionGraph
264
+ ? this.tryInit(() => new GraphLearner(this.executionGraph), 'GraphLearner')
143
265
  : null;
144
- this.metaSelector = this.executionGraph
145
- ? this.tryInit(() => new MetaPatternSelector(this.executionGraph), 'MetaPatternSelector')
266
+ this.graphAnalyzer = this.executionGraph
267
+ ? this.tryInit(() => new GraphAnalyzer(this.executionGraph), 'GraphAnalyzer')
146
268
  : null;
147
- this.predictiveDropout = this.executionGraph
148
- ? this.tryInit(() => new PredictiveDropout(this.executionGraph), 'PredictiveDropout')
149
- : null;
150
- // Initialize GNN weights (training happens lazily via background scheduling)
151
- if (this.gnnPredictor) {
152
- try {
153
- this.gnnWeights = this.gnnPredictor.initWeights();
154
- }
155
- catch { /* silent */ }
156
- }
157
- // Self-aware engine features
158
- this.patternSynthesizer = this.executionGraph ? this.tryInit(() => new PatternSynthesizer(this.executionGraph), 'PatternSynthesizer') : null;
159
- this.trajectoryPredictor = this.executionGraph ? this.tryInit(() => new TrajectoryPredictor(this.executionGraph), 'TrajectoryPredictor') : null;
160
- this.metaAdversarial = this.executionGraph ? this.tryInit(() => new MetaAdversarialTester(this.executionGraph), 'MetaAdversarialTester') : null;
161
- this.ruleEvolver = this.executionGraph ? this.tryInit(() => new RuleEvolver(this.executionGraph), 'RuleEvolver') : null;
162
- this.taskDiscovery = this.executionGraph ? this.tryInit(() => new TaskDiscovery(this.executionGraph), 'TaskDiscovery') : null;
163
- this.orchestrationEmbedder = this.executionGraph ? this.tryInit(() => new OrchestrationEmbedder(this.executionGraph), 'OrchestrationEmbedder') : null;
269
+ // ML modules (reviewFeedback, causalEngine, gnnPredictor, adversarialEvolver,
270
+ // metaSelector, predictiveDropout, gnnWeights, patternSynthesizer, trajectoryPredictor,
271
+ // metaAdversarial, ruleEvolver, taskDiscovery, orchestrationEmbedder) are lazy-initialized
272
+ // via property getters they are only constructed on first access.
164
273
  // Prune stale graph data on init (Feature 6: graph decay)
165
274
  if (this.executionGraph) {
166
275
  try {
167
276
  this.executionGraph.pruneOlderThan(90);
168
277
  }
169
- catch { /* silent */ }
278
+ catch {
279
+ /* silent */
280
+ }
170
281
  }
171
282
  // Rule evolution proposal on startup (skip in mock mode and when no historical data)
172
283
  if (this.ruleEvolver && !this.options.mock) {
@@ -219,10 +330,35 @@ export class SwarmEngine {
219
330
  /**
220
331
  * Execute an orchestration from config.
221
332
  */
222
- async execute(config) {
333
+ async execute(config, executeOptions) {
223
334
  // Re-use existing instance if present (e.g., when resuming from checkpoint)
224
335
  const orchestration = this.orchestrations.get(config.id) ?? this.createInstance(config);
225
336
  this.orchestrations.set(config.id, orchestration);
337
+ // Dry run mode: execute only the first phase in 'plan' mode, then return
338
+ if (executeOptions?.dryRun) {
339
+ this.log.info('Dry run mode: running first phase in plan mode');
340
+ const firstPhase = orchestration.phases[0];
341
+ if (firstPhase) {
342
+ this.executor.setPermissionMode('plan');
343
+ try {
344
+ this.phaseOutputs.clear();
345
+ orchestration.status = 'running';
346
+ orchestration.startedAt = new Date();
347
+ const outputs = await this.executePhase(orchestration, firstPhase);
348
+ this.phaseOutputs.set(firstPhase.config.name, outputs);
349
+ orchestration.status = 'completed';
350
+ }
351
+ catch (error) {
352
+ orchestration.status = 'failed';
353
+ this.log.error('Dry run failed', { error: error instanceof Error ? error.message : String(error) });
354
+ }
355
+ finally {
356
+ this.executor.setPermissionMode(this.options.permissionMode ?? 'default');
357
+ orchestration.completedAt = new Date();
358
+ }
359
+ return orchestration;
360
+ }
361
+ }
226
362
  this.log.info(`Starting orchestration: ${config.name}`, {
227
363
  pattern: config.pattern,
228
364
  phases: config.phases.length,
@@ -233,9 +369,45 @@ export class SwarmEngine {
233
369
  pattern: config.pattern,
234
370
  phaseCount: config.phases.length,
235
371
  }, 'engine');
372
+ // Single-agent fast path for simple tasks
373
+ if (config.phases?.length === 1 && config.phases[0].agents?.length === 1) {
374
+ try {
375
+ const complexity = classifyTaskComplexity(config.description || '');
376
+ if (complexity === 'simple') {
377
+ this.log.info('Simple task detected — using single-agent fast path');
378
+ this.bus.emit('system:warning', { type: 'single-agent-bypass', task: config.description }, 'engine');
379
+ try {
380
+ const phase = config.phases[0];
381
+ const agent = phase.agents[0];
382
+ const runContext = {
383
+ phaseOutputs: new Map(),
384
+ intraPhaseOutputs: new Map(),
385
+ livingSpec: null,
386
+ sharedContextFiles: [],
387
+ };
388
+ const instance = await this.agentRunner.executeAgentWithRetry(agent, orchestration.phases[0], orchestration, 0, runContext);
389
+ orchestration.status = instance.result?.output ? 'completed' : 'failed';
390
+ orchestration.startedAt = new Date();
391
+ orchestration.completedAt = new Date();
392
+ orchestration.phases[0].status = orchestration.status;
393
+ if (instance.result)
394
+ orchestration.phases[0].agents = [instance];
395
+ orchestration.usage = instance.usage;
396
+ return orchestration;
397
+ }
398
+ catch (e) {
399
+ this.log.debug(`Single-agent bypass failed, falling through to normal execution: ${e}`);
400
+ // Fall through to normal execution
401
+ }
402
+ }
403
+ }
404
+ catch {
405
+ /* classification failed — fall through */
406
+ }
407
+ }
236
408
  orchestration.status = 'running';
237
409
  orchestration.startedAt = new Date();
238
- this.phaseOutputs = new Map();
410
+ this.phaseOutputs.clear();
239
411
  // TODO: Checkpoint doesn't store phase outputs — resumed phases lose inter-phase context
240
412
  this.reflections = [];
241
413
  this.sharedContextFiles = [];
@@ -257,7 +429,7 @@ export class SwarmEngine {
257
429
  if (this.gnnPredictor && this.gnnWeights) {
258
430
  try {
259
431
  const risks = this.gnnPredictor.predict(orchestration.config.id, this.gnnWeights);
260
- const highRisk = risks.filter(r => r.riskScore > 0.7);
432
+ const highRisk = risks.filter((r) => r.riskScore > 0.7);
261
433
  if (highRisk.length > 0) {
262
434
  this.log.warn(`GNN predicts high failure risk for ${highRisk.length} nodes`);
263
435
  this.bus.emit('system:warning', { type: 'gnn-risk', risks: highRisk }, 'engine');
@@ -341,17 +513,31 @@ export class SwarmEngine {
341
513
  const reflection = this.reflectionEngine.reflect(phase);
342
514
  const patternStats = this.graphLearner?.getPatternStats(orchestration.config.pattern)?.[0];
343
515
  const gateOutputs = this.phaseOutputs.get(phase.config.name) || [];
344
- const gate = this.graphAnalyzer.evaluatePhaseConfidence({ kind: phase.config.kind ?? 'implement', agentCount: phase.agents.length, outputs: gateOutputs, usage: phase.usage }, { confidence: reflection.confidence }, patternStats ? { successRate: patternStats.successRate, avgDurationMs: patternStats.avgDurationMs } : undefined);
516
+ const gate = this.graphAnalyzer.evaluatePhaseConfidence({
517
+ kind: phase.config.kind ?? 'implement',
518
+ agentCount: phase.agents.length,
519
+ outputs: gateOutputs,
520
+ usage: phase.usage,
521
+ }, { confidence: reflection.confidence }, patternStats
522
+ ? { successRate: patternStats.successRate, avgDurationMs: patternStats.avgDurationMs }
523
+ : undefined);
345
524
  if (gate.result === 'halt') {
346
525
  this.log.warn(`Phase gate HALT: ${gate.reason}`);
347
526
  orchestration.status = 'failed';
348
527
  }
349
528
  if (gate.result === 'warn') {
350
529
  this.log.warn(`Phase gate WARNING: ${gate.reason}`);
351
- this.bus.emit('system:warning', { type: 'gate-warning', orchestrationId: orchestration.config.id, phase: phase.config.name, reason: gate.reason }, 'engine');
530
+ this.bus.emit('system:warning', {
531
+ type: 'gate-warning',
532
+ orchestrationId: orchestration.config.id,
533
+ phase: phase.config.name,
534
+ reason: gate.reason,
535
+ }, 'engine');
352
536
  }
353
537
  }
354
- catch { /* Graph gate evaluation failed — continue */ }
538
+ catch {
539
+ /* Graph gate evaluation failed — continue */
540
+ }
355
541
  }
356
542
  // Trajectory prediction after each completed phase
357
543
  if (this.trajectoryPredictor) {
@@ -376,9 +562,17 @@ export class SwarmEngine {
376
562
  if (this.trajectoryPredictor) {
377
563
  try {
378
564
  const forecastCompleted = orchestration.phases
379
- .filter(p => p.status === 'completed')
380
- .map(p => ({ name: p.config.name, status: p.status, tokens: p.usage?.totalTokens || 0, durationMs: p.usage?.durationMs || 0, confidence: 'medium' }));
381
- const forecastRemaining = orchestration.phases.filter(p => p.status === 'pending').map(p => p.config.name);
565
+ .filter((p) => p.status === 'completed')
566
+ .map((p) => ({
567
+ name: p.config.name,
568
+ status: p.status,
569
+ tokens: p.usage?.totalTokens || 0,
570
+ durationMs: p.usage?.durationMs || 0,
571
+ confidence: 'medium',
572
+ }));
573
+ const forecastRemaining = orchestration.phases
574
+ .filter((p) => p.status === 'pending')
575
+ .map((p) => p.config.name);
382
576
  if (forecastRemaining.length > 0) {
383
577
  const forecast = this.trajectoryPredictor.predictTrajectory(orchestration.config.id, forecastCompleted, forecastRemaining, orchestration.config.pattern);
384
578
  if (forecast.overallSuccessProb < 0.3) {
@@ -450,11 +644,13 @@ export class SwarmEngine {
450
644
  const report = this.metaAdversarial.runFullAudit({
451
645
  patternSelector: this.metaSelector ?? undefined,
452
646
  dropout: this.predictiveDropout ?? undefined,
453
- causal: this.causalEngine ? {
454
- estimateCausalEffect: (t, tv, cv) =>
455
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
456
- this.causalEngine.estimateCausalEffect(t, tv, cv),
457
- } : undefined,
647
+ causal: this.causalEngine
648
+ ? {
649
+ estimateCausalEffect: (t, tv, cv) =>
650
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
651
+ this.causalEngine.estimateCausalEffect(t, tv, cv),
652
+ }
653
+ : undefined,
458
654
  });
459
655
  if (report.overallRisk !== 'low') {
460
656
  this.log.warn(`Self-audit found ${report.vulnerabilities.length} ML vulnerabilities (risk: ${report.overallRisk})`);
@@ -661,6 +857,24 @@ export class SwarmEngine {
661
857
  quality: c.cost.qualityScore,
662
858
  }));
663
859
  }
860
+ // Emit plan preview event so listeners can inspect before execution
861
+ try {
862
+ this.bus.emit('system:warning', {
863
+ type: 'plan-preview',
864
+ pattern: patternName,
865
+ phases: bestPlan.phases.map((p) => ({
866
+ name: p.name,
867
+ kind: p.kind,
868
+ agentCount: p.agents?.length || 0,
869
+ agents: p.agents?.map((a) => ({ type: a.type, model: a.model })) || [],
870
+ })),
871
+ estimatedCost: bestPlan.estimates,
872
+ task: task.slice(0, 200),
873
+ }, 'engine');
874
+ }
875
+ catch {
876
+ /* plan preview emit failed — non-critical */
877
+ }
664
878
  return bestPlan;
665
879
  }
666
880
  /**
@@ -681,20 +895,86 @@ export class SwarmEngine {
681
895
  // Predictive dropout from ML model (Feature 5: Predictive Dropout)
682
896
  if (this.predictiveDropout && phase.config.agents.length > 1) {
683
897
  try {
684
- const recommendations = this.predictiveDropout.predictRedundant(phase.config.agents.map(a => ({ id: a.name || a.type, agentType: a.type })), phase.config.kind ?? 'implement', 0, orchestration.config.pattern || '');
685
- const toDrop = recommendations.filter(r => r.shouldDropOut);
898
+ const recommendations = this.predictiveDropout.predictRedundant(phase.config.agents.map((a) => ({ id: a.name || a.type, agentType: a.type })), phase.config.kind ?? 'implement', 0, orchestration.config.pattern || '');
899
+ const toDrop = recommendations.filter((r) => r.shouldDropOut);
686
900
  if (toDrop.length > 0) {
687
- this.log.info(`Predictive dropout: skipping ${toDrop.map(d => d.agentType).join(', ')}`);
688
- phase.config.agents = phase.config.agents.filter(a => !toDrop.some(d => d.agentType === a.type));
901
+ this.log.info(`Predictive dropout: skipping ${toDrop.map((d) => d.agentType).join(', ')}`);
902
+ phase.config.agents = phase.config.agents.filter((a) => !toDrop.some((d) => d.agentType === a.type));
689
903
  }
690
904
  }
691
905
  catch (e) {
692
906
  this.log.debug(`Predictive dropout failed: ${e}`);
693
907
  }
694
908
  }
909
+ // Feature 5: Diff-based review context — inject git diff before review phases
910
+ if (phase.config.kind === 'review' && !this.options.mock) {
911
+ try {
912
+ const cwd = this.options.cwd || process.cwd();
913
+ const { stdout: diff } = await execFileAsync('git', ['diff', '--stat', 'HEAD'], {
914
+ cwd,
915
+ timeout: 10000,
916
+ encoding: 'utf-8',
917
+ });
918
+ const trimmedDiff = diff.slice(0, 3000);
919
+ if (trimmedDiff.trim()) {
920
+ const { stdout: diffDetail } = await execFileAsync('git', ['diff', 'HEAD'], {
921
+ cwd,
922
+ timeout: 10000,
923
+ encoding: 'utf-8',
924
+ });
925
+ const existing = this.phaseOutputs.get(phase.config.name) || [];
926
+ existing.unshift(`## Git Diff Summary\n\`\`\`\n${trimmedDiff}\n\`\`\`\n\n## Detailed Changes\n\`\`\`diff\n${diffDetail.slice(0, 5000)}\n\`\`\``);
927
+ this.phaseOutputs.set(phase.config.name, existing);
928
+ }
929
+ }
930
+ catch (e) {
931
+ this.log.debug(`Diff context failed: ${e}`);
932
+ }
933
+ }
934
+ // Feature 6: Pre-review lint wiring — run lint/typecheck before review phases
935
+ if (phase.config.kind === 'review' && this.verifier && !this.options.mock) {
936
+ try {
937
+ const lintResults = await this.verifier.verify();
938
+ const failures = lintResults.filter((r) => !r.passed);
939
+ if (failures.length > 0) {
940
+ const lintOutput = failures.map((f) => `${f.step}: ${f.output?.slice(0, 500)}`).join('\n');
941
+ const existing = this.phaseOutputs.get(phase.config.name) || [];
942
+ existing.unshift(`## Pre-Review Lint/Typecheck Results\n${lintOutput}`);
943
+ this.phaseOutputs.set(phase.config.name, existing);
944
+ }
945
+ }
946
+ catch (e) {
947
+ this.log.debug(`Pre-review checks failed: ${e}`);
948
+ }
949
+ }
695
950
  const maxRetries = phase.config.maxRetries ?? 2;
951
+ // Smart phase output routing: filter prior outputs by relevance instead of passing everything
952
+ let filteredOutputs = this.phaseOutputs;
953
+ if (phase.config.kind === 'review') {
954
+ // Review phase only needs implement/integrate/test outputs, not research
955
+ const reviewFiltered = new Map();
956
+ for (const [phaseName, outputs] of this.phaseOutputs) {
957
+ if (phaseName.includes('implement') || phaseName.includes('integrate') || phaseName.includes('test')) {
958
+ reviewFiltered.set(phaseName, outputs);
959
+ }
960
+ }
961
+ // Fall back to all outputs if nothing matched
962
+ if (reviewFiltered.size > 0)
963
+ filteredOutputs = reviewFiltered;
964
+ }
965
+ if (phase.config.kind === 'test') {
966
+ // Test phase only needs implement outputs
967
+ const testFiltered = new Map();
968
+ for (const [phaseName, outputs] of this.phaseOutputs) {
969
+ if (phaseName.includes('implement')) {
970
+ testFiltered.set(phaseName, outputs);
971
+ }
972
+ }
973
+ if (testFiltered.size > 0)
974
+ filteredOutputs = testFiltered;
975
+ }
696
976
  const runContext = {
697
- phaseOutputs: this.phaseOutputs,
977
+ phaseOutputs: filteredOutputs,
698
978
  livingSpec: this.livingSpec,
699
979
  sharedContextFiles: this.sharedContextFiles,
700
980
  cwd: this.options.cwd,
@@ -788,7 +1068,7 @@ export class SwarmEngine {
788
1068
  }
789
1069
  }
790
1070
  // Run verification after implementation phases
791
- if (phase.config.kind === 'implement' || phase.config.kind === 'integrate') {
1071
+ if ((phase.config.kind === 'implement' || phase.config.kind === 'integrate') && !this.options.mock) {
792
1072
  const results = await this.verifier.verify();
793
1073
  const summary = Verifier.summarize(results);
794
1074
  this.log.info(`Verification: ${summary}`, { phase: phase.config.name });
@@ -817,10 +1097,18 @@ export class SwarmEngine {
817
1097
  agents: phase.agents.length,
818
1098
  usage: phase.usage,
819
1099
  });
820
- // Collect agent outputs for inter-phase context
1100
+ // Collect agent outputs for inter-phase context (structured handoff)
821
1101
  const outputs = phase.agents
822
1102
  .filter((a) => a.result?.output)
823
- .map((a) => `[${a.config.name}]: ${a.result.output.slice(0, 2000)}`);
1103
+ .map((a) => {
1104
+ try {
1105
+ const handoff = HandoffParser.parseAgentOutput(a.result.output);
1106
+ return HandoffParser.serializeForNextPhase([{ agentName: a.config.name, handoff }], 4000);
1107
+ }
1108
+ catch {
1109
+ return `[${a.config.name}]: ${a.result.output.slice(0, 2000)}`;
1110
+ }
1111
+ });
824
1112
  // Update living spec with phase outputs
825
1113
  if (this.livingSpec) {
826
1114
  this.livingSpec = updateSpecFromPhase(this.livingSpec, phase.config.name, outputs);