claude-flow 3.5.70 → 3.5.72

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 (90) hide show
  1. package/package.json +1 -1
  2. package/v3/@claude-flow/cli/dist/src/commands/plugins.js +6 -0
  3. package/v3/@claude-flow/cli/dist/src/commands/security.js +350 -34
  4. package/v3/@claude-flow/cli/dist/src/commands/swarm.js +100 -15
  5. package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-tools.js +35 -1
  6. package/v3/@claude-flow/cli/dist/src/mcp-tools/agentdb-tools.js +81 -0
  7. package/v3/@claude-flow/cli/dist/src/mcp-tools/analyze-tools.js +29 -0
  8. package/v3/@claude-flow/cli/dist/src/mcp-tools/autopilot-tools.js +4 -0
  9. package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-tools.js +146 -0
  10. package/v3/@claude-flow/cli/dist/src/mcp-tools/claims-tools.js +116 -0
  11. package/v3/@claude-flow/cli/dist/src/mcp-tools/coordination-tools.js +31 -0
  12. package/v3/@claude-flow/cli/dist/src/mcp-tools/daa-tools.js +61 -0
  13. package/v3/@claude-flow/cli/dist/src/mcp-tools/embeddings-tools.js +26 -0
  14. package/v3/@claude-flow/cli/dist/src/mcp-tools/github-tools.js +96 -0
  15. package/v3/@claude-flow/cli/dist/src/mcp-tools/guidance-tools.js +21 -0
  16. package/v3/@claude-flow/cli/dist/src/mcp-tools/hive-mind-tools.js +56 -0
  17. package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +231 -13
  18. package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +18 -2
  19. package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +92 -2
  20. package/v3/@claude-flow/cli/dist/src/mcp-tools/performance-tools.js +18 -5
  21. package/v3/@claude-flow/cli/dist/src/mcp-tools/ruvllm-tools.js +31 -0
  22. package/v3/@claude-flow/cli/dist/src/mcp-tools/security-tools.js +36 -0
  23. package/v3/@claude-flow/cli/dist/src/mcp-tools/system-tools.js +59 -18
  24. package/v3/@claude-flow/cli/dist/src/mcp-tools/transfer-tools.js +51 -0
  25. package/v3/@claude-flow/cli/dist/src/mcp-tools/wasm-agent-tools.js +61 -0
  26. package/v3/@claude-flow/cli/package.json +1 -1
  27. package/v3/@claude-flow/guidance/dist/adversarial.d.ts +284 -0
  28. package/v3/@claude-flow/guidance/dist/adversarial.js +572 -0
  29. package/v3/@claude-flow/guidance/dist/analyzer.d.ts +530 -0
  30. package/v3/@claude-flow/guidance/dist/analyzer.js +2518 -0
  31. package/v3/@claude-flow/guidance/dist/artifacts.d.ts +283 -0
  32. package/v3/@claude-flow/guidance/dist/artifacts.js +356 -0
  33. package/v3/@claude-flow/guidance/dist/authority.d.ts +290 -0
  34. package/v3/@claude-flow/guidance/dist/authority.js +558 -0
  35. package/v3/@claude-flow/guidance/dist/capabilities.d.ts +209 -0
  36. package/v3/@claude-flow/guidance/dist/capabilities.js +485 -0
  37. package/v3/@claude-flow/guidance/dist/coherence.d.ts +233 -0
  38. package/v3/@claude-flow/guidance/dist/coherence.js +372 -0
  39. package/v3/@claude-flow/guidance/dist/compiler.d.ts +87 -0
  40. package/v3/@claude-flow/guidance/dist/compiler.js +419 -0
  41. package/v3/@claude-flow/guidance/dist/conformance-kit.d.ts +225 -0
  42. package/v3/@claude-flow/guidance/dist/conformance-kit.js +629 -0
  43. package/v3/@claude-flow/guidance/dist/continue-gate.d.ts +214 -0
  44. package/v3/@claude-flow/guidance/dist/continue-gate.js +353 -0
  45. package/v3/@claude-flow/guidance/dist/crypto-utils.d.ts +17 -0
  46. package/v3/@claude-flow/guidance/dist/crypto-utils.js +24 -0
  47. package/v3/@claude-flow/guidance/dist/evolution.d.ts +282 -0
  48. package/v3/@claude-flow/guidance/dist/evolution.js +500 -0
  49. package/v3/@claude-flow/guidance/dist/gates.d.ts +79 -0
  50. package/v3/@claude-flow/guidance/dist/gates.js +302 -0
  51. package/v3/@claude-flow/guidance/dist/gateway.d.ts +206 -0
  52. package/v3/@claude-flow/guidance/dist/gateway.js +452 -0
  53. package/v3/@claude-flow/guidance/dist/generators.d.ts +153 -0
  54. package/v3/@claude-flow/guidance/dist/generators.js +682 -0
  55. package/v3/@claude-flow/guidance/dist/headless.d.ts +177 -0
  56. package/v3/@claude-flow/guidance/dist/headless.js +342 -0
  57. package/v3/@claude-flow/guidance/dist/hooks.d.ts +109 -0
  58. package/v3/@claude-flow/guidance/dist/hooks.js +347 -0
  59. package/v3/@claude-flow/guidance/dist/index.d.ts +205 -0
  60. package/v3/@claude-flow/guidance/dist/index.js +321 -0
  61. package/v3/@claude-flow/guidance/dist/ledger.d.ts +162 -0
  62. package/v3/@claude-flow/guidance/dist/ledger.js +375 -0
  63. package/v3/@claude-flow/guidance/dist/manifest-validator.d.ts +289 -0
  64. package/v3/@claude-flow/guidance/dist/manifest-validator.js +838 -0
  65. package/v3/@claude-flow/guidance/dist/memory-gate.d.ts +222 -0
  66. package/v3/@claude-flow/guidance/dist/memory-gate.js +382 -0
  67. package/v3/@claude-flow/guidance/dist/meta-governance.d.ts +265 -0
  68. package/v3/@claude-flow/guidance/dist/meta-governance.js +348 -0
  69. package/v3/@claude-flow/guidance/dist/optimizer.d.ts +104 -0
  70. package/v3/@claude-flow/guidance/dist/optimizer.js +329 -0
  71. package/v3/@claude-flow/guidance/dist/persistence.d.ts +189 -0
  72. package/v3/@claude-flow/guidance/dist/persistence.js +464 -0
  73. package/v3/@claude-flow/guidance/dist/proof.d.ts +185 -0
  74. package/v3/@claude-flow/guidance/dist/proof.js +238 -0
  75. package/v3/@claude-flow/guidance/dist/retriever.d.ts +116 -0
  76. package/v3/@claude-flow/guidance/dist/retriever.js +394 -0
  77. package/v3/@claude-flow/guidance/dist/ruvbot-integration.d.ts +370 -0
  78. package/v3/@claude-flow/guidance/dist/ruvbot-integration.js +738 -0
  79. package/v3/@claude-flow/guidance/dist/temporal.d.ts +426 -0
  80. package/v3/@claude-flow/guidance/dist/temporal.js +658 -0
  81. package/v3/@claude-flow/guidance/dist/trust.d.ts +283 -0
  82. package/v3/@claude-flow/guidance/dist/trust.js +473 -0
  83. package/v3/@claude-flow/guidance/dist/truth-anchors.d.ts +276 -0
  84. package/v3/@claude-flow/guidance/dist/truth-anchors.js +488 -0
  85. package/v3/@claude-flow/guidance/dist/types.d.ts +378 -0
  86. package/v3/@claude-flow/guidance/dist/types.js +10 -0
  87. package/v3/@claude-flow/guidance/dist/uncertainty.d.ts +372 -0
  88. package/v3/@claude-flow/guidance/dist/uncertainty.js +619 -0
  89. package/v3/@claude-flow/guidance/dist/wasm-kernel.d.ts +48 -0
  90. package/v3/@claude-flow/guidance/dist/wasm-kernel.js +158 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.5.70",
