@hanzo/dev 2.1.1 → 3.0.2

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.
@@ -1,340 +0,0 @@
1
- import { describe, test, expect, beforeEach, afterEach, jest } from '@jest/globals';
2
- import { PeerAgentNetwork, AgentConfig } from '../src/lib/peer-agent-network';
3
- import * as fs from 'fs';
4
- import * as path from 'path';
5
- import * as os from 'os';
6
-
7
- describe('PeerAgentNetwork', () => {
8
- let network: PeerAgentNetwork;
9
- let testDir: string;
10
-
11
- beforeEach(() => {
12
- network = new PeerAgentNetwork();
13
- testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'peer-network-test-'));
14
-
15
- // Create test file structure
16
- fs.mkdirSync(path.join(testDir, 'src'));
17
- fs.mkdirSync(path.join(testDir, 'tests'));
18
- fs.writeFileSync(path.join(testDir, 'src', 'index.js'), 'console.log("Hello");');
19
- fs.writeFileSync(path.join(testDir, 'src', 'utils.js'), 'export function util() {}');
20
- fs.writeFileSync(path.join(testDir, 'tests', 'index.test.js'), 'test("sample", () => {});');
21
- });
22
-
23
- afterEach(() => {
24
- // Clean up
25
- fs.rmSync(testDir, { recursive: true, force: true });
26
- });
27
-
28
- describe('agent spawning', () => {
29
- test('should spawn agent with configuration', async () => {
30
- const config: AgentConfig = {
31
- id: 'test-agent',
32
- name: 'Test Agent',
33
- type: 'claude-code',
34
- responsibility: 'Test file processing',
35
- tools: ['edit_file', 'view_file']
36
- };
37
-
38
- await network.spawnAgent(config);
39
-
40
- const agents = network.getActiveAgents();
41
- expect(agents).toHaveLength(1);
42
- expect(agents[0].id).toBe('test-agent');
43
- expect(agents[0].status).toBe('active');
44
- });
45
-
46
- test('should prevent duplicate agent IDs', async () => {
47
- const config: AgentConfig = {
48
- id: 'duplicate',
49
- name: 'Agent 1',
50
- type: 'claude-code'
51
- };
52
-
53
- await network.spawnAgent(config);
54
-
55
- // Try to spawn with same ID
56
- await expect(network.spawnAgent({
57
- ...config,
58
- name: 'Agent 2'
59
- })).rejects.toThrow('already exists');
60
- });
61
- });
62
-
63
- describe('codebase agent spawning', () => {
64
- test('should spawn one agent per file', async () => {
65
- await network.spawnAgentsForCodebase(testDir, 'claude-code', 'one-per-file');
66
-
67
- const agents = network.getActiveAgents();
68
- // Should have 3 agents (3 files)
69
- expect(agents).toHaveLength(3);
70
-
71
- // Check agent responsibilities
72
- const responsibilities = agents.map(a => a.responsibility);
73
- expect(responsibilities).toContain(expect.stringContaining('src/index.js'));
74
- expect(responsibilities).toContain(expect.stringContaining('src/utils.js'));
75
- expect(responsibilities).toContain(expect.stringContaining('tests/index.test.js'));
76
- });
77
-
78
- test('should spawn one agent per directory', async () => {
79
- await network.spawnAgentsForCodebase(testDir, 'claude-code', 'one-per-directory');
80
-
81
- const agents = network.getActiveAgents();
82
- // Should have 2 agents (src and tests directories)
83
- expect(agents).toHaveLength(2);
84
-
85
- const responsibilities = agents.map(a => a.responsibility);
86
- expect(responsibilities).toContain(expect.stringContaining('src'));
87
- expect(responsibilities).toContain(expect.stringContaining('tests'));
88
- });
89
-
90
- test('should respect file patterns', async () => {
91
- await network.spawnAgentsForCodebase(
92
- testDir,
93
- 'claude-code',
94
- 'one-per-file',
95
- ['**/*.test.js'] // Only test files
96
- );
97
-
98
- const agents = network.getActiveAgents();
99
- expect(agents).toHaveLength(1);
100
- expect(agents[0].responsibility).toContain('tests/index.test.js');
101
- });
102
- });
103
-
104
- describe('agent communication', () => {
105
- test('should enable agent-to-agent messaging', async () => {
106
- // Spawn two agents
107
- await network.spawnAgent({
108
- id: 'agent1',
109
- name: 'Agent 1',
110
- type: 'claude-code'
111
- });
112
-
113
- await network.spawnAgent({
114
- id: 'agent2',
115
- name: 'Agent 2',
116
- type: 'claude-code'
117
- });
118
-
119
- // Send message from agent1 to agent2
120
- const response = await network.sendMessage('agent1', 'agent2', {
121
- type: 'query',
122
- content: 'What files are you working on?'
123
- });
124
-
125
- expect(response).toBeDefined();
126
- expect(response.from).toBe('agent2');
127
- expect(response.to).toBe('agent1');
128
- });
129
-
130
- test('should broadcast messages to all agents', async () => {
131
- // Spawn three agents
132
- for (let i = 1; i <= 3; i++) {
133
- await network.spawnAgent({
134
- id: `agent${i}`,
135
- name: `Agent ${i}`,
136
- type: 'claude-code'
137
- });
138
- }
139
-
140
- const responses = await network.broadcast('agent1', {
141
- type: 'announcement',
142
- content: 'Starting code review'
143
- });
144
-
145
- expect(responses).toHaveLength(2); // Response from agent2 and agent3
146
- expect(responses.every(r => r.from !== 'agent1')).toBe(true);
147
- });
148
- });
149
-
150
- describe('MCP tool exposure', () => {
151
- test('should expose agents as MCP tools to each other', async () => {
152
- await network.spawnAgent({
153
- id: 'file-agent',
154
- name: 'File Agent',
155
- type: 'claude-code',
156
- responsibility: 'File operations',
157
- tools: ['edit_file', 'create_file']
158
- });
159
-
160
- await network.spawnAgent({
161
- id: 'test-agent',
162
- name: 'Test Agent',
163
- type: 'aider',
164
- responsibility: 'Test writing'
165
- });
166
-
167
- // Check that each agent can see the other as a tool
168
- const fileAgentTools = await network.getAgentTools('file-agent');
169
- expect(fileAgentTools).toContain(expect.objectContaining({
170
- name: 'ask_test_agent',
171
- description: expect.stringContaining('Test Agent')
172
- }));
173
-
174
- const testAgentTools = await network.getAgentTools('test-agent');
175
- expect(testAgentTools).toContain(expect.objectContaining({
176
- name: 'ask_file_agent',
177
- description: expect.stringContaining('File Agent')
178
- }));
179
- });
180
-
181
- test('should allow recursive agent calls via MCP', async () => {
182
- // Set up agents
183
- await network.spawnAgent({
184
- id: 'coordinator',
185
- name: 'Coordinator',
186
- type: 'claude-code'
187
- });
188
-
189
- await network.spawnAgent({
190
- id: 'worker1',
191
- name: 'Worker 1',
192
- type: 'claude-code'
193
- });
194
-
195
- await network.spawnAgent({
196
- id: 'worker2',
197
- name: 'Worker 2',
198
- type: 'claude-code'
199
- });
200
-
201
- // Coordinator delegates to workers
202
- const result = await network.callAgentTool('coordinator', 'delegate_to_worker1', {
203
- task: 'Process data'
204
- });
205
-
206
- expect(result).toBeDefined();
207
- expect(result.success).toBe(true);
208
- });
209
- });
210
-
211
- describe('task coordination', () => {
212
- test('should coordinate parallel tasks across agents', async () => {
213
- // Create a task that can be parallelized
214
- const files = [
215
- 'file1.js',
216
- 'file2.js',
217
- 'file3.js',
218
- 'file4.js'
219
- ];
220
-
221
- // Spawn agents for parallel processing
222
- const agents = await network.spawnAgentsForTask(
223
- 'Process multiple files',
224
- files.map(f => ({
225
- subtask: `Process ${f}`,
226
- data: { file: f }
227
- }))
228
- );
229
-
230
- expect(agents).toHaveLength(4);
231
-
232
- // Execute all tasks in parallel
233
- const results = await network.executeParallelTasks(
234
- agents.map(a => ({
235
- agentId: a.id,
236
- task: a.config.responsibility!
237
- }))
238
- );
239
-
240
- expect(results).toHaveLength(4);
241
- expect(results.every(r => r.status === 'completed')).toBe(true);
242
- });
243
-
244
- test('should handle agent failures gracefully', async () => {
245
- await network.spawnAgent({
246
- id: 'failing-agent',
247
- name: 'Failing Agent',
248
- type: 'claude-code'
249
- });
250
-
251
- // Make agent fail
252
- (network as any).agents.get('failing-agent').status = 'error';
253
-
254
- const agents = network.getActiveAgents();
255
- expect(agents).toHaveLength(0); // Failed agents not in active list
256
-
257
- const allAgents = network.getAllAgents();
258
- expect(allAgents).toHaveLength(1);
259
- expect(allAgents[0].status).toBe('error');
260
- });
261
- });
262
-
263
- describe('swarm optimization', () => {
264
- test('should optimize agent allocation based on workload', async () => {
265
- // Create initial agents
266
- for (let i = 1; i <= 3; i++) {
267
- await network.spawnAgent({
268
- id: `agent${i}`,
269
- name: `Agent ${i}`,
270
- type: 'claude-code'
271
- });
272
- }
273
-
274
- // Simulate workload
275
- const metrics = {
276
- agent1: { tasksCompleted: 10, avgTime: 2.5 },
277
- agent2: { tasksCompleted: 5, avgTime: 5.0 },
278
- agent3: { tasksCompleted: 8, avgTime: 3.0 }
279
- };
280
-
281
- const optimization = network.optimizeSwarm(metrics);
282
-
283
- // Should recommend spawning more agents like agent1 (best performance)
284
- expect(optimization.recommendations).toContain(
285
- expect.stringContaining('agent1')
286
- );
287
- });
288
-
289
- test('should monitor swarm health', async () => {
290
- // Spawn multiple agents
291
- for (let i = 1; i <= 5; i++) {
292
- await network.spawnAgent({
293
- id: `agent${i}`,
294
- name: `Agent ${i}`,
295
- type: i % 2 === 0 ? 'aider' : 'claude-code'
296
- });
297
- }
298
-
299
- const health = network.getSwarmHealth();
300
-
301
- expect(health.totalAgents).toBe(5);
302
- expect(health.activeAgents).toBe(5);
303
- expect(health.agentTypes).toContain('claude-code');
304
- expect(health.agentTypes).toContain('aider');
305
- });
306
- });
307
-
308
- describe('cleanup and lifecycle', () => {
309
- test('should terminate individual agents', async () => {
310
- await network.spawnAgent({
311
- id: 'temp-agent',
312
- name: 'Temporary Agent',
313
- type: 'claude-code'
314
- });
315
-
316
- expect(network.getActiveAgents()).toHaveLength(1);
317
-
318
- await network.terminateAgent('temp-agent');
319
-
320
- expect(network.getActiveAgents()).toHaveLength(0);
321
- });
322
-
323
- test('should terminate all agents on shutdown', async () => {
324
- // Spawn multiple agents
325
- for (let i = 1; i <= 3; i++) {
326
- await network.spawnAgent({
327
- id: `agent${i}`,
328
- name: `Agent ${i}`,
329
- type: 'claude-code'
330
- });
331
- }
332
-
333
- expect(network.getActiveAgents()).toHaveLength(3);
334
-
335
- await network.shutdown();
336
-
337
- expect(network.getActiveAgents()).toHaveLength(0);
338
- });
339
- });
340
- });
@@ -1,301 +0,0 @@
1
- import { describe, test, expect, beforeEach, afterEach, afterAll, vi } from 'vitest';
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
- import * as os from 'os';
5
- import { SwarmRunner, SwarmOptions } from '../src/lib/swarm-runner';
6
- import { EventEmitter } from 'events';
7
- import * as child_process from 'child_process';
8
- import { glob } from 'glob';
9
-
10
- // Mock modules
11
- vi.mock('child_process');
12
- vi.mock('glob');
13
- vi.mock('ora', () => ({
14
- default: () => ({
15
- start: vi.fn().mockReturnThis(),
16
- succeed: vi.fn().mockReturnThis(),
17
- fail: vi.fn().mockReturnThis(),
18
- stop: vi.fn().mockReturnThis()
19
- })
20
- }));
21
-
22
- describe('SwarmRunner', () => {
23
- let testDir: string;
24
- let runner: SwarmRunner;
25
-
26
- beforeEach(() => {
27
- // Create test directory
28
- testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarm-test-'));
29
-
30
- // Create test files
31
- fs.writeFileSync(path.join(testDir, 'file1.js'), '// Test file 1');
32
- fs.writeFileSync(path.join(testDir, 'file2.ts'), '// Test file 2');
33
- fs.writeFileSync(path.join(testDir, 'file3.py'), '# Test file 3');
34
-
35
- // Reset mocks
36
- vi.clearAllMocks();
37
- });
38
-
39
- afterEach(() => {
40
- fs.rmSync(testDir, { recursive: true, force: true });
41
- vi.clearAllMocks();
42
- vi.restoreAllMocks();
43
- });
44
-
45
- afterAll(() => {
46
- // Force exit after all tests complete
47
- setTimeout(() => process.exit(0), 100);
48
- });
49
-
50
- describe('initialization', () => {
51
- test('should create swarm runner with options', () => {
52
- const options: SwarmOptions = {
53
- provider: 'claude',
54
- count: 5,
55
- prompt: 'Add copyright header',
56
- cwd: testDir
57
- };
58
-
59
- runner = new SwarmRunner(options);
60
- expect(runner).toBeDefined();
61
- });
62
-
63
- test('should limit agent count to 100', () => {
64
- const options: SwarmOptions = {
65
- provider: 'claude',
66
- count: 150,
67
- prompt: 'Test prompt',
68
- cwd: testDir
69
- };
70
-
71
- runner = new SwarmRunner(options);
72
- // We can't directly test private properties, but this ensures no crash
73
- expect(runner).toBeDefined();
74
- });
75
- });
76
-
77
- describe('file finding', () => {
78
- test('should find editable files in directory', async () => {
79
- // Mock glob to return our test files immediately
80
- vi.mocked(glob).mockImplementation((pattern, options, callback) => {
81
- if (typeof callback === 'function') {
82
- // Call callback synchronously
83
- callback(null, ['file1.js', 'file2.ts', 'file3.py']);
84
- }
85
- return undefined as any;
86
- });
87
-
88
- const options: SwarmOptions = {
89
- provider: 'claude',
90
- count: 3,
91
- prompt: 'Test prompt',
92
- cwd: testDir
93
- };
94
-
95
- runner = new SwarmRunner(options);
96
-
97
- // Mock auth to return true
98
- vi.spyOn(runner, 'ensureProviderAuth').mockResolvedValue(true);
99
-
100
- // Mock spawn to return immediately closing processes
101
- let spawnCount = 0;
102
- vi.mocked(child_process.spawn).mockImplementation(() => {
103
- spawnCount++;
104
- const proc = new EventEmitter();
105
- proc.stdout = new EventEmitter();
106
- proc.stderr = new EventEmitter();
107
- proc.kill = vi.fn();
108
-
109
- // Close immediately
110
- process.nextTick(() => proc.emit('close', 0));
111
-
112
- return proc as any;
113
- });
114
-
115
- await runner.run();
116
-
117
- // Should have spawned 3 processes (one for each file)
118
- expect(spawnCount).toBe(3);
119
- });
120
- });
121
-
122
- describe('provider authentication', () => {
123
- test('should check Claude authentication', async () => {
124
- const options: SwarmOptions = {
125
- provider: 'claude',
126
- count: 1,
127
- prompt: 'Test',
128
- cwd: testDir
129
- };
130
-
131
- runner = new SwarmRunner(options);
132
-
133
- // Mock environment variable
134
- process.env.ANTHROPIC_API_KEY = 'test-key';
135
-
136
- // Mock successful auth check
137
- vi.mocked(child_process.spawn).mockImplementationOnce(() => {
138
- const authCheckProcess = new EventEmitter();
139
- authCheckProcess.stderr = new EventEmitter();
140
- authCheckProcess.kill = vi.fn();
141
-
142
- // Emit close immediately
143
- process.nextTick(() => authCheckProcess.emit('close', 0));
144
-
145
- return authCheckProcess as any;
146
- });
147
-
148
- const result = await runner.ensureProviderAuth();
149
- expect(result).toBe(true);
150
- });
151
-
152
- test('should return true for local provider', async () => {
153
- const options: SwarmOptions = {
154
- provider: 'local',
155
- count: 1,
156
- prompt: 'Test',
157
- cwd: testDir
158
- };
159
-
160
- runner = new SwarmRunner(options);
161
- const result = await runner.ensureProviderAuth();
162
- expect(result).toBe(true);
163
- });
164
-
165
- test('should check API key for OpenAI', async () => {
166
- const options: SwarmOptions = {
167
- provider: 'openai',
168
- count: 1,
169
- prompt: 'Test',
170
- cwd: testDir
171
- };
172
-
173
- runner = new SwarmRunner(options);
174
-
175
- // Without API key
176
- delete process.env.OPENAI_API_KEY;
177
- expect(await runner.ensureProviderAuth()).toBe(false);
178
-
179
- // With API key
180
- process.env.OPENAI_API_KEY = 'test-key';
181
- expect(await runner.ensureProviderAuth()).toBe(true);
182
- });
183
- });
184
-
185
- describe('command building', () => {
186
- test('should build correct command for Claude', () => {
187
- const options: SwarmOptions = {
188
- provider: 'claude',
189
- count: 1,
190
- prompt: 'Add header',
191
- cwd: testDir
192
- };
193
-
194
- runner = new SwarmRunner(options);
195
- const command = (runner as any).buildCommand('test.js');
196
-
197
- expect(command.cmd).toBe('claude');
198
- expect(command.args).toContain('-p');
199
- expect(command.args.join(' ')).toContain('Add header');
200
- expect(command.args).toContain('--max-turns');
201
- expect(command.args).toContain('5');
202
- });
203
-
204
- test('should build correct command for local provider', () => {
205
- const options: SwarmOptions = {
206
- provider: 'local',
207
- count: 1,
208
- prompt: 'Format code',
209
- cwd: testDir
210
- };
211
-
212
- runner = new SwarmRunner(options);
213
- const command = (runner as any).buildCommand('test.js');
214
-
215
- expect(command.cmd).toBe('dev');
216
- expect(command.args).toContain('agent');
217
- expect(command.args.join(' ')).toContain('Format code');
218
- });
219
- });
220
-
221
- describe('parallel processing', () => {
222
- test('should process multiple files in parallel', async () => {
223
- vi.mocked(glob).mockImplementation((pattern, options, callback) => {
224
- if (typeof callback === 'function') {
225
- callback(null, ['file1.js', 'file2.js', 'file3.js']);
226
- }
227
- return undefined as any;
228
- });
229
-
230
- const options: SwarmOptions = {
231
- provider: 'local',
232
- count: 3,
233
- prompt: 'Add copyright',
234
- cwd: testDir
235
- };
236
-
237
- runner = new SwarmRunner(options);
238
-
239
- // Mock auth
240
- vi.spyOn(runner, 'ensureProviderAuth').mockResolvedValue(true);
241
-
242
- let processCount = 0;
243
- vi.mocked(child_process.spawn).mockImplementation(() => {
244
- processCount++;
245
- const proc = new EventEmitter();
246
- proc.stdout = new EventEmitter();
247
- proc.stderr = new EventEmitter();
248
- proc.kill = vi.fn();
249
-
250
- // Simulate successful completion
251
- process.nextTick(() => proc.emit('close', 0));
252
-
253
- return proc as any;
254
- });
255
-
256
- await runner.run();
257
-
258
- // Should have spawned 3 processes
259
- expect(processCount).toBe(3);
260
- });
261
-
262
- test('should handle process failures', async () => {
263
- vi.mocked(glob).mockImplementation((pattern, options, callback) => {
264
- if (typeof callback === 'function') {
265
- callback(null, ['file1.js']);
266
- }
267
- return undefined as any;
268
- });
269
-
270
- const options: SwarmOptions = {
271
- provider: 'local',
272
- count: 1,
273
- prompt: 'Test',
274
- cwd: testDir
275
- };
276
-
277
- runner = new SwarmRunner(options);
278
-
279
- // Mock auth
280
- vi.spyOn(runner, 'ensureProviderAuth').mockResolvedValue(true);
281
-
282
- vi.mocked(child_process.spawn).mockImplementation(() => {
283
- const proc = new EventEmitter();
284
- proc.stdout = new EventEmitter();
285
- proc.stderr = new EventEmitter();
286
- proc.kill = vi.fn();
287
-
288
- // Simulate failure
289
- process.nextTick(() => {
290
- proc.stderr!.emit('data', 'Error occurred');
291
- proc.emit('close', 1);
292
- });
293
-
294
- return proc as any;
295
- });
296
-
297
- // Should complete without throwing
298
- await expect(runner.run()).resolves.not.toThrow();
299
- });
300
- });
301
- });