claude-flow 3.5.71 → 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.
- package/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/plugins.js +6 -0
- package/v3/@claude-flow/cli/dist/src/commands/security.js +350 -34
- package/v3/@claude-flow/cli/dist/src/commands/swarm.js +100 -15
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +55 -13
- package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +41 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/performance-tools.js +7 -5
- package/v3/@claude-flow/cli/dist/src/mcp-tools/system-tools.js +53 -18
- package/v3/@claude-flow/cli/package.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-flow",
|
|
3
|
-
"version": "3.5.
|
|
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.
|
|
300
|
-
|
|
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(
|
|
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
|
|
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:
|
|
607
|
+
const spinner = output.createSpinner({ text: `Scanning ${scanPath} for secrets...`, spinner: 'dots' });
|
|
409
608
|
spinner.start();
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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:
|
|
149
|
-
avgResponseTime:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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('
|
|
437
|
-
output.writeln(output.dim(' Claude Code
|
|
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
|
|
@@ -2801,18 +2801,59 @@ export const hooksIntelligenceAttention = {
|
|
|
2801
2801
|
return { success: false, error: v.error };
|
|
2802
2802
|
}
|
|
2803
2803
|
let implementation = 'placeholder';
|
|
2804
|
+
let embeddingSource = 'none';
|
|
2804
2805
|
const results = [];
|
|
2806
|
+
// Helper: generate query embedding, preferring real ONNX embeddings over hash fallback
|
|
2807
|
+
async function getQueryEmbedding(text, dims) {
|
|
2808
|
+
// Try ONNX via @claude-flow/embeddings
|
|
2809
|
+
try {
|
|
2810
|
+
const embeddingsModule = await import('@claude-flow/embeddings').catch(() => null);
|
|
2811
|
+
if (embeddingsModule?.createEmbeddingService) {
|
|
2812
|
+
const service = embeddingsModule.createEmbeddingService({ provider: 'onnx' });
|
|
2813
|
+
const result = await service.embed(text);
|
|
2814
|
+
const arr = new Float32Array(dims);
|
|
2815
|
+
for (let i = 0; i < Math.min(dims, result.embedding.length); i++) {
|
|
2816
|
+
arr[i] = result.embedding[i];
|
|
2817
|
+
}
|
|
2818
|
+
return { embedding: arr, source: 'onnx' };
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
catch {
|
|
2822
|
+
// ONNX not available, try agentic-flow
|
|
2823
|
+
}
|
|
2824
|
+
// Try agentic-flow embeddings
|
|
2825
|
+
try {
|
|
2826
|
+
const embeddingsModule = await import('@claude-flow/embeddings').catch(() => null);
|
|
2827
|
+
if (embeddingsModule?.createEmbeddingService) {
|
|
2828
|
+
const service = embeddingsModule.createEmbeddingService({ provider: 'agentic-flow' });
|
|
2829
|
+
const result = await service.embed(text);
|
|
2830
|
+
const arr = new Float32Array(dims);
|
|
2831
|
+
for (let i = 0; i < Math.min(dims, result.embedding.length); i++) {
|
|
2832
|
+
arr[i] = result.embedding[i];
|
|
2833
|
+
}
|
|
2834
|
+
return { embedding: arr, source: 'onnx' };
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
catch {
|
|
2838
|
+
// agentic-flow not available
|
|
2839
|
+
}
|
|
2840
|
+
// Hash-based fallback (deterministic but not semantic)
|
|
2841
|
+
const arr = new Float32Array(dims);
|
|
2842
|
+
let seed = text.split('').reduce((acc, char, i) => acc + char.charCodeAt(0) * (i + 1), 0);
|
|
2843
|
+
for (let i = 0; i < dims; i++) {
|
|
2844
|
+
seed = (seed * 1103515245 + 12345) & 0x7fffffff;
|
|
2845
|
+
arr[i] = (seed / 0x7fffffff) * 2 - 1;
|
|
2846
|
+
}
|
|
2847
|
+
return { embedding: arr, source: 'hash-fallback' };
|
|
2848
|
+
}
|
|
2805
2849
|
if (mode === 'moe') {
|
|
2806
2850
|
// Try MoE routing
|
|
2807
2851
|
const moe = await getMoERouter();
|
|
2808
2852
|
if (moe) {
|
|
2809
2853
|
try {
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
embedding[i] = Math.sin(query.charCodeAt(i % query.length) * (i + 1) * 0.01);
|
|
2814
|
-
}
|
|
2815
|
-
const routingResult = moe.route(embedding);
|
|
2854
|
+
const embResult = await getQueryEmbedding(query, 384);
|
|
2855
|
+
embeddingSource = embResult.source;
|
|
2856
|
+
const routingResult = moe.route(embResult.embedding);
|
|
2816
2857
|
for (let i = 0; i < Math.min(topK, routingResult.experts.length); i++) {
|
|
2817
2858
|
const expert = routingResult.experts[i];
|
|
2818
2859
|
results.push({
|
|
@@ -2834,13 +2875,11 @@ export const hooksIntelligenceAttention = {
|
|
|
2834
2875
|
const flash = await getFlashAttention();
|
|
2835
2876
|
if (flash) {
|
|
2836
2877
|
try {
|
|
2837
|
-
|
|
2838
|
-
|
|
2878
|
+
const embResult = await getQueryEmbedding(query, 384);
|
|
2879
|
+
embeddingSource = embResult.source;
|
|
2880
|
+
const q = embResult.embedding;
|
|
2839
2881
|
const keys = [];
|
|
2840
2882
|
const values = [];
|
|
2841
|
-
for (let i = 0; i < 384; i++) {
|
|
2842
|
-
q[i] = Math.sin(query.charCodeAt(i % query.length) * (i + 1) * 0.01);
|
|
2843
|
-
}
|
|
2844
2883
|
// Generate some keys/values
|
|
2845
2884
|
for (let k = 0; k < topK; k++) {
|
|
2846
2885
|
const key = new Float32Array(384);
|
|
@@ -2883,10 +2922,13 @@ export const hooksIntelligenceAttention = {
|
|
|
2883
2922
|
results,
|
|
2884
2923
|
stats: {
|
|
2885
2924
|
computeTimeMs,
|
|
2886
|
-
|
|
2887
|
-
|
|
2925
|
+
implementation,
|
|
2926
|
+
_embeddingSource: embeddingSource,
|
|
2888
2927
|
_stub: implementation === 'none',
|
|
2889
2928
|
_note: implementation === 'none' ? 'No attention backend available. Install @ruvector/attention for real computation.' : undefined,
|
|
2929
|
+
...(embeddingSource === 'hash-fallback' && implementation !== 'none'
|
|
2930
|
+
? { _embeddingNote: 'Query embeddings are hash-based (not semantic). Install @claude-flow/embeddings for real ONNX embeddings.' }
|
|
2931
|
+
: {}),
|
|
2890
2932
|
},
|
|
2891
2933
|
implementation,
|
|
2892
2934
|
};
|
|
@@ -25,7 +25,7 @@ try {
|
|
|
25
25
|
realEmbeddings = { embed: (text) => rb.computeEmbedding(text) };
|
|
26
26
|
embeddingServiceName = 'agentic-flow/reasoningbank';
|
|
27
27
|
}
|
|
28
|
-
// Tier 2: @claude-flow/embeddings
|
|
28
|
+
// Tier 2: @claude-flow/embeddings with agentic-flow provider
|
|
29
29
|
if (!realEmbeddings) {
|
|
30
30
|
const embeddingsModule = await import('@claude-flow/embeddings').catch(() => null);
|
|
31
31
|
if (embeddingsModule?.createEmbeddingService) {
|
|
@@ -40,6 +40,34 @@ try {
|
|
|
40
40
|
embeddingServiceName = 'agentic-flow';
|
|
41
41
|
}
|
|
42
42
|
catch {
|
|
43
|
+
// agentic-flow provider not available, try ONNX
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Tier 3: @claude-flow/embeddings with ONNX provider
|
|
48
|
+
if (!realEmbeddings) {
|
|
49
|
+
const embeddingsModule = await import('@claude-flow/embeddings').catch(() => null);
|
|
50
|
+
if (embeddingsModule?.createEmbeddingService) {
|
|
51
|
+
try {
|
|
52
|
+
const service = embeddingsModule.createEmbeddingService({ provider: 'onnx' });
|
|
53
|
+
realEmbeddings = {
|
|
54
|
+
embed: async (text) => {
|
|
55
|
+
const result = await service.embed(text);
|
|
56
|
+
return Array.from(result.embedding);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
embeddingServiceName = 'onnx';
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// ONNX provider not available, fall through to mock
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Tier 4: mock fallback (last resort — embeddings are not semantic)
|
|
67
|
+
if (!realEmbeddings) {
|
|
68
|
+
const embeddingsModule = await import('@claude-flow/embeddings').catch(() => null);
|
|
69
|
+
if (embeddingsModule?.createEmbeddingService) {
|
|
70
|
+
try {
|
|
43
71
|
const service = embeddingsModule.createEmbeddingService({ provider: 'mock' });
|
|
44
72
|
realEmbeddings = {
|
|
45
73
|
embed: async (text) => {
|
|
@@ -47,7 +75,10 @@ try {
|
|
|
47
75
|
return Array.from(result.embedding);
|
|
48
76
|
},
|
|
49
77
|
};
|
|
50
|
-
embeddingServiceName = 'mock';
|
|
78
|
+
embeddingServiceName = 'mock-fallback';
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// No embedding service available at all
|
|
51
82
|
}
|
|
52
83
|
}
|
|
53
84
|
}
|
|
@@ -207,6 +238,7 @@ export const neuralTools = [
|
|
|
207
238
|
return {
|
|
208
239
|
success: true,
|
|
209
240
|
_realEmbedding: !!realEmbeddings,
|
|
241
|
+
embeddingProvider: embeddingServiceName,
|
|
210
242
|
modelId,
|
|
211
243
|
type: modelType,
|
|
212
244
|
status: model.status,
|
|
@@ -275,6 +307,7 @@ export const neuralTools = [
|
|
|
275
307
|
return {
|
|
276
308
|
success: true,
|
|
277
309
|
_realEmbedding: !!realEmbeddings,
|
|
310
|
+
embeddingProvider: embeddingServiceName,
|
|
278
311
|
_hasStoredPatterns: storedPatterns.length > 0,
|
|
279
312
|
modelId: model?.id || 'default',
|
|
280
313
|
input: inputText,
|
|
@@ -364,6 +397,7 @@ export const neuralTools = [
|
|
|
364
397
|
return {
|
|
365
398
|
success: true,
|
|
366
399
|
_realEmbedding: !!realEmbeddings,
|
|
400
|
+
embeddingProvider: embeddingServiceName,
|
|
367
401
|
patternId,
|
|
368
402
|
name: pattern.name,
|
|
369
403
|
type: pattern.type,
|
|
@@ -386,6 +420,7 @@ export const neuralTools = [
|
|
|
386
420
|
return {
|
|
387
421
|
_realSimilarity: true,
|
|
388
422
|
_realEmbedding: !!realEmbeddings,
|
|
423
|
+
embeddingProvider: embeddingServiceName,
|
|
389
424
|
query,
|
|
390
425
|
results: results.map(r => ({
|
|
391
426
|
id: r.id,
|
|
@@ -455,6 +490,7 @@ export const neuralTools = [
|
|
|
455
490
|
saveNeuralStore(store);
|
|
456
491
|
return {
|
|
457
492
|
success: true, _real: true, method,
|
|
493
|
+
embeddingProvider: embeddingServiceName,
|
|
458
494
|
patternsCompressed: totalCompressed,
|
|
459
495
|
compressionRatio: '3.92x (Int8)',
|
|
460
496
|
beforeBytes: beforeSize,
|
|
@@ -478,6 +514,7 @@ export const neuralTools = [
|
|
|
478
514
|
saveNeuralStore(store);
|
|
479
515
|
return {
|
|
480
516
|
success: true, _real: true, method,
|
|
517
|
+
embeddingProvider: embeddingServiceName,
|
|
481
518
|
threshold,
|
|
482
519
|
patternsRemoved: toRemove.length,
|
|
483
520
|
patternsBefore: beforeCount,
|
|
@@ -511,6 +548,7 @@ export const neuralTools = [
|
|
|
511
548
|
saveNeuralStore(store);
|
|
512
549
|
return {
|
|
513
550
|
success: true, _real: true, method,
|
|
551
|
+
embeddingProvider: embeddingServiceName,
|
|
514
552
|
patternsMerged: merged.length,
|
|
515
553
|
patternsBefore: beforeCount,
|
|
516
554
|
patternsAfter: Object.keys(store.patterns).length,
|
|
@@ -670,6 +708,7 @@ export const neuralTools = [
|
|
|
670
708
|
const elapsed = Math.round(performance.now() - startTime);
|
|
671
709
|
return {
|
|
672
710
|
success: true, _real: true, target,
|
|
711
|
+
embeddingProvider: embeddingServiceName,
|
|
673
712
|
actions,
|
|
674
713
|
patternsBefore: beforeCount,
|
|
675
714
|
patternsAfter: Object.keys(store.patterns).length,
|
|
@@ -233,15 +233,15 @@ export const performanceTools = [
|
|
|
233
233
|
const suite = input.suite || 'all';
|
|
234
234
|
const iterations = input.iterations || 100;
|
|
235
235
|
const warmup = input.warmup !== false;
|
|
236
|
-
//
|
|
236
|
+
// Synthetic data benchmarks — measures actual CPU/memory throughput
|
|
237
237
|
const benchmarkFunctions = {
|
|
238
238
|
memory: () => {
|
|
239
|
-
//
|
|
239
|
+
// Synthetic data benchmark — measures actual allocation + sort throughput
|
|
240
240
|
const arr = new Array(1000).fill(0).map(() => Math.random());
|
|
241
241
|
arr.sort();
|
|
242
242
|
},
|
|
243
243
|
neural: () => {
|
|
244
|
-
//
|
|
244
|
+
// Synthetic data benchmark — measures actual matrix multiplication throughput
|
|
245
245
|
const size = 64;
|
|
246
246
|
const a = Array.from({ length: size }, () => Array.from({ length: size }, () => Math.random()));
|
|
247
247
|
const b = Array.from({ length: size }, () => Array.from({ length: size }, () => Math.random()));
|
|
@@ -255,14 +255,14 @@ export const performanceTools = [
|
|
|
255
255
|
}
|
|
256
256
|
},
|
|
257
257
|
swarm: () => {
|
|
258
|
-
//
|
|
258
|
+
// Synthetic data benchmark — measures actual object creation + manipulation throughput
|
|
259
259
|
const agents = Array.from({ length: 10 }, (_, i) => ({ id: i, status: 'active', tasks: [] }));
|
|
260
260
|
agents.forEach(a => { for (let i = 0; i < 100; i++)
|
|
261
261
|
a.tasks.push(i); });
|
|
262
262
|
agents.sort((a, b) => a.tasks.length - b.tasks.length);
|
|
263
263
|
},
|
|
264
264
|
io: () => {
|
|
265
|
-
//
|
|
265
|
+
// Synthetic data benchmark — measures actual JSON serialization throughput
|
|
266
266
|
const data = { agents: Array.from({ length: 50 }, (_, i) => ({ id: i, name: `agent-${i}` })) };
|
|
267
267
|
const json = JSON.stringify(data);
|
|
268
268
|
JSON.parse(json);
|
|
@@ -313,6 +313,7 @@ export const performanceTools = [
|
|
|
313
313
|
avgLatency: `${avgLatencyMs.toFixed(3)}ms`,
|
|
314
314
|
memoryUsage: `${Math.abs(memoryDelta)}KB`,
|
|
315
315
|
_real: true,
|
|
316
|
+
_dataSource: 'synthetic',
|
|
316
317
|
});
|
|
317
318
|
}
|
|
318
319
|
}
|
|
@@ -330,6 +331,7 @@ export const performanceTools = [
|
|
|
330
331
|
: { note: 'First benchmark run - no comparison available', totalBenchmarks: allBenchmarks.length };
|
|
331
332
|
return {
|
|
332
333
|
_real: true,
|
|
334
|
+
_note: 'Benchmarks use synthetic workloads to measure throughput. Results reflect actual CPU/memory performance.',
|
|
333
335
|
suite,
|
|
334
336
|
iterations,
|
|
335
337
|
warmup,
|
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { getProjectCwd } from './types.js';
|
|
12
12
|
import { validateIdentifier } from './validate-input.js';
|
|
13
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, statfsSync } from 'node:fs';
|
|
14
14
|
import { join, dirname } from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
16
|
import * as os from 'node:os';
|
|
17
|
+
import * as dns from 'node:dns';
|
|
17
18
|
// Read version dynamically from package.json
|
|
18
19
|
function getPackageVersion() {
|
|
19
20
|
try {
|
|
@@ -332,26 +333,60 @@ export const systemTools = [
|
|
|
332
333
|
message: 'Neural network health not monitored',
|
|
333
334
|
});
|
|
334
335
|
if (input.deep) {
|
|
335
|
-
// Disk check — real free space
|
|
336
|
+
// Disk check — real free space via statfsSync (Node 18.15+)
|
|
336
337
|
{
|
|
337
338
|
const t0 = performance.now();
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
339
|
+
try {
|
|
340
|
+
const stats = statfsSync(projectCwd);
|
|
341
|
+
const totalBytes = stats.blocks * stats.bsize;
|
|
342
|
+
const freeBytes = stats.bfree * stats.bsize;
|
|
343
|
+
const totalGB = Math.round((totalBytes / (1024 ** 3)) * 10) / 10;
|
|
344
|
+
const freeGB = Math.round((freeBytes / (1024 ** 3)) * 10) / 10;
|
|
345
|
+
const freePercent = Math.round((freeBytes / totalBytes) * 100);
|
|
346
|
+
const elapsed = performance.now() - t0;
|
|
347
|
+
checks.push({
|
|
348
|
+
name: 'disk',
|
|
349
|
+
status: freePercent > 10 ? 'healthy' : 'warning',
|
|
350
|
+
latency: Math.round(elapsed * 100) / 100,
|
|
351
|
+
message: `${freeGB}GB free of ${totalGB}GB (${freePercent}%)`,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
const elapsed = performance.now() - t0;
|
|
356
|
+
checks.push({
|
|
357
|
+
name: 'disk',
|
|
358
|
+
status: 'unknown',
|
|
359
|
+
latency: Math.round(elapsed * 100) / 100,
|
|
360
|
+
message: 'Disk space check failed — statfsSync unavailable',
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// Network — DNS resolution check with timeout
|
|
365
|
+
{
|
|
366
|
+
const t0 = performance.now();
|
|
367
|
+
try {
|
|
368
|
+
await Promise.race([
|
|
369
|
+
dns.promises.lookup('registry.npmjs.org'),
|
|
370
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 3000)),
|
|
371
|
+
]);
|
|
372
|
+
const elapsed = performance.now() - t0;
|
|
373
|
+
checks.push({
|
|
374
|
+
name: 'network',
|
|
375
|
+
status: 'healthy',
|
|
376
|
+
latency: Math.round(elapsed * 100) / 100,
|
|
377
|
+
message: 'DNS resolution working',
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
const elapsed = performance.now() - t0;
|
|
382
|
+
checks.push({
|
|
383
|
+
name: 'network',
|
|
384
|
+
status: 'warning',
|
|
385
|
+
latency: Math.round(elapsed * 100) / 100,
|
|
386
|
+
message: 'DNS resolution failed — check network',
|
|
387
|
+
});
|
|
388
|
+
}
|
|
348
389
|
}
|
|
349
|
-
// Network — cannot verify without making a request
|
|
350
|
-
checks.push({
|
|
351
|
-
name: 'network',
|
|
352
|
-
status: 'unknown',
|
|
353
|
-
message: 'Network connectivity not monitored',
|
|
354
|
-
});
|
|
355
390
|
// Database — check if coordination store exists
|
|
356
391
|
{
|
|
357
392
|
const t0 = performance.now();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.72",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|