claude-flow 1.0.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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +612 -0
  3. package/bin/claude-flow +0 -0
  4. package/bin/claude-flow-simple +0 -0
  5. package/bin/claude-flow-typecheck +0 -0
  6. package/deno.json +84 -0
  7. package/package.json +45 -0
  8. package/scripts/check-links.ts +274 -0
  9. package/scripts/check-performance-regression.ts +168 -0
  10. package/scripts/claude-sparc.sh +562 -0
  11. package/scripts/coverage-report.ts +692 -0
  12. package/scripts/demo-task-system.ts +224 -0
  13. package/scripts/install.js +72 -0
  14. package/scripts/test-batch-tasks.ts +29 -0
  15. package/scripts/test-coordination-features.ts +238 -0
  16. package/scripts/test-mcp.ts +251 -0
  17. package/scripts/test-runner.ts +571 -0
  18. package/scripts/validate-examples.ts +288 -0
  19. package/src/cli/cli-core.ts +273 -0
  20. package/src/cli/commands/agent.ts +83 -0
  21. package/src/cli/commands/config.ts +442 -0
  22. package/src/cli/commands/help.ts +765 -0
  23. package/src/cli/commands/index.ts +963 -0
  24. package/src/cli/commands/mcp.ts +191 -0
  25. package/src/cli/commands/memory.ts +74 -0
  26. package/src/cli/commands/monitor.ts +403 -0
  27. package/src/cli/commands/session.ts +595 -0
  28. package/src/cli/commands/start.ts +156 -0
  29. package/src/cli/commands/status.ts +345 -0
  30. package/src/cli/commands/task.ts +79 -0
  31. package/src/cli/commands/workflow.ts +763 -0
  32. package/src/cli/completion.ts +553 -0
  33. package/src/cli/formatter.ts +310 -0
  34. package/src/cli/index.ts +211 -0
  35. package/src/cli/main.ts +23 -0
  36. package/src/cli/repl.ts +1050 -0
  37. package/src/cli/simple-cli.js +211 -0
  38. package/src/cli/simple-cli.ts +211 -0
  39. package/src/coordination/README.md +400 -0
  40. package/src/coordination/advanced-scheduler.ts +487 -0
  41. package/src/coordination/circuit-breaker.ts +366 -0
  42. package/src/coordination/conflict-resolution.ts +490 -0
  43. package/src/coordination/dependency-graph.ts +475 -0
  44. package/src/coordination/index.ts +63 -0
  45. package/src/coordination/manager.ts +460 -0
  46. package/src/coordination/messaging.ts +290 -0
  47. package/src/coordination/metrics.ts +585 -0
  48. package/src/coordination/resources.ts +322 -0
  49. package/src/coordination/scheduler.ts +390 -0
  50. package/src/coordination/work-stealing.ts +224 -0
  51. package/src/core/config.ts +627 -0
  52. package/src/core/event-bus.ts +186 -0
  53. package/src/core/json-persistence.ts +183 -0
  54. package/src/core/logger.ts +262 -0
  55. package/src/core/orchestrator-fixed.ts +312 -0
  56. package/src/core/orchestrator.ts +1234 -0
  57. package/src/core/persistence.ts +276 -0
  58. package/src/mcp/auth.ts +438 -0
  59. package/src/mcp/claude-flow-tools.ts +1280 -0
  60. package/src/mcp/load-balancer.ts +510 -0
  61. package/src/mcp/router.ts +240 -0
  62. package/src/mcp/server.ts +548 -0
  63. package/src/mcp/session-manager.ts +418 -0
  64. package/src/mcp/tools.ts +180 -0
  65. package/src/mcp/transports/base.ts +21 -0
  66. package/src/mcp/transports/http.ts +457 -0
  67. package/src/mcp/transports/stdio.ts +254 -0
  68. package/src/memory/backends/base.ts +22 -0
  69. package/src/memory/backends/markdown.ts +283 -0
  70. package/src/memory/backends/sqlite.ts +329 -0
  71. package/src/memory/cache.ts +238 -0
  72. package/src/memory/indexer.ts +238 -0
  73. package/src/memory/manager.ts +572 -0
  74. package/src/terminal/adapters/base.ts +29 -0
  75. package/src/terminal/adapters/native.ts +504 -0
  76. package/src/terminal/adapters/vscode.ts +340 -0
  77. package/src/terminal/manager.ts +308 -0
  78. package/src/terminal/pool.ts +271 -0
  79. package/src/terminal/session.ts +250 -0
  80. package/src/terminal/vscode-bridge.ts +242 -0
  81. package/src/utils/errors.ts +231 -0
  82. package/src/utils/helpers.ts +476 -0
  83. package/src/utils/types.ts +493 -0
