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.
Files changed (77) hide show
  1. package/.claude/settings.local.json +4 -1
  2. package/.claude/workflow-state.json +3 -7
  3. package/README.md +3 -1
  4. package/bin/build-embeddings.mjs +59 -3
  5. package/bin/generate-code-map.mjs +3 -1
  6. package/bin/hooks.mjs +23 -20
  7. package/bin/index-guidance.mjs +3 -1
  8. package/bin/lib/moflo-resolve.mjs +14 -0
  9. package/bin/semantic-search.mjs +10 -5
  10. package/bin/session-start-launcher.mjs +116 -3
  11. package/package.json +6 -6
  12. package/src/@claude-flow/cli/dist/src/appliance/ruvllm-bridge.js +3 -7
  13. package/src/@claude-flow/cli/dist/src/commands/daemon.js +42 -95
  14. package/src/@claude-flow/cli/dist/src/commands/doctor.js +127 -6
  15. package/src/@claude-flow/cli/dist/src/commands/embeddings.js +4 -3
  16. package/src/@claude-flow/cli/dist/src/commands/hooks.js +3 -2
  17. package/src/@claude-flow/cli/dist/src/commands/mcp.js +38 -22
  18. package/src/@claude-flow/cli/dist/src/commands/memory.js +2 -1
  19. package/src/@claude-flow/cli/dist/src/commands/neural.js +10 -5
  20. package/src/@claude-flow/cli/dist/src/config/moflo-config.d.ts +5 -0
  21. package/src/@claude-flow/cli/dist/src/config/moflo-config.js +16 -0
  22. package/src/@claude-flow/cli/dist/src/index.js +12 -0
  23. package/src/@claude-flow/cli/dist/src/init/executor.js +74 -0
  24. package/src/@claude-flow/cli/dist/src/init/moflo-init.js +49 -0
  25. package/src/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +2 -2
  26. package/src/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +2 -1
  27. package/src/@claude-flow/cli/dist/src/memory/memory-bridge.js +5 -1
  28. package/src/@claude-flow/cli/dist/src/memory/memory-initializer.js +29 -24
  29. package/src/@claude-flow/cli/dist/src/ruvector/ast-analyzer.js +2 -1
  30. package/src/@claude-flow/cli/dist/src/ruvector/coverage-router.js +2 -1
  31. package/src/@claude-flow/cli/dist/src/ruvector/diff-classifier.js +2 -1
  32. package/src/@claude-flow/cli/dist/src/ruvector/enhanced-model-router.js +3 -3
  33. package/src/@claude-flow/cli/dist/src/ruvector/index.js +6 -13
  34. package/src/@claude-flow/cli/dist/src/ruvector/q-learning-router.js +4 -1
  35. package/src/@claude-flow/cli/dist/src/services/daemon-lock.d.ts +39 -0
  36. package/src/@claude-flow/cli/dist/src/services/daemon-lock.js +213 -0
  37. package/src/@claude-flow/cli/dist/src/services/learning-service.js +2 -1
  38. package/src/@claude-flow/cli/dist/src/services/moflo-require.d.ts +34 -0
  39. package/src/@claude-flow/cli/dist/src/services/moflo-require.js +67 -0
  40. package/src/@claude-flow/cli/dist/src/services/ruvector-training.js +8 -6
  41. package/src/@claude-flow/cli/package.json +6 -6
  42. package/.claude/helpers/README.md +0 -97
  43. package/.claude/helpers/adr-compliance.sh +0 -186
  44. package/.claude/helpers/aggressive-microcompact.mjs +0 -36
  45. package/.claude/helpers/auto-commit.sh +0 -178
  46. package/.claude/helpers/checkpoint-manager.sh +0 -251
  47. package/.claude/helpers/context-persistence-hook.mjs +0 -1979
  48. package/.claude/helpers/daemon-manager.sh +0 -252
  49. package/.claude/helpers/ddd-tracker.sh +0 -144
  50. package/.claude/helpers/github-safe.js +0 -106
  51. package/.claude/helpers/github-setup.sh +0 -28
  52. package/.claude/helpers/guidance-hook.sh +0 -13
  53. package/.claude/helpers/guidance-hooks.sh +0 -102
  54. package/.claude/helpers/health-monitor.sh +0 -108
  55. package/.claude/helpers/learning-hooks.sh +0 -329
  56. package/.claude/helpers/learning-optimizer.sh +0 -127
  57. package/.claude/helpers/learning-service.mjs +0 -1211
  58. package/.claude/helpers/memory.cjs +0 -84
  59. package/.claude/helpers/metrics-db.mjs +0 -492
  60. package/.claude/helpers/patch-aggressive-prune.mjs +0 -184
  61. package/.claude/helpers/pattern-consolidator.sh +0 -86
  62. package/.claude/helpers/perf-worker.sh +0 -160
  63. package/.claude/helpers/quick-start.sh +0 -19
  64. package/.claude/helpers/router.cjs +0 -62
  65. package/.claude/helpers/security-scanner.sh +0 -127
  66. package/.claude/helpers/session.cjs +0 -125
  67. package/.claude/helpers/setup-mcp.sh +0 -18
  68. package/.claude/helpers/standard-checkpoint-hooks.sh +0 -189
  69. package/.claude/helpers/swarm-comms.sh +0 -353
  70. package/.claude/helpers/swarm-hooks.sh +0 -761
  71. package/.claude/helpers/swarm-monitor.sh +0 -211
  72. package/.claude/helpers/sync-v3-metrics.sh +0 -245
  73. package/.claude/helpers/update-v3-progress.sh +0 -166
  74. package/.claude/helpers/v3-quick-status.sh +0 -58
  75. package/.claude/helpers/v3.sh +0 -111
  76. package/.claude/helpers/validate-v3-config.sh +0 -216
  77. 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
