claude-flow 3.5.76 → 3.5.78
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/bin/cli.js +11 -4
- package/v3/@claude-flow/cli/dist/src/autopilot-state.js +37 -45
- package/v3/@claude-flow/cli/dist/src/commands/autopilot.js +3 -3
- package/v3/@claude-flow/cli/dist/src/commands/claims.js +12 -16
- package/v3/@claude-flow/cli/dist/src/commands/cleanup.js +36 -4
- package/v3/@claude-flow/cli/dist/src/commands/daemon.js +46 -1
- package/v3/@claude-flow/cli/dist/src/commands/security.js +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-server.js +4 -5
- package/v3/@claude-flow/cli/dist/src/mcp-tools/performance-tools.js +2 -6
- package/v3/@claude-flow/cli/dist/src/ruvector/coverage-router.js +6 -4
- package/v3/@claude-flow/cli/dist/src/ruvector/diff-classifier.js +4 -3
- package/v3/@claude-flow/cli/dist/src/update/checker.js +5 -4
- 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.78",
|
|
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",
|
|
@@ -158,8 +158,15 @@ if (isMCPMode) {
|
|
|
158
158
|
// Run normal CLI mode
|
|
159
159
|
const { CLI } = await import('../dist/src/index.js');
|
|
160
160
|
const cli = new CLI();
|
|
161
|
-
cli.run()
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
161
|
+
cli.run()
|
|
162
|
+
.then(() => {
|
|
163
|
+
// #1552: Exit cleanly after one-shot commands.
|
|
164
|
+
// Long-running commands (daemon foreground, mcp, status --watch) never resolve,
|
|
165
|
+
// so this only fires for normal CLI commands.
|
|
166
|
+
process.exit(0);
|
|
167
|
+
})
|
|
168
|
+
.catch((error) => {
|
|
169
|
+
console.error('Fatal error:', error.message);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
});
|
|
165
172
|
}
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* ADR-072: Autopilot Integration
|
|
8
8
|
* Security: Addresses prototype pollution, NaN bypass, input validation
|
|
9
9
|
*/
|
|
10
|
+
import { randomUUID } from 'node:crypto';
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, renameSync } from 'node:fs';
|
|
12
|
+
import { resolve, join } from 'node:path';
|
|
13
|
+
import { homedir } from 'node:os';
|
|
10
14
|
// ── Constants ─────────────────────────────────────────────────
|
|
11
15
|
export const STATE_DIR = '.claude-flow/data';
|
|
12
16
|
export const STATE_FILE = `${STATE_DIR}/autopilot-state.json`;
|
|
@@ -71,9 +75,8 @@ export function validateTaskSources(sources) {
|
|
|
71
75
|
}
|
|
72
76
|
// ── State Management ──────────────────────────────────────────
|
|
73
77
|
export function getDefaultState() {
|
|
74
|
-
const crypto = require('crypto');
|
|
75
78
|
return {
|
|
76
|
-
sessionId:
|
|
79
|
+
sessionId: randomUUID(),
|
|
77
80
|
enabled: false,
|
|
78
81
|
startTime: Date.now(),
|
|
79
82
|
iterations: 0,
|
|
@@ -85,13 +88,11 @@ export function getDefaultState() {
|
|
|
85
88
|
};
|
|
86
89
|
}
|
|
87
90
|
export function loadState() {
|
|
88
|
-
const
|
|
89
|
-
const path = require('path');
|
|
90
|
-
const filePath = path.resolve(STATE_FILE);
|
|
91
|
+
const filePath = resolve(STATE_FILE);
|
|
91
92
|
const defaults = getDefaultState();
|
|
92
93
|
try {
|
|
93
|
-
if (
|
|
94
|
-
const raw = safeJsonParse(
|
|
94
|
+
if (existsSync(filePath)) {
|
|
95
|
+
const raw = safeJsonParse(readFileSync(filePath, 'utf-8'));
|
|
95
96
|
const merged = { ...defaults, ...raw };
|
|
96
97
|
// Re-validate fields that could be tampered with
|
|
97
98
|
merged.maxIterations = validateNumber(merged.maxIterations, 1, 1000, 50);
|
|
@@ -111,30 +112,26 @@ export function loadState() {
|
|
|
111
112
|
return defaults;
|
|
112
113
|
}
|
|
113
114
|
export function saveState(state) {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (!fs.existsSync(dir))
|
|
118
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
115
|
+
const dir = resolve(STATE_DIR);
|
|
116
|
+
if (!existsSync(dir))
|
|
117
|
+
mkdirSync(dir, { recursive: true });
|
|
119
118
|
// Cap history before saving
|
|
120
119
|
if (state.history.length > MAX_HISTORY_ENTRIES) {
|
|
121
120
|
state.history = state.history.slice(-MAX_HISTORY_ENTRIES);
|
|
122
121
|
}
|
|
123
|
-
const tmpFile =
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
const tmpFile = resolve(STATE_FILE) + '.tmp';
|
|
123
|
+
writeFileSync(tmpFile, JSON.stringify(state, null, 2));
|
|
124
|
+
renameSync(tmpFile, resolve(STATE_FILE));
|
|
126
125
|
}
|
|
127
126
|
export function appendLog(entry) {
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (!fs.existsSync(dir))
|
|
133
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
127
|
+
const filePath = resolve(LOG_FILE);
|
|
128
|
+
const dir = resolve(STATE_DIR);
|
|
129
|
+
if (!existsSync(dir))
|
|
130
|
+
mkdirSync(dir, { recursive: true });
|
|
134
131
|
let log = [];
|
|
135
132
|
try {
|
|
136
|
-
if (
|
|
137
|
-
log = safeJsonParse(
|
|
133
|
+
if (existsSync(filePath)) {
|
|
134
|
+
log = safeJsonParse(readFileSync(filePath, 'utf-8'));
|
|
138
135
|
if (!Array.isArray(log))
|
|
139
136
|
log = [];
|
|
140
137
|
}
|
|
@@ -146,16 +143,14 @@ export function appendLog(entry) {
|
|
|
146
143
|
if (log.length > MAX_LOG_ENTRIES)
|
|
147
144
|
log = log.slice(-MAX_LOG_ENTRIES);
|
|
148
145
|
const tmpFile = filePath + '.tmp';
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
writeFileSync(tmpFile, JSON.stringify(log, null, 2));
|
|
147
|
+
renameSync(tmpFile, filePath);
|
|
151
148
|
}
|
|
152
149
|
export function loadLog() {
|
|
153
|
-
const
|
|
154
|
-
const path = require('path');
|
|
155
|
-
const filePath = path.resolve(LOG_FILE);
|
|
150
|
+
const filePath = resolve(LOG_FILE);
|
|
156
151
|
try {
|
|
157
|
-
if (
|
|
158
|
-
const result = safeJsonParse(
|
|
152
|
+
if (existsSync(filePath)) {
|
|
153
|
+
const result = safeJsonParse(readFileSync(filePath, 'utf-8'));
|
|
159
154
|
return Array.isArray(result) ? result : [];
|
|
160
155
|
}
|
|
161
156
|
}
|
|
@@ -166,26 +161,23 @@ export function loadLog() {
|
|
|
166
161
|
}
|
|
167
162
|
// ── Task Discovery ────────────────────────────────────────────
|
|
168
163
|
export function discoverTasks(sources) {
|
|
169
|
-
const fs = require('fs');
|
|
170
|
-
const path = require('path');
|
|
171
|
-
const os = require('os');
|
|
172
164
|
const tasks = [];
|
|
173
165
|
// Only process valid sources
|
|
174
166
|
const validSources = sources.filter(s => VALID_TASK_SOURCES.has(s));
|
|
175
167
|
for (const source of validSources) {
|
|
176
168
|
if (source === 'team-tasks') {
|
|
177
|
-
const tasksDir =
|
|
169
|
+
const tasksDir = join(homedir(), '.claude', 'tasks');
|
|
178
170
|
try {
|
|
179
|
-
if (
|
|
180
|
-
const teams =
|
|
171
|
+
if (existsSync(tasksDir)) {
|
|
172
|
+
const teams = readdirSync(tasksDir, { withFileTypes: true });
|
|
181
173
|
for (const team of teams) {
|
|
182
174
|
if (!team.isDirectory())
|
|
183
175
|
continue;
|
|
184
|
-
const teamDir =
|
|
185
|
-
const files =
|
|
176
|
+
const teamDir = join(tasksDir, team.name);
|
|
177
|
+
const files = readdirSync(teamDir).filter((f) => f.endsWith('.json'));
|
|
186
178
|
for (const file of files) {
|
|
187
179
|
try {
|
|
188
|
-
const data = safeJsonParse(
|
|
180
|
+
const data = safeJsonParse(readFileSync(join(teamDir, file), 'utf-8'));
|
|
189
181
|
tasks.push({
|
|
190
182
|
id: String(data.id || file.replace('.json', '')),
|
|
191
183
|
subject: String(data.subject || data.title || file),
|
|
@@ -201,10 +193,10 @@ export function discoverTasks(sources) {
|
|
|
201
193
|
catch { /* skip source */ }
|
|
202
194
|
}
|
|
203
195
|
if (source === 'swarm-tasks') {
|
|
204
|
-
const swarmFile =
|
|
196
|
+
const swarmFile = resolve('.claude-flow/swarm-tasks.json');
|
|
205
197
|
try {
|
|
206
|
-
if (
|
|
207
|
-
const data = safeJsonParse(
|
|
198
|
+
if (existsSync(swarmFile)) {
|
|
199
|
+
const data = safeJsonParse(readFileSync(swarmFile, 'utf-8'));
|
|
208
200
|
const swarmTasks = Array.isArray(data) ? data : (data.tasks || []);
|
|
209
201
|
for (const t of swarmTasks) {
|
|
210
202
|
if (t && typeof t === 'object') {
|
|
@@ -222,10 +214,10 @@ export function discoverTasks(sources) {
|
|
|
222
214
|
catch { /* skip source */ }
|
|
223
215
|
}
|
|
224
216
|
if (source === 'file-checklist') {
|
|
225
|
-
const checklistFile =
|
|
217
|
+
const checklistFile = resolve('.claude-flow/data/checklist.json');
|
|
226
218
|
try {
|
|
227
|
-
if (
|
|
228
|
-
const data = safeJsonParse(
|
|
219
|
+
if (existsSync(checklistFile)) {
|
|
220
|
+
const data = safeJsonParse(readFileSync(checklistFile, 'utf-8'));
|
|
229
221
|
const items = Array.isArray(data) ? data : (data.items || []);
|
|
230
222
|
for (const item of items) {
|
|
231
223
|
if (item && typeof item === 'object') {
|
|
@@ -182,10 +182,10 @@ const logCommand = {
|
|
|
182
182
|
],
|
|
183
183
|
action: async (ctx) => {
|
|
184
184
|
if (ctx.flags?.clear) {
|
|
185
|
-
const
|
|
186
|
-
const
|
|
185
|
+
const { writeFileSync } = await import('node:fs');
|
|
186
|
+
const { resolve } = await import('node:path');
|
|
187
187
|
try {
|
|
188
|
-
|
|
188
|
+
writeFileSync(resolve(LOG_FILE), '[]');
|
|
189
189
|
}
|
|
190
190
|
catch { /* ignore */ }
|
|
191
191
|
output.writeln('Autopilot log cleared');
|
|
@@ -4,27 +4,25 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Created with ❤️ by ruv.io
|
|
6
6
|
*/
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'node:fs';
|
|
8
|
+
import { resolve, dirname } from 'node:path';
|
|
7
9
|
import { output } from '../output.js';
|
|
8
10
|
const CLAIMS_CONFIG_PATHS = [
|
|
9
11
|
'.claude-flow/claims.json',
|
|
10
12
|
'claude-flow.claims.json',
|
|
11
13
|
];
|
|
12
14
|
function getClaimsConfigPaths() {
|
|
13
|
-
// Lazy import to keep top-level synchronous
|
|
14
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
15
|
-
const path = require('path');
|
|
16
15
|
return [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
resolve(CLAIMS_CONFIG_PATHS[0]),
|
|
17
|
+
resolve(CLAIMS_CONFIG_PATHS[1]),
|
|
18
|
+
resolve(process.env.HOME || '~', '.config/claude-flow/claims.json'),
|
|
20
19
|
];
|
|
21
20
|
}
|
|
22
21
|
function loadClaimsConfig() {
|
|
23
|
-
const fs = require('fs');
|
|
24
22
|
const configPaths = getClaimsConfigPaths();
|
|
25
23
|
for (const configPath of configPaths) {
|
|
26
|
-
if (
|
|
27
|
-
const content =
|
|
24
|
+
if (existsSync(configPath)) {
|
|
25
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
28
26
|
return { config: JSON.parse(content), path: configPath };
|
|
29
27
|
}
|
|
30
28
|
}
|
|
@@ -41,15 +39,13 @@ function loadClaimsConfig() {
|
|
|
41
39
|
return { config: defaultConfig, path: configPaths[0] };
|
|
42
40
|
}
|
|
43
41
|
function saveClaimsConfig(config, configPath) {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (!fs.existsSync(dir)) {
|
|
48
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
42
|
+
const dir = dirname(configPath);
|
|
43
|
+
if (!existsSync(dir)) {
|
|
44
|
+
mkdirSync(dir, { recursive: true });
|
|
49
45
|
}
|
|
50
46
|
const tmpPath = configPath + '.tmp';
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
|
|
48
|
+
renameSync(tmpPath, configPath);
|
|
53
49
|
}
|
|
54
50
|
// List subcommand
|
|
55
51
|
const listCommand = {
|
|
@@ -5,13 +5,21 @@
|
|
|
5
5
|
* Created with ruv.io
|
|
6
6
|
*/
|
|
7
7
|
import { output } from '../output.js';
|
|
8
|
-
import { existsSync, statSync, rmSync, readdirSync } from 'fs';
|
|
8
|
+
import { existsSync, statSync, rmSync, readdirSync, readFileSync, writeFileSync } from 'fs';
|
|
9
9
|
import { join } from 'path';
|
|
10
|
+
/**
|
|
11
|
+
* Ruflo-owned subdirectories within .claude/ that are safe to delete.
|
|
12
|
+
* Everything else in .claude/ (agents, skills, commands, settings.local.json,
|
|
13
|
+
* memory.db, worktrees, launch.json) belongs to Claude Code and must be preserved.
|
|
14
|
+
* See: https://github.com/ruvnet/ruflo/issues/1557
|
|
15
|
+
*/
|
|
16
|
+
const CLAUDE_OWNED_SUBDIRS = [
|
|
17
|
+
{ path: join('.claude', 'helpers'), description: 'Ruflo hook scripts' },
|
|
18
|
+
];
|
|
10
19
|
/**
|
|
11
20
|
* Artifact directories and files that claude-flow/ruflo may create
|
|
12
21
|
*/
|
|
13
22
|
const ARTIFACT_DIRS = [
|
|
14
|
-
{ path: '.claude', description: 'Claude settings, helpers, agents' },
|
|
15
23
|
{ path: '.claude-flow', description: 'Capabilities and configuration' },
|
|
16
24
|
{ path: 'data', description: 'Memory databases' },
|
|
17
25
|
{ path: '.swarm', description: 'Swarm state' },
|
|
@@ -119,7 +127,21 @@ export const cleanupCommand = {
|
|
|
119
127
|
output.writeln();
|
|
120
128
|
const found = [];
|
|
121
129
|
let totalSize = 0;
|
|
122
|
-
// Scan
|
|
130
|
+
// Scan ruflo-owned subdirs within .claude/ (surgical — preserves Claude Code files)
|
|
131
|
+
for (const artifact of CLAUDE_OWNED_SUBDIRS) {
|
|
132
|
+
const fullPath = join(cwd, artifact.path);
|
|
133
|
+
if (existsSync(fullPath)) {
|
|
134
|
+
const size = getSize(fullPath);
|
|
135
|
+
found.push({ path: artifact.path, description: artifact.description, size, type: 'dir' });
|
|
136
|
+
totalSize += size;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Check if .claude/settings.json has ruflo hooks/claudeFlow blocks to clean
|
|
140
|
+
const settingsPath = join(cwd, '.claude', 'settings.json');
|
|
141
|
+
if (existsSync(settingsPath)) {
|
|
142
|
+
found.push({ path: join('.claude', 'settings.json'), description: 'Remove ruflo hooks/claudeFlow blocks (preserves rest)', size: 0, type: 'file' });
|
|
143
|
+
}
|
|
144
|
+
// Scan standalone artifact directories
|
|
123
145
|
for (const artifact of ARTIFACT_DIRS) {
|
|
124
146
|
const fullPath = join(cwd, artifact.path);
|
|
125
147
|
if (existsSync(fullPath)) {
|
|
@@ -170,7 +192,17 @@ export const cleanupCommand = {
|
|
|
170
192
|
// Actually delete
|
|
171
193
|
try {
|
|
172
194
|
const fullPath = join(cwd, item.path);
|
|
173
|
-
|
|
195
|
+
// Special handling: surgically clean settings.json instead of deleting
|
|
196
|
+
if (item.path === join('.claude', 'settings.json')) {
|
|
197
|
+
try {
|
|
198
|
+
const raw = JSON.parse(readFileSync(fullPath, 'utf-8'));
|
|
199
|
+
delete raw.hooks;
|
|
200
|
+
delete raw.claudeFlow;
|
|
201
|
+
writeFileSync(fullPath, JSON.stringify(raw, null, 2) + '\n', 'utf-8');
|
|
202
|
+
}
|
|
203
|
+
catch { /* settings.json parse failed, skip */ }
|
|
204
|
+
}
|
|
205
|
+
else if (item.type === 'dir') {
|
|
174
206
|
rmSync(fullPath, { recursive: true, force: true });
|
|
175
207
|
}
|
|
176
208
|
else {
|
|
@@ -69,10 +69,12 @@ const startCommand = {
|
|
|
69
69
|
const bgPid = getBackgroundDaemonPid(projectRoot);
|
|
70
70
|
if (bgPid && isProcessRunning(bgPid)) {
|
|
71
71
|
if (!quiet) {
|
|
72
|
-
output.printWarning(`Daemon already running in background (PID: ${bgPid})`);
|
|
72
|
+
output.printWarning(`Daemon already running in background (PID: ${bgPid}). Stop it first with: daemon stop`);
|
|
73
73
|
}
|
|
74
74
|
return { success: true };
|
|
75
75
|
}
|
|
76
|
+
// #1551: Kill any stale daemon processes that weren't tracked by PID file
|
|
77
|
+
await killStaleDaemons(projectRoot, quiet);
|
|
76
78
|
}
|
|
77
79
|
// Background mode (default): fork a detached process
|
|
78
80
|
if (!foreground) {
|
|
@@ -302,11 +304,14 @@ const stopCommand = {
|
|
|
302
304
|
await stopDaemon();
|
|
303
305
|
// Also kill any background daemon by PID
|
|
304
306
|
const killed = await killBackgroundDaemon(projectRoot);
|
|
307
|
+
// #1551: Also kill stale daemon processes not tracked by PID file
|
|
308
|
+
await killStaleDaemons(projectRoot, true);
|
|
305
309
|
spinner.succeed(killed ? 'Worker daemon stopped' : 'Worker daemon was not running');
|
|
306
310
|
}
|
|
307
311
|
else {
|
|
308
312
|
await stopDaemon();
|
|
309
313
|
await killBackgroundDaemon(projectRoot);
|
|
314
|
+
await killStaleDaemons(projectRoot, true);
|
|
310
315
|
}
|
|
311
316
|
return { success: true };
|
|
312
317
|
}
|
|
@@ -365,6 +370,46 @@ async function killBackgroundDaemon(projectRoot) {
|
|
|
365
370
|
return false;
|
|
366
371
|
}
|
|
367
372
|
}
|
|
373
|
+
/**
|
|
374
|
+
* Kill stale daemon processes not tracked by the PID file (#1551).
|
|
375
|
+
* Uses `ps` to find all daemon processes for this project and kills them.
|
|
376
|
+
*/
|
|
377
|
+
async function killStaleDaemons(projectRoot, quiet) {
|
|
378
|
+
try {
|
|
379
|
+
const { execFileSync } = await import('child_process');
|
|
380
|
+
const psOutput = execFileSync('ps', ['-eo', 'pid,command'], { encoding: 'utf-8', timeout: 5000 });
|
|
381
|
+
const lines = psOutput.split('\n');
|
|
382
|
+
const currentPid = process.pid;
|
|
383
|
+
const trackedPid = getBackgroundDaemonPid(projectRoot);
|
|
384
|
+
let killed = 0;
|
|
385
|
+
for (const line of lines) {
|
|
386
|
+
if (!line.includes('daemon start --foreground'))
|
|
387
|
+
continue;
|
|
388
|
+
if (!line.includes('claude-flow') && !line.includes('@claude-flow/cli'))
|
|
389
|
+
continue;
|
|
390
|
+
const pidStr = line.trim().split(/\s+/)[0];
|
|
391
|
+
const pid = parseInt(pidStr, 10);
|
|
392
|
+
if (isNaN(pid) || pid === currentPid || pid === trackedPid)
|
|
393
|
+
continue;
|
|
394
|
+
if (!isProcessRunning(pid))
|
|
395
|
+
continue;
|
|
396
|
+
try {
|
|
397
|
+
process.kill(pid, 'SIGTERM');
|
|
398
|
+
killed++;
|
|
399
|
+
if (!quiet) {
|
|
400
|
+
output.printWarning(`Killed stale daemon process (PID: ${pid})`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
catch { /* ignore — may have exited between check and kill */ }
|
|
404
|
+
}
|
|
405
|
+
if (killed > 0 && !quiet) {
|
|
406
|
+
output.printInfo(`Cleaned up ${killed} stale daemon process(es)`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
catch {
|
|
410
|
+
// ps not available or failed — skip stale cleanup
|
|
411
|
+
}
|
|
412
|
+
}
|
|
368
413
|
/**
|
|
369
414
|
* Get PID of background daemon from PID file
|
|
370
415
|
*/
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Created with ❤️ by ruv.io
|
|
6
6
|
*/
|
|
7
7
|
import { output } from '../output.js';
|
|
8
|
+
import { execSync } from 'node:child_process';
|
|
8
9
|
// Scan subcommand
|
|
9
10
|
const scanCommand = {
|
|
10
11
|
name: 'scan',
|
|
@@ -337,7 +338,6 @@ const threatsCommand = {
|
|
|
337
338
|
// Check for .env files committed to git
|
|
338
339
|
const checkEnvInGit = () => {
|
|
339
340
|
try {
|
|
340
|
-
const { execSync } = require('child_process');
|
|
341
341
|
const tracked = execSync('git ls-files --cached', { cwd: rootDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
342
342
|
const envFiles = tracked.split('\n').filter((f) => /(?:^|\/)\.env(?:\.|$)/.test(f));
|
|
343
343
|
for (const envFile of envFiles) {
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
* @version 3.0.0
|
|
18
18
|
*/
|
|
19
19
|
import { EventEmitter } from 'events';
|
|
20
|
+
import { execFileSync } from 'child_process';
|
|
21
|
+
import { request as httpRequestFn } from 'http';
|
|
20
22
|
import { randomUUID } from 'crypto';
|
|
21
23
|
import * as path from 'path';
|
|
22
24
|
import * as fs from 'fs';
|
|
@@ -572,13 +574,11 @@ export class MCPServerManager extends EventEmitter {
|
|
|
572
574
|
// Verify it's actually a node process (guards against PID reuse)
|
|
573
575
|
// DA-CRIT-3: Use execFileSync to prevent command injection via PID values
|
|
574
576
|
try {
|
|
575
|
-
const { execFileSync } = require('child_process');
|
|
576
577
|
const safePid = String(Math.floor(Math.abs(pid)));
|
|
577
578
|
let cmdline = '';
|
|
578
579
|
try {
|
|
579
580
|
// Try /proc on Linux
|
|
580
|
-
|
|
581
|
-
cmdline = readFileSync(`/proc/${safePid}/cmdline`, 'utf8');
|
|
581
|
+
cmdline = fs.readFileSync(`/proc/${safePid}/cmdline`, 'utf8');
|
|
582
582
|
}
|
|
583
583
|
catch {
|
|
584
584
|
// Fall back to ps on macOS/other
|
|
@@ -608,8 +608,7 @@ export class MCPServerManager extends EventEmitter {
|
|
|
608
608
|
async httpRequest(url, method, timeout) {
|
|
609
609
|
return new Promise((resolve, reject) => {
|
|
610
610
|
const urlObj = new URL(url);
|
|
611
|
-
const
|
|
612
|
-
const req = http.request({
|
|
611
|
+
const req = httpRequestFn({
|
|
613
612
|
hostname: urlObj.hostname,
|
|
614
613
|
port: urlObj.port,
|
|
615
614
|
path: urlObj.pathname,
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { getProjectCwd } from './types.js';
|
|
15
15
|
import { validateIdentifier } from './validate-input.js';
|
|
16
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
16
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync, readdirSync } from 'node:fs';
|
|
17
17
|
import { join } from 'node:path';
|
|
18
18
|
import * as os from 'node:os';
|
|
19
19
|
// Storage paths
|
|
@@ -183,7 +183,6 @@ export const performanceTools = [
|
|
|
183
183
|
readFileSync(probeFile);
|
|
184
184
|
diskLatencyMs = Math.round((performance.now() - t0) * 100) / 100;
|
|
185
185
|
try {
|
|
186
|
-
const { unlinkSync } = require('node:fs');
|
|
187
186
|
unlinkSync(probeFile);
|
|
188
187
|
}
|
|
189
188
|
catch { /* best-effort */ }
|
|
@@ -421,7 +420,6 @@ export const performanceTools = [
|
|
|
421
420
|
.sort((a, b) => b.percentOfTotal - a.percentOfTotal);
|
|
422
421
|
// Cleanup probe file
|
|
423
422
|
try {
|
|
424
|
-
const { unlinkSync } = require('node:fs');
|
|
425
423
|
unlinkSync(join(getPerfDir(), '.profile-probe'));
|
|
426
424
|
}
|
|
427
425
|
catch { /* ok */ }
|
|
@@ -472,7 +470,6 @@ export const performanceTools = [
|
|
|
472
470
|
readFileSync(probe);
|
|
473
471
|
diskLatencyBefore = Math.round((performance.now() - t0) * 100) / 100;
|
|
474
472
|
try {
|
|
475
|
-
const { unlinkSync } = require('node:fs');
|
|
476
473
|
unlinkSync(probe);
|
|
477
474
|
}
|
|
478
475
|
catch { /* ok */ }
|
|
@@ -517,10 +514,9 @@ export const performanceTools = [
|
|
|
517
514
|
if (aggressive) {
|
|
518
515
|
try {
|
|
519
516
|
const dir = getPerfDir();
|
|
520
|
-
const { readdirSync, unlinkSync: ul } = require('node:fs');
|
|
521
517
|
const probes = readdirSync(dir).filter((f) => f.startsWith('.'));
|
|
522
518
|
probes.forEach((f) => { try {
|
|
523
|
-
|
|
519
|
+
unlinkSync(join(dir, f));
|
|
524
520
|
}
|
|
525
521
|
catch { /* ok */ } });
|
|
526
522
|
if (probes.length > 0)
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* - TTL-based caching of coverage data
|
|
7
7
|
* - Singleton router instance
|
|
8
8
|
*/
|
|
9
|
+
import * as pathMod from 'node:path';
|
|
10
|
+
import { existsSync } from 'node:fs';
|
|
11
|
+
import { readFile } from 'node:fs/promises';
|
|
9
12
|
// ============================================================================
|
|
10
13
|
// Caching for Performance
|
|
11
14
|
// ============================================================================
|
|
@@ -378,7 +381,8 @@ export async function coverageGaps(options = {}) {
|
|
|
378
381
|
* Returns null if path is invalid or attempts traversal
|
|
379
382
|
*/
|
|
380
383
|
function validateProjectPath(inputPath) {
|
|
381
|
-
|
|
384
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires -- sync function, top-level import at file head
|
|
385
|
+
const { resolve, normalize, isAbsolute } = pathMod;
|
|
382
386
|
// Default to cwd if not provided
|
|
383
387
|
const basePath = inputPath || process.cwd();
|
|
384
388
|
// Normalize and resolve the path
|
|
@@ -417,9 +421,7 @@ async function loadProjectCoverage(projectRoot, skipCache) {
|
|
|
417
421
|
return cached.report;
|
|
418
422
|
}
|
|
419
423
|
}
|
|
420
|
-
const {
|
|
421
|
-
const { readFile } = require('fs/promises');
|
|
422
|
-
const { join, normalize } = require('path');
|
|
424
|
+
const { join, normalize } = pathMod;
|
|
423
425
|
// Try common coverage locations (all relative to validated root)
|
|
424
426
|
const coverageLocations = [
|
|
425
427
|
['coverage', 'coverage-final.json'],
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Diff Classifier for Change Analysis
|
|
3
3
|
*/
|
|
4
|
+
import { execFileSync, execFile } from 'node:child_process';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
4
6
|
const DEFAULT_CONFIG = {
|
|
5
7
|
maxDiffSize: 10000,
|
|
6
8
|
classifyByImpact: true,
|
|
@@ -344,7 +346,7 @@ export function getGitDiffNumstat(ref = 'HEAD') {
|
|
|
344
346
|
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
|
|
345
347
|
return cached.files;
|
|
346
348
|
}
|
|
347
|
-
|
|
349
|
+
// execFileSync imported at top level
|
|
348
350
|
try {
|
|
349
351
|
// SECURITY: Use execFileSync with args array instead of shell string
|
|
350
352
|
// This prevents command injection via the ref parameter
|
|
@@ -417,8 +419,7 @@ export async function getGitDiffNumstatAsync(ref = 'HEAD') {
|
|
|
417
419
|
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
|
|
418
420
|
return cached.files;
|
|
419
421
|
}
|
|
420
|
-
|
|
421
|
-
const { promisify } = require('util');
|
|
422
|
+
// execFile + promisify imported at top level
|
|
422
423
|
const execFileAsync = promisify(execFile);
|
|
423
424
|
try {
|
|
424
425
|
// SECURITY: Use execFile with args array instead of shell string
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Queries npm registry and compares versions
|
|
4
4
|
*/
|
|
5
5
|
import * as semver from 'semver';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
6
7
|
import { shouldCheckForUpdates, recordCheck, getCachedVersions } from './rate-limiter.js';
|
|
7
8
|
const DEFAULT_CONFIG = {
|
|
8
9
|
enabled: true,
|
|
@@ -88,10 +89,10 @@ export function getInstalledVersion(packageName) {
|
|
|
88
89
|
];
|
|
89
90
|
for (const modulePath of possiblePaths) {
|
|
90
91
|
try {
|
|
91
|
-
// Use
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
const pkg =
|
|
92
|
+
// Use createRequire for ESM-compatible package.json loading
|
|
93
|
+
const esmRequire = createRequire(import.meta.url);
|
|
94
|
+
const resolved = esmRequire.resolve(modulePath, { paths: [process.cwd()] });
|
|
95
|
+
const pkg = esmRequire(resolved);
|
|
95
96
|
return pkg.version;
|
|
96
97
|
}
|
|
97
98
|
catch {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.78",
|
|
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",
|