@@ -0,0 +1,571 @@
1
+ #!/usr/bin/env -S deno run --allow-all
2
+ /**
3
+ * Comprehensive test runner for Claude-Flow
4
+ * Runs all tests with coverage and generates reports
5
+ */
6
+
7
+ import { parseArgs } from "https://deno.land/std@0.220.0/cli/parse_args.ts";
8
+ import { exists } from "https://deno.land/std@0.220.0/fs/exists.ts";
9
+ import { ensureDir } from "https://deno.land/std@0.220.0/fs/ensure_dir.ts";
10
+
11
+ interface TestSuite {
12
+ name: string;
13
+ pattern: string;
14
+ description: string;
15
+ timeout?: number;
16
+ parallel?: boolean;
17
+ }
18
+
19
+ const TEST_SUITES: TestSuite[] = [
20
+ {
21
+ name: "unit",
22
+ pattern: "tests/unit/**/*.test.ts",
23
+ description: "Unit tests for individual components",
24
+ timeout: 30000,
25
+ parallel: true,
26
+ },
27
+ {
28
+ name: "integration",
29
+ pattern: "tests/integration/**/*.test.ts",
30
+ description: "Integration tests for component interactions",
31
+ timeout: 60000,
32
+ parallel: true,
33
+ },
34
+ {
35
+ name: "e2e",
36
+ pattern: "tests/e2e/**/*.test.ts",
37
+ description: "End-to-end CLI and workflow tests",
38
+ timeout: 120000,
39
+ parallel: false,
40
+ },
41
+ ];
42
+
43
+ interface TestOptions {
44
+ suites: string[];
45
+ coverage: boolean;
46
+ watch: boolean;
47
+ filter?: string;
48
+ parallel: boolean;
49
+ verbose: boolean;
50
+ outputDir: string;
51
+ failFast: boolean;
52
+ updateSnapshots: boolean;
53
+ }
54
+
55
+ class TestRunner {
56
+ private options: TestOptions;
57
+
58
+ constructor(options: TestOptions) {
59
+ this.options = options;
60
+ }
61
+
62
+ async run(): Promise<boolean> {
63
+ console.log("🧪 Claude-Flow Test Runner");
64
+ console.log("=".repeat(50));
65
+
66
+ // Ensure output directory exists
67
+ await ensureDir(this.options.outputDir);
68
+
69
+ let allPassed = true;
70
+ const results: Array<{ suite: string; passed: boolean; duration: number }> = [];
71
+
72
+ // Run each test suite
73
+ for (const suiteName of this.options.suites) {
74
+ const suite = TEST_SUITES.find(s => s.name === suiteName);
75
+ if (!suite) {
76
+ console.error(`❌ Unknown test suite: ${suiteName}`);
77
+ allPassed = false;
78
+ continue;
79
+ }
80
+
81
+ console.log(`\n📋 Running ${suite.name} tests: ${suite.description}`);
82
+
83
+ const startTime = Date.now();
84
+ const passed = await this.runSuite(suite);
85
+ const duration = Date.now() - startTime;
86
+
87
+ results.push({ suite: suiteName, passed, duration });
88
+
89
+ if (!passed) {
90
+ allPassed = false;
91
+
92
+ if (this.options.failFast) {
93
+ console.log("\n💥 Fail-fast enabled, stopping test execution");
94
+ break;
95
+ }
96
+ }
97
+ }
98
+
99
+ // Generate reports
100
+ await this.generateReports(results);
101
+
102
+ // Print summary
103
+ this.printSummary(results, allPassed);
104
+
105
+ return allPassed;
106
+ }
107
+
108
+ private async runSuite(suite: TestSuite): Promise<boolean> {
109
+ const args = [
110
+ "test",
111
+ "--allow-all",
112
+ "--unstable-temporal-api",
113
+ ];
114
+
115
+ // Add coverage if enabled
116
+ if (this.options.coverage) {
117
+ args.push("--coverage", `${this.options.outputDir}/coverage`);
118
+ }
119
+
120
+ // Add parallel execution
121
+ if (this.options.parallel && suite.parallel) {
122
+ args.push("--parallel");
123
+ }
124
+
125
+ // Add timeout
126
+ if (suite.timeout) {
127
+ args.push("--timeout", suite.timeout.toString());
128
+ }
129
+
130
+ // Add filter if specified
131
+ if (this.options.filter) {
132
+ args.push("--filter", this.options.filter);
133
+ }
134
+
135
+ // Add verbose output
136
+ if (this.options.verbose) {
137
+ args.push("--verbose");
138
+ }
139
+
140
+ // Add fail-fast
141
+ if (this.options.failFast) {
142
+ args.push("--fail-fast");
143
+ }
144
+
145
+ // Add update snapshots
146
+ if (this.options.updateSnapshots) {
147
+ args.push("--update-snapshots");
148
+ }
149
+
150
+ // Add test pattern
151
+ args.push(suite.pattern);
152
+
153
+ console.log(` Command: deno ${args.join(" ")}`);
154
+
155
+ const command = new Deno.Command(Deno.execPath(), {
156
+ args,
157
+ stdout: "piped",
158
+ stderr: "piped",
159
+ });
160
+
161
+ const start = Date.now();
162
+ const { code, stdout, stderr } = await command.output();
163
+ const duration = Date.now() - start;
164
+
165
+ const output = new TextDecoder().decode(stdout);
166
+ const errorOutput = new TextDecoder().decode(stderr);
167
+
168
+ // Write output to files
169
+ const suiteOutputFile = `${this.options.outputDir}/${suite.name}-output.txt`;
170
+ await Deno.writeTextFile(suiteOutputFile, output);
171
+
172
+ if (errorOutput) {
173
+ const suiteErrorFile = `${this.options.outputDir}/${suite.name}-errors.txt`;
174
+ await Deno.writeTextFile(suiteErrorFile, errorOutput);
175
+ }
176
+
177
+ // Print output in real-time if verbose
178
+ if (this.options.verbose) {
179
+ console.log(output);
180
+ if (errorOutput) {
181
+ console.error(errorOutput);
182
+ }
183
+ }
184
+
185
+ const passed = code === 0;
186
+ const status = passed ? "✅ PASSED" : "❌ FAILED";
187
+ const durationStr = `${duration}ms`;
188
+
189
+ console.log(` ${status} (${durationStr})`);
190
+
191
+ if (!passed && !this.options.verbose) {
192
+ console.log(" Error output:");
193
+ console.log(errorOutput);
194
+ }
195
+
196
+ return passed;
197
+ }
198
+
199
+ private async generateReports(results: Array<{ suite: string; passed: boolean; duration: number }>): Promise<void> {
200
+ console.log("\n📊 Generating test reports...");
201
+
202
+ // Generate coverage report if enabled
203
+ if (this.options.coverage) {
204
+ await this.generateCoverageReport();
205
+ }
206
+
207
+ // Generate JUnit XML report
208
+ await this.generateJUnitReport(results);
209
+
210
+ // Generate HTML report
211
+ await this.generateHTMLReport(results);
212
+
213
+ // Generate JSON report
214
+ await this.generateJSONReport(results);
215
+ }
216
+
217
+ private async generateCoverageReport(): Promise<void> {
218
+ const coverageDir = `${this.options.outputDir}/coverage`;
219
+
220
+ if (await exists(coverageDir)) {
221
+ console.log(" Generating coverage reports...");
222
+
223
+ // Generate HTML coverage report
224
+ const htmlCommand = new Deno.Command(Deno.execPath(), {
225
+ args: [
226
+ "coverage",
227
+ coverageDir,
228
+ "--html",
229
+ `--output=${this.options.outputDir}/coverage-html`,
230
+ ],
231
+ stdout: "piped",
232
+ stderr: "piped",
233
+ });
234
+
235
+ await htmlCommand.output();
236
+
237
+ // Generate LCOV coverage report
238
+ const lcovCommand = new Deno.Command(Deno.execPath(), {
239
+ args: [
240
+ "coverage",
241
+ coverageDir,
242
+ "--lcov",
243
+ `--output=${this.options.outputDir}/coverage.lcov`,
244
+ ],
245
+ stdout: "piped",
246
+ stderr: "piped",
247
+ });
248
+
249
+ await lcovCommand.output();
250
+
251
+ // Generate text coverage summary
252
+ const textCommand = new Deno.Command(Deno.execPath(), {
253
+ args: [
254
+ "coverage",
255
+ coverageDir,
256
+ "--detailed",
257
+ ],
258
+ stdout: "piped",
259
+ stderr: "piped",
260
+ });
261
+
262
+ const { stdout } = await textCommand.output();
263
+ const coverageSummary = new TextDecoder().decode(stdout);
264
+
265
+ await Deno.writeTextFile(
266
+ `${this.options.outputDir}/coverage-summary.txt`,
267
+ coverageSummary
268
+ );
269
+
270
+ console.log(" ✅ Coverage reports generated");
271
+ }
272
+ }
273
+
274
+ private async generateJUnitReport(results: Array<{ suite: string; passed: boolean; duration: number }>): Promise<void> {
275
+ const testsuites = results.map(result => {
276
+ const errors = result.passed ? 0 : 1;
277
+ const failures = result.passed ? 0 : 1;
278
+
279
+ return ` <testsuite name="${result.suite}" tests="1" errors="${errors}" failures="${failures}" time="${result.duration / 1000}">
280
+ <testcase name="${result.suite}-tests" classname="Claude-Flow.${result.suite}" time="${result.duration / 1000}">
281
+ ${!result.passed ? '<failure message="Test suite failed" type="TestFailure">Test suite execution failed</failure>' : ''}
282
+ </testcase>
283
+ </testsuite>`;
284
+ }).join('\n');
285
+
286
+ const totalTests = results.length;
287
+ const totalErrors = results.filter(r => !r.passed).length;
288
+ const totalFailures = totalErrors;
289
+ const totalTime = results.reduce((sum, r) => sum + r.duration, 0) / 1000;
290
+
291
+ const junit = `<?xml version="1.0" encoding="UTF-8"?>
292
+ <testsuites name="Claude-Flow" tests="${totalTests}" errors="${totalErrors}" failures="${totalFailures}" time="${totalTime}">
293
+ ${testsuites}
294
+ </testsuites>`;
295
+
296
+ await Deno.writeTextFile(`${this.options.outputDir}/junit.xml`, junit);
297
+ console.log(" ✅ JUnit XML report generated");
298
+ }
299
+
300
+ private async generateHTMLReport(results: Array<{ suite: string; passed: boolean; duration: number }>): Promise<void> {
301
+ const suiteRows = results.map(result => {
302
+ const status = result.passed ? "✅ PASSED" : "❌ FAILED";
303
+ const statusClass = result.passed ? "passed" : "failed";
304
+
305
+ return `
306
+ <tr class="${statusClass}">
307
+ <td>${result.suite}</td>
308
+ <td>${status}</td>
309
+ <td>${result.duration}ms</td>
310
+ </tr>`;
311
+ }).join('');
312
+
313
+ const totalTests = results.length;
314
+ const passedTests = results.filter(r => r.passed).length;
315
+ const failedTests = totalTests - passedTests;
316
+ const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);
317
+ const timestamp = new Date().toISOString();
318
+
319
+ const html = `<!DOCTYPE html>
320
+ <html>
321
+ <head>
322
+ <title>Claude-Flow Test Report</title>
323
+ <style>
324
+ body { font-family: Arial, sans-serif; margin: 20px; }
325
+ .header { background: #f5f5f5; padding: 15px; border-radius: 5px; margin-bottom: 20px; }
326
+ .summary { display: flex; gap: 20px; margin-bottom: 20px; }
327
+ .metric { background: white; padding: 15px; border-radius: 5px; border: 1px solid #ddd; text-align: center; }
328
+ .metric h3 { margin: 0; color: #333; }
329
+ .metric .value { font-size: 24px; font-weight: bold; margin: 10px 0; }
330
+ .passed .value { color: #28a745; }
331
+ .failed .value { color: #dc3545; }
332
+ .total .value { color: #007bff; }
333
+ table { width: 100%; border-collapse: collapse; margin-top: 20px; }
334
+ th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
335
+ th { background-color: #f8f9fa; }
336
+ .passed { background-color: #d4edda; }
337
+ .failed { background-color: #f8d7da; }
338
+ .footer { margin-top: 30px; text-align: center; color: #666; }
339
+ </style>
340
+ </head>
341
+ <body>
342
+ <div class="header">
343
+ <h1>🧪 Claude-Flow Test Report</h1>
344
+ <p>Generated on: ${timestamp}</p>
345
+ </div>
346
+
347
+ <div class="summary">
348
+ <div class="metric total">
349
+ <h3>Total Tests</h3>
350
+ <div class="value">${totalTests}</div>
351
+ </div>
352
+ <div class="metric passed">
353
+ <h3>Passed</h3>
354
+ <div class="value">${passedTests}</div>
355
+ </div>
356
+ <div class="metric failed">
357
+ <h3>Failed</h3>
358
+ <div class="value">${failedTests}</div>
359
+ </div>
360
+ <div class="metric total">
361
+ <h3>Duration</h3>
362
+ <div class="value">${totalDuration}ms</div>
363
+ </div>
364
+ </div>
365
+
366
+ <table>
367
+ <thead>
368
+ <tr>
369
+ <th>Test Suite</th>
370
+ <th>Status</th>
371
+ <th>Duration</th>
372
+ </tr>
373
+ </thead>
374
+ <tbody>
375
+ ${suiteRows}
376
+ </tbody>
377
+ </table>
378
+
379
+ <div class="footer">
380
+ <p>Claude-Flow Test Suite - Advanced AI Agent Orchestration System</p>
381
+ </div>
382
+ </body>
383
+ </html>`;
384
+
385
+ await Deno.writeTextFile(`${this.options.outputDir}/report.html`, html);
386
+ console.log(" ✅ HTML report generated");
387
+ }
388
+
389
+ private async generateJSONReport(results: Array<{ suite: string; passed: boolean; duration: number }>): Promise<void> {
390
+ const report = {
391
+ timestamp: new Date().toISOString(),
392
+ total: results.length,
393
+ passed: results.filter(r => r.passed).length,
394
+ failed: results.filter(r => !r.passed).length,
395
+ duration: results.reduce((sum, r) => sum + r.duration, 0),
396
+ suites: results,
397
+ environment: {
398
+ deno: Deno.version.deno,
399
+ v8: Deno.version.v8,
400
+ typescript: Deno.version.typescript,
401
+ platform: Deno.build.os,
402
+ arch: Deno.build.arch,
403
+ },
404
+ options: this.options,
405
+ };
406
+
407
+ await Deno.writeTextFile(
408
+ `${this.options.outputDir}/report.json`,
409
+ JSON.stringify(report, null, 2)
410
+ );
411
+ console.log(" ✅ JSON report generated");
412
+ }
413
+
414
+ private printSummary(results: Array<{ suite: string; passed: boolean; duration: number }>, allPassed: boolean): void {
415
+ console.log("\n" + "=".repeat(50));
416
+ console.log("📊 TEST SUMMARY");
417
+ console.log("=".repeat(50));
418
+
419
+ const total = results.length;
420
+ const passed = results.filter(r => r.passed).length;
421
+ const failed = total - passed;
422
+ const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);
423
+
424
+ console.log(`Total Suites: ${total}`);
425
+ console.log(`Passed: ${passed} ✅`);
426
+ console.log(`Failed: ${failed} ${failed > 0 ? '❌' : '✅'}`);
427
+ console.log(`Total Duration: ${totalDuration}ms`);
428
+
429
+ if (this.options.coverage) {
430
+ console.log(`Coverage Report: ${this.options.outputDir}/coverage-html/index.html`);
431
+ }
432
+
433
+ console.log(`Detailed Report: ${this.options.outputDir}/report.html`);
434
+
435
+ const overallStatus = allPassed ? "✅ ALL TESTS PASSED" : "❌ SOME TESTS FAILED";
436
+ console.log(`\n${overallStatus}`);
437
+
438
+ if (!allPassed) {
439
+ console.log("\nFailed suites:");
440
+ results.filter(r => !r.passed).forEach(r => {
441
+ console.log(` - ${r.suite}`);
442
+ });
443
+ }
444
+ }
445
+ }
446
+
447
+ async function main(): Promise<void> {
448
+ const args = parseArgs(Deno.args, {
449
+ string: ["suites", "filter", "output-dir"],
450
+ boolean: [
451
+ "coverage",
452
+ "watch",
453
+ "parallel",
454
+ "verbose",
455
+ "fail-fast",
456
+ "update-snapshots",
457
+ "help"
458
+ ],
459
+ default: {
460
+ suites: "unit,integration,e2e",
461
+ coverage: true,
462
+ watch: false,
463
+ parallel: true,
464
+ verbose: false,
465
+ "fail-fast": false,
466
+ "update-snapshots": false,
467
+ "output-dir": "./test-results",
468
+ },
469
+ alias: {
470
+ h: "help",
471
+ c: "coverage",
472
+ w: "watch",
473
+ p: "parallel",
474
+ v: "verbose",
475
+ f: "filter",
476
+ o: "output-dir",
477
+ },
478
+ });
479
+
480
+ if (args.help) {
481
+ console.log(`
482
+ 🧪 Claude-Flow Test Runner
483
+
484
+ USAGE:
485
+ test-runner.ts [OPTIONS]
486
+
487
+ OPTIONS:
488
+ --suites, -s Test suites to run (comma-separated)
489
+ Available: ${TEST_SUITES.map(s => s.name).join(", ")}
490
+ Default: unit,integration,e2e
491
+
492
+ --coverage, -c Generate coverage reports (default: true)
493
+ --watch, -w Watch mode for continuous testing
494
+ --parallel, -p Run tests in parallel (default: true)
495
+ --verbose, -v Verbose output
496
+ --fail-fast Stop on first failure
497
+ --filter, -f Filter tests by pattern
498
+ --output-dir, -o Output directory for reports (default: ./test-results)
499
+ --update-snapshots Update test snapshots
500
+ --help, -h Show this help
501
+
502
+ EXAMPLES:
503
+ # Run all tests with coverage
504
+ ./scripts/test-runner.ts
505
+
506
+ # Run only unit tests
507
+ ./scripts/test-runner.ts --suites unit
508
+
509
+ # Run tests with filter
510
+ ./scripts/test-runner.ts --filter "orchestrator"
511
+
512
+ # Run in watch mode
513
+ ./scripts/test-runner.ts --watch --suites unit
514
+
515
+ # Run without coverage
516
+ ./scripts/test-runner.ts --no-coverage
517
+ `);
518
+ return;
519
+ }
520
+
521
+ const options: TestOptions = {
522
+ suites: args.suites.split(",").map(s => s.trim()),
523
+ coverage: args.coverage,
524
+ watch: args.watch,
525
+ filter: args.filter,
526
+ parallel: args.parallel,
527
+ verbose: args.verbose,
528
+ outputDir: args["output-dir"],
529
+ failFast: args["fail-fast"],
530
+ updateSnapshots: args["update-snapshots"],
531
+ };
532
+
533
+ // Validate suites
534
+ const validSuites = TEST_SUITES.map(s => s.name);
535
+ const invalidSuites = options.suites.filter(s => !validSuites.includes(s));
536
+
537
+ if (invalidSuites.length > 0) {
538
+ console.error(`❌ Invalid test suites: ${invalidSuites.join(", ")}`);
539
+ console.error(`Available suites: ${validSuites.join(", ")}`);
540
+ Deno.exit(1);
541
+ }
542
+
543
+ const runner = new TestRunner(options);
544
+
545
+ if (options.watch) {
546
+ console.log("👀 Watch mode enabled - tests will re-run on file changes");
547
+
548
+ // Simple watch implementation
549
+ const watcher = Deno.watchFs(["./src", "./tests"], { recursive: true });
550
+
551
+ // Run tests initially
552
+ await runner.run();
553
+
554
+ for await (const event of watcher) {
555
+ if (event.kind === "modify" && event.paths.some(p => p.endsWith(".ts"))) {
556
+ console.log("\n🔄 Files changed, re-running tests...");
557
+ await runner.run();
558
+ }
559
+ }
560
+ } else {
561
+ const success = await runner.run();
562
+ Deno.exit(success ? 0 : 1);
563
+ }
564
+ }
565
+
566
+ if (import.meta.main) {
567
+ main().catch(error => {
568
+ console.error("❌ Test runner failed:", error);
569
+ Deno.exit(1);
570
+ });
571
+ }