- const stateDir = join(projectRoot, '.claude-flow');
84
- const pidFile = join(stateDir, 'daemon.pid');
85
- // Ensure state directory exists
86
- if (!fs.existsSync(stateDir)) {
87
- fs.mkdirSync(stateDir, { recursive: true });
88
- }
89
- // Check if another foreground daemon is already running (prevents duplicate daemons)
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
- // Write PID file for foreground mode
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
- try {
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 writing PID file — prevents race where parent exits
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
- // Save PID only after child is detached
285
- fs.writeFileSync(pidFile, String(pid));
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 PID file
307
+ * Kill background daemon process using lock file
330
308
  */
331
309
  async function killBackgroundDaemon(projectRoot) {
332
- const pidFile = join(projectRoot, '.claude-flow', 'daemon.pid');
333
- if (!fs.existsSync(pidFile)) {
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(pid, 'SIGTERM');
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(pid, 0);
322
+ process.kill(holderPid, 0);
357
323
  // Still alive, force kill
358
- process.kill(pid, 'SIGKILL');
324
+ process.kill(holderPid, 'SIGKILL');
359
325
  }
360
326
  catch {
361
327
  // Process terminated
362
328
  }
363
- // Clean up PID file
364
- if (fs.existsSync(pidFile)) {
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 PID file on any error
371
- if (fs.existsSync(pidFile)) {
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
- const pidFile = join(projectRoot, '.claude-flow', 'daemon.pid');
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); // Signal 0 = check if alive
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 pidFile = '.claude-flow/daemon.pid';
97
- if (existsSync(pidFile)) {
98
- const pid = readFileSync(pidFile, 'utf8').trim();
97
+ const lockFile = '.claude-flow/daemon.lock';
98
+ if (existsSync(lockFile)) {
99
99
  try {
100
- process.kill(parseInt(pid, 10), 0); // Check if process exists
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 PID file', fix: 'rm .claude-flow/daemon.pid && claude-flow daemon start' };
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 import('sql.js')).default;
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 import('sql.js')).default;
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 import('sql.js')).default;
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 import('agentic-flow/reasoningbank').catch(() => null);
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 import('agentic-flow').catch(() => null);
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 if already running
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 transport, always force restart since we can't health check it
104
- // For other transports, check health unless --force is specified
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
- // Force restart or unresponsive - auto-recover
117
- output.printWarning(`MCP Server (PID: ${existingStatus.pid}) - restarting...`);
118
- try {
119
- // Force kill the existing process
120
- if (existingStatus.pid) {
121
- try {
122
- process.kill(existingStatus.pid, 'SIGKILL');
123
- }
124
- catch {
125
- // Process may already be dead
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 import('sql.js')).default;
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 import('@ruvector/attention');
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 { createRequire } = await import('module');
1385
- const require = createRequire(import.meta.url);
1386
- const wasmPath = require.resolve('@ruvector/learning-wasm/ruvector_learning_wasm_bg.wasm');
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 import('@ruvector/learning-wasm');
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