3
+ "version": "3.5.72",
4
4
  "description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -113,12 +113,14 @@ const listCommand = {
113
113
  output.writeln(output.dim('─'.repeat(70)));
114
114
  // Fetch real ratings from Cloud Function (non-blocking)
115
115
  let realRatings = {};
116
+ let ratingsSource = 'live';
116
117
  try {
117
118
  const pluginIds = plugins.map(p => p.name);
118
119
  realRatings = await getBulkRatings(pluginIds, 'plugin');
119
120
  }
120
121
  catch {
121
122
  // Fall back to static ratings if Cloud Function unavailable
123
+ ratingsSource = 'unavailable';
122
124
  }
123
125
  if (ctx.flags.format === 'json') {
124
126
  // Merge real ratings into plugin data
@@ -126,6 +128,7 @@ const listCommand = {
126
128
  ...p,
127
129
  rating: realRatings[p.name]?.average || p.rating,
128
130
  ratingCount: realRatings[p.name]?.count || 0,
131
+ ...(ratingsSource === 'unavailable' ? { ratingsSource: 'cached' } : {}),
129
132
  }));
130
133
  output.printJson(pluginsWithRatings);
131
134
  return { success: true, data: pluginsWithRatings };
@@ -157,6 +160,9 @@ const listCommand = {
157
160
  }),
158
161
  });
159
162
  output.writeln();
163
+ if (ratingsSource === 'unavailable') {
164
+ output.writeln(output.dim('(ratings: cached — cloud unavailable)'));
165
+ }
160
166
  output.writeln(output.dim(`Source: ${result.source}${result.fromCache ? ' (cached)' : ''}`));
