claude-flow-novice 2.15.9 → 2.15.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/cfn-loop-orchestration/IMPLEMENTATION_SUMMARY.md +519 -0
- package/.claude/skills/cfn-loop-orchestration/ORCHESTRATOR_IMPLEMENTATION.md +493 -0
- package/.claude/skills/cfn-loop-orchestration/ORCHESTRATOR_QUICK_START.md +499 -0
- package/.claude/skills/cfn-loop-orchestration/helpers/orchestrate-ts.sh +104 -0
- package/.claude/skills/cfn-loop-orchestration/orchestrate.sh +2 -2
- package/.claude/skills/cfn-loop-orchestration/src/orchestrate.ts +648 -0
- package/.claude/skills/cfn-loop-orchestration/tests/orchestrate.test.ts +836 -0
- package/README.md +205 -10
- package/claude-assets/agents/cfn-dev-team/coordinators/cfn-v3-coordinator.md +180 -229
- package/claude-assets/agents/cfn-dev-team/dev-ops/docker-specialist.md +2 -0
- package/claude-assets/agents/cfn-dev-team/dev-ops/kubernetes-specialist.md +2 -0
- package/claude-assets/agents/cfn-dev-team/developers/api-gateway-specialist.md +2 -1
- package/claude-assets/agents/cfn-dev-team/developers/database/database-architect.md +3 -0
- package/claude-assets/agents/cfn-dev-team/developers/frontend/mobile-dev.md +4 -1
- package/claude-assets/agents/cfn-dev-team/developers/frontend/react-frontend-engineer.md +4 -1
- package/claude-assets/agents/cfn-dev-team/developers/frontend/typescript-specialist.md +4 -1
- package/claude-assets/agents/cfn-dev-team/developers/frontend/ui-designer.md +5 -0
- package/claude-assets/agents/cfn-dev-team/developers/graphql-specialist.md +2 -1
- package/claude-assets/agents/cfn-dev-team/developers/rust-developer.md +2 -1
- package/claude-assets/agents/cfn-dev-team/documentation/pseudocode.md +2 -7
- package/claude-assets/agents/cfn-dev-team/utility/epic-creator.md +16 -9
- package/claude-assets/agents/cfn-dev-team/utility/memory-leak-specialist.md +16 -9
- package/claude-assets/agents/cfn-dev-team/utility/z-ai-specialist.md +16 -9
- package/claude-assets/skills/cfn-loop-orchestration/IMPLEMENTATION_SUMMARY.md +519 -0
- package/claude-assets/skills/cfn-loop-orchestration/ORCHESTRATOR_IMPLEMENTATION.md +493 -0
- package/claude-assets/skills/cfn-loop-orchestration/ORCHESTRATOR_QUICK_START.md +499 -0
- package/claude-assets/skills/cfn-loop-orchestration/helpers/orchestrate-ts.sh +104 -0
- package/claude-assets/skills/cfn-loop-orchestration/orchestrate.sh +2 -2
- package/claude-assets/skills/cfn-loop-orchestration/src/orchestrate.ts +648 -0
- package/claude-assets/skills/cfn-loop-orchestration/tests/orchestrate.test.ts +836 -0
- package/dist/cli/config-manager.js +91 -109
- package/dist/cli/config-manager.js.map +1 -1
- package/dist/coordination/coordinate.js +369 -0
- package/dist/coordination/coordinate.js.map +1 -0
- package/dist/coordination/spawn-agent.js +364 -0
- package/dist/coordination/spawn-agent.js.map +1 -0
- package/dist/coordination/types-export.js +38 -0
- package/dist/coordination/types-export.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,836 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive test suite for CFN Loop orchestration
|
|
3
|
+
* Covers all execution modes (MVP/Standard/Enterprise) with 60+ tests
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Orchestrator,
|
|
8
|
+
type OrchestrationConfig,
|
|
9
|
+
type LoopPhase,
|
|
10
|
+
} from '../src/orchestrate';
|
|
11
|
+
import type { TestResult } from '../src/types';
|
|
12
|
+
|
|
13
|
+
describe('Orchestrator - Core Initialization', () => {
|
|
14
|
+
test('creates orchestrator with valid MVP config', () => {
|
|
15
|
+
const config: OrchestrationConfig = {
|
|
16
|
+
taskId: 'test-task-1',
|
|
17
|
+
mode: 'mvp',
|
|
18
|
+
maxIterations: 5,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const orchestrator = new Orchestrator(config);
|
|
22
|
+
expect(orchestrator.getTaskId()).toBe('test-task-1');
|
|
23
|
+
expect(orchestrator.getMode()).toBe('mvp');
|
|
24
|
+
expect(orchestrator.getMaxIterations()).toBe(5);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('creates orchestrator with Standard mode', () => {
|
|
28
|
+
const config: OrchestrationConfig = {
|
|
29
|
+
taskId: 'test-standard',
|
|
30
|
+
mode: 'standard',
|
|
31
|
+
maxIterations: 10,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const orchestrator = new Orchestrator(config);
|
|
35
|
+
expect(orchestrator.getMode()).toBe('standard');
|
|
36
|
+
expect(orchestrator.getMaxIterations()).toBe(10);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('creates orchestrator with Enterprise mode', () => {
|
|
40
|
+
const config: OrchestrationConfig = {
|
|
41
|
+
taskId: 'test-enterprise',
|
|
42
|
+
mode: 'enterprise',
|
|
43
|
+
maxIterations: 15,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const orchestrator = new Orchestrator(config);
|
|
47
|
+
expect(orchestrator.getMode()).toBe('enterprise');
|
|
48
|
+
expect(orchestrator.getMaxIterations()).toBe(15);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('throws error on invalid task ID', () => {
|
|
52
|
+
const config: OrchestrationConfig = {
|
|
53
|
+
taskId: '',
|
|
54
|
+
mode: 'standard',
|
|
55
|
+
maxIterations: 10,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
expect(() => new Orchestrator(config)).toThrow('Task ID cannot be empty');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('throws error on invalid mode', () => {
|
|
62
|
+
const config: OrchestrationConfig = {
|
|
63
|
+
taskId: 'test',
|
|
64
|
+
mode: 'invalid' as any,
|
|
65
|
+
maxIterations: 10,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
expect(() => new Orchestrator(config)).toThrow('Invalid execution mode');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('throws error on invalid max iterations', () => {
|
|
72
|
+
const config: OrchestrationConfig = {
|
|
73
|
+
taskId: 'test',
|
|
74
|
+
mode: 'standard',
|
|
75
|
+
maxIterations: 0,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
expect(() => new Orchestrator(config)).toThrow('Max iterations must be at least 1');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('throws error on max iterations exceeding limit', () => {
|
|
82
|
+
const config: OrchestrationConfig = {
|
|
83
|
+
taskId: 'test',
|
|
84
|
+
mode: 'standard',
|
|
85
|
+
maxIterations: 101,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
expect(() => new Orchestrator(config)).toThrow('Max iterations cannot exceed 100');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('Orchestrator - State Management', () => {
|
|
93
|
+
let orchestrator: Orchestrator;
|
|
94
|
+
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
const config: OrchestrationConfig = {
|
|
97
|
+
taskId: 'test-state',
|
|
98
|
+
mode: 'standard',
|
|
99
|
+
maxIterations: 10,
|
|
100
|
+
};
|
|
101
|
+
orchestrator = new Orchestrator(config);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('initializes orchestration state correctly', () => {
|
|
105
|
+
const state = orchestrator.getState();
|
|
106
|
+
expect(state.taskId).toBe('test-state');
|
|
107
|
+
expect(state.iteration).toBe(0);
|
|
108
|
+
expect(state.currentPhase).toBe('loop3');
|
|
109
|
+
expect(state.completedAgents.size).toBe(0);
|
|
110
|
+
expect(state.failedAgents.size).toBe(0);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('updates iteration count', () => {
|
|
114
|
+
orchestrator.incrementIteration();
|
|
115
|
+
expect(orchestrator.getState().iteration).toBe(1);
|
|
116
|
+
|
|
117
|
+
orchestrator.incrementIteration();
|
|
118
|
+
expect(orchestrator.getState().iteration).toBe(2);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('tracks completed agents', () => {
|
|
122
|
+
orchestrator.markAgentComplete('agent-1', 'loop3');
|
|
123
|
+
const state = orchestrator.getState();
|
|
124
|
+
expect(state.completedAgents.has('agent-1')).toBe(true);
|
|
125
|
+
expect(state.completedAgents.size).toBe(1);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('tracks failed agents', () => {
|
|
129
|
+
orchestrator.markAgentFailed('agent-2', 'loop3');
|
|
130
|
+
const state = orchestrator.getState();
|
|
131
|
+
expect(state.failedAgents.has('agent-2')).toBe(true);
|
|
132
|
+
expect(state.failedAgents.size).toBe(1);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('transitions phase correctly', () => {
|
|
136
|
+
orchestrator.transitionPhase('loop2');
|
|
137
|
+
expect(orchestrator.getState().currentPhase).toBe('loop2');
|
|
138
|
+
|
|
139
|
+
orchestrator.transitionPhase('product-owner');
|
|
140
|
+
expect(orchestrator.getState().currentPhase).toBe('product-owner');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('Orchestrator - Loop 3 Execution', () => {
|
|
145
|
+
let orchestrator: Orchestrator;
|
|
146
|
+
|
|
147
|
+
beforeEach(() => {
|
|
148
|
+
const config: OrchestrationConfig = {
|
|
149
|
+
taskId: 'test-loop3',
|
|
150
|
+
mode: 'standard',
|
|
151
|
+
maxIterations: 10,
|
|
152
|
+
};
|
|
153
|
+
orchestrator = new Orchestrator(config);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('spawns Loop 3 agents', async () => {
|
|
157
|
+
const agents = await orchestrator.spawnLoop3Agents([
|
|
158
|
+
'backend-developer',
|
|
159
|
+
'frontend-developer',
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
expect(agents.length).toBe(2);
|
|
163
|
+
expect(agents).toHaveLength(2);
|
|
164
|
+
expect(agents[0]!.loopType).toBe('loop3');
|
|
165
|
+
expect(agents[1]!.loopType).toBe('loop3');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('builds agent context for Loop 3', () => {
|
|
169
|
+
const context = orchestrator.buildAgentContext('agent-1', 'loop3', 1, {});
|
|
170
|
+
|
|
171
|
+
expect(context.agentId).toBe('agent-1');
|
|
172
|
+
expect(context.loopType).toBe('loop3');
|
|
173
|
+
expect(context.iteration).toBe(1);
|
|
174
|
+
expect(context.taskId).toBe('test-loop3');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('executes tests after Loop 3 completion', async () => {
|
|
178
|
+
const testResult: TestResult = { pass: 95, fail: 5 };
|
|
179
|
+
orchestrator.recordTestResult('agent-1', testResult);
|
|
180
|
+
|
|
181
|
+
const result = orchestrator.getTestResult('agent-1');
|
|
182
|
+
expect(result?.pass).toBe(95);
|
|
183
|
+
expect(result?.fail).toBe(5);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('handles Loop 3 timeout gracefully', async () => {
|
|
187
|
+
orchestrator.recordTimeout('agent-timeout', 60);
|
|
188
|
+
const state = orchestrator.getState();
|
|
189
|
+
expect(state.failedAgents.has('agent-timeout')).toBe(true);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('Orchestrator - Gate Check (Loop 3 → Loop 2 transition)', () => {
|
|
194
|
+
let orchestrator: Orchestrator;
|
|
195
|
+
|
|
196
|
+
beforeEach(() => {
|
|
197
|
+
const config: OrchestrationConfig = {
|
|
198
|
+
taskId: 'test-gate',
|
|
199
|
+
mode: 'standard',
|
|
200
|
+
maxIterations: 10,
|
|
201
|
+
};
|
|
202
|
+
orchestrator = new Orchestrator(config);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('MVP mode gate passes at 70%', () => {
|
|
206
|
+
const mvpOrch = new Orchestrator({
|
|
207
|
+
taskId: 'mvp-test',
|
|
208
|
+
mode: 'mvp',
|
|
209
|
+
maxIterations: 5,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const result = mvpOrch.checkGate(0.70);
|
|
213
|
+
expect(result.passed).toBe(true);
|
|
214
|
+
expect(result.threshold).toBe(0.70);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('MVP mode gate fails below 70%', () => {
|
|
218
|
+
const mvpOrch = new Orchestrator({
|
|
219
|
+
taskId: 'mvp-test',
|
|
220
|
+
mode: 'mvp',
|
|
221
|
+
maxIterations: 5,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const result = mvpOrch.checkGate(0.69);
|
|
225
|
+
expect(result.passed).toBe(false);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('Standard mode gate passes at 95%', () => {
|
|
229
|
+
const result = orchestrator.checkGate(0.95);
|
|
230
|
+
expect(result.passed).toBe(true);
|
|
231
|
+
expect(result.threshold).toBe(0.95);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('Standard mode gate fails below 95%', () => {
|
|
235
|
+
const result = orchestrator.checkGate(0.94);
|
|
236
|
+
expect(result.passed).toBe(false);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test('Enterprise mode gate passes at 98%', () => {
|
|
240
|
+
const entOrch = new Orchestrator({
|
|
241
|
+
taskId: 'ent-test',
|
|
242
|
+
mode: 'enterprise',
|
|
243
|
+
maxIterations: 15,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const result = entOrch.checkGate(0.98);
|
|
247
|
+
expect(result.passed).toBe(true);
|
|
248
|
+
expect(result.threshold).toBe(0.98);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test('Enterprise mode gate fails below 98%', () => {
|
|
252
|
+
const entOrch = new Orchestrator({
|
|
253
|
+
taskId: 'ent-test',
|
|
254
|
+
mode: 'enterprise',
|
|
255
|
+
maxIterations: 15,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const result = entOrch.checkGate(0.97);
|
|
259
|
+
expect(result.passed).toBe(false);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('gate check stores pass rate', () => {
|
|
263
|
+
const result = orchestrator.checkGate(0.92);
|
|
264
|
+
expect(result.passRate).toBe(0.92);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test('gate check calculates gap correctly', () => {
|
|
268
|
+
const result = orchestrator.checkGate(0.92);
|
|
269
|
+
expect(result.gap).toBeLessThan(0.04); // gap should be small
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('gate passes at exactly threshold', () => {
|
|
273
|
+
const result = orchestrator.checkGate(0.95);
|
|
274
|
+
expect(result.passed).toBe(true);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test('gate fails just below threshold', () => {
|
|
278
|
+
const result = orchestrator.checkGate(0.9499);
|
|
279
|
+
expect(result.passed).toBe(false);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
describe('Orchestrator - Loop 2 Execution', () => {
|
|
284
|
+
let orchestrator: Orchestrator;
|
|
285
|
+
|
|
286
|
+
beforeEach(() => {
|
|
287
|
+
const config: OrchestrationConfig = {
|
|
288
|
+
taskId: 'test-loop2',
|
|
289
|
+
mode: 'standard',
|
|
290
|
+
maxIterations: 10,
|
|
291
|
+
};
|
|
292
|
+
orchestrator = new Orchestrator(config);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test('spawns Loop 2 validators', async () => {
|
|
296
|
+
const validators = await orchestrator.spawnLoop2Validators(['validator-1', 'validator-2', 'validator-3']);
|
|
297
|
+
|
|
298
|
+
expect(validators.length).toBe(3);
|
|
299
|
+
expect(validators).toHaveLength(3);
|
|
300
|
+
expect(validators[0]!.loopType).toBe('loop2');
|
|
301
|
+
expect(validators.every(v => v.loopType === 'loop2')).toBe(true);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('collects consensus scores from validators', () => {
|
|
305
|
+
orchestrator.recordConsensusScore('validator-1', 0.92);
|
|
306
|
+
orchestrator.recordConsensusScore('validator-2', 0.88);
|
|
307
|
+
orchestrator.recordConsensusScore('validator-3', 0.95);
|
|
308
|
+
|
|
309
|
+
const consensus = orchestrator.getConsensusScores();
|
|
310
|
+
expect(consensus.length).toBe(3);
|
|
311
|
+
expect(consensus).toContain(0.92);
|
|
312
|
+
expect(consensus).toContain(0.88);
|
|
313
|
+
expect(consensus).toContain(0.95);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('calculates consensus average', () => {
|
|
317
|
+
orchestrator.recordConsensusScore('v1', 0.90);
|
|
318
|
+
orchestrator.recordConsensusScore('v2', 0.90);
|
|
319
|
+
orchestrator.recordConsensusScore('v3', 0.90);
|
|
320
|
+
|
|
321
|
+
const average = orchestrator.getConsensusAverage();
|
|
322
|
+
expect(average).toBe(0.90);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test('MVP mode consensus threshold is 80%', () => {
|
|
326
|
+
const mvpOrch = new Orchestrator({
|
|
327
|
+
taskId: 'mvp-consensus',
|
|
328
|
+
mode: 'mvp',
|
|
329
|
+
maxIterations: 5,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
expect(mvpOrch.getConsensusThreshold()).toBe(0.80);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test('Standard mode consensus threshold is 90%', () => {
|
|
336
|
+
expect(orchestrator.getConsensusThreshold()).toBe(0.90);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test('Enterprise mode consensus threshold is 95%', () => {
|
|
340
|
+
const entOrch = new Orchestrator({
|
|
341
|
+
taskId: 'ent-consensus',
|
|
342
|
+
mode: 'enterprise',
|
|
343
|
+
maxIterations: 15,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
expect(entOrch.getConsensusThreshold()).toBe(0.95);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test('validates consensus against threshold', () => {
|
|
350
|
+
orchestrator.recordConsensusScore('v1', 0.92);
|
|
351
|
+
orchestrator.recordConsensusScore('v2', 0.88);
|
|
352
|
+
orchestrator.recordConsensusScore('v3', 0.95);
|
|
353
|
+
|
|
354
|
+
const validation = orchestrator.validateConsensus();
|
|
355
|
+
expect(validation.passed).toBe(true);
|
|
356
|
+
expect(validation.average).toBe((0.92 + 0.88 + 0.95) / 3);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test('fails consensus when below threshold', () => {
|
|
360
|
+
const mvpOrch = new Orchestrator({
|
|
361
|
+
taskId: 'mvp-consensus-fail',
|
|
362
|
+
mode: 'mvp',
|
|
363
|
+
maxIterations: 5,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
mvpOrch.recordConsensusScore('v1', 0.75);
|
|
367
|
+
mvpOrch.recordConsensusScore('v2', 0.76);
|
|
368
|
+
|
|
369
|
+
const validation = mvpOrch.validateConsensus();
|
|
370
|
+
expect(validation.passed).toBe(false);
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
describe('Orchestrator - Product Owner Decision', () => {
|
|
375
|
+
let orchestrator: Orchestrator;
|
|
376
|
+
|
|
377
|
+
beforeEach(() => {
|
|
378
|
+
const config: OrchestrationConfig = {
|
|
379
|
+
taskId: 'test-po',
|
|
380
|
+
mode: 'standard',
|
|
381
|
+
maxIterations: 10,
|
|
382
|
+
};
|
|
383
|
+
orchestrator = new Orchestrator(config);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test('records PROCEED decision', () => {
|
|
387
|
+
orchestrator.recordDecision('PROCEED');
|
|
388
|
+
expect(orchestrator.getDecision()).toBe('PROCEED');
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test('records ITERATE decision', () => {
|
|
392
|
+
orchestrator.recordDecision('ITERATE');
|
|
393
|
+
expect(orchestrator.getDecision()).toBe('ITERATE');
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
test('records ABORT decision', () => {
|
|
397
|
+
orchestrator.recordDecision('ABORT');
|
|
398
|
+
expect(orchestrator.getDecision()).toBe('ABORT');
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test('parses decision from agent output', () => {
|
|
402
|
+
const output = 'After review, decision: PROCEED';
|
|
403
|
+
const decision = orchestrator.parseDecisionFromOutput(output);
|
|
404
|
+
expect(decision).toBe('PROCEED');
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test('parses ITERATE from agent output', () => {
|
|
408
|
+
const output = 'Issues found. Decision: ITERATE for improvements.';
|
|
409
|
+
const decision = orchestrator.parseDecisionFromOutput(output);
|
|
410
|
+
expect(decision).toBe('ITERATE');
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test('parses ABORT from agent output', () => {
|
|
414
|
+
const output = 'Critical issues. Decision: ABORT mission.';
|
|
415
|
+
const decision = orchestrator.parseDecisionFromOutput(output);
|
|
416
|
+
expect(decision).toBe('ABORT');
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test('returns null for unparseable output', () => {
|
|
420
|
+
const output = 'No decision found here';
|
|
421
|
+
const decision = orchestrator.parseDecisionFromOutput(output);
|
|
422
|
+
expect(decision).toBeNull();
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test('handles case-insensitive decision parsing', () => {
|
|
426
|
+
const output = 'Decision: proceed';
|
|
427
|
+
const decision = orchestrator.parseDecisionFromOutput(output);
|
|
428
|
+
expect(decision).toBe('PROCEED');
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
describe('Orchestrator - Iteration Management', () => {
|
|
433
|
+
let orchestrator: Orchestrator;
|
|
434
|
+
|
|
435
|
+
beforeEach(() => {
|
|
436
|
+
const config: OrchestrationConfig = {
|
|
437
|
+
taskId: 'test-iteration',
|
|
438
|
+
mode: 'standard',
|
|
439
|
+
maxIterations: 3,
|
|
440
|
+
};
|
|
441
|
+
orchestrator = new Orchestrator(config);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
test('tracks current iteration', () => {
|
|
445
|
+
expect(orchestrator.getState().iteration).toBe(0);
|
|
446
|
+
orchestrator.incrementIteration();
|
|
447
|
+
expect(orchestrator.getState().iteration).toBe(1);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test('stops before exceeding max iterations', () => {
|
|
451
|
+
for (let i = 0; i < 3; i++) {
|
|
452
|
+
orchestrator.incrementIteration();
|
|
453
|
+
}
|
|
454
|
+
expect(orchestrator.canContinueIterating()).toBe(false);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
test('can continue iterating within limit', () => {
|
|
458
|
+
orchestrator.incrementIteration();
|
|
459
|
+
expect(orchestrator.canContinueIterating()).toBe(true);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test('handles PROCEED decision termination', () => {
|
|
463
|
+
orchestrator.recordDecision('PROCEED');
|
|
464
|
+
orchestrator.incrementIteration();
|
|
465
|
+
expect(orchestrator.shouldTerminate()).toBe(true);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
test('handles ABORT decision termination', () => {
|
|
469
|
+
orchestrator.recordDecision('ABORT');
|
|
470
|
+
expect(orchestrator.shouldTerminate()).toBe(true);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test('continues on ITERATE decision', () => {
|
|
474
|
+
orchestrator.recordDecision('ITERATE');
|
|
475
|
+
orchestrator.incrementIteration();
|
|
476
|
+
expect(orchestrator.shouldTerminate()).toBe(false);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
test('prepares feedback for next iteration', () => {
|
|
480
|
+
const feedback = orchestrator.prepareFeedback({
|
|
481
|
+
gatePassRate: 0.92,
|
|
482
|
+
consensusAverage: 0.88,
|
|
483
|
+
previousFailures: ['test-1', 'test-2'],
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
expect(feedback.gatePassRate).toBe(0.92);
|
|
487
|
+
expect(feedback.consensusAverage).toBe(0.88);
|
|
488
|
+
expect(feedback.previousFailures).toContain('test-1');
|
|
489
|
+
});
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
describe('Orchestrator - Mode-Specific Thresholds', () => {
|
|
493
|
+
test('MVP mode has correct thresholds', () => {
|
|
494
|
+
const orch = new Orchestrator({
|
|
495
|
+
taskId: 'mvp',
|
|
496
|
+
mode: 'mvp',
|
|
497
|
+
maxIterations: 5,
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
expect(orch.getGateThreshold()).toBe(0.70);
|
|
501
|
+
expect(orch.getConsensusThreshold()).toBe(0.80);
|
|
502
|
+
expect(orch.getMaxIterations()).toBe(5);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
test('Standard mode has correct thresholds', () => {
|
|
506
|
+
const orch = new Orchestrator({
|
|
507
|
+
taskId: 'std',
|
|
508
|
+
mode: 'standard',
|
|
509
|
+
maxIterations: 10,
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
expect(orch.getGateThreshold()).toBe(0.95);
|
|
513
|
+
expect(orch.getConsensusThreshold()).toBe(0.90);
|
|
514
|
+
expect(orch.getMaxIterations()).toBe(10);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test('Enterprise mode has correct thresholds', () => {
|
|
518
|
+
const orch = new Orchestrator({
|
|
519
|
+
taskId: 'ent',
|
|
520
|
+
mode: 'enterprise',
|
|
521
|
+
maxIterations: 15,
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
expect(orch.getGateThreshold()).toBe(0.98);
|
|
525
|
+
expect(orch.getConsensusThreshold()).toBe(0.95);
|
|
526
|
+
expect(orch.getMaxIterations()).toBe(15);
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
describe('Orchestrator - Error Handling', () => {
|
|
531
|
+
let orchestrator: Orchestrator;
|
|
532
|
+
|
|
533
|
+
beforeEach(() => {
|
|
534
|
+
const config: OrchestrationConfig = {
|
|
535
|
+
taskId: 'test-errors',
|
|
536
|
+
mode: 'standard',
|
|
537
|
+
maxIterations: 10,
|
|
538
|
+
};
|
|
539
|
+
orchestrator = new Orchestrator(config);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
test('handles agent execution error', () => {
|
|
543
|
+
const error = new Error('Agent execution failed');
|
|
544
|
+
orchestrator.recordExecutionError('agent-1', error);
|
|
545
|
+
expect(orchestrator.getState().failedAgents.has('agent-1')).toBe(true);
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
test('handles timeout errors', () => {
|
|
549
|
+
orchestrator.recordTimeout('slow-agent', 120);
|
|
550
|
+
expect(orchestrator.getState().failedAgents.has('slow-agent')).toBe(true);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
test('tracks multiple errors per iteration', () => {
|
|
554
|
+
orchestrator.recordExecutionError('agent-1', new Error('error 1'));
|
|
555
|
+
orchestrator.recordExecutionError('agent-2', new Error('error 2'));
|
|
556
|
+
expect(orchestrator.getState().failedAgents.size).toBe(2);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
test('recovers from partial agent failure', () => {
|
|
560
|
+
orchestrator.markAgentComplete('success-agent', 'loop3');
|
|
561
|
+
orchestrator.markAgentFailed('failed-agent', 'loop3');
|
|
562
|
+
|
|
563
|
+
const state = orchestrator.getState();
|
|
564
|
+
expect(state.completedAgents.has('success-agent')).toBe(true);
|
|
565
|
+
expect(state.failedAgents.has('failed-agent')).toBe(true);
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
describe('Orchestrator - Test Result Aggregation', () => {
|
|
570
|
+
let orchestrator: Orchestrator;
|
|
571
|
+
|
|
572
|
+
beforeEach(() => {
|
|
573
|
+
const config: OrchestrationConfig = {
|
|
574
|
+
taskId: 'test-aggregation',
|
|
575
|
+
mode: 'standard',
|
|
576
|
+
maxIterations: 10,
|
|
577
|
+
};
|
|
578
|
+
orchestrator = new Orchestrator(config);
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
test('aggregates test results from multiple agents', () => {
|
|
582
|
+
orchestrator.recordTestResult('agent-1', { pass: 80, fail: 20 });
|
|
583
|
+
orchestrator.recordTestResult('agent-2', { pass: 90, fail: 10 });
|
|
584
|
+
|
|
585
|
+
const aggregated = orchestrator.aggregateTestResults();
|
|
586
|
+
expect(aggregated.totalPass).toBe(170);
|
|
587
|
+
expect(aggregated.totalFail).toBe(30);
|
|
588
|
+
expect(aggregated.passRate).toBe(170 / 200);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
test('calculates pass rate correctly', () => {
|
|
592
|
+
orchestrator.recordTestResult('agent-1', { pass: 50, fail: 50 });
|
|
593
|
+
const aggregated = orchestrator.aggregateTestResults();
|
|
594
|
+
expect(aggregated.passRate).toBe(0.50);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
test('handles empty test results', () => {
|
|
598
|
+
const aggregated = orchestrator.aggregateTestResults();
|
|
599
|
+
expect(aggregated.passRate).toBe(0);
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
test('excludes skipped tests from pass rate calculation', () => {
|
|
603
|
+
orchestrator.recordTestResult('agent-1', {
|
|
604
|
+
pass: 80,
|
|
605
|
+
fail: 10,
|
|
606
|
+
skip: 10,
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
const aggregated = orchestrator.aggregateTestResults();
|
|
610
|
+
expect(aggregated.passRate).toBe(80 / (80 + 10 + 10));
|
|
611
|
+
});
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
describe('Orchestrator - Integration (Happy Path)', () => {
|
|
615
|
+
test('completes full orchestration cycle - MVP mode', async () => {
|
|
616
|
+
const orchestrator = new Orchestrator({
|
|
617
|
+
taskId: 'integration-mvp',
|
|
618
|
+
mode: 'mvp',
|
|
619
|
+
maxIterations: 5,
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// Simulate Loop 3
|
|
623
|
+
orchestrator.transitionPhase('loop3');
|
|
624
|
+
await orchestrator.spawnLoop3Agents(['developer']);
|
|
625
|
+
orchestrator.recordTestResult('developer-1-1', { pass: 75, fail: 25 });
|
|
626
|
+
orchestrator.markAgentComplete('developer-1-1', 'loop3');
|
|
627
|
+
|
|
628
|
+
// Check gate (should pass)
|
|
629
|
+
const gateResult = orchestrator.checkGate(0.75);
|
|
630
|
+
expect(gateResult.passed).toBe(true);
|
|
631
|
+
|
|
632
|
+
// Simulate Loop 2
|
|
633
|
+
orchestrator.transitionPhase('loop2');
|
|
634
|
+
await orchestrator.spawnLoop2Validators(['validator']);
|
|
635
|
+
orchestrator.recordConsensusScore('validator-1-1', 0.82);
|
|
636
|
+
orchestrator.markAgentComplete('validator-1-1', 'loop2');
|
|
637
|
+
|
|
638
|
+
// Check consensus (should pass)
|
|
639
|
+
const consensusValidation = orchestrator.validateConsensus();
|
|
640
|
+
expect(consensusValidation.passed).toBe(true);
|
|
641
|
+
|
|
642
|
+
// Simulate Product Owner decision
|
|
643
|
+
orchestrator.transitionPhase('product-owner');
|
|
644
|
+
orchestrator.recordDecision('PROCEED');
|
|
645
|
+
|
|
646
|
+
expect(orchestrator.shouldTerminate()).toBe(true);
|
|
647
|
+
expect(orchestrator.getDecision()).toBe('PROCEED');
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
test('completes full orchestration cycle - Standard mode', async () => {
|
|
651
|
+
const orchestrator = new Orchestrator({
|
|
652
|
+
taskId: 'integration-std',
|
|
653
|
+
mode: 'standard',
|
|
654
|
+
maxIterations: 10,
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
// Loop 3 with 95%+ pass rate
|
|
658
|
+
orchestrator.transitionPhase('loop3');
|
|
659
|
+
await orchestrator.spawnLoop3Agents(['backend-dev', 'frontend-dev']);
|
|
660
|
+
orchestrator.recordTestResult('backend-dev-1-1', { pass: 95, fail: 5 });
|
|
661
|
+
orchestrator.recordTestResult('frontend-dev-1-1', { pass: 96, fail: 4 });
|
|
662
|
+
orchestrator.markAgentComplete('backend-dev-1-1', 'loop3');
|
|
663
|
+
orchestrator.markAgentComplete('frontend-dev-1-1', 'loop3');
|
|
664
|
+
|
|
665
|
+
const gateResult = orchestrator.checkGate(0.955);
|
|
666
|
+
expect(gateResult.passed).toBe(true);
|
|
667
|
+
|
|
668
|
+
// Loop 2 with 90%+ consensus
|
|
669
|
+
orchestrator.transitionPhase('loop2');
|
|
670
|
+
await orchestrator.spawnLoop2Validators(['validator-1', 'validator-2', 'validator-3']);
|
|
671
|
+
orchestrator.recordConsensusScore('validator-1-1-1', 0.92);
|
|
672
|
+
orchestrator.recordConsensusScore('validator-1-2-1', 0.91);
|
|
673
|
+
orchestrator.recordConsensusScore('validator-1-3-1', 0.90);
|
|
674
|
+
|
|
675
|
+
const consensusValidation = orchestrator.validateConsensus();
|
|
676
|
+
expect(consensusValidation.passed).toBe(true);
|
|
677
|
+
|
|
678
|
+
// Product Owner decision
|
|
679
|
+
orchestrator.transitionPhase('product-owner');
|
|
680
|
+
orchestrator.recordDecision('PROCEED');
|
|
681
|
+
|
|
682
|
+
expect(orchestrator.shouldTerminate()).toBe(true);
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
test('handles gate failure with iteration', async () => {
|
|
686
|
+
const orchestrator = new Orchestrator({
|
|
687
|
+
taskId: 'gate-fail-iterate',
|
|
688
|
+
mode: 'standard',
|
|
689
|
+
maxIterations: 3,
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
// Iteration 1: Gate fails
|
|
693
|
+
orchestrator.transitionPhase('loop3');
|
|
694
|
+
orchestrator.recordTestResult('dev-1-1', { pass: 85, fail: 15 });
|
|
695
|
+
const gateResult = orchestrator.checkGate(0.85);
|
|
696
|
+
expect(gateResult.passed).toBe(false);
|
|
697
|
+
|
|
698
|
+
// Record ITERATE decision
|
|
699
|
+
orchestrator.recordDecision('ITERATE');
|
|
700
|
+
orchestrator.incrementIteration();
|
|
701
|
+
|
|
702
|
+
// Iteration 2: Gate passes
|
|
703
|
+
orchestrator.recordTestResult('dev-1-2', { pass: 96, fail: 4 });
|
|
704
|
+
const gateResult2 = orchestrator.checkGate(0.96);
|
|
705
|
+
expect(gateResult2.passed).toBe(true);
|
|
706
|
+
|
|
707
|
+
expect(orchestrator.getState().iteration).toBe(1);
|
|
708
|
+
expect(orchestrator.canContinueIterating()).toBe(true);
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
describe('Orchestrator - Edge Cases', () => {
|
|
713
|
+
test('handles all agents failing in Loop 3', () => {
|
|
714
|
+
const orchestrator = new Orchestrator({
|
|
715
|
+
taskId: 'all-fail',
|
|
716
|
+
mode: 'standard',
|
|
717
|
+
maxIterations: 10,
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
orchestrator.markAgentFailed('agent-1', 'loop3');
|
|
721
|
+
orchestrator.markAgentFailed('agent-2', 'loop3');
|
|
722
|
+
orchestrator.markAgentFailed('agent-3', 'loop3');
|
|
723
|
+
|
|
724
|
+
const state = orchestrator.getState();
|
|
725
|
+
expect(state.failedAgents.size).toBe(3);
|
|
726
|
+
expect(state.completedAgents.size).toBe(0);
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
test('handles single agent in Loop 2', () => {
|
|
730
|
+
const orchestrator = new Orchestrator({
|
|
731
|
+
taskId: 'single-validator',
|
|
732
|
+
mode: 'mvp',
|
|
733
|
+
maxIterations: 5,
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
orchestrator.recordConsensusScore('validator-1', 0.85);
|
|
737
|
+
const validation = orchestrator.validateConsensus();
|
|
738
|
+
expect(validation.passed).toBe(true);
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
test('handles zero consensus scores', () => {
|
|
742
|
+
const orchestrator = new Orchestrator({
|
|
743
|
+
taskId: 'zero-consensus',
|
|
744
|
+
mode: 'standard',
|
|
745
|
+
maxIterations: 10,
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
expect(() => orchestrator.getConsensusAverage()).toThrow(
|
|
749
|
+
'No consensus scores recorded'
|
|
750
|
+
);
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
test('handles phase transitions in sequence', () => {
|
|
754
|
+
const orchestrator = new Orchestrator({
|
|
755
|
+
taskId: 'phase-sequence',
|
|
756
|
+
mode: 'standard',
|
|
757
|
+
maxIterations: 10,
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
const phases: LoopPhase[] = ['loop3', 'loop2', 'product-owner', 'complete'];
|
|
761
|
+
|
|
762
|
+
for (const phase of phases) {
|
|
763
|
+
orchestrator.transitionPhase(phase);
|
|
764
|
+
expect(orchestrator.getState().currentPhase).toBe(phase);
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
test('handles max iterations boundary', () => {
|
|
769
|
+
const orchestrator = new Orchestrator({
|
|
770
|
+
taskId: 'max-iter',
|
|
771
|
+
mode: 'standard',
|
|
772
|
+
maxIterations: 2,
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
orchestrator.incrementIteration(); // 1
|
|
776
|
+
expect(orchestrator.canContinueIterating()).toBe(true);
|
|
777
|
+
|
|
778
|
+
orchestrator.incrementIteration(); // 2
|
|
779
|
+
expect(orchestrator.canContinueIterating()).toBe(false);
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
test('handles decision override on subsequent iteration', () => {
|
|
783
|
+
const orchestrator = new Orchestrator({
|
|
784
|
+
taskId: 'override',
|
|
785
|
+
mode: 'standard',
|
|
786
|
+
maxIterations: 10,
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
orchestrator.recordDecision('ITERATE');
|
|
790
|
+
expect(orchestrator.getDecision()).toBe('ITERATE');
|
|
791
|
+
|
|
792
|
+
orchestrator.recordDecision('PROCEED');
|
|
793
|
+
expect(orchestrator.getDecision()).toBe('PROCEED');
|
|
794
|
+
});
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
describe('Orchestrator - Type Safety', () => {
|
|
798
|
+
test('enforces ExecutionMode type', () => {
|
|
799
|
+
const config: OrchestrationConfig = {
|
|
800
|
+
taskId: 'test',
|
|
801
|
+
mode: 'standard',
|
|
802
|
+
maxIterations: 10,
|
|
803
|
+
};
|
|
804
|
+
|
|
805
|
+
const orchestrator = new Orchestrator(config);
|
|
806
|
+
expect(orchestrator.getMode()).toBe('standard');
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
test('enforces LoopPhase type', () => {
|
|
810
|
+
const orchestrator = new Orchestrator({
|
|
811
|
+
taskId: 'test',
|
|
812
|
+
mode: 'standard',
|
|
813
|
+
maxIterations: 10,
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
const validPhases: LoopPhase[] = ['loop3', 'loop2', 'product-owner', 'complete'];
|
|
817
|
+
validPhases.forEach(phase => {
|
|
818
|
+
orchestrator.transitionPhase(phase);
|
|
819
|
+
expect(orchestrator.getState().currentPhase).toBe(phase);
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
test('enforces ProductOwnerDecision type', () => {
|
|
824
|
+
const orchestrator = new Orchestrator({
|
|
825
|
+
taskId: 'test',
|
|
826
|
+
mode: 'standard',
|
|
827
|
+
maxIterations: 10,
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
const decisions = ['PROCEED', 'ITERATE', 'ABORT'] as const;
|
|
831
|
+
decisions.forEach(decision => {
|
|
832
|
+
orchestrator.recordDecision(decision);
|
|
833
|
+
expect(orchestrator.getDecision()).toBe(decision);
|
|
834
|
+
});
|
|
835
|
+
});
|
|
836
|
+
});
|