moflo 4.7.8 → 4.8.1
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/settings.local.json +4 -1
- package/.claude/workflow-state.json +3 -7
- package/README.md +3 -1
- package/bin/build-embeddings.mjs +59 -3
- package/bin/generate-code-map.mjs +3 -1
- package/bin/hooks.mjs +23 -20
- package/bin/index-guidance.mjs +3 -1
- package/bin/lib/moflo-resolve.mjs +14 -0
- package/bin/semantic-search.mjs +10 -5
- package/bin/session-start-launcher.mjs +116 -3
- package/package.json +6 -6
- package/src/@claude-flow/cli/dist/src/appliance/ruvllm-bridge.js +3 -7
- package/src/@claude-flow/cli/dist/src/commands/daemon.js +42 -95
- package/src/@claude-flow/cli/dist/src/commands/doctor.js +127 -6
- package/src/@claude-flow/cli/dist/src/commands/embeddings.js +4 -3
- package/src/@claude-flow/cli/dist/src/commands/hooks.js +3 -2
- package/src/@claude-flow/cli/dist/src/commands/mcp.js +38 -22
- package/src/@claude-flow/cli/dist/src/commands/memory.js +2 -1
- package/src/@claude-flow/cli/dist/src/commands/neural.js +10 -5
- package/src/@claude-flow/cli/dist/src/config/moflo-config.d.ts +5 -0
- package/src/@claude-flow/cli/dist/src/config/moflo-config.js +16 -0
- package/src/@claude-flow/cli/dist/src/index.js +12 -0
- package/src/@claude-flow/cli/dist/src/init/executor.js +74 -0
- package/src/@claude-flow/cli/dist/src/init/moflo-init.js +49 -0
- package/src/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +2 -2
- package/src/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +2 -1
- package/src/@claude-flow/cli/dist/src/memory/memory-bridge.js +5 -1
- package/src/@claude-flow/cli/dist/src/memory/memory-initializer.js +29 -24
- package/src/@claude-flow/cli/dist/src/ruvector/ast-analyzer.js +2 -1
- package/src/@claude-flow/cli/dist/src/ruvector/coverage-router.js +2 -1
- package/src/@claude-flow/cli/dist/src/ruvector/diff-classifier.js +2 -1
- package/src/@claude-flow/cli/dist/src/ruvector/enhanced-model-router.js +3 -3
- package/src/@claude-flow/cli/dist/src/ruvector/index.js +6 -13
- package/src/@claude-flow/cli/dist/src/ruvector/q-learning-router.js +4 -1
- package/src/@claude-flow/cli/dist/src/services/daemon-lock.d.ts +39 -0
- package/src/@claude-flow/cli/dist/src/services/daemon-lock.js +213 -0
- package/src/@claude-flow/cli/dist/src/services/learning-service.js +2 -1
- package/src/@claude-flow/cli/dist/src/services/moflo-require.d.ts +34 -0
- package/src/@claude-flow/cli/dist/src/services/moflo-require.js +67 -0
- package/src/@claude-flow/cli/dist/src/services/ruvector-training.js +8 -6
- package/src/@claude-flow/cli/package.json +6 -6
- package/.claude/helpers/README.md +0 -97
- package/.claude/helpers/adr-compliance.sh +0 -186
- package/.claude/helpers/aggressive-microcompact.mjs +0 -36
- package/.claude/helpers/auto-commit.sh +0 -178
- package/.claude/helpers/checkpoint-manager.sh +0 -251
- package/.claude/helpers/context-persistence-hook.mjs +0 -1979
- package/.claude/helpers/daemon-manager.sh +0 -252
- package/.claude/helpers/ddd-tracker.sh +0 -144
- package/.claude/helpers/github-safe.js +0 -106
- package/.claude/helpers/github-setup.sh +0 -28
- package/.claude/helpers/guidance-hook.sh +0 -13
- package/.claude/helpers/guidance-hooks.sh +0 -102
- package/.claude/helpers/health-monitor.sh +0 -108
- package/.claude/helpers/learning-hooks.sh +0 -329
- package/.claude/helpers/learning-optimizer.sh +0 -127
- package/.claude/helpers/learning-service.mjs +0 -1211
- package/.claude/helpers/memory.cjs +0 -84
- package/.claude/helpers/metrics-db.mjs +0 -492
- package/.claude/helpers/patch-aggressive-prune.mjs +0 -184
- package/.claude/helpers/pattern-consolidator.sh +0 -86
- package/.claude/helpers/perf-worker.sh +0 -160
- package/.claude/helpers/quick-start.sh +0 -19
- package/.claude/helpers/router.cjs +0 -62
- package/.claude/helpers/security-scanner.sh +0 -127
- package/.claude/helpers/session.cjs +0 -125
- package/.claude/helpers/setup-mcp.sh +0 -18
- package/.claude/helpers/standard-checkpoint-hooks.sh +0 -189
- package/.claude/helpers/swarm-comms.sh +0 -353
- package/.claude/helpers/swarm-hooks.sh +0 -761
- package/.claude/helpers/swarm-monitor.sh +0 -211
- package/.claude/helpers/sync-v3-metrics.sh +0 -245
- package/.claude/helpers/update-v3-progress.sh +0 -166
- package/.claude/helpers/v3-quick-status.sh +0 -58
- package/.claude/helpers/v3.sh +0 -111
- package/.claude/helpers/validate-v3-config.sh +0 -216
- package/.claude/helpers/worker-manager.sh +0 -170
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { output } from '../output.js';
|
|
6
6
|
import { getDaemon, startDaemon, stopDaemon } from '../services/worker-daemon.js';
|
|
7
|
+
import { acquireDaemonLock, releaseDaemonLock, getDaemonLockHolder } from '../services/daemon-lock.js';
|
|
7
8
|
import { spawn } from 'child_process';
|
|
8
9
|
import { fileURLToPath } from 'url';
|
|
9
10
|
import { dirname, join, resolve } from 'path';
|
|
@@ -64,60 +65,26 @@ const startCommand = {
|
|
|
64
65
|
config.resourceThresholds = thresholds;
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
|
-
// Check if background daemon already running (skip if we ARE the daemon process)
|
|
68
|
-
if (!isDaemonProcess) {
|
|
69
|
-
const bgPid = getBackgroundDaemonPid(projectRoot);
|
|
70
|
-
if (bgPid && isProcessRunning(bgPid)) {
|
|
71
|
-
if (!quiet) {
|
|
72
|
-
output.printWarning(`Daemon already running in background (PID: ${bgPid})`);
|
|
73
|
-
}
|
|
74
|
-
return { success: true };
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
68
|
// Background mode (default): fork a detached process
|
|
78
69
|
if (!foreground) {
|
|
79
70
|
return startBackgroundDaemon(projectRoot, quiet, rawMaxCpu, rawMinMem);
|
|
80
71
|
}
|
|
81
72
|
// Foreground mode: run in current process (blocks terminal)
|
|
82
73
|
try {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (fs.existsSync(pidFile)) {
|
|
91
|
-
try {
|
|
92
|
-
const existingPid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
93
|
-
if (!isNaN(existingPid) && existingPid !== process.pid) {
|
|
94
|
-
try {
|
|
95
|
-
process.kill(existingPid, 0); // Check if alive
|
|
96
|
-
// Another daemon is running — exit silently
|
|
97
|
-
if (!quiet) {
|
|
98
|
-
output.printWarning(`Daemon already running (PID: ${existingPid})`);
|
|
99
|
-
}
|
|
100
|
-
return { success: true };
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
// Process not running — stale PID file, continue startup
|
|
104
|
-
}
|
|
74
|
+
// Acquire atomic daemon lock (prevents duplicate daemons)
|
|
75
|
+
// Skip lock acquisition if we're the spawned child — parent already holds it
|
|
76
|
+
if (!isDaemonProcess) {
|
|
77
|
+
const lockResult = acquireDaemonLock(projectRoot);
|
|
78
|
+
if (!lockResult.acquired) {
|
|
79
|
+
if (!quiet) {
|
|
80
|
+
output.printWarning(`Daemon already running (PID: ${lockResult.holder})`);
|
|
105
81
|
}
|
|
106
|
-
|
|
107
|
-
catch {
|
|
108
|
-
// Can't read PID file — continue startup
|
|
82
|
+
return { success: true };
|
|
109
83
|
}
|
|
110
84
|
}
|
|
111
|
-
//
|
|
112
|
-
fs.writeFileSync(pidFile, String(process.pid));
|
|
113
|
-
// Clean up PID file on exit
|
|
85
|
+
// Clean up lock file on exit
|
|
114
86
|
const cleanup = () => {
|
|
115
|
-
|
|
116
|
-
if (fs.existsSync(pidFile)) {
|
|
117
|
-
fs.unlinkSync(pidFile);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
catch { /* ignore */ }
|
|
87
|
+
releaseDaemonLock(projectRoot, process.pid, true);
|
|
121
88
|
};
|
|
122
89
|
process.on('exit', cleanup);
|
|
123
90
|
process.on('SIGINT', () => { cleanup(); process.exit(0); });
|
|
@@ -219,12 +186,18 @@ async function startBackgroundDaemon(projectRoot, quiet, maxCpuLoad, minFreeMemo
|
|
|
219
186
|
const resolvedRoot = resolve(projectRoot);
|
|
220
187
|
validatePath(resolvedRoot, 'Project root');
|
|
221
188
|
const stateDir = join(resolvedRoot, '.claude-flow');
|
|
222
|
-
const pidFile = join(stateDir, 'daemon.pid');
|
|
223
189
|
const logFile = join(stateDir, 'daemon.log');
|
|
224
190
|
// Validate all paths
|
|
225
191
|
validatePath(stateDir, 'State directory');
|
|
226
|
-
validatePath(pidFile, 'PID file');
|
|
227
192
|
validatePath(logFile, 'Log file');
|
|
193
|
+
// Acquire atomic lock BEFORE spawning to prevent races
|
|
194
|
+
const lockResult = acquireDaemonLock(resolvedRoot);
|
|
195
|
+
if (!lockResult.acquired) {
|
|
196
|
+
if (!quiet) {
|
|
197
|
+
output.printWarning(`Daemon already running in background (PID: ${lockResult.holder})`);
|
|
198
|
+
}
|
|
199
|
+
return { success: true };
|
|
200
|
+
}
|
|
228
201
|
// Ensure state directory exists
|
|
229
202
|
if (!fs.existsSync(stateDir)) {
|
|
230
203
|
fs.mkdirSync(stateDir, { recursive: true });
|
|
@@ -273,16 +246,21 @@ async function startBackgroundDaemon(projectRoot, quiet, maxCpuLoad, minFreeMemo
|
|
|
273
246
|
// Get PID from spawned process directly (no shell echo needed)
|
|
274
247
|
const pid = child.pid;
|
|
275
248
|
if (!pid || pid <= 0) {
|
|
249
|
+
// Release lock — spawn failed, no daemon running
|
|
250
|
+
releaseDaemonLock(resolvedRoot, process.pid, true);
|
|
276
251
|
output.printError('Failed to get daemon PID');
|
|
277
252
|
return { success: false, exitCode: 1 };
|
|
278
253
|
}
|
|
279
|
-
// Unref BEFORE
|
|
254
|
+
// Unref BEFORE updating lock — prevents race where parent exits
|
|
280
255
|
// but child hasn't fully detached yet (fixes macOS daemon death #1283)
|
|
281
256
|
child.unref();
|
|
282
257
|
// Small delay to let the child process fully detach on macOS
|
|
283
258
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
284
|
-
//
|
|
285
|
-
|
|
259
|
+
// Update the lock file with the child's PID (parent acquired it, child owns it)
|
|
260
|
+
// We force-release our lock and re-acquire with the child PID so the lock
|
|
261
|
+
// accurately reflects the running daemon process.
|
|
262
|
+
releaseDaemonLock(resolvedRoot, process.pid, true);
|
|
263
|
+
acquireDaemonLock(resolvedRoot, pid);
|
|
286
264
|
if (!quiet) {
|
|
287
265
|
output.printSuccess(`Daemon started in background (PID: ${pid})`);
|
|
288
266
|
output.printInfo(`Logs: ${logFile}`);
|
|
@@ -326,76 +304,45 @@ const stopCommand = {
|
|
|
326
304
|
},
|
|
327
305
|
};
|
|
328
306
|
/**
|
|
329
|
-
* Kill background daemon process using
|
|
307
|
+
* Kill background daemon process using lock file
|
|
330
308
|
*/
|
|
331
309
|
async function killBackgroundDaemon(projectRoot) {
|
|
332
|
-
const
|
|
333
|
-
if (!
|
|
310
|
+
const holderPid = getDaemonLockHolder(projectRoot);
|
|
311
|
+
if (!holderPid) {
|
|
312
|
+
// No live daemon — clean up any stale lock
|
|
313
|
+
releaseDaemonLock(projectRoot, 0, true);
|
|
334
314
|
return false;
|
|
335
315
|
}
|
|
336
316
|
try {
|
|
337
|
-
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
338
|
-
if (isNaN(pid)) {
|
|
339
|
-
fs.unlinkSync(pidFile);
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
// Check if process is running
|
|
343
|
-
try {
|
|
344
|
-
process.kill(pid, 0); // Signal 0 = check if alive
|
|
345
|
-
}
|
|
346
|
-
catch {
|
|
347
|
-
// Process not running, clean up stale PID file
|
|
348
|
-
fs.unlinkSync(pidFile);
|
|
349
|
-
return false;
|
|
350
|
-
}
|
|
351
317
|
// Kill the process
|
|
352
|
-
process.kill(
|
|
318
|
+
process.kill(holderPid, 'SIGTERM');
|
|
353
319
|
// Wait a moment then force kill if needed
|
|
354
320
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
355
321
|
try {
|
|
356
|
-
process.kill(
|
|
322
|
+
process.kill(holderPid, 0);
|
|
357
323
|
// Still alive, force kill
|
|
358
|
-
process.kill(
|
|
324
|
+
process.kill(holderPid, 'SIGKILL');
|
|
359
325
|
}
|
|
360
326
|
catch {
|
|
361
327
|
// Process terminated
|
|
362
328
|
}
|
|
363
|
-
//
|
|
364
|
-
|
|
365
|
-
fs.unlinkSync(pidFile);
|
|
366
|
-
}
|
|
329
|
+
// Release lock
|
|
330
|
+
releaseDaemonLock(projectRoot, holderPid, true);
|
|
367
331
|
return true;
|
|
368
332
|
}
|
|
369
333
|
catch (error) {
|
|
370
|
-
// Clean up
|
|
371
|
-
|
|
372
|
-
fs.unlinkSync(pidFile);
|
|
373
|
-
}
|
|
334
|
+
// Clean up lock on any error
|
|
335
|
+
releaseDaemonLock(projectRoot, 0, true);
|
|
374
336
|
return false;
|
|
375
337
|
}
|
|
376
338
|
}
|
|
377
|
-
|
|
378
|
-
* Get PID of background daemon from PID file
|
|
379
|
-
*/
|
|
339
|
+
// Legacy aliases — delegate to daemon-lock module
|
|
380
340
|
function getBackgroundDaemonPid(projectRoot) {
|
|
381
|
-
|
|
382
|
-
if (!fs.existsSync(pidFile)) {
|
|
383
|
-
return null;
|
|
384
|
-
}
|
|
385
|
-
try {
|
|
386
|
-
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
387
|
-
return isNaN(pid) ? null : pid;
|
|
388
|
-
}
|
|
389
|
-
catch {
|
|
390
|
-
return null;
|
|
391
|
-
}
|
|
341
|
+
return getDaemonLockHolder(projectRoot);
|
|
392
342
|
}
|
|
393
|
-
/**
|
|
394
|
-
* Check if a process is running
|
|
395
|
-
*/
|
|
396
343
|
function isProcessRunning(pid) {
|
|
397
344
|
try {
|
|
398
|
-
process.kill(pid, 0);
|
|
345
|
+
process.kill(pid, 0);
|
|
399
346
|
return true;
|
|
400
347
|
}
|
|
401
348
|
catch {
|
|
@@ -10,6 +10,7 @@ import { join, dirname } from 'path';
|
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import { execSync, exec } from 'child_process';
|
|
12
12
|
import { promisify } from 'util';
|
|
13
|
+
import { getDaemonLockHolder, releaseDaemonLock } from '../services/daemon-lock.js';
|
|
13
14
|
// Promisified exec with proper shell and env inheritance for cross-platform support
|
|
14
15
|
const execAsync = promisify(exec);
|
|
15
16
|
/**
|
|
@@ -93,17 +94,23 @@ async function checkConfigFile() {
|
|
|
93
94
|
// Check daemon status
|
|
94
95
|
async function checkDaemonStatus() {
|
|
95
96
|
try {
|
|
96
|
-
const
|
|
97
|
-
if (existsSync(
|
|
98
|
-
const pid = readFileSync(pidFile, 'utf8').trim();
|
|
97
|
+
const lockFile = '.claude-flow/daemon.lock';
|
|
98
|
+
if (existsSync(lockFile)) {
|
|
99
99
|
try {
|
|
100
|
-
|
|
100
|
+
const data = JSON.parse(readFileSync(lockFile, 'utf8'));
|
|
101
|
+
const pid = data.pid;
|
|
102
|
+
process.kill(pid, 0); // Check if process exists
|
|
101
103
|
return { name: 'Daemon Status', status: 'pass', message: `Running (PID: ${pid})` };
|
|
102
104
|
}
|
|
103
105
|
catch {
|
|
104
|
-
return { name: 'Daemon Status', status: 'warn', message: 'Stale
|
|
106
|
+
return { name: 'Daemon Status', status: 'warn', message: 'Stale lock file', fix: 'rm .claude-flow/daemon.lock && claude-flow daemon start' };
|
|
105
107
|
}
|
|
106
108
|
}
|
|
109
|
+
// Also check legacy PID file
|
|
110
|
+
const pidFile = '.claude-flow/daemon.pid';
|
|
111
|
+
if (existsSync(pidFile)) {
|
|
112
|
+
return { name: 'Daemon Status', status: 'warn', message: 'Legacy PID file found', fix: 'rm .claude-flow/daemon.pid && claude-flow daemon start' };
|
|
113
|
+
}
|
|
107
114
|
return { name: 'Daemon Status', status: 'warn', message: 'Not running', fix: 'claude-flow daemon start' };
|
|
108
115
|
}
|
|
109
116
|
catch {
|
|
@@ -400,6 +407,69 @@ async function checkAgenticFlow() {
|
|
|
400
407
|
return { name: 'agentic-flow', status: 'warn', message: 'Check failed' };
|
|
401
408
|
}
|
|
402
409
|
}
|
|
410
|
+
// Find and optionally kill orphaned moflo/claude-flow node processes
|
|
411
|
+
async function findZombieProcesses(kill = false) {
|
|
412
|
+
const legitimatePid = getDaemonLockHolder(process.cwd());
|
|
413
|
+
const currentPid = process.pid;
|
|
414
|
+
const parentPid = process.ppid;
|
|
415
|
+
const found = [];
|
|
416
|
+
let killed = 0;
|
|
417
|
+
try {
|
|
418
|
+
if (process.platform === 'win32') {
|
|
419
|
+
// Windows: use WMIC to find node processes with moflo/claude-flow in command line
|
|
420
|
+
const result = execSync('powershell -NoProfile -Command "Get-CimInstance Win32_Process -Filter \\"Name=\'node.exe\'\\" | Select-Object ProcessId,CommandLine | Format-Table -AutoSize -Wrap"', { encoding: 'utf-8', timeout: 10000, windowsHide: true });
|
|
421
|
+
const lines = result.split('\n');
|
|
422
|
+
for (const line of lines) {
|
|
423
|
+
if (/moflo|claude-flow|flo\s+(hooks|gate|mcp|daemon)/i.test(line)) {
|
|
424
|
+
const pidMatch = line.match(/^\s*(\d+)/);
|
|
425
|
+
if (pidMatch) {
|
|
426
|
+
const pid = parseInt(pidMatch[1], 10);
|
|
427
|
+
// Skip our own process, parent, and the legitimate daemon
|
|
428
|
+
if (pid === currentPid || pid === parentPid || pid === legitimatePid)
|
|
429
|
+
continue;
|
|
430
|
+
found.push(pid);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
// Unix/macOS: use ps to find node processes
|
|
437
|
+
const result = execSync('ps aux | grep -E "node.*(moflo|claude-flow)" | grep -v grep', { encoding: 'utf-8', timeout: 5000 });
|
|
438
|
+
const lines = result.trim().split('\n');
|
|
439
|
+
for (const line of lines) {
|
|
440
|
+
const parts = line.trim().split(/\s+/);
|
|
441
|
+
const pid = parseInt(parts[1], 10);
|
|
442
|
+
if (pid === currentPid || pid === parentPid || pid === legitimatePid)
|
|
443
|
+
continue;
|
|
444
|
+
found.push(pid);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
// No matches found (grep exits non-zero) or command failed
|
|
450
|
+
}
|
|
451
|
+
if (kill && found.length > 0) {
|
|
452
|
+
for (const pid of found) {
|
|
453
|
+
try {
|
|
454
|
+
if (process.platform === 'win32') {
|
|
455
|
+
execSync(`taskkill /F /PID ${pid}`, { timeout: 5000, windowsHide: true });
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
process.kill(pid, 'SIGKILL');
|
|
459
|
+
}
|
|
460
|
+
killed++;
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
// Process may have already exited
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
// Clean up stale daemon lock if we killed the holder
|
|
467
|
+
if (legitimatePid && found.includes(legitimatePid)) {
|
|
468
|
+
releaseDaemonLock(process.cwd(), legitimatePid, true);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return { found: found.length, killed, pids: found };
|
|
472
|
+
}
|
|
403
473
|
// Format health check result
|
|
404
474
|
function formatCheck(check) {
|
|
405
475
|
const icon = check.status === 'pass' ? output.success('✓') :
|
|
@@ -438,12 +508,20 @@ export const doctorCommand = {
|
|
|
438
508
|
description: 'Verbose output',
|
|
439
509
|
type: 'boolean',
|
|
440
510
|
default: false
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
name: 'kill-zombies',
|
|
514
|
+
short: 'k',
|
|
515
|
+
description: 'Find and kill orphaned moflo/claude-flow node processes',
|
|
516
|
+
type: 'boolean',
|
|
517
|
+
default: false
|
|
441
518
|
}
|
|
442
519
|
],
|
|
443
520
|
examples: [
|
|
444
521
|
{ command: 'claude-flow doctor', description: 'Run full health check' },
|
|
445
522
|
{ command: 'claude-flow doctor --fix', description: 'Show fixes for issues' },
|
|
446
523
|
{ command: 'claude-flow doctor --install', description: 'Auto-install missing dependencies' },
|
|
524
|
+
{ command: 'claude-flow doctor --kill-zombies', description: 'Find and kill zombie processes' },
|
|
447
525
|
{ command: 'claude-flow doctor -c version', description: 'Check for stale npx cache' },
|
|
448
526
|
{ command: 'claude-flow doctor -c claude', description: 'Check Claude Code CLI only' }
|
|
449
527
|
],
|
|
@@ -452,11 +530,53 @@ export const doctorCommand = {
|
|
|
452
530
|
const autoInstall = ctx.flags.install;
|
|
453
531
|
const component = ctx.flags.component;
|
|
454
532
|
const verbose = ctx.flags.verbose;
|
|
533
|
+
const killZombies = ctx.flags['kill-zombies'];
|
|
455
534
|
output.writeln();
|
|
456
535
|
output.writeln(output.bold('MoFlo Doctor'));
|
|
457
536
|
output.writeln(output.dim('System diagnostics and health check'));
|
|
458
537
|
output.writeln(output.dim('─'.repeat(50)));
|
|
459
538
|
output.writeln();
|
|
539
|
+
// Handle --kill-zombies early
|
|
540
|
+
if (killZombies) {
|
|
541
|
+
output.writeln(output.bold('Zombie Process Scan'));
|
|
542
|
+
output.writeln();
|
|
543
|
+
// First scan without killing to show what would be killed
|
|
544
|
+
const scan = await findZombieProcesses(false);
|
|
545
|
+
if (scan.found === 0) {
|
|
546
|
+
output.writeln(output.success(' No orphaned moflo processes found'));
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
output.writeln(output.warning(` Found ${scan.found} orphaned process(es): PIDs ${scan.pids.join(', ')}`));
|
|
550
|
+
// Kill them
|
|
551
|
+
const result = await findZombieProcesses(true);
|
|
552
|
+
if (result.killed > 0) {
|
|
553
|
+
output.writeln(output.success(` Killed ${result.killed} zombie process(es)`));
|
|
554
|
+
}
|
|
555
|
+
if (result.killed < result.found) {
|
|
556
|
+
output.writeln(output.warning(` ${result.found - result.killed} process(es) could not be killed`));
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
output.writeln();
|
|
560
|
+
output.writeln(output.dim('─'.repeat(50)));
|
|
561
|
+
output.writeln();
|
|
562
|
+
}
|
|
563
|
+
const checkZombieProcesses = async () => {
|
|
564
|
+
try {
|
|
565
|
+
const scan = await findZombieProcesses(false);
|
|
566
|
+
if (scan.found === 0) {
|
|
567
|
+
return { name: 'Zombie Processes', status: 'pass', message: 'No orphaned processes' };
|
|
568
|
+
}
|
|
569
|
+
return {
|
|
570
|
+
name: 'Zombie Processes',
|
|
571
|
+
status: 'warn',
|
|
572
|
+
message: `${scan.found} orphaned process(es) (PIDs: ${scan.pids.join(', ')})`,
|
|
573
|
+
fix: 'moflo doctor --kill-zombies'
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
catch {
|
|
577
|
+
return { name: 'Zombie Processes', status: 'pass', message: 'Check skipped' };
|
|
578
|
+
}
|
|
579
|
+
};
|
|
460
580
|
const allChecks = [
|
|
461
581
|
checkVersionFreshness,
|
|
462
582
|
checkNodeVersion,
|
|
@@ -470,7 +590,8 @@ export const doctorCommand = {
|
|
|
470
590
|
checkMcpServers,
|
|
471
591
|
checkDiskSpace,
|
|
472
592
|
checkBuildTools,
|
|
473
|
-
checkAgenticFlow
|
|
593
|
+
checkAgenticFlow,
|
|
594
|
+
checkZombieProcesses
|
|
474
595
|
];
|
|
475
596
|
const componentMap = {
|
|
476
597
|
'version': checkVersionFreshness,
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* Created with ❤️ by motailz.com
|
|
14
14
|
*/
|
|
15
15
|
import { output } from '../output.js';
|
|
16
|
+
import { mofloImport } from '../services/moflo-require.js';
|
|
16
17
|
// Dynamic imports for embeddings package (optional — may not be installed)
|
|
17
18
|
async function getEmbeddings() {
|
|
18
19
|
try {
|
|
@@ -135,7 +136,7 @@ const searchCommand = {
|
|
|
135
136
|
return { success: false, exitCode: 1 };
|
|
136
137
|
}
|
|
137
138
|
// Load sql.js
|
|
138
|
-
const initSqlJs = (await
|
|
139
|
+
const initSqlJs = (await mofloImport('sql.js')).default;
|
|
139
140
|
const SQL = await initSqlJs();
|
|
140
141
|
const fileBuffer = fs.readFileSync(fullDbPath);
|
|
141
142
|
const db = new SQL.Database(fileBuffer);
|
|
@@ -378,7 +379,7 @@ const collectionsCommand = {
|
|
|
378
379
|
return { success: true, data: [] };
|
|
379
380
|
}
|
|
380
381
|
// Load sql.js and query real data
|
|
381
|
-
const initSqlJs = (await
|
|
382
|
+
const initSqlJs = (await mofloImport('sql.js')).default;
|
|
382
383
|
const SQL = await initSqlJs();
|
|
383
384
|
const fileBuffer = fs.readFileSync(fullDbPath);
|
|
384
385
|
const db = new SQL.Database(fileBuffer);
|
|
@@ -1200,7 +1201,7 @@ const cacheCommand = {
|
|
|
1200
1201
|
}
|
|
1201
1202
|
// Try to count real entries via sql.js
|
|
1202
1203
|
try {
|
|
1203
|
-
const initSqlJs = (await
|
|
1204
|
+
const initSqlJs = (await mofloImport('sql.js')).default;
|
|
1204
1205
|
const SQL = await initSqlJs();
|
|
1205
1206
|
const fileBuffer = fs.readFileSync(resolvedDbPath);
|
|
1206
1207
|
const db = new SQL.Database(fileBuffer);
|
|
@@ -6,6 +6,7 @@ import { output } from '../output.js';
|
|
|
6
6
|
import { confirm } from '../prompt.js';
|
|
7
7
|
import { callMCPTool, MCPClientError } from '../mcp-client.js';
|
|
8
8
|
import { storeCommand } from './transfer-store.js';
|
|
9
|
+
import { mofloImport } from '../services/moflo-require.js';
|
|
9
10
|
// Hook types
|
|
10
11
|
const HOOK_TYPES = [
|
|
11
12
|
{ value: 'pre-edit', label: 'Pre-Edit', hint: 'Get context before editing files' },
|
|
@@ -3074,7 +3075,7 @@ const tokenOptimizeCommand = {
|
|
|
3074
3075
|
let reasoningBank = null;
|
|
3075
3076
|
try {
|
|
3076
3077
|
// Check if agentic-flow v3 is available
|
|
3077
|
-
const rb = await
|
|
3078
|
+
const rb = await mofloImport('agentic-flow/reasoningbank');
|
|
3078
3079
|
if (rb) {
|
|
3079
3080
|
agenticFlowAvailable = true;
|
|
3080
3081
|
if (typeof rb.retrieveMemories === 'function') {
|
|
@@ -3083,7 +3084,7 @@ const tokenOptimizeCommand = {
|
|
|
3083
3084
|
}
|
|
3084
3085
|
else {
|
|
3085
3086
|
// Legacy check for older agentic-flow
|
|
3086
|
-
const af = await
|
|
3087
|
+
const af = await mofloImport('agentic-flow');
|
|
3087
3088
|
if (af)
|
|
3088
3089
|
agenticFlowAvailable = true;
|
|
3089
3090
|
}
|
|
@@ -9,6 +9,7 @@ import { output } from '../output.js';
|
|
|
9
9
|
import { confirm } from '../prompt.js';
|
|
10
10
|
import { getServerManager, getMCPServerStatus, } from '../mcp-server.js';
|
|
11
11
|
import { listMCPTools, callMCPTool, hasTool } from '../mcp-client.js';
|
|
12
|
+
import { acquireDaemonLock, releaseDaemonLock, getDaemonLockHolder } from '../services/daemon-lock.js';
|
|
12
13
|
// MCP tools categories
|
|
13
14
|
const TOOL_CATEGORIES = [
|
|
14
15
|
{ value: 'coordination', label: 'Coordination', hint: 'Swarm and agent coordination tools' },
|
|
@@ -97,14 +98,19 @@ const startCommand = {
|
|
|
97
98
|
output.writeln();
|
|
98
99
|
output.printInfo('Starting MCP Server...');
|
|
99
100
|
output.writeln();
|
|
100
|
-
// Check
|
|
101
|
+
// Check daemon lock first — prevents duplicate MCP servers across all platforms
|
|
102
|
+
const projectRoot = process.cwd();
|
|
103
|
+
const lockHolder = getDaemonLockHolder(projectRoot);
|
|
104
|
+
if (lockHolder && lockHolder !== process.pid && !force) {
|
|
105
|
+
output.printWarning(`MCP Server already running (PID: ${lockHolder}, detected via daemon lock)`);
|
|
106
|
+
output.writeln(output.dim('Use --force to override, or stop the existing server first'));
|
|
107
|
+
return { success: true };
|
|
108
|
+
}
|
|
109
|
+
// Check if already running via server status
|
|
101
110
|
const existingStatus = await getMCPServerStatus();
|
|
102
111
|
if (existingStatus.running) {
|
|
103
|
-
// For stdio
|
|
104
|
-
|
|
105
|
-
const shouldForceRestart = force || transport === 'stdio';
|
|
106
|
-
if (!shouldForceRestart) {
|
|
107
|
-
// Verify the server is actually healthy/responsive
|
|
112
|
+
// For non-stdio transports, check health unless --force is specified
|
|
113
|
+
if (!force && transport !== 'stdio') {
|
|
108
114
|
const manager = getServerManager();
|
|
109
115
|
const health = await manager.checkHealth();
|
|
110
116
|
if (health.healthy) {
|
|
@@ -113,26 +119,36 @@ const startCommand = {
|
|
|
113
119
|
return { success: false, exitCode: 1 };
|
|
114
120
|
}
|
|
115
121
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
if (force) {
|
|
123
|
+
// Force restart — kill existing and continue
|
|
124
|
+
output.printWarning(`MCP Server (PID: ${existingStatus.pid}) - restarting...`);
|
|
125
|
+
try {
|
|
126
|
+
if (existingStatus.pid) {
|
|
127
|
+
try {
|
|
128
|
+
process.kill(existingStatus.pid, 'SIGKILL');
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Process may already be dead
|
|
132
|
+
}
|
|
126
133
|
}
|
|
134
|
+
const manager = getServerManager();
|
|
135
|
+
await manager.stop();
|
|
136
|
+
// Release stale daemon lock from old process
|
|
137
|
+
releaseDaemonLock(projectRoot, existingStatus.pid || 0, true);
|
|
138
|
+
output.writeln(output.dim(' Cleaned up existing server'));
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Continue anyway - the stop/cleanup may partially fail
|
|
127
142
|
}
|
|
128
|
-
const manager = getServerManager();
|
|
129
|
-
await manager.stop();
|
|
130
|
-
output.writeln(output.dim(' Cleaned up existing server'));
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
// Continue anyway - the stop/cleanup may partially fail
|
|
134
143
|
}
|
|
135
144
|
}
|
|
145
|
+
// Acquire daemon lock for the new server
|
|
146
|
+
const lockResult = acquireDaemonLock(projectRoot);
|
|
147
|
+
if (!lockResult.acquired) {
|
|
148
|
+
output.printWarning(`Cannot acquire daemon lock (held by PID: ${lockResult.holder})`);
|
|
149
|
+
output.writeln(output.dim('Use --force to override'));
|
|
150
|
+
return { success: true };
|
|
151
|
+
}
|
|
136
152
|
const options = {
|
|
137
153
|
transport,
|
|
138
154
|
host,
|
|
@@ -7,6 +7,7 @@ import * as pathModule from 'path';
|
|
|
7
7
|
import { output } from '../output.js';
|
|
8
8
|
import { select, confirm, input } from '../prompt.js';
|
|
9
9
|
import { callMCPTool, MCPClientError } from '../mcp-client.js';
|
|
10
|
+
import { mofloImport } from '../services/moflo-require.js';
|
|
10
11
|
// Memory backends
|
|
11
12
|
const BACKENDS = [
|
|
12
13
|
{ value: 'agentdb', label: 'AgentDB', hint: 'Vector database with HNSW indexing (150x-12,500x faster)' },
|
|
@@ -1263,7 +1264,7 @@ const SWARM_DIR = '.swarm';
|
|
|
1263
1264
|
async function openDb(cwd) {
|
|
1264
1265
|
const fs = await import('fs');
|
|
1265
1266
|
const path = await import('path');
|
|
1266
|
-
const initSqlJs = (await
|
|
1267
|
+
const initSqlJs = (await mofloImport('sql.js')).default;
|
|
1267
1268
|
const SQL = await initSqlJs();
|
|
1268
1269
|
const dbPath = path.join(cwd, SWARM_DIR, DB_FILENAME);
|
|
1269
1270
|
const dir = path.dirname(dbPath);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Created with ❤️ by motailz.com
|
|
6
6
|
*/
|
|
7
7
|
import { output } from '../output.js';
|
|
8
|
+
import { mofloImport, mofloResolve } from '../services/moflo-require.js';
|
|
8
9
|
// Train subcommand - REAL WASM training with RuVector
|
|
9
10
|
const trainCommand = {
|
|
10
11
|
name: 'train',
|
|
@@ -1305,7 +1306,9 @@ const benchmarkCommand = {
|
|
|
1305
1306
|
const spinner = output.createSpinner({ text: 'Running benchmarks...', spinner: 'dots' });
|
|
1306
1307
|
spinner.start();
|
|
1307
1308
|
try {
|
|
1308
|
-
const attention = await
|
|
1309
|
+
const attention = await mofloImport('@ruvector/attention');
|
|
1310
|
+
if (!attention)
|
|
1311
|
+
throw new Error('@ruvector/attention not available');
|
|
1309
1312
|
// Manual benchmark since benchmarkAttention has a binding bug
|
|
1310
1313
|
const benchmarkMechanism = async (name, mechanism) => {
|
|
1311
1314
|
const query = new Float32Array(dim);
|
|
@@ -1381,11 +1384,13 @@ const benchmarkCommand = {
|
|
|
1381
1384
|
spinner.setText('Benchmarking MicroLoRA adaptation...');
|
|
1382
1385
|
// Load WASM file directly (Node.js compatible)
|
|
1383
1386
|
const fs = await import('fs');
|
|
1384
|
-
const
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
+
const wasmPath = mofloResolve('@ruvector/learning-wasm/ruvector_learning_wasm_bg.wasm');
|
|
1388
|
+
if (!wasmPath)
|
|
1389
|
+
throw new Error('@ruvector/learning-wasm not found');
|
|
1387
1390
|
const wasmBuffer = fs.readFileSync(wasmPath);
|
|
1388
|
-
const learningWasm = await
|
|
1391
|
+
const learningWasm = await mofloImport('@ruvector/learning-wasm');
|
|
1392
|
+
if (!learningWasm)
|
|
1393
|
+
throw new Error('@ruvector/learning-wasm not available');
|
|
1389
1394
|
learningWasm.initSync({ module: wasmBuffer });
|
|
1390
1395
|
const lora = new learningWasm.WasmMicroLoRA(dim, 0.1, 0.01);
|
|
1391
1396
|
const gradient = new Float32Array(dim);
|
|
@@ -50,6 +50,11 @@ export interface MofloConfig {
|
|
|
50
50
|
circuit_breaker: boolean;
|
|
51
51
|
agent_overrides: Record<string, string>;
|
|
52
52
|
};
|
|
53
|
+
auto_update: {
|
|
54
|
+
enabled: boolean;
|
|
55
|
+
scripts: boolean;
|
|
56
|
+
helpers: boolean;
|
|
57
|
+
};
|
|
53
58
|
status_line: {
|
|
54
59
|
enabled: boolean;
|
|
55
60
|
branding: string;
|
|
@@ -69,6 +69,11 @@ const DEFAULT_CONFIG = {
|
|
|
69
69
|
circuit_breaker: true,
|
|
70
70
|
agent_overrides: {},
|
|
71
71
|
},
|
|
72
|
+
auto_update: {
|
|
73
|
+
enabled: true,
|
|
74
|
+
scripts: true,
|
|
75
|
+
helpers: true,
|
|
76
|
+
},
|
|
72
77
|
status_line: {
|
|
73
78
|
enabled: true,
|
|
74
79
|
branding: 'Moflo V4',
|
|
@@ -159,6 +164,11 @@ function mergeConfig(raw, root) {
|
|
|
159
164
|
circuit_breaker: raw.model_routing?.circuit_breaker ?? raw.modelRouting?.circuitBreaker ?? DEFAULT_CONFIG.model_routing.circuit_breaker,
|
|
160
165
|
agent_overrides: raw.model_routing?.agent_overrides ?? raw.modelRouting?.agentOverrides ?? DEFAULT_CONFIG.model_routing.agent_overrides,
|
|
161
166
|
},
|
|
167
|
+
auto_update: {
|
|
168
|
+
enabled: raw.auto_update?.enabled ?? raw.autoUpdate?.enabled ?? DEFAULT_CONFIG.auto_update.enabled,
|
|
169
|
+
scripts: raw.auto_update?.scripts ?? raw.autoUpdate?.scripts ?? DEFAULT_CONFIG.auto_update.scripts,
|
|
170
|
+
helpers: raw.auto_update?.helpers ?? raw.autoUpdate?.helpers ?? DEFAULT_CONFIG.auto_update.helpers,
|
|
171
|
+
},
|
|
162
172
|
status_line: {
|
|
163
173
|
enabled: raw.status_line?.enabled ?? raw.statusLine?.enabled ?? DEFAULT_CONFIG.status_line.enabled,
|
|
164
174
|
branding: raw.status_line?.branding ?? raw.statusLine?.branding ?? DEFAULT_CONFIG.status_line.branding,
|
|
@@ -314,6 +324,12 @@ model_routing:
|
|
|
314
324
|
# security-architect: opus # Always use opus for security
|
|
315
325
|
# researcher: sonnet # Pin research to sonnet
|
|
316
326
|
|
|
327
|
+
# Auto-update on session start (syncs scripts and helpers when moflo version changes)
|
|
328
|
+
auto_update:
|
|
329
|
+
enabled: true # Master toggle for version-change auto-sync
|
|
330
|
+
scripts: true # Sync .claude/scripts/ from moflo bin/
|
|
331
|
+
helpers: true # Sync .claude/helpers/ from moflo source
|
|
332
|
+
|
|
317
333
|
# Status line items (show/hide individual sections)
|
|
318
334
|
status_line:
|
|
319
335
|
enabled: true
|