161
167
  if (result.cid) {
162
168
  output.writeln(output.dim(`Registry CID: ${result.cid.slice(0, 30)}...`));
@@ -293,11 +293,201 @@ const threatsCommand = {
293
293
  ],
294
294
  action: async (ctx) => {
295
295
  const model = ctx.flags.model || 'stride';
296
+ const scope = ctx.flags.scope || '.';
297
+ const exportFormat = ctx.flags.export;
296
298
  output.writeln();
297
299
  output.writeln(output.bold(`Threat Model: ${model.toUpperCase()}`));
298
300
  output.writeln(output.dim('─'.repeat(50)));
299
- output.writeln(output.warning('⚠ Automated threat modeling is not yet implemented.'));
300
- output.writeln(output.dim('The table below is a STRIDE reference template, not an analysis of your code.'));
301
+ const spinner = output.createSpinner({ text: `Scanning ${scope} for threat indicators...`, spinner: 'dots' });
302
+ spinner.start();
303
+ const fs = await import('fs');
304
+ const path = await import('path');
305
+ const rootDir = path.resolve(scope);
306
+ const findings = [];
307
+ const extensions = new Set(['.ts', '.js', '.json', '.yaml', '.yml', '.tsx', '.jsx']);
308
+ const skipDirs = new Set(['node_modules', 'dist', '.git']);
309
+ let filesScanned = 0;
310
+ const MAX_FILES = 500;
311
+ // Threat indicator patterns mapped to STRIDE categories
312
+ const threatPatterns = [
313
+ // Spoofing — weak/missing authentication
314
+ { pattern: /(?:app|router|server)\s*\.\s*(?:get|post|put|patch|delete)\s*\(\s*['"][^'"]+['"]\s*,\s*(?:async\s+)?\(?(?:req|request)/g, category: 'Spoofing', severity: 'medium', description: 'HTTP endpoint without auth middleware' },
315
+ // Tampering — code injection vectors
316
+ { pattern: /\beval\s*\(/g, category: 'Tampering', severity: 'high', description: 'eval() usage — arbitrary code execution risk' },
317
+ { pattern: /\bexecSync\s*\(/g, category: 'Tampering', severity: 'high', description: 'execSync() usage — command injection risk' },
318
+ { pattern: /\bexec\s*\(\s*[^)]*\$\{/g, category: 'Tampering', severity: 'high', description: 'exec() with template literal — injection risk' },
319
+ { pattern: /child_process.*\bexec\b/g, category: 'Tampering', severity: 'medium', description: 'child_process exec import — review for injection' },
320
+ { pattern: /new\s+Function\s*\(/g, category: 'Tampering', severity: 'high', description: 'new Function() — dynamic code execution risk' },
321
+ // Repudiation — missing audit/logging
322
+ // (checked via absence of logging imports, handled separately)
323
+ // Info Disclosure — secrets and data leaks
324
+ { pattern: /(?:api[_-]?key|secret|token|password|passwd|credential)\s*[:=]\s*['"][^'"]{8,}['"]/gi, category: 'Info Disclosure', severity: 'high', description: 'Hardcoded credential or secret' },
325
+ { pattern: /AKIA[0-9A-Z]{16}/g, category: 'Info Disclosure', severity: 'critical', description: 'AWS Access Key ID detected' },
326
+ { pattern: /gh[ps]_[A-Za-z0-9_]{36,}/g, category: 'Info Disclosure', severity: 'high', description: 'GitHub token detected' },
327
+ { pattern: /-----BEGIN (?:RSA|EC|DSA|OPENSSH) PRIVATE KEY-----/g, category: 'Info Disclosure', severity: 'critical', description: 'Private key detected' },
328
+ { pattern: /http:\/\/(?!localhost|127\.0\.0\.1|0\.0\.0\.0)/g, category: 'Info Disclosure', severity: 'medium', description: 'Non-localhost HTTP URL — should use HTTPS' },
329
+ // DoS — missing rate limiting / resource protection
330
+ { pattern: /require\s*\(\s*['"]express['"]\s*\)/g, category: 'DoS', severity: 'low', description: 'Express detected — verify rate-limiting is configured' },
331
+ { pattern: /require\s*\(\s*['"]fastify['"]\s*\)/g, category: 'DoS', severity: 'low', description: 'Fastify detected — verify rate-limiting is configured' },
332
+ // Elevation of privilege — unsafe deserialization, prototype pollution
333
+ { pattern: /JSON\.parse\s*\(\s*(?:req\.|request\.)/g, category: 'Elevation', severity: 'medium', description: 'Unsanitized JSON.parse from request — validate input' },
334
+ { pattern: /\.__proto__/g, category: 'Elevation', severity: 'high', description: '__proto__ access — prototype pollution risk' },
335
+ { pattern: /Object\.assign\s*\(\s*\{\s*\}\s*,\s*(?:req|request)\./g, category: 'Elevation', severity: 'medium', description: 'Object.assign from request — prototype pollution risk' },
336
+ ];
337
+ // Check for .env files committed to git
338
+ const checkEnvInGit = () => {
339
+ try {
340
+ const { execSync } = require('child_process');
341
+ const tracked = execSync('git ls-files --cached', { cwd: rootDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
342
+ const envFiles = tracked.split('\n').filter((f) => /(?:^|\/)\.env(?:\.|$)/.test(f));
343
+ for (const envFile of envFiles) {
344
+ findings.push({
345
+ category: 'Info Disclosure',
346
+ severity: output.error('CRITICAL'),
347
+ location: envFile,
348
+ description: '.env file tracked in git — secrets may be exposed',
349
+ });
350
+ }
351
+ }
352
+ catch { /* not a git repo or git not available */ }
353
+ };
354
+ // Recursive file scanner
355
+ const scanDir = (dir) => {
356
+ if (filesScanned >= MAX_FILES)
357
+ return;
358
+ let entries;
359
+ try {
360
+ entries = fs.readdirSync(dir, { withFileTypes: true });
361
+ }
362
+ catch {
363
+ return;
364
+ }
365
+ for (const entry of entries) {
366
+ if (filesScanned >= MAX_FILES)
367
+ break;
368
+ if (skipDirs.has(entry.name) || entry.name.startsWith('.'))
369
+ continue;
370
+ const fullPath = path.join(dir, entry.name);
371
+ if (entry.isDirectory()) {
372
+ scanDir(fullPath);
373
+ }
374
+ else if (entry.isFile() && extensions.has(path.extname(entry.name)) && !entry.name.endsWith('.d.ts')) {
375
+ filesScanned++;
376
+ try {
377
+ const stat = fs.statSync(fullPath);
378
+ if (stat.size > 1024 * 1024)
379
+ continue; // skip files > 1MB
380
+ const content = fs.readFileSync(fullPath, 'utf-8');
381
+ const lines = content.split('\n');
382
+ const relPath = path.relative(rootDir, fullPath);
383
+ for (let i = 0; i < lines.length; i++) {
384
+ for (const tp of threatPatterns) {
385
+ tp.pattern.lastIndex = 0;
386
+ if (tp.pattern.test(lines[i])) {
387
+ const sevLabel = tp.severity === 'critical' ? output.error('CRITICAL') :
388
+ tp.severity === 'high' ? output.warning('HIGH') :
389
+ tp.severity === 'medium' ? output.warning('MEDIUM') : output.info('LOW');
390
+ findings.push({
391
+ category: tp.category,
392
+ severity: sevLabel,
393
+ location: `${relPath}:${i + 1}`,
394
+ description: tp.description,
395
+ });
396
+ tp.pattern.lastIndex = 0;
397
+ }
398
+ }
399
+ }
400
+ }
401
+ catch { /* file read error */ }
402
+ }
403
+ }
404
+ };
405
+ // Check for missing security middleware in Express/Fastify apps
406
+ const checkMissingMiddleware = () => {
407
+ const serverFiles = [];
408
+ const collectServerFiles = (dir, depth) => {
409
+ if (depth <= 0 || filesScanned >= MAX_FILES)
410
+ return;
411
+ try {
412
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
413
+ for (const entry of entries) {
414
+ if (skipDirs.has(entry.name) || entry.name.startsWith('.'))
415
+ continue;
416
+ const fullPath = path.join(dir, entry.name);
417
+ if (entry.isDirectory()) {
418
+ collectServerFiles(fullPath, depth - 1);
419
+ }
420
+ else if (/\.(ts|js)$/.test(entry.name) && !entry.name.endsWith('.d.ts')) {
421
+ try {
422
+ const content = fs.readFileSync(fullPath, 'utf-8');
423
+ if (/require\s*\(\s*['"](?:express|fastify)['"]\s*\)/.test(content) || /from\s+['"](?:express|fastify)['"]/.test(content)) {
424
+ serverFiles.push(fullPath);
425
+ const relPath = path.relative(rootDir, fullPath);
426
+ if (!/(?:helmet|lusca)/.test(content)) {
427
+ findings.push({ category: 'Tampering', severity: output.warning('MEDIUM'), location: relPath, description: 'No helmet/lusca security headers middleware' });
428
+ }
429
+ if (!/(?:cors)/.test(content)) {
430
+ findings.push({ category: 'Spoofing', severity: output.info('LOW'), location: relPath, description: 'No CORS middleware detected' });
431
+ }
432
+ if (!/(?:rate.?limit|throttle)/.test(content)) {
433
+ findings.push({ category: 'DoS', severity: output.warning('MEDIUM'), location: relPath, description: 'No rate-limiting middleware detected' });
434
+ }
435
+ }
436
+ }
437
+ catch { /* skip */ }
438
+ }
439
+ }
440
+ }
441
+ catch { /* skip */ }
442
+ };
443
+ collectServerFiles(rootDir, 5);
444
+ };
445
+ checkEnvInGit();
446
+ scanDir(rootDir);
447
+ checkMissingMiddleware();
448
+ spinner.succeed(`Scanned ${filesScanned} files`);
449
+ // STRIDE reference framework
450
+ const strideRef = [
451
+ { category: 'Spoofing', description: 'Can an attacker impersonate a user or service?', example: 'Strong authentication, mTLS' },
452
+ { category: 'Tampering', description: 'Can data or code be modified without detection?', example: 'Input validation, integrity checks' },
453
+ { category: 'Repudiation', description: 'Can actions be performed without accountability?', example: 'Audit logging, signed commits' },
454
+ { category: 'Info Disclosure', description: 'Can sensitive data leak to unauthorized parties?', example: 'Encryption at rest and in transit' },
455
+ { category: 'DoS', description: 'Can service availability be degraded?', example: 'Rate limiting, resource quotas' },
456
+ { category: 'Elevation', description: 'Can privileges be escalated beyond granted level?', example: 'RBAC, principle of least privilege' },
457
+ ];
458
+ // Display real findings
459
+ output.writeln();
460
+ if (findings.length > 0) {
461
+ output.writeln(output.bold(`Findings (${findings.length}):`));
462
+ output.writeln();
463
+ output.printTable({
464
+ columns: [
465
+ { key: 'category', header: 'STRIDE Category', width: 18 },
466
+ { key: 'severity', header: 'Severity', width: 12 },
467
+ { key: 'location', header: 'Location', width: 30 },
468
+ { key: 'description', header: 'Description', width: 40 },
469
+ ],
470
+ data: findings.slice(0, 30),
471
+ });
472
+ if (findings.length > 30) {
473
+ output.writeln(output.dim(`... and ${findings.length - 30} more findings`));
474
+ }
475
+ // Summary by STRIDE category
476
+ const byCat = {};
477
+ for (const f of findings)
478
+ byCat[f.category] = (byCat[f.category] || 0) + 1;
479
+ output.writeln();
480
+ output.writeln(output.bold('Summary by STRIDE category:'));
481
+ for (const [cat, count] of Object.entries(byCat).sort((a, b) => b[1] - a[1])) {
482
+ output.writeln(` ${cat}: ${count} finding${count === 1 ? '' : 's'}`);
483
+ }
484
+ }
485
+ else {
486
+ output.writeln(output.success('No threat indicators detected in scanned files.'));
487
+ }
488
+ // Always show STRIDE reference
489
+ output.writeln();
490
+ output.writeln(output.bold(`${model.toUpperCase()} Reference Framework${findings.length === 0 ? ' (reference only — no issues detected)' : ''}:`));
301
491
  output.writeln();
302
492
  output.printTable({
303
493
  columns: [
@@ -305,18 +495,26 @@ const threatsCommand = {
305
495
  { key: 'description', header: 'What to Assess', width: 40 },
306
496
  { key: 'example', header: 'Example Mitigation', width: 30 },
307
497
  ],
308
- data: [
309
- { category: 'Spoofing', description: 'Can an attacker impersonate a user or service?', example: 'Strong authentication, mTLS' },
310
- { category: 'Tampering', description: 'Can data or code be modified without detection?', example: 'Input validation, integrity checks' },
311
- { category: 'Repudiation', description: 'Can actions be performed without accountability?', example: 'Audit logging, signed commits' },
312
- { category: 'Info Disclosure', description: 'Can sensitive data leak to unauthorized parties?', example: 'Encryption at rest and in transit' },
313
- { category: 'DoS', description: 'Can service availability be degraded?', example: 'Rate limiting, resource quotas' },
314
- { category: 'Elevation', description: 'Can privileges be escalated beyond granted level?', example: 'RBAC, principle of least privilege' },
315
- ],
498
+ data: strideRef,
316
499
  });
500
+ // Export if requested
501
+ if (exportFormat && findings.length > 0) {
502
+ const exportData = {
503
+ model: model.toUpperCase(),
504
+ timestamp: new Date().toISOString(),
505
+ scope,
506
+ filesScanned,
507
+ totalFindings: findings.length,
508
+ findings: findings.map(f => ({ ...f, severity: f.severity.replace(/\x1b\[[0-9;]*m/g, '') })),
509
+ strideReference: strideRef,
510
+ };
511
+ if (exportFormat === 'json') {
512
+ output.writeln();
513
+ output.writeln(JSON.stringify(exportData, null, 2));
514
+ }
515
+ }
317
516
  output.writeln();
318
- output.writeln(output.dim('For real threat modeling, review your architecture manually using this framework.'));
319
- output.writeln(output.dim('Run "claude-flow security scan" for automated vulnerability detection.'));
517
+ output.writeln(output.dim(`Files scanned: ${filesScanned} (max ${MAX_FILES})`));
320
518
  return { success: true };
321
519
  },
322
520
  };
@@ -401,33 +599,151 @@ const secretsCommand = {
401
599
  { command: 'claude-flow security secrets -a rotate', description: 'Rotate compromised secrets' },
402
600
  ],
403
601
  action: async (ctx) => {
404
- const path = ctx.flags.path || '.';
602
+ const scanPath = ctx.flags.path || '.';
603
+ const ignorePatterns = ctx.flags.ignore;
405
604
  output.writeln();
406
605
  output.writeln(output.bold('Secret Detection'));
407
606
  output.writeln(output.dim('─'.repeat(50)));
408
- const spinner = output.createSpinner({ text: 'Scanning for secrets...', spinner: 'dots' });
607
+ const spinner = output.createSpinner({ text: `Scanning ${scanPath} for secrets...`, spinner: 'dots' });
409
608
  spinner.start();
410
- await new Promise(r => setTimeout(r, 800));
411
- spinner.succeed('Scan complete');
412
- output.writeln();
413
- output.writeln(output.warning('⚠ No real secrets scan performed. Showing example findings.'));
414
- output.writeln(output.dim('Run "claude-flow security scan --depth full" for real secret detection.'));
609
+ const fs = await import('fs');
610
+ const path = await import('path');
611
+ const rootDir = path.resolve(scanPath);
612
+ const skipDirs = new Set(['node_modules', 'dist', '.git']);
613
+ const extensions = new Set(['.ts', '.js', '.json', '.yaml', '.yml', '.tsx', '.jsx', '.env', '.toml', '.cfg', '.conf', '.ini', '.properties', '.sh', '.bash', '.zsh']);
614
+ const ignoreList = ignorePatterns ? ignorePatterns.split(',').map(p => p.trim()) : [];
615
+ const secretPatterns = [
616
+ { pattern: /AKIA[0-9A-Z]{16}/g, type: 'AWS Access Key', risk: 'Critical', action: 'Rotate immediately' },
617
+ { pattern: /gh[ps]_[A-Za-z0-9_]{36,}/g, type: 'GitHub Token', risk: 'Critical', action: 'Revoke and rotate' },
618
+ { pattern: /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g, type: 'JWT Token', risk: 'High', action: 'Remove from source' },
619
+ { pattern: /-----BEGIN (?:RSA|EC|DSA|OPENSSH) PRIVATE KEY-----/g, type: 'Private Key', risk: 'Critical', action: 'Remove and regenerate' },
620
+ { pattern: /(?:mongodb|postgres|mysql|redis):\/\/[^\s'"]+/g, type: 'Connection String', risk: 'High', action: 'Use env variable' },
621
+ { pattern: /['"](?:sk-|sk_live_|sk_test_)[a-zA-Z0-9]{20,}['"]/g, type: 'API Key (Stripe/OpenAI)', risk: 'Critical', action: 'Rotate immediately' },
622
+ { pattern: /['"]xox[baprs]-[a-zA-Z0-9-]+['"]/g, type: 'Slack Token', risk: 'High', action: 'Revoke and rotate' },
623
+ { pattern: /[a-zA-Z0-9_-]*(?:api[_-]?key|secret[_-]?key|auth[_-]?token|access[_-]?token|private[_-]?key)\s*[:=]\s*['"][^'"]{8,}['"]/gi, type: 'Generic Secret/API Key', risk: 'High', action: 'Use env variable' },
624
+ { pattern: /(?:password|passwd|pwd)\s*[:=]\s*['"][^'"]{8,}['"]/gi, type: 'Hardcoded Password', risk: 'High', action: 'Use secrets manager' },
625
+ ];
626
+ const findings = [];
627
+ let filesScanned = 0;
628
+ const MAX_FILES = 500;
629
+ const shouldIgnore = (filePath) => {
630
+ return ignoreList.some(p => filePath.includes(p));
631
+ };
632
+ const scanDir = (dir) => {
633
+ if (filesScanned >= MAX_FILES)
634
+ return;
635
+ let entries;
636
+ try {
637
+ entries = fs.readdirSync(dir, { withFileTypes: true });
638
+ }
639
+ catch {
640
+ return;
641
+ }
642
+ for (const entry of entries) {
643
+ if (filesScanned >= MAX_FILES)
644
+ break;
645
+ if (skipDirs.has(entry.name))
646
+ continue;
647
+ // Allow dotfiles like .env but skip .git
648
+ const fullPath = path.join(dir, entry.name);
649
+ if (entry.isDirectory()) {
650
+ if (entry.name.startsWith('.') && entry.name !== '.env')
651
+ continue;
652
+ scanDir(fullPath);
653
+ }
654
+ else if (entry.isFile()) {
655
+ const ext = path.extname(entry.name);
656
+ const isEnvFile = entry.name.startsWith('.env');
657
+ if (!extensions.has(ext) && !isEnvFile)
658
+ continue;
659
+ if (entry.name.endsWith('.d.ts'))
660
+ continue;
661
+ const relPath = path.relative(rootDir, fullPath);
662
+ if (shouldIgnore(relPath))
663
+ continue;
664
+ filesScanned++;
665
+ try {
666
+ const stat = fs.statSync(fullPath);
667
+ if (stat.size > 1024 * 1024)
668
+ continue; // skip files > 1MB
669
+ const content = fs.readFileSync(fullPath, 'utf-8');
670
+ // Quick binary check — skip if null bytes present
671
+ if (content.includes('\0'))
672
+ continue;
673
+ const lines = content.split('\n');
674
+ for (let i = 0; i < lines.length; i++) {
675
+ const line = lines[i];
676
+ for (const sp of secretPatterns) {
677
+ sp.pattern.lastIndex = 0;
678
+ const match = sp.pattern.exec(line);
679
+ if (match) {
680
+ // Mask the matched secret for safe display
681
+ const matched = match[0];
682
+ const masked = matched.length > 12
683
+ ? matched.substring(0, 6) + '***' + matched.substring(matched.length - 3)
684
+ : '***';
685
+ findings.push({
686
+ type: sp.type,
687
+ location: `${relPath}:${i + 1}`,
688
+ risk: sp.risk,
689
+ action: sp.action,
690
+ line: masked,
691
+ });
692
+ sp.pattern.lastIndex = 0;
693
+ }
694
+ }
695
+ }
696
+ }
697
+ catch { /* file read error */ }
698
+ }
699
+ }
700
+ };
701
+ scanDir(rootDir);
702
+ spinner.succeed(`Scanned ${filesScanned} files`);
415
703
  output.writeln();
416
- output.printTable({
417
- columns: [
418
- { key: 'type', header: 'Secret Type (Example)', width: 25 },
419
- { key: 'location', header: 'Location', width: 30 },
420
- { key: 'risk', header: 'Risk', width: 12 },
421
- { key: 'action', header: 'Recommended', width: 20 },
422
- ],
423
- data: [
424
- { type: 'AWS Access Key', location: 'example/config.ts:15', risk: output.error('Critical'), action: 'Rotate immediately' },
425
- { type: 'GitHub Token', location: 'example/.env:8', risk: output.warning('High'), action: 'Remove from repo' },
426
- { type: 'JWT Secret', location: 'example/auth.ts:42', risk: output.warning('High'), action: 'Use env variable' },
427
- { type: 'DB Password', location: 'example/compose.yml:23', risk: output.warning('Medium'), action: 'Use secrets mgmt' },
428
- ],
429
- });
430
- return { success: true };
704
+ if (findings.length > 0) {
705
+ const criticalCount = findings.filter(f => f.risk === 'Critical').length;
706
+ const highCount = findings.filter(f => f.risk === 'High').length;
707
+ const mediumCount = findings.filter(f => f.risk === 'Medium').length;
708
+ output.printTable({
709
+ columns: [
710
+ { key: 'type', header: 'Secret Type', width: 25 },
711
+ { key: 'location', header: 'Location', width: 35 },
712
+ { key: 'risk', header: 'Risk', width: 12 },
713
+ { key: 'action', header: 'Recommended', width: 22 },
714
+ ],
715
+ data: findings.slice(0, 25).map(f => ({
716
+ type: f.type,
717
+ location: f.location,
718
+ risk: f.risk === 'Critical' ? output.error(f.risk) :
719
+ f.risk === 'High' ? output.warning(f.risk) :
720
+ output.warning(f.risk),
721
+ action: f.action,
722
+ })),
723
+ });
724
+ if (findings.length > 25) {
725
+ output.writeln(output.dim(`... and ${findings.length - 25} more secrets found`));
726
+ }
727
+ output.writeln();
728
+ output.printBox([
729
+ `Path: ${scanPath}`,
730
+ `Files scanned: ${filesScanned}`,
731
+ ``,
732
+ `Critical: ${criticalCount} High: ${highCount} Medium: ${mediumCount}`,
733
+ `Total secrets found: ${findings.length}`,
734
+ ].join('\n'), 'Secrets Summary');
735
+ }
736
+ else {
737
+ output.writeln(output.success('No secrets detected.'));
738
+ output.writeln();
739
+ output.printBox([
740
+ `Path: ${scanPath}`,
741
+ `Files scanned: ${filesScanned}`,
742
+ ``,
743
+ `No hardcoded secrets, API keys, tokens, or credentials found.`,
744
+ ].join('\n'), 'Secrets Summary');
745
+ }
746
+ return { success: findings.length === 0 };
431
747
  },
432
748
  };
433
749
  // Defend subcommand (AIDefence integration)
@@ -145,16 +145,99 @@ function getSwarmStatus(swarmId) {
145
145
  pending: pendingTasks
146
146
  },
147
147
  metrics: {
148
- tokensUsed: 0,
149
- avgResponseTime: '--',
150
- successRate: totalTasks > 0 ? `${Math.round((completedTasks / totalTasks) * 100)}%` : '--',
151
- elapsedTime: '--'
152
- },
153
- coordination: {
154
- consensusRounds: 0,
155
- messagesSent: 0,
156
- conflictsResolved: 0
148
+ tokensUsed: swarmState?.tokensUsed ?? null,
149
+ avgResponseTime: (() => {
150
+ // Calculate average response time from task files with startedAt/completedAt
151
+ const taskTimesMs = [];
152
+ if (fs.existsSync(tasksDir)) {
153
+ try {
154
+ const taskFiles = fs.readdirSync(tasksDir).filter(f => f.endsWith('.json'));
155
+ for (const file of taskFiles) {
156
+ try {
157
+ const task = JSON.parse(fs.readFileSync(path.join(tasksDir, file), 'utf-8'));
158
+ if (task.startedAt && task.completedAt) {
159
+ const elapsed = new Date(task.completedAt).getTime() - new Date(task.startedAt).getTime();
160
+ if (elapsed > 0)
161
+ taskTimesMs.push(elapsed);
162
+ }
163
+ }
164
+ catch { /* skip malformed task files */ }
165
+ }
166
+ }
167
+ catch { /* skip if dir unreadable */ }
168
+ }
169
+ if (taskTimesMs.length === 0)
170
+ return null;
171
+ const avgMs = Math.round(taskTimesMs.reduce((a, b) => a + b, 0) / taskTimesMs.length);
172
+ return avgMs < 1000 ? `${avgMs}ms` : `${(avgMs / 1000).toFixed(1)}s`;
173
+ })(),
174
+ successRate: totalTasks > 0 ? `${Math.round((completedTasks / totalTasks) * 100)}%` : null,
175
+ elapsedTime: (() => {
176
+ // Calculate from swarm startedAt in state.json
177
+ const startedAt = swarmState?.startedAt
178
+ || swarmState?.initializedAt;
179
+ if (!startedAt)
180
+ return null;
181
+ const elapsedMs = Date.now() - new Date(startedAt).getTime();
182
+ if (elapsedMs < 0)
183
+ return null;
184
+ const secs = Math.floor(elapsedMs / 1000);
185
+ if (secs < 60)
186
+ return `${secs}s`;
187
+ const mins = Math.floor(secs / 60);
188
+ const remSecs = secs % 60;
189
+ if (mins < 60)
190
+ return `${mins}m ${remSecs}s`;
191
+ const hrs = Math.floor(mins / 60);
192
+ const remMins = mins % 60;
193
+ return `${hrs}h ${remMins}m`;
194
+ })()
157
195
  },
196
+ coordination: (() => {
197
+ // Read real coordination counts from .swarm/coordination/ directory
198
+ const coordDir = path.join(swarmDir, 'coordination');
199
+ let consensusRounds = 0;
200
+ let messagesSent = 0;
201
+ let conflictsResolved = 0;
202
+ if (fs.existsSync(coordDir)) {
203
+ try {
204
+ const coordFiles = fs.readdirSync(coordDir).filter(f => f.endsWith('.json'));
205
+ for (const file of coordFiles) {
206
+ try {
207
+ const entry = JSON.parse(fs.readFileSync(path.join(coordDir, file), 'utf-8'));
208
+ if (entry.type === 'consensus')
209
+ consensusRounds++;
210
+ else if (entry.type === 'message')
211
+ messagesSent++;
212
+ else if (entry.type === 'conflict' || entry.type === 'conflict-resolution')
213
+ conflictsResolved++;
214
+ // Also aggregate pre-counted fields if present
215
+ if (typeof entry.consensusRounds === 'number')
216
+ consensusRounds += entry.consensusRounds;
217
+ if (typeof entry.messagesSent === 'number')
218
+ messagesSent += entry.messagesSent;
219
+ if (typeof entry.conflictsResolved === 'number')
220
+ conflictsResolved += entry.conflictsResolved;
221
+ }
222
+ catch { /* skip malformed coordination files */ }
223
+ }
224
+ }
225
+ catch { /* skip if dir unreadable */ }
226
+ }
227
+ // Also check state.json for aggregate coordination stats
228
+ if (swarmState) {
229
+ const coord = swarmState.coordination;
230
+ if (coord) {
231
+ if (typeof coord.consensusRounds === 'number')
232
+ consensusRounds += coord.consensusRounds;
233
+ if (typeof coord.messagesSent === 'number')
234
+ messagesSent += coord.messagesSent;
235
+ if (typeof coord.conflictsResolved === 'number')
236
+ conflictsResolved += coord.conflictsResolved;
237
+ }
238
+ }
239
+ return { consensusRounds, messagesSent, conflictsResolved };
240
+ })(),
158
241
  hasActiveSwarm: !!swarmState || totalAgents > 0
159
242
  };
160
243
  }
@@ -433,8 +516,10 @@ const startCommand = {
433
516
  fs.writeFileSync(path.join(swarmDir, 'state.json'), JSON.stringify(executionState, null, 2));
434
517
  output.writeln();
435
518
  output.printSuccess(`Swarm ${swarmId} initialized with ${totalAgents} agent slots`);
436
- output.writeln(output.dim(' Note: Agents are registered but actual task execution requires'));
437
- output.writeln(output.dim(' Claude Code Task tool or hive-mind spawn --claude to drive work.'));
519
+ output.writeln(output.dim(' This CLI coordinates agent state. Execution happens via:'));
520
+ output.writeln(output.dim(' - Claude Code Agent tool (interactive)'));
521
+ output.writeln(output.dim(' - claude -p (headless background)'));
522
+ output.writeln(output.dim(' - hive-mind spawn --claude (autonomous)'));
438
523
  output.writeln(output.dim(` Monitor: claude-flow swarm status ${swarmId}`));
439
524
  return { success: true, data: executionState };
440
525
  }
@@ -500,10 +585,10 @@ const statusCommand = {
500
585
  // Metrics
501
586
  output.writeln(output.bold('Performance Metrics'));
502
587
  output.printList([
503
- `Tokens Used: ${status.metrics.tokensUsed.toLocaleString()}`,
504
- `Avg Response Time: ${status.metrics.avgResponseTime}`,
505
- `Success Rate: ${status.metrics.successRate}`,
506
- `Elapsed Time: ${status.metrics.elapsedTime}`
588
+ `Tokens Used: ${status.metrics.tokensUsed != null ? status.metrics.tokensUsed.toLocaleString() : output.dim('unknown')}`,
589
+ `Avg Response Time: ${status.metrics.avgResponseTime ?? output.dim('no data')}`,
590
+ `Success Rate: ${status.metrics.successRate ?? output.dim('no data')}`,
591
+ `Elapsed Time: ${status.metrics.elapsedTime ?? output.dim('no data')}`
507
592
  ]);
508
593
  output.writeln();
509
594
  // Coordination stats