claude-flow 3.5.76 → 3.5.77
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/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.77",
|
|
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
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.77",
|
|
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",
|