@claude-flow/cli 3.32.0 → 3.32.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/helpers/.helpers-version +1 -1
- package/.claude/helpers/helpers.manifest.json +3 -3
- package/.claude/helpers/statusline.cjs +38 -31
- package/.claude/helpers/statusline.js +19 -31
- package/catalog-manifest.json +2 -2
- package/dist/src/commands/hooks.js +20 -9
- package/dist/src/commands/init.d.ts +5 -0
- package/dist/src/commands/init.js +30 -1
- package/dist/src/commands/security.js +16 -11
- package/dist/src/funnel/insights.d.ts +1 -0
- package/dist/src/funnel/insights.js +4 -4
- package/dist/src/funnel/local-signals.d.ts +6 -1
- package/dist/src/funnel/local-signals.js +29 -20
- package/dist/src/init/executor.js +2 -2
- package/dist/src/mcp-tools/hooks-tools.js +13 -1
- package/dist/src/security/builtin-aidefence.d.ts +34 -0
- package/dist/src/security/builtin-aidefence.js +86 -0
- package/dist/src/services/fable-harness.d.ts +1 -0
- package/package.json +9 -8
- package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +1 -1
- package/plugins/ruflo-metaharness/scripts/smoke.sh +15 -12
- package/plugins/ruflo-metaharness/skills/harness-similarity/SKILL.md +1 -1
- package/.claude/.proven-config-version +0 -1
- package/.claude/proven-config.json +0 -42
- package/dist/src/ruvector/flash-attention.d.ts +0 -195
- package/dist/src/ruvector/flash-attention.js +0 -643
- package/dist/src/ruvector/moe-router.d.ts +0 -206
- package/dist/src/ruvector/moe-router.js +0 -626
- package/dist/src/services/event-stream.d.ts +0 -25
- package/dist/src/services/event-stream.js +0 -27
- package/dist/src/services/loop-worker-runner.d.ts +0 -16
- package/dist/src/services/loop-worker-runner.js +0 -34
- package/dist/src/services/runtime-capabilities.d.ts +0 -22
- package/dist/src/services/runtime-capabilities.js +0 -45
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.32.2
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": {
|
|
3
|
-
"version": "3.32.
|
|
3
|
+
"version": "3.32.2",
|
|
4
4
|
"files": {
|
|
5
5
|
"auto-memory-hook.mjs": "e3e1033b24704992ddef6b31c7fa9dd7fcd9e1af7935dd77ef73402b916b31e6",
|
|
6
6
|
"hook-handler.cjs": "f87ec28684bfc5fd0a54c46bd2a53a3fb037defe71d0ea4b8a5cb88a2cd87a3f",
|
|
7
7
|
"intelligence.cjs": "5a55d979cb7ba5c8c4f27f3b2e6d686fbb1045d180023b803da672a37e05b915",
|
|
8
|
-
"statusline.cjs": "
|
|
8
|
+
"statusline.cjs": "f6fcaedad7b521248ba57c494eb1a0ce696679fa3f8709f81edf3fdecef369c2"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
|
-
"signature": "
|
|
11
|
+
"signature": "r7TaWKVLZ6gxssGRM31J6usZrRm1Y5TAZXNmOfeLn+7VzWMrH6pZZPGTfSv6WMW49j8Ko3aamDBtahasWybSAw==",
|
|
12
12
|
"algorithm": "ed25519"
|
|
13
13
|
}
|
|
@@ -26,8 +26,11 @@ const { execSync } = require('child_process');
|
|
|
26
26
|
const os = require('os');
|
|
27
27
|
|
|
28
28
|
// Configuration
|
|
29
|
-
const CONFIG = {
|
|
30
|
-
maxAgents: 15,
|
|
29
|
+
const CONFIG = {
|
|
30
|
+
maxAgents: 15,
|
|
31
|
+
// Header identity defaults to project/repository name. Set `author` to
|
|
32
|
+
// retain the previous `git config user.name` display (#2682).
|
|
33
|
+
identityMode: (process.env.RUFLO_STATUSLINE_IDENTITY || 'project').toLowerCase(),
|
|
31
34
|
// Session-cost display. Claude Code's cost.total_cost_usd is a client-side
|
|
32
35
|
// estimate that "may differ from your actual bill" and reads as misleading on
|
|
33
36
|
// subscription plans, where token usage is not billed per dollar. These let
|
|
@@ -210,7 +213,9 @@ function getStatuslineData() {
|
|
|
210
213
|
// 60s TTL (#2337 — don't re-spawn the CLI on every rapid re-render) AND the
|
|
211
214
|
// tighter promo-rotation clock (this fix — don't let a still-fresh 60s
|
|
212
215
|
// cache silently freeze the promo/insight row across multiple 20s slots).
|
|
213
|
-
if (cache.fresh && cache.promoFresh)
|
|
216
|
+
if (cache.fresh && cache.promoFresh) {
|
|
217
|
+
return applyLocalOverlays(overlayMemoPromo(cache.data));
|
|
218
|
+
}
|
|
214
219
|
|
|
215
220
|
// #2337: prefer an already-installed CLI bin via direct `node` invocation —
|
|
216
221
|
// no npx, no registry round-trip, no @latest re-resolve per render. Try
|
|
@@ -417,7 +422,7 @@ function buildLocalFallback() {
|
|
|
417
422
|
return applyLocalOverlays({
|
|
418
423
|
user: { name: 'user', gitBranch: '', modelName: 'Claude Code' },
|
|
419
424
|
v3Progress: { domainsCompleted: 0, totalDomains: 5, dddProgress: 0, patternsLearned: 0, sessionsCompleted: 0 },
|
|
420
|
-
security: { status: 'NONE', cvesFixed: 0, totalCves: 0 },
|
|
425
|
+
security: { status: 'NONE', findings: 0, cvesFixed: 0, totalCves: 0 },
|
|
421
426
|
swarm: { activeAgents: 0, maxAgents: CONFIG.maxAgents, coordinationActive: false },
|
|
422
427
|
system: { memoryMB: memMB, contextPct: 0, intelligencePct: 0, subAgents: 0 },
|
|
423
428
|
lastUpdated: new Date().toISOString(),
|
|
@@ -474,14 +479,16 @@ function readJSON(filePath) {
|
|
|
474
479
|
|
|
475
480
|
// ─── Git info (pure-Node / single exec — needed for branch display) ──────────
|
|
476
481
|
|
|
477
|
-
function getGitInfo() {
|
|
478
|
-
const result = {
|
|
479
|
-
name: '
|
|
480
|
-
staged: 0, ahead: 0, behind: 0,
|
|
481
|
-
};
|
|
482
|
-
|
|
483
|
-
const script = [
|
|
484
|
-
'git
|
|
482
|
+
function getGitInfo() {
|
|
483
|
+
const result = {
|
|
484
|
+
name: path.basename(CWD) || 'project', gitBranch: '', modified: 0, untracked: 0,
|
|
485
|
+
staged: 0, ahead: 0, behind: 0,
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
const script = [
|
|
489
|
+
'git rev-parse --show-toplevel 2>/dev/null || pwd',
|
|
490
|
+
'echo "---SEP---"',
|
|
491
|
+
'git config user.name 2>/dev/null || echo user',
|
|
485
492
|
'echo "---SEP---"',
|
|
486
493
|
'git branch --show-current 2>/dev/null',
|
|
487
494
|
'echo "---SEP---"',
|
|
@@ -494,12 +501,14 @@ function getGitInfo() {
|
|
|
494
501
|
if (!raw) return result;
|
|
495
502
|
|
|
496
503
|
const parts = raw.split('---SEP---').map(function(s) { return s.trim(); });
|
|
497
|
-
if (parts.length >=
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
504
|
+
if (parts.length >= 5) {
|
|
505
|
+
const projectName = path.basename(parts[0] || CWD) || path.basename(CWD) || 'project';
|
|
506
|
+
const authorName = parts[1] || 'user';
|
|
507
|
+
result.name = CONFIG.identityMode === 'author' ? authorName : projectName;
|
|
508
|
+
result.gitBranch = parts[2] || '';
|
|
509
|
+
|
|
510
|
+
if (parts[3]) {
|
|
511
|
+
for (const line of parts[3].split('\n')) {
|
|
503
512
|
if (!line || line.length < 2) continue;
|
|
504
513
|
const x = line[0], y = line[1];
|
|
505
514
|
if (x === '?' && y === '?') { result.untracked++; continue; }
|
|
@@ -508,7 +517,7 @@ function getGitInfo() {
|
|
|
508
517
|
}
|
|
509
518
|
}
|
|
510
519
|
|
|
511
|
-
const ab = (parts[
|
|
520
|
+
const ab = (parts[4] || '0 0').split(/\s+/);
|
|
512
521
|
result.ahead = parseInt(ab[0]) || 0;
|
|
513
522
|
result.behind = parseInt(ab[1]) || 0;
|
|
514
523
|
}
|
|
@@ -722,8 +731,7 @@ function generateStatusline() {
|
|
|
722
731
|
const intelligencePct = system.intelligencePct || 0;
|
|
723
732
|
const memoryMB = system.memoryMB || 0;
|
|
724
733
|
const subAgents = system.subAgents || 0;
|
|
725
|
-
const
|
|
726
|
-
const totalCves = security.totalCves || 0;
|
|
734
|
+
const findings = Math.max(0, security.findings || 0);
|
|
727
735
|
const secStatus = security.status || 'NONE';
|
|
728
736
|
const adrCount = adrs.count || 0;
|
|
729
737
|
const adrImpl = adrs.implemented || 0;
|
|
@@ -778,8 +786,7 @@ function generateStatusline() {
|
|
|
778
786
|
const hooksColor = hooksEnabled > 0 ? c.brightGreen : c.dim;
|
|
779
787
|
const intellColor = intelligencePct >= 80 ? c.brightGreen : intelligencePct >= 40 ? c.brightYellow : c.dim;
|
|
780
788
|
const swarmInd = coordinationActive ? c.brightGreen + '◉' + c.reset + ' ' : c.dim + '○' + c.reset + ' ';
|
|
781
|
-
const
|
|
782
|
-
const healthAllGreen = (secStatus === 'CLEAN' || secStatus === 'NONE') && cvesClean;
|
|
789
|
+
const healthAllGreen = (secStatus === 'CLEAN' || secStatus === 'NONE') && findings === 0;
|
|
783
790
|
const opsParts = [];
|
|
784
791
|
opsParts.push(c.cyan + 'Swarm ' + swarmInd + agentsColor + activeAgents + c.reset + '/' + c.brightWhite + maxAgents + c.reset);
|
|
785
792
|
if (subAgents > 0) opsParts.push(c.brightPurple + '👥 ' + subAgents + c.reset);
|
|
@@ -790,14 +797,14 @@ function generateStatusline() {
|
|
|
790
797
|
if (healthAllGreen) {
|
|
791
798
|
opsParts.push(c.brightGreen + '🛡 ✓' + c.reset);
|
|
792
799
|
} else {
|
|
793
|
-
if (secStatus === 'PENDING') opsParts.push(c.brightYellow + '🛡 scan pending' + c.reset);
|
|
794
|
-
else if (secStatus === 'IN_PROGRESS') opsParts.push(c.brightYellow + '🛡 scanning…' + c.reset);
|
|
795
|
-
else if (secStatus === '
|
|
796
|
-
else if (secStatus
|
|
797
|
-
if (
|
|
798
|
-
|
|
799
|
-
opsParts.push(c.brightRed + '⚠ ' +
|
|
800
|
-
}
|
|
800
|
+
if (secStatus === 'PENDING') opsParts.push(c.brightYellow + '🛡 scan pending' + c.reset);
|
|
801
|
+
else if (secStatus === 'IN_PROGRESS') opsParts.push(c.brightYellow + '🛡 scanning…' + c.reset);
|
|
802
|
+
else if (secStatus === 'ISSUES') opsParts.push(c.brightRed + '🛡 findings' + c.reset);
|
|
803
|
+
else if (secStatus === 'STALE') opsParts.push(c.brightYellow + '🛡 scan stale' + c.reset);
|
|
804
|
+
else if (secStatus !== 'NONE' && secStatus !== 'CLEAN') opsParts.push(c.brightRed + '🛡 ' + secStatus.toLowerCase() + c.reset);
|
|
805
|
+
if (findings > 0) {
|
|
806
|
+
opsParts.push(c.brightRed + '⚠ ' + findings + ' finding' + (findings === 1 ? '' : 's') + c.reset);
|
|
807
|
+
}
|
|
801
808
|
}
|
|
802
809
|
lines.push(opsParts.join(' ' + c.dim + '·' + c.reset + ' '));
|
|
803
810
|
|
|
@@ -167,39 +167,27 @@ function getV3Progress() {
|
|
|
167
167
|
|
|
168
168
|
// Get security status based on actual scans
|
|
169
169
|
function getSecurityStatus() {
|
|
170
|
-
//
|
|
170
|
+
// ponytail: read the NEWEST scan in .claude/security-scans/ and surface its
|
|
171
|
+
// real findings count. Previously hardcoded totalCves=3 and counted scan
|
|
172
|
+
// *files* as "CVEs fixed", fabricating "⚠ 3 CVEs" for every project.
|
|
171
173
|
const scanResultsPath = path.join(process.cwd(), '.claude', 'security-scans');
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
// Ignore
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Also check .swarm/security for audit results
|
|
186
|
-
const auditPath = path.join(process.cwd(), '.swarm', 'security');
|
|
187
|
-
if (fs.existsSync(auditPath)) {
|
|
188
|
-
try {
|
|
189
|
-
const audits = fs.readdirSync(auditPath).filter(f => f.includes('audit'));
|
|
190
|
-
cvesFixed = Math.min(totalCves, Math.max(cvesFixed, audits.length));
|
|
191
|
-
} catch (e) {
|
|
192
|
-
// Ignore
|
|
174
|
+
if (!fs.existsSync(scanResultsPath)) return { status: 'PENDING', findings: 0, cvesFixed: 0, totalCves: 0 };
|
|
175
|
+
try {
|
|
176
|
+
const files = fs.readdirSync(scanResultsPath).filter(f => f.endsWith('.json'));
|
|
177
|
+
if (files.length === 0) return { status: 'PENDING', findings: 0, cvesFixed: 0, totalCves: 0 };
|
|
178
|
+
let newest = files[0];
|
|
179
|
+
let newestMtime = -1;
|
|
180
|
+
for (const f of files) {
|
|
181
|
+
const st = fs.statSync(path.join(scanResultsPath, f));
|
|
182
|
+
if (st.mtimeMs > newestMtime) { newestMtime = st.mtimeMs; newest = f; }
|
|
193
183
|
}
|
|
184
|
+
const scan = JSON.parse(fs.readFileSync(path.join(scanResultsPath, newest), 'utf-8'));
|
|
185
|
+
const findings = Math.max(0, scan.summary?.total ?? scan.totalFindings ?? scan.findings?.length ?? 0);
|
|
186
|
+
const status = findings > 0 ? 'ISSUES' : 'CLEAN';
|
|
187
|
+
return { status, findings, scannedAt: scan.timestamp, cvesFixed: 0, totalCves: 0 };
|
|
188
|
+
} catch (e) {
|
|
189
|
+
return { status: 'PENDING', findings: 0, cvesFixed: 0, totalCves: 0 };
|
|
194
190
|
}
|
|
195
|
-
|
|
196
|
-
const status = cvesFixed >= totalCves ? 'CLEAN' : cvesFixed > 0 ? 'IN_PROGRESS' : 'PENDING';
|
|
197
|
-
|
|
198
|
-
return {
|
|
199
|
-
status,
|
|
200
|
-
cvesFixed,
|
|
201
|
-
totalCves,
|
|
202
|
-
};
|
|
203
191
|
}
|
|
204
192
|
|
|
205
193
|
// Get swarm status
|
|
@@ -306,7 +294,7 @@ function generateStatusline() {
|
|
|
306
294
|
lines.push(
|
|
307
295
|
`${c.brightYellow}🤖 Swarm${c.reset} ${swarmIndicator} [${agentsColor}${String(swarm.activeAgents).padStart(2)}${c.reset}/${c.brightWhite}${swarm.maxAgents}${c.reset}] ` +
|
|
308
296
|
`${c.brightPurple}👥 ${system.subAgents}${c.reset} ` +
|
|
309
|
-
`${securityIcon} ${securityColor}
|
|
297
|
+
`${securityIcon} ${securityColor}Findings ${security.findings || 0}${c.reset} ` +
|
|
310
298
|
`${c.brightCyan}💾 ${system.memoryMB}MB${c.reset} ` +
|
|
311
299
|
`${c.brightGreen}📂 ${String(system.contextPct).padStart(3)}%${c.reset} ` +
|
|
312
300
|
`${c.dim}🧠 ${String(system.intelligencePct).padStart(3)}%${c.reset}`
|
package/catalog-manifest.json
CHANGED
|
@@ -3487,18 +3487,26 @@ const statuslineCommand = {
|
|
|
3487
3487
|
}
|
|
3488
3488
|
// Get user info
|
|
3489
3489
|
function getUserInfo() {
|
|
3490
|
-
|
|
3490
|
+
const identityMode = (process.env.RUFLO_STATUSLINE_IDENTITY || 'project').toLowerCase();
|
|
3491
|
+
let name = path.basename(process.cwd()) || 'project';
|
|
3491
3492
|
let gitBranch = '';
|
|
3492
3493
|
const modelName = 'Opus 4.6 (1M context)';
|
|
3493
3494
|
const isWindows = process.platform === 'win32';
|
|
3494
3495
|
try {
|
|
3495
|
-
const
|
|
3496
|
-
? 'git
|
|
3497
|
-
: 'git
|
|
3496
|
+
const rootCmd = isWindows
|
|
3497
|
+
? 'git rev-parse --show-toplevel 2>NUL'
|
|
3498
|
+
: 'git rev-parse --show-toplevel 2>/dev/null';
|
|
3498
3499
|
const branchCmd = isWindows
|
|
3499
3500
|
? 'git branch --show-current 2>NUL || echo.'
|
|
3500
3501
|
: 'git branch --show-current 2>/dev/null || echo ""';
|
|
3501
|
-
|
|
3502
|
+
const root = execSync(rootCmd, { encoding: 'utf-8' }).trim();
|
|
3503
|
+
name = path.basename(root) || name;
|
|
3504
|
+
if (identityMode === 'author') {
|
|
3505
|
+
const authorCmd = isWindows
|
|
3506
|
+
? 'git config user.name 2>NUL || echo user'
|
|
3507
|
+
: 'git config user.name 2>/dev/null || echo "user"';
|
|
3508
|
+
name = execSync(authorCmd, { encoding: 'utf-8' }).trim() || 'user';
|
|
3509
|
+
}
|
|
3502
3510
|
gitBranch = execSync(branchCmd, { encoding: 'utf-8' }).trim();
|
|
3503
3511
|
if (gitBranch === '.')
|
|
3504
3512
|
gitBranch = '';
|
|
@@ -3555,7 +3563,10 @@ const statuslineCommand = {
|
|
|
3555
3563
|
const terminalCols = process.stdout.columns ?? 80;
|
|
3556
3564
|
const autoCompact = !ctx.flags.full && terminalCols < COMPACT_WIDTH_THRESHOLD;
|
|
3557
3565
|
if (ctx.flags.compact || autoCompact) {
|
|
3558
|
-
const
|
|
3566
|
+
const securityCompact = security.findings > 0
|
|
3567
|
+
? `Findings:${security.findings}`
|
|
3568
|
+
: `Security:${security.status}`;
|
|
3569
|
+
const line = `DDD:${progress.domainsCompleted}/${progress.totalDomains} ${securityCompact} Swarm:${swarm.activeAgents}/${swarm.maxAgents} Ctx:${system.contextPct}% Int:${system.intelligencePct}%`;
|
|
3559
3570
|
output.writeln(line);
|
|
3560
3571
|
return { success: true, data: statusData };
|
|
3561
3572
|
}
|
|
@@ -3729,13 +3740,13 @@ const statuslineCommand = {
|
|
|
3729
3740
|
perfIndicator;
|
|
3730
3741
|
const swarmIndicator = swarm.coordinationActive ? `${c.brightGreen}◉${c.reset}` : `${c.dim}○${c.reset}`;
|
|
3731
3742
|
const agentsColor = swarm.activeAgents > 0 ? c.brightGreen : c.red;
|
|
3732
|
-
const securityIcon = security.status === 'CLEAN' ? '🟢' : security.status === '
|
|
3733
|
-
const securityColor = security.status === 'CLEAN' ? c.brightGreen : security.status === '
|
|
3743
|
+
const securityIcon = security.status === 'CLEAN' ? '🟢' : security.status === 'PENDING' ? '🟡' : '🔴';
|
|
3744
|
+
const securityColor = security.status === 'CLEAN' ? c.brightGreen : security.status === 'PENDING' ? c.brightYellow : c.brightRed;
|
|
3734
3745
|
const hooksColor = hooksStats.enabled > 0 ? c.brightGreen : c.dim;
|
|
3735
3746
|
const line2 = `${c.brightYellow}🤖 Swarm${c.reset} ${swarmIndicator} [${agentsColor}${String(swarm.activeAgents).padStart(2)}${c.reset}/${c.brightWhite}${swarm.maxAgents}${c.reset}] ` +
|
|
3736
3747
|
`${c.brightPurple}👥 ${system.subAgents}${c.reset} ` +
|
|
3737
3748
|
`${c.brightBlue}🪝 ${hooksColor}${hooksStats.enabled}${c.reset}/${c.brightWhite}${hooksStats.total}${c.reset} ` +
|
|
3738
|
-
`${securityIcon} ${securityColor}
|
|
3749
|
+
`${securityIcon} ${securityColor}${security.findings > 0 ? `Findings ${security.findings}` : `Security ${security.status}`}${c.reset} ` +
|
|
3739
3750
|
`${c.brightCyan}💾 ${system.memoryMB}MB${c.reset} ` +
|
|
3740
3751
|
`${c.brightPurple}🧠 ${String(system.intelligencePct).padStart(3)}%${c.reset}`;
|
|
3741
3752
|
const dddColor = progress.dddProgress >= 50 ? c.brightGreen : progress.dddProgress > 0 ? c.yellow : c.red;
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
* Comprehensive initialization for Claude Flow with Claude Code integration
|
|
4
4
|
*/
|
|
5
5
|
import type { Command } from '../types.js';
|
|
6
|
+
export declare function runCodexInitializerCli(cwd: string, options: {
|
|
7
|
+
template: string;
|
|
8
|
+
force: boolean;
|
|
9
|
+
dual: boolean;
|
|
10
|
+
}): boolean;
|
|
6
11
|
export declare const initCommand: Command;
|
|
7
12
|
export default initCommand;
|
|
8
13
|
//# sourceMappingURL=init.d.ts.map
|
|
@@ -6,6 +6,7 @@ import { output } from '../output.js';
|
|
|
6
6
|
import { confirm, select, multiSelect, input } from '../prompt.js';
|
|
7
7
|
import * as fs from 'fs';
|
|
8
8
|
import * as path from 'path';
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
9
10
|
import { executeInit, executeUpgrade, executeUpgradeWithMissing, DEFAULT_INIT_OPTIONS, MINIMAL_INIT_OPTIONS, FULL_INIT_OPTIONS, } from '../init/index.js';
|
|
10
11
|
import { ENROLLMENT_SCREEN, recordEnrollmentOutcome, shouldOfferEnrollment, } from '../funnel/enrollment.js';
|
|
11
12
|
import { commandExists } from '../services/harness-hosts.js';
|
|
@@ -82,6 +83,27 @@ async function resolveCodexInitializer(cwd) {
|
|
|
82
83
|
}
|
|
83
84
|
return undefined;
|
|
84
85
|
}
|
|
86
|
+
// Keep Codex out of the CLI dependency graph so cold `npx ruflo --version`
|
|
87
|
+
// remains fast (#2561). An explicit `init --codex` may fetch the small,
|
|
88
|
+
// stable adapter on demand when it is not already installed by an umbrella
|
|
89
|
+
// package, the current project, or the global npm prefix.
|
|
90
|
+
export function runCodexInitializerCli(cwd, options) {
|
|
91
|
+
const npxArgs = [
|
|
92
|
+
'-y',
|
|
93
|
+
'@claude-flow/codex@latest',
|
|
94
|
+
'init',
|
|
95
|
+
'--template',
|
|
96
|
+
options.template,
|
|
97
|
+
...(options.force ? ['--force'] : []),
|
|
98
|
+
...(options.dual ? ['--dual'] : []),
|
|
99
|
+
];
|
|
100
|
+
const result = process.platform === 'win32'
|
|
101
|
+
? spawnSync(process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', ['npx', ...npxArgs].join(' ')], { cwd, stdio: 'inherit', windowsHide: true })
|
|
102
|
+
: spawnSync('npx', npxArgs, { cwd, stdio: 'inherit' });
|
|
103
|
+
if (result.error)
|
|
104
|
+
throw result.error;
|
|
105
|
+
return result.status === 0;
|
|
106
|
+
}
|
|
85
107
|
// #2666-adjacent — quietly wire up Codex too when a plain `ruflo init` (no
|
|
86
108
|
// --codex/--dual) runs on a machine that also has the OpenAI Codex CLI on
|
|
87
109
|
// PATH: registers its MCP server and installs skills alongside the Claude
|
|
@@ -194,7 +216,14 @@ async function initCodexAction(ctx, options) {
|
|
|
194
216
|
try {
|
|
195
217
|
const CodexInitializer = await resolveCodexInitializer(ctx.cwd);
|
|
196
218
|
if (!CodexInitializer) {
|
|
197
|
-
|
|
219
|
+
spinner.stop();
|
|
220
|
+
output.printInfo('Fetching the stable Codex adapter for this initialization...');
|
|
221
|
+
const success = runCodexInitializerCli(ctx.cwd, { template, force, dual: dualMode });
|
|
222
|
+
if (!success) {
|
|
223
|
+
output.printError('Codex initialization failed while running @claude-flow/codex@latest.');
|
|
224
|
+
return { success: false, exitCode: 1 };
|
|
225
|
+
}
|
|
226
|
+
return { success: true, data: { adapter: '@claude-flow/codex@latest' } };
|
|
198
227
|
}
|
|
199
228
|
const initializer = new CodexInitializer();
|
|
200
229
|
const result = await initializer.initialize({
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { output } from '../output.js';
|
|
8
8
|
import { execSync } from 'node:child_process';
|
|
9
|
+
import { createBuiltinAIDefence } from '../security/builtin-aidefence.js';
|
|
9
10
|
// Scan subcommand
|
|
10
11
|
const scanCommand = {
|
|
11
12
|
name: 'scan',
|
|
@@ -861,16 +862,17 @@ const defendCommand = {
|
|
|
861
862
|
output.writeln(output.bold('🛡️ AIDefence - AI Manipulation Defense System'));
|
|
862
863
|
output.writeln(output.dim('─'.repeat(55)));
|
|
863
864
|
// Dynamic import of aidefence (allows package to be optional)
|
|
864
|
-
let
|
|
865
|
+
let defender;
|
|
865
866
|
try {
|
|
866
867
|
const aidefence = await import('@claude-flow/aidefence');
|
|
867
|
-
|
|
868
|
+
defender = aidefence.createAIDefence({ enableLearning });
|
|
868
869
|
}
|
|
869
870
|
catch {
|
|
870
|
-
|
|
871
|
-
|
|
871
|
+
// Keep cold npx startup lean (#2561): the full learning engine remains
|
|
872
|
+
// user-installable, while the CLI always ships a deterministic scanner.
|
|
873
|
+
defender = createBuiltinAIDefence();
|
|
874
|
+
output.writeln(output.dim('Using built-in defense engine (install @claude-flow/aidefence for adaptive learning)'));
|
|
872
875
|
}
|
|
873
|
-
const defender = createAIDefence({ enableLearning });
|
|
874
876
|
// Show stats mode
|
|
875
877
|
if (showStats) {
|
|
876
878
|
const stats = await defender.getStats();
|
|
@@ -893,8 +895,8 @@ const defendCommand = {
|
|
|
893
895
|
output.writeln(output.dim(`Reading file: ${filePath}`));
|
|
894
896
|
}
|
|
895
897
|
catch (err) {
|
|
896
|
-
output.
|
|
897
|
-
return { success: false, message: 'File not found' };
|
|
898
|
+
output.printError(`Failed to read file: ${filePath}`);
|
|
899
|
+
return { success: false, exitCode: 2, message: 'File not found' };
|
|
898
900
|
}
|
|
899
901
|
}
|
|
900
902
|
if (!textToScan) {
|
|
@@ -914,8 +916,9 @@ const defendCommand = {
|
|
|
914
916
|
spinner.start();
|
|
915
917
|
// Perform scan
|
|
916
918
|
const startTime = performance.now();
|
|
917
|
-
const
|
|
918
|
-
|
|
919
|
+
const quickResult = quickMode ? defender.quickScan(textToScan) : undefined;
|
|
920
|
+
const result = quickResult
|
|
921
|
+
? { ...quickResult, threats: [], piiFound: false, detectionTimeMs: 0, inputHash: '', safe: !quickResult.threat }
|
|
919
922
|
: await defender.detect(textToScan);
|
|
920
923
|
const scanTime = performance.now() - startTime;
|
|
921
924
|
spinner.stop();
|
|
@@ -927,7 +930,8 @@ const defendCommand = {
|
|
|
927
930
|
piiFound: result.piiFound,
|
|
928
931
|
detectionTimeMs: scanTime,
|
|
929
932
|
}, null, 2));
|
|
930
|
-
|
|
933
|
+
const safe = result.safe && !result.piiFound;
|
|
934
|
+
return { success: safe, exitCode: safe ? 0 : 1 };
|
|
931
935
|
}
|
|
932
936
|
// Text output
|
|
933
937
|
output.writeln();
|
|
@@ -969,7 +973,8 @@ const defendCommand = {
|
|
|
969
973
|
}
|
|
970
974
|
}
|
|
971
975
|
output.writeln(output.dim(`Detection time: ${scanTime.toFixed(3)}ms`));
|
|
972
|
-
|
|
976
|
+
const safe = result.safe && !result.piiFound;
|
|
977
|
+
return { success: safe, exitCode: safe ? 0 : 1 };
|
|
973
978
|
},
|
|
974
979
|
};
|
|
975
980
|
// Main security command
|
|
@@ -42,11 +42,11 @@ function securityInsight(ctx) {
|
|
|
42
42
|
const s = ctx.security;
|
|
43
43
|
if (!s)
|
|
44
44
|
return null;
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
45
|
+
const findings = Math.max(0, s.findings ?? 0);
|
|
46
|
+
if (s.status === 'ISSUES' || findings > 0) {
|
|
47
47
|
return {
|
|
48
|
-
id: 'insight-
|
|
49
|
-
text: `⚠ ${
|
|
48
|
+
id: 'insight-security-findings',
|
|
49
|
+
text: `⚠ ${findings} security finding${findings === 1 ? '' : 's'} — Review the latest ruflo security scan`,
|
|
50
50
|
priority: 90,
|
|
51
51
|
};
|
|
52
52
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export interface SecurityStatus {
|
|
2
|
-
status: 'CLEAN' | '
|
|
2
|
+
status: 'CLEAN' | 'ISSUES' | 'PENDING';
|
|
3
|
+
/** Generic code-pattern findings from `ruflo security scan` (not CVEs). */
|
|
4
|
+
findings: number;
|
|
5
|
+
scannedAt?: string;
|
|
6
|
+
/** @deprecated Retained as a zero-valued compatibility field. */
|
|
3
7
|
cvesFixed: number;
|
|
8
|
+
/** @deprecated Retained as a zero-valued compatibility field. */
|
|
4
9
|
totalCves: number;
|
|
5
10
|
}
|
|
6
11
|
export declare function getSecurityStatus(cwd?: string): SecurityStatus;
|
|
@@ -11,30 +11,39 @@ import * as fs from 'fs';
|
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import { execSync } from 'child_process';
|
|
13
13
|
export function getSecurityStatus(cwd = process.cwd()) {
|
|
14
|
+
const empty = {
|
|
15
|
+
status: 'PENDING', findings: 0, cvesFixed: 0, totalCves: 0,
|
|
16
|
+
};
|
|
14
17
|
const scanResultsPath = path.join(cwd, '.claude', 'security-scans');
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
if (!fs.existsSync(scanResultsPath))
|
|
19
|
+
return empty;
|
|
20
|
+
try {
|
|
21
|
+
const files = fs.readdirSync(scanResultsPath).filter((f) => f.endsWith('.json'));
|
|
22
|
+
if (files.length === 0)
|
|
23
|
+
return empty;
|
|
24
|
+
let newest = files[0];
|
|
25
|
+
let newestMtime = -1;
|
|
26
|
+
for (const f of files) {
|
|
27
|
+
const st = fs.statSync(path.join(scanResultsPath, f));
|
|
28
|
+
if (st.mtimeMs > newestMtime) {
|
|
29
|
+
newestMtime = st.mtimeMs;
|
|
30
|
+
newest = f;
|
|
31
|
+
}
|
|
24
32
|
}
|
|
33
|
+
const scan = JSON.parse(fs.readFileSync(path.join(scanResultsPath, newest), 'utf-8'));
|
|
34
|
+
const rawFindings = scan.summary?.total ?? scan.totalFindings ?? scan.findings?.length ?? 0;
|
|
35
|
+
const findings = Math.max(0, Number.isFinite(rawFindings) ? rawFindings : 0);
|
|
36
|
+
return {
|
|
37
|
+
status: findings > 0 ? 'ISSUES' : 'CLEAN',
|
|
38
|
+
findings,
|
|
39
|
+
scannedAt: typeof scan.timestamp === 'string' ? scan.timestamp : undefined,
|
|
40
|
+
cvesFixed: 0,
|
|
41
|
+
totalCves: 0,
|
|
42
|
+
};
|
|
25
43
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
const audits = fs.readdirSync(auditPath).filter((f) => f.includes('audit'));
|
|
30
|
-
cvesFixed = Math.min(totalCves, Math.max(cvesFixed, audits.length));
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
// Ignore
|
|
34
|
-
}
|
|
44
|
+
catch {
|
|
45
|
+
return empty;
|
|
35
46
|
}
|
|
36
|
-
const status = cvesFixed >= totalCves ? 'CLEAN' : cvesFixed > 0 ? 'IN_PROGRESS' : 'PENDING';
|
|
37
|
-
return { status, cvesFixed, totalCves };
|
|
38
47
|
}
|
|
39
48
|
export function getSwarmStatus() {
|
|
40
49
|
let activeAgents = 0;
|
|
@@ -619,7 +619,7 @@ export async function executeUpgrade(targetDir, upgradeSettings = false) {
|
|
|
619
619
|
initialized: new Date().toISOString(),
|
|
620
620
|
status: 'PENDING',
|
|
621
621
|
cvesFixed: 0,
|
|
622
|
-
totalCves:
|
|
622
|
+
totalCves: 0,
|
|
623
623
|
lastScan: null,
|
|
624
624
|
_note: 'Run: npx @claude-flow/cli@latest security scan'
|
|
625
625
|
};
|
|
@@ -1541,7 +1541,7 @@ async function writeInitialMetrics(targetDir, options, result) {
|
|
|
1541
1541
|
initialized: new Date().toISOString(),
|
|
1542
1542
|
status: 'PENDING',
|
|
1543
1543
|
cvesFixed: 0,
|
|
1544
|
-
totalCves:
|
|
1544
|
+
totalCves: 0,
|
|
1545
1545
|
lastScan: null,
|
|
1546
1546
|
_note: 'Run: npx @claude-flow/cli@latest security scan'
|
|
1547
1547
|
};
|
|
@@ -2010,8 +2010,9 @@ export const hooksSessionEnd = {
|
|
|
2010
2010
|
}
|
|
2011
2011
|
// Phase 5: Wire ReflexionMemory session end + NightlyLearner consolidation via bridge
|
|
2012
2012
|
let sessionPersistence = null;
|
|
2013
|
+
let bridge = null;
|
|
2013
2014
|
try {
|
|
2014
|
-
|
|
2015
|
+
bridge = await import('../memory/memory-bridge.js');
|
|
2015
2016
|
const result = await bridge.bridgeSessionEnd({
|
|
2016
2017
|
sessionId,
|
|
2017
2018
|
summary: saveState ? 'Session ended with state saved' : 'Session ended',
|
|
@@ -2028,6 +2029,17 @@ export const hooksSessionEnd = {
|
|
|
2028
2029
|
catch {
|
|
2029
2030
|
// Bridge not available
|
|
2030
2031
|
}
|
|
2032
|
+
finally {
|
|
2033
|
+
// Release AgentDB/ONNX resources after one-shot session persistence.
|
|
2034
|
+
// A partially initialized native pool can otherwise keep Node alive
|
|
2035
|
+
// after the command has completed all logical work (#2691).
|
|
2036
|
+
try {
|
|
2037
|
+
await bridge?.shutdownBridge();
|
|
2038
|
+
}
|
|
2039
|
+
catch {
|
|
2040
|
+
// Cleanup is best-effort and must not fail session-end.
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2031
2043
|
return {
|
|
2032
2044
|
sessionId,
|
|
2033
2045
|
duration: 3600000, // 1 hour in ms
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface BuiltinThreat {
|
|
2
|
+
type: string;
|
|
3
|
+
severity: 'critical' | 'high' | 'medium' | 'low';
|
|
4
|
+
description: string;
|
|
5
|
+
confidence: number;
|
|
6
|
+
}
|
|
7
|
+
export interface DefenceResult {
|
|
8
|
+
safe: boolean;
|
|
9
|
+
threats: BuiltinThreat[];
|
|
10
|
+
piiFound: boolean;
|
|
11
|
+
detectionTimeMs: number;
|
|
12
|
+
inputHash: string;
|
|
13
|
+
}
|
|
14
|
+
export interface DefenceEngine {
|
|
15
|
+
detect(input: string): Promise<DefenceResult>;
|
|
16
|
+
quickScan(input: string): {
|
|
17
|
+
threat: boolean;
|
|
18
|
+
confidence: number;
|
|
19
|
+
type?: string;
|
|
20
|
+
};
|
|
21
|
+
getStats(): Promise<{
|
|
22
|
+
detectionCount: number;
|
|
23
|
+
avgDetectionTimeMs: number;
|
|
24
|
+
learnedPatterns: number;
|
|
25
|
+
mitigationStrategies: number;
|
|
26
|
+
avgMitigationEffectiveness: number;
|
|
27
|
+
}>;
|
|
28
|
+
getBestMitigation(type: string): Promise<{
|
|
29
|
+
strategy: string;
|
|
30
|
+
effectiveness: number;
|
|
31
|
+
} | null>;
|
|
32
|
+
}
|
|
33
|
+
export declare function createBuiltinAIDefence(): DefenceEngine;
|
|
34
|
+
//# sourceMappingURL=builtin-aidefence.d.ts.map
|