@hanzo/dev 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +24 -0
- package/README.md +359 -0
- package/dist/cli/dev.js +15577 -2097
- package/package.json +13 -10
- package/src/cli/dev.ts +168 -1
- package/src/lib/benchmark-runner.ts +431 -0
- package/src/lib/editor.ts +27 -0
- package/src/lib/swarm-runner.ts +389 -0
- package/test-swarm/file1.js +6 -0
- package/test-swarm/file2.ts +12 -0
- package/test-swarm/file3.py +15 -0
- package/test-swarm/file4.md +13 -0
- package/test-swarm/file5.json +12 -0
- package/test-swarm-demo.sh +22 -0
- package/tests/editor.test.ts +7 -7
- package/tests/fixtures/sample-code.js +13 -0
- package/tests/fixtures/sample-code.py +28 -0
- package/tests/fixtures/sample-code.ts +22 -0
- package/tests/mcp-client.test.ts +6 -6
- package/tests/swarm-runner.test.ts +301 -0
- package/vitest.config.ts +37 -0
- package/.eslintrc.js +0 -25
- package/jest.config.js +0 -30
- package/tests/setup.ts +0 -25
package/src/lib/editor.ts
CHANGED
|
@@ -29,6 +29,10 @@ export class FileEditor {
|
|
|
29
29
|
private editHistory: Map<string, string[]> = new Map();
|
|
30
30
|
private currentFile: string | null = null;
|
|
31
31
|
|
|
32
|
+
constructor(workingDir: string = process.cwd()) {
|
|
33
|
+
// No need to instantiate ChunkLocalizer as it has static methods
|
|
34
|
+
}
|
|
35
|
+
|
|
32
36
|
async execute(command: EditCommand): Promise<EditResult> {
|
|
33
37
|
switch (command.command) {
|
|
34
38
|
case 'view':
|
|
@@ -290,6 +294,29 @@ export class FileEditor {
|
|
|
290
294
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
|
|
291
295
|
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
292
296
|
}
|
|
297
|
+
|
|
298
|
+
async getRelevantChunks(filePath: string, query: string): Promise<any[]> {
|
|
299
|
+
try {
|
|
300
|
+
if (!fs.existsSync(filePath)) {
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
305
|
+
const chunk = ChunkLocalizer.findRelevantChunk(content, query);
|
|
306
|
+
|
|
307
|
+
if (!chunk) {
|
|
308
|
+
return [];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return [{
|
|
312
|
+
startLine: chunk.startLine,
|
|
313
|
+
endLine: chunk.endLine,
|
|
314
|
+
content: chunk.content
|
|
315
|
+
}];
|
|
316
|
+
} catch (error) {
|
|
317
|
+
return [];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
293
320
|
}
|
|
294
321
|
|
|
295
322
|
// Chunk localizer for finding relevant code sections
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { glob } from 'glob';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { spawn, ChildProcess } from 'child_process';
|
|
7
|
+
import { EventEmitter } from 'events';
|
|
8
|
+
|
|
9
|
+
export interface SwarmOptions {
|
|
10
|
+
provider: 'claude' | 'openai' | 'gemini' | 'grok' | 'local';
|
|
11
|
+
count: number;
|
|
12
|
+
prompt: string;
|
|
13
|
+
cwd?: string;
|
|
14
|
+
pattern?: string;
|
|
15
|
+
autoLogin?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SwarmAgent {
|
|
19
|
+
id: string;
|
|
20
|
+
process?: ChildProcess;
|
|
21
|
+
file?: string;
|
|
22
|
+
status: 'idle' | 'busy' | 'done' | 'error';
|
|
23
|
+
result?: string;
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class SwarmRunner extends EventEmitter {
|
|
28
|
+
private agents: Map<string, SwarmAgent> = new Map();
|
|
29
|
+
private fileQueue: string[] = [];
|
|
30
|
+
private options: SwarmOptions;
|
|
31
|
+
private activeCount: number = 0;
|
|
32
|
+
|
|
33
|
+
constructor(options: SwarmOptions) {
|
|
34
|
+
super();
|
|
35
|
+
this.options = {
|
|
36
|
+
cwd: process.cwd(),
|
|
37
|
+
pattern: '**/*',
|
|
38
|
+
autoLogin: true,
|
|
39
|
+
...options
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async run(): Promise<void> {
|
|
44
|
+
const spinner = ora(`Initializing swarm with ${this.options.count} agents...`).start();
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Find files to process
|
|
48
|
+
spinner.text = `Searching for files in ${this.options.cwd || process.cwd()}...`;
|
|
49
|
+
this.fileQueue = await this.findFiles();
|
|
50
|
+
spinner.succeed(`Found ${this.fileQueue.length} files to process`);
|
|
51
|
+
|
|
52
|
+
if (this.fileQueue.length === 0) {
|
|
53
|
+
console.log(chalk.yellow('No files found matching pattern'));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Initialize agent pool
|
|
58
|
+
const agentCount = Math.min(this.options.count, this.fileQueue.length);
|
|
59
|
+
spinner.start(`Spawning ${agentCount} agents...`);
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < agentCount; i++) {
|
|
62
|
+
const agent: SwarmAgent = {
|
|
63
|
+
id: `agent-${i}`,
|
|
64
|
+
status: 'idle'
|
|
65
|
+
};
|
|
66
|
+
this.agents.set(agent.id, agent);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
spinner.succeed(`Spawned ${agentCount} agents`);
|
|
70
|
+
|
|
71
|
+
// Process files in parallel
|
|
72
|
+
spinner.start('Processing files...');
|
|
73
|
+
const startTime = Date.now();
|
|
74
|
+
|
|
75
|
+
// Start processing
|
|
76
|
+
await this.processFiles();
|
|
77
|
+
|
|
78
|
+
const duration = (Date.now() - startTime) / 1000;
|
|
79
|
+
spinner.succeed(`Completed in ${duration.toFixed(1)}s`);
|
|
80
|
+
|
|
81
|
+
// Show results
|
|
82
|
+
this.showResults();
|
|
83
|
+
|
|
84
|
+
} catch (error) {
|
|
85
|
+
spinner.fail(`Swarm error: ${error}`);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private async findFiles(): Promise<string[]> {
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
const options = {
|
|
93
|
+
cwd: this.options.cwd,
|
|
94
|
+
nodir: true,
|
|
95
|
+
ignore: [
|
|
96
|
+
'**/node_modules/**',
|
|
97
|
+
'**/.git/**',
|
|
98
|
+
'**/dist/**',
|
|
99
|
+
'**/build/**',
|
|
100
|
+
'**/*.min.js',
|
|
101
|
+
'**/*.map'
|
|
102
|
+
]
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Add timeout to prevent hanging
|
|
106
|
+
const timeout = setTimeout(() => {
|
|
107
|
+
reject(new Error('File search timed out'));
|
|
108
|
+
}, 30000);
|
|
109
|
+
|
|
110
|
+
const pattern = this.options.pattern || '**/*';
|
|
111
|
+
console.log(chalk.gray(`Searching with pattern: ${pattern} in ${options.cwd || process.cwd()}`));
|
|
112
|
+
|
|
113
|
+
glob(pattern, options, (err, files) => {
|
|
114
|
+
clearTimeout(timeout);
|
|
115
|
+
if (err) {
|
|
116
|
+
console.error(chalk.red('Glob error:'), err);
|
|
117
|
+
reject(err);
|
|
118
|
+
} else {
|
|
119
|
+
console.log(chalk.gray(`Found ${files.length} total files`));
|
|
120
|
+
// Filter to only editable files
|
|
121
|
+
const editableFiles = files.filter(file => {
|
|
122
|
+
const ext = path.extname(file);
|
|
123
|
+
return ['.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.cpp', '.c', '.h', '.go', '.rs', '.rb', '.php', '.swift', '.kt', '.scala', '.r', '.m', '.mm', '.md', '.txt', '.json', '.xml', '.yaml', '.yml', '.toml', '.ini', '.conf', '.sh', '.bash', '.zsh', '.fish', '.ps1', '.bat', '.cmd'].includes(ext);
|
|
124
|
+
});
|
|
125
|
+
console.log(chalk.gray(`Filtered to ${editableFiles.length} editable files`));
|
|
126
|
+
resolve(editableFiles);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private async processFiles(): Promise<void> {
|
|
133
|
+
const promises: Promise<void>[] = [];
|
|
134
|
+
|
|
135
|
+
// Start initial batch of work
|
|
136
|
+
for (const [id, agent] of this.agents) {
|
|
137
|
+
if (this.fileQueue.length > 0) {
|
|
138
|
+
promises.push(this.processNextFile(agent));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Wait for all agents to complete
|
|
143
|
+
await Promise.all(promises);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private async processNextFile(agent: SwarmAgent): Promise<void> {
|
|
147
|
+
while (this.fileQueue.length > 0) {
|
|
148
|
+
const file = this.fileQueue.shift();
|
|
149
|
+
if (!file) break;
|
|
150
|
+
|
|
151
|
+
agent.file = file;
|
|
152
|
+
agent.status = 'busy';
|
|
153
|
+
this.activeCount++;
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
await this.processFile(agent, file);
|
|
157
|
+
agent.status = 'done';
|
|
158
|
+
} catch (error) {
|
|
159
|
+
agent.status = 'error';
|
|
160
|
+
agent.error = error instanceof Error ? error.message : String(error);
|
|
161
|
+
} finally {
|
|
162
|
+
this.activeCount--;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private async processFile(agent: SwarmAgent, file: string): Promise<void> {
|
|
168
|
+
const fullPath = path.join(this.options.cwd!, file);
|
|
169
|
+
|
|
170
|
+
// Build command based on provider
|
|
171
|
+
const command = this.buildCommand(file);
|
|
172
|
+
|
|
173
|
+
return new Promise((resolve, reject) => {
|
|
174
|
+
const child = spawn(command.cmd, command.args, {
|
|
175
|
+
cwd: this.options.cwd,
|
|
176
|
+
env: {
|
|
177
|
+
...process.env,
|
|
178
|
+
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
|
|
179
|
+
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
|
180
|
+
GOOGLE_API_KEY: process.env.GOOGLE_API_KEY,
|
|
181
|
+
GROK_API_KEY: process.env.GROK_API_KEY,
|
|
182
|
+
// Auto-accept edits for non-interactive mode
|
|
183
|
+
CLAUDE_CODE_PERMISSION_MODE: 'acceptEdits'
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
agent.process = child;
|
|
188
|
+
|
|
189
|
+
let output = '';
|
|
190
|
+
let error = '';
|
|
191
|
+
|
|
192
|
+
child.stdout?.on('data', (data) => {
|
|
193
|
+
output += data.toString();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
child.stderr?.on('data', (data) => {
|
|
197
|
+
error += data.toString();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
child.on('close', (code) => {
|
|
201
|
+
if (code === 0) {
|
|
202
|
+
agent.result = output;
|
|
203
|
+
resolve();
|
|
204
|
+
} else {
|
|
205
|
+
reject(new Error(`Process exited with code ${code}: ${error}`));
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
child.on('error', (err) => {
|
|
210
|
+
reject(err);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private buildCommand(file: string): { cmd: string, args: string[] } {
|
|
216
|
+
const fullPath = path.join(this.options.cwd!, file);
|
|
217
|
+
const filePrompt = `${this.options.prompt}\n\nFile: ${file}`;
|
|
218
|
+
|
|
219
|
+
switch (this.options.provider) {
|
|
220
|
+
case 'claude':
|
|
221
|
+
return {
|
|
222
|
+
cmd: 'claude',
|
|
223
|
+
args: [
|
|
224
|
+
'-p',
|
|
225
|
+
filePrompt,
|
|
226
|
+
'--max-turns', '5',
|
|
227
|
+
'--allowedTools', 'Read,Write,Edit',
|
|
228
|
+
'--permission-mode', 'acceptEdits'
|
|
229
|
+
]
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
case 'openai':
|
|
233
|
+
return {
|
|
234
|
+
cmd: 'openai',
|
|
235
|
+
args: [
|
|
236
|
+
'chat',
|
|
237
|
+
'--prompt', filePrompt,
|
|
238
|
+
'--file', fullPath,
|
|
239
|
+
'--edit'
|
|
240
|
+
]
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
case 'gemini':
|
|
244
|
+
return {
|
|
245
|
+
cmd: 'gemini',
|
|
246
|
+
args: [
|
|
247
|
+
'edit',
|
|
248
|
+
fullPath,
|
|
249
|
+
'--prompt', filePrompt
|
|
250
|
+
]
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
case 'grok':
|
|
254
|
+
return {
|
|
255
|
+
cmd: 'grok',
|
|
256
|
+
args: [
|
|
257
|
+
'--edit',
|
|
258
|
+
fullPath,
|
|
259
|
+
'--prompt', filePrompt
|
|
260
|
+
]
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
case 'local':
|
|
264
|
+
return {
|
|
265
|
+
cmd: 'dev',
|
|
266
|
+
args: [
|
|
267
|
+
'agent',
|
|
268
|
+
filePrompt
|
|
269
|
+
]
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
default:
|
|
273
|
+
throw new Error(`Unknown provider: ${this.options.provider}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private showResults(): void {
|
|
278
|
+
console.log(chalk.bold.cyan('\n📊 Swarm Results\n'));
|
|
279
|
+
|
|
280
|
+
let successful = 0;
|
|
281
|
+
let failed = 0;
|
|
282
|
+
|
|
283
|
+
for (const [id, agent] of this.agents) {
|
|
284
|
+
if (agent.status === 'done') {
|
|
285
|
+
successful++;
|
|
286
|
+
console.log(chalk.green(`✓ ${agent.file || id}`));
|
|
287
|
+
} else if (agent.status === 'error') {
|
|
288
|
+
failed++;
|
|
289
|
+
console.log(chalk.red(`✗ ${agent.file || id}: ${agent.error}`));
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
console.log(chalk.gray('\n─────────────────'));
|
|
294
|
+
console.log(chalk.white('Total files:'), this.fileQueue.length + successful + failed);
|
|
295
|
+
console.log(chalk.green('Successful:'), successful);
|
|
296
|
+
if (failed > 0) {
|
|
297
|
+
console.log(chalk.red('Failed:'), failed);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async ensureProviderAuth(): Promise<boolean> {
|
|
302
|
+
switch (this.options.provider) {
|
|
303
|
+
case 'claude':
|
|
304
|
+
return this.ensureClaudeAuth();
|
|
305
|
+
case 'openai':
|
|
306
|
+
return !!process.env.OPENAI_API_KEY;
|
|
307
|
+
case 'gemini':
|
|
308
|
+
return !!process.env.GOOGLE_API_KEY || !!process.env.GEMINI_API_KEY;
|
|
309
|
+
case 'grok':
|
|
310
|
+
return !!process.env.GROK_API_KEY;
|
|
311
|
+
case 'local':
|
|
312
|
+
return true;
|
|
313
|
+
default:
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private async ensureClaudeAuth(): Promise<boolean> {
|
|
319
|
+
// Check if already authenticated
|
|
320
|
+
try {
|
|
321
|
+
const testResult = await new Promise<boolean>((resolve) => {
|
|
322
|
+
const child = spawn('claude', ['-p', 'test', '--max-turns', '1'], {
|
|
323
|
+
env: process.env
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
let hasError = false;
|
|
327
|
+
let resolved = false;
|
|
328
|
+
|
|
329
|
+
const cleanup = () => {
|
|
330
|
+
if (!resolved) {
|
|
331
|
+
resolved = true;
|
|
332
|
+
clearTimeout(timeout);
|
|
333
|
+
child.kill();
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
child.stderr?.on('data', (data) => {
|
|
338
|
+
const output = data.toString();
|
|
339
|
+
if (output.includes('not authenticated') || output.includes('API key')) {
|
|
340
|
+
hasError = true;
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
child.on('close', () => {
|
|
345
|
+
cleanup();
|
|
346
|
+
resolve(!hasError);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// Timeout after 5 seconds
|
|
350
|
+
const timeout = setTimeout(() => {
|
|
351
|
+
cleanup();
|
|
352
|
+
resolve(!hasError);
|
|
353
|
+
}, 5000);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
if (testResult) {
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Try to login automatically if we have API key
|
|
361
|
+
if (process.env.ANTHROPIC_API_KEY && this.options.autoLogin) {
|
|
362
|
+
console.log(chalk.yellow('Attempting automatic Claude login...'));
|
|
363
|
+
|
|
364
|
+
const loginResult = await new Promise<boolean>((resolve) => {
|
|
365
|
+
const child = spawn('claude', ['login'], {
|
|
366
|
+
env: {
|
|
367
|
+
...process.env,
|
|
368
|
+
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY
|
|
369
|
+
},
|
|
370
|
+
stdio: 'inherit'
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
child.on('close', (code) => {
|
|
374
|
+
resolve(code === 0);
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
if (loginResult) {
|
|
379
|
+
console.log(chalk.green('✓ Claude login successful'));
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return false;
|
|
385
|
+
} catch (error) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// TypeScript utility functions
|
|
2
|
+
export function formatDate(date: Date): string {
|
|
3
|
+
return date.toISOString().split('T')[0];
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function parseJSON<T>(json: string): T | null {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(json);
|
|
9
|
+
} catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Python data processing
|
|
2
|
+
import json
|
|
3
|
+
from typing import List, Dict
|
|
4
|
+
|
|
5
|
+
def process_data(items: List[Dict]) -> Dict:
|
|
6
|
+
"""Process a list of items and return summary statistics."""
|
|
7
|
+
total = sum(item.get('value', 0) for item in items)
|
|
8
|
+
count = len(items)
|
|
9
|
+
average = total / count if count > 0 else 0
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
'total': total,
|
|
13
|
+
'count': count,
|
|
14
|
+
'average': average
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "test-project",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Test project for swarm processing",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": ["test", "swarm"],
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "ISC"
|
|
12
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
echo "🐝 Hanzo Dev Swarm Demo"
|
|
4
|
+
echo "======================"
|
|
5
|
+
echo ""
|
|
6
|
+
echo "This demo will add copyright headers to 5 test files in parallel"
|
|
7
|
+
echo ""
|
|
8
|
+
echo "Files before:"
|
|
9
|
+
echo "-------------"
|
|
10
|
+
head -n 1 test-swarm/*.{js,ts,py,md,json} 2>/dev/null
|
|
11
|
+
|
|
12
|
+
echo ""
|
|
13
|
+
echo "Running swarm with 5 agents..."
|
|
14
|
+
echo ""
|
|
15
|
+
|
|
16
|
+
# Run the swarm command
|
|
17
|
+
node dist/cli/dev.js --claude --swarm 5 -p "Add this copyright header at the very top of each file: '// Copyright 2025 Hanzo Industries Inc.' (use # for Python, // for JS/TS/JSON)"
|
|
18
|
+
|
|
19
|
+
echo ""
|
|
20
|
+
echo "Files after:"
|
|
21
|
+
echo "------------"
|
|
22
|
+
head -n 2 test-swarm/*.{js,ts,py,md,json} 2>/dev/null
|
package/tests/editor.test.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach, afterEach } from '
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import * as os from 'os';
|
|
5
|
-
import {
|
|
5
|
+
import { FileEditor, EditCommand } from '../src/lib/editor';
|
|
6
6
|
|
|
7
7
|
describe('Editor', () => {
|
|
8
|
-
let editor:
|
|
8
|
+
let editor: FileEditor;
|
|
9
9
|
let testDir: string;
|
|
10
10
|
let testFile: string;
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@ describe('Editor', () => {
|
|
|
13
13
|
// Create temporary test directory
|
|
14
14
|
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hanzo-dev-test-'));
|
|
15
15
|
testFile = path.join(testDir, 'test.txt');
|
|
16
|
-
editor = new
|
|
16
|
+
editor = new FileEditor(testDir);
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
afterEach(() => {
|
|
@@ -46,7 +46,7 @@ describe('Editor', () => {
|
|
|
46
46
|
|
|
47
47
|
const result = await editor.execute(command);
|
|
48
48
|
expect(result.success).toBe(false);
|
|
49
|
-
expect(result.error).
|
|
49
|
+
expect(result.error).toBe('FILE_EXISTS');
|
|
50
50
|
});
|
|
51
51
|
});
|
|
52
52
|
|
|
@@ -121,7 +121,7 @@ describe('Editor', () => {
|
|
|
121
121
|
|
|
122
122
|
const result = await editor.execute(command);
|
|
123
123
|
expect(result.success).toBe(false);
|
|
124
|
-
expect(result.error).
|
|
124
|
+
expect(result.error).toBe('STRING_NOT_FOUND');
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
test('should fail when old string appears multiple times', async () => {
|
|
@@ -137,7 +137,7 @@ describe('Editor', () => {
|
|
|
137
137
|
|
|
138
138
|
const result = await editor.execute(command);
|
|
139
139
|
expect(result.success).toBe(false);
|
|
140
|
-
expect(result.error).
|
|
140
|
+
expect(result.error).toBe('STRING_NOT_UNIQUE');
|
|
141
141
|
});
|
|
142
142
|
});
|
|
143
143
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Sample Python code for testing
|
|
2
|
+
import json
|
|
3
|
+
from typing import List, Dict, Optional
|
|
4
|
+
|
|
5
|
+
class DataProcessor:
|
|
6
|
+
"""Process and analyze data."""
|
|
7
|
+
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self.data = []
|
|
10
|
+
|
|
11
|
+
def add_item(self, item: Dict) -> None:
|
|
12
|
+
"""Add an item to the dataset."""
|
|
13
|
+
self.data.append(item)
|
|
14
|
+
|
|
15
|
+
def get_summary(self) -> Dict:
|
|
16
|
+
"""Get summary statistics."""
|
|
17
|
+
if not self.data:
|
|
18
|
+
return {"count": 0, "total": 0, "average": 0}
|
|
19
|
+
|
|
20
|
+
values = [item.get("value", 0) for item in self.data]
|
|
21
|
+
total = sum(values)
|
|
22
|
+
count = len(values)
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
"count": count,
|
|
26
|
+
"total": total,
|
|
27
|
+
"average": total / count if count > 0 else 0
|
|
28
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Sample TypeScript code for testing
|
|
2
|
+
interface User {
|
|
3
|
+
id: number;
|
|
4
|
+
name: string;
|
|
5
|
+
email: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class UserService {
|
|
9
|
+
private users: User[] = [];
|
|
10
|
+
|
|
11
|
+
addUser(user: User): void {
|
|
12
|
+
this.users.push(user);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getUser(id: number): User | undefined {
|
|
16
|
+
return this.users.find(user => user.id === id);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getAllUsers(): User[] {
|
|
20
|
+
return [...this.users];
|
|
21
|
+
}
|
|
22
|
+
}
|
package/tests/mcp-client.test.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach, afterEach,
|
|
1
|
+
import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
2
|
import { MCPClient, MCPSession, MCPServerConfig } from '../src/lib/mcp-client';
|
|
3
3
|
import { EventEmitter } from 'events';
|
|
4
4
|
import * as child_process from 'child_process';
|
|
5
5
|
|
|
6
6
|
// Mock child_process
|
|
7
|
-
|
|
7
|
+
vi.mock('child_process');
|
|
8
8
|
|
|
9
9
|
describe('MCPClient', () => {
|
|
10
10
|
let client: MCPClient;
|
|
@@ -15,16 +15,16 @@ describe('MCPClient', () => {
|
|
|
15
15
|
|
|
16
16
|
// Mock spawn to return a fake process
|
|
17
17
|
mockProcess = new EventEmitter();
|
|
18
|
-
mockProcess.stdin = { write:
|
|
18
|
+
mockProcess.stdin = { write: vi.fn() };
|
|
19
19
|
mockProcess.stdout = new EventEmitter();
|
|
20
20
|
mockProcess.stderr = new EventEmitter();
|
|
21
|
-
mockProcess.kill =
|
|
21
|
+
mockProcess.kill = vi.fn();
|
|
22
22
|
|
|
23
|
-
(child_process.spawn
|
|
23
|
+
vi.mocked(child_process.spawn).mockReturnValue(mockProcess as any);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
afterEach(() => {
|
|
27
|
-
|
|
27
|
+
vi.clearAllMocks();
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
describe('stdio transport', () => {
|