moflo 4.6.10 → 4.6.12

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.
@@ -145,6 +145,19 @@ const handlers = {
145
145
  try {
146
146
  var projectDir = path.resolve(path.dirname(helpersDir), '..');
147
147
  var cp = require('child_process');
148
+ var pidFile = path.join(projectDir, '.claude-flow', 'background-pids.json');
149
+
150
+ // Kill stale background processes from previous sessions
151
+ try {
152
+ if (fs.existsSync(pidFile)) {
153
+ var stalePids = JSON.parse(fs.readFileSync(pidFile, 'utf-8'));
154
+ for (var i = 0; i < stalePids.length; i++) {
155
+ try { process.kill(stalePids[i].pid, 0); /* test if alive */ } catch (e) { continue; }
156
+ try { process.kill(stalePids[i].pid, 'SIGTERM'); } catch (e) { /* already gone */ }
157
+ }
158
+ fs.unlinkSync(pidFile);
159
+ }
160
+ } catch (e) { /* non-fatal: best-effort cleanup */ }
148
161
 
149
162
  // Read moflo.yaml auto_index flags (default: both true)
150
163
  var autoGuidance = true;
@@ -183,26 +196,33 @@ const handlers = {
183
196
  return null;
184
197
  }
185
198
 
186
- function spawnBackground(script, extraArgs) {
199
+ // Track PIDs of background processes so next session can clean them up
200
+ var trackedPids = [];
201
+
202
+ function spawnBackground(script, label, extraArgs) {
187
203
  var args = [script].concat(extraArgs || []);
188
- cp.spawn('node', args, {
204
+ var child = cp.spawn('node', args, {
189
205
  stdio: 'ignore',
190
206
  cwd: projectDir,
191
207
  detached: true,
192
208
  windowsHide: true
193
- }).unref();
209
+ });
210
+ if (child.pid) {
211
+ trackedPids.push({ pid: child.pid, script: label, startedAt: new Date().toISOString() });
212
+ }
213
+ child.unref();
194
214
  }
195
215
 
196
216
  // 1. Index guidance docs (with embeddings for semantic search)
197
217
  if (autoGuidance) {
198
218
  var guidanceScript = findMofloScript('index-guidance.mjs');
199
- if (guidanceScript) spawnBackground(guidanceScript);
219
+ if (guidanceScript) spawnBackground(guidanceScript, 'index-guidance');
200
220
  }
201
221
 
202
222
  // 2. Generate code map (structural index of source files)
203
223
  if (autoCodeMap) {
204
224
  var codeMapScript = findMofloScript('generate-code-map.mjs');
205
- if (codeMapScript) spawnBackground(codeMapScript);
225
+ if (codeMapScript) spawnBackground(codeMapScript, 'generate-code-map');
206
226
  }
207
227
 
208
228
  // 3. Start learning service (pattern research on codebase)
@@ -218,7 +238,16 @@ const handlers = {
218
238
  var nmLearn = path.join(projectDir, 'node_modules', 'moflo', '.claude', 'helpers', 'learning-service.mjs');
219
239
  if (fs.existsSync(nmLearn)) learnScript = nmLearn;
220
240
  }
221
- if (learnScript) spawnBackground(learnScript);
241
+ if (learnScript) spawnBackground(learnScript, 'learning-service');
242
+
243
+ // Persist tracked PIDs for cleanup on next session start
244
+ if (trackedPids.length > 0) {
245
+ try {
246
+ var pidDir = path.dirname(pidFile);
247
+ if (!fs.existsSync(pidDir)) fs.mkdirSync(pidDir, { recursive: true });
248
+ fs.writeFileSync(pidFile, JSON.stringify(trackedPids));
249
+ } catch (e) { /* non-fatal */ }
250
+ }
222
251
 
223
252
  } catch (e) { /* non-fatal: session-start indexing is best-effort */ }
224
253
  },
@@ -458,12 +458,13 @@ function getHooksStatus() {
458
458
  return { enabled, total };
459
459
  }
460
460
 
461
- // AgentDB stats (pure stat calls)
461
+ // AgentDB stats queries real count from sqlite when possible, falls back to file size estimate
462
462
  function getAgentDBStats() {
463
463
  let vectorCount = 0;
464
464
  let dbSizeKB = 0;
465
465
  let namespaces = 0;
466
466
  let hasHnsw = false;
467
+ let dbPath = null;
467
468
 
468
469
  const dbFiles = [
469
470
  path.join(CWD, '.swarm', 'memory.db'),
@@ -476,31 +477,25 @@ function getAgentDBStats() {
476
477
  const stat = safeStat(f);
477
478
  if (stat) {
478
479
  dbSizeKB = stat.size / 1024;
479
- vectorCount = Math.floor(dbSizeKB / 2);
480
- namespaces = 1;
480
+ dbPath = f;
481
481
  break;
482
482
  }
483
483
  }
484
484
 
485
- if (vectorCount === 0) {
486
- const dbDirs = [
487
- path.join(CWD, '.claude-flow', 'agentdb'),
488
- path.join(CWD, '.swarm', 'agentdb'),
489
- path.join(CWD, '.agentdb'),
490
- ];
491
- for (const dir of dbDirs) {
485
+ // Try to get real count from sqlite (fast single COUNT query)
486
+ if (dbPath) {
487
+ const countOutput = safeExec(`node -e "const S=require('sql.js');const f=require('fs');S().then(Q=>{const d=new Q.Database(f.readFileSync('${dbPath.replace(/\\/g, '/')}'));const s=d.prepare('SELECT COUNT(*) as c FROM memory_entries WHERE status=\\"active\\" AND embedding IS NOT NULL');s.step();console.log(JSON.stringify(s.getAsObject()));s.free();const n=d.prepare('SELECT COUNT(DISTINCT namespace) as n FROM memory_entries WHERE status=\\"active\\"');n.step();console.log(JSON.stringify(n.getAsObject()));n.free();d.close();})"`, 3000);
488
+ if (countOutput) {
492
489
  try {
493
- if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
494
- const files = fs.readdirSync(dir);
495
- namespaces = files.filter(f => f.endsWith('.db') || f.endsWith('.sqlite')).length;
496
- for (const file of files) {
497
- const stat = safeStat(path.join(dir, file));
498
- if (stat?.isFile()) dbSizeKB += stat.size / 1024;
499
- }
500
- vectorCount = Math.floor(dbSizeKB / 2);
501
- break;
502
- }
503
- } catch { /* ignore */ }
490
+ const lines = countOutput.trim().split('\n');
491
+ vectorCount = JSON.parse(lines[0]).c || 0;
492
+ namespaces = lines[1] ? JSON.parse(lines[1]).n || 0 : 0;
493
+ } catch { /* fall back to estimate */ }
494
+ }
495
+ // Fallback to file size estimate if query failed
496
+ if (vectorCount === 0) {
497
+ vectorCount = Math.floor(dbSizeKB / 2);
498
+ namespaces = 1;
504
499
  }
505
500
  }
506
501
 
@@ -509,10 +504,8 @@ function getAgentDBStats() {
509
504
  path.join(CWD, '.claude-flow', 'hnsw.index'),
510
505
  ];
511
506
  for (const p of hnswPaths) {
512
- const stat = safeStat(p);
513
- if (stat) {
507
+ if (safeStat(p)) {
514
508
  hasHnsw = true;
515
- vectorCount = Math.max(vectorCount, Math.floor(stat.size / 512));
516
509
  break;
517
510
  }
518
511
  }
@@ -168,7 +168,7 @@
168
168
  "pr": "\ud83e\udd16 Generated with [claude-flow](https://github.com/eric-cielo/moflo)"
169
169
  },
170
170
  "claudeFlow": {
171
- "version": "3.5.2",
171
+ "version": "4.6.12",
172
172
  "enabled": true,
173
173
  "modelPreferences": {
174
174
  "default": "claude-opus-4-6",
@@ -0,0 +1,9 @@
1
+ {
2
+ "tasksCreated": false,
3
+ "taskCount": 0,
4
+ "memorySearched": false,
5
+ "memoryRequired": true,
6
+ "interactionCount": 10,
7
+ "sessionStart": null,
8
+ "lastBlockedAt": "2026-03-20T20:34:44.738Z"
9
+ }
package/README.md CHANGED
@@ -257,6 +257,10 @@ auto_index:
257
257
  guidance: true # Auto-index docs on session start
258
258
  code_map: true # Auto-index code on session start
259
259
 
260
+ mcp:
261
+ tool_defer: true # Defer 150+ tool schemas; loaded on demand via ToolSearch
262
+ auto_start: false # Auto-start MCP server on session begin
263
+
260
264
  hooks:
261
265
  pre_edit: true # Track file edits for learning
262
266
  post_edit: true # Record edit outcomes
@@ -291,6 +295,12 @@ status_line:
291
295
  show_mcp: true
292
296
  ```
293
297
 
298
+ ### Tool Deferral
299
+
300
+ By default, `tool_defer` is `true`. MoFlo exposes 150+ MCP tools — loading all their schemas at conversation start consumes significant context. With deferral enabled, only tool **names** are listed at startup (compact), and full schemas are fetched on demand via `ToolSearch` when actually needed. Hooks and CLI commands continue to work normally since they call the daemon directly, not through MCP tool schemas.
301
+
302
+ Set `tool_defer: false` if you want all tool schemas available immediately (useful for offline/air-gapped environments where `ToolSearch` may not work).
303
+
294
304
  ### Model Routing
295
305
 
296
306
  By default, MoFlo uses **static model preferences** — each agent role uses the model specified in `models:`. This is predictable and gives you full control.
package/bin/hooks.mjs ADDED
@@ -0,0 +1,515 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Cross-platform Claude Code hook runner
4
+ * Works on Windows (cmd/powershell) and Linux/WSL (bash)
5
+ *
6
+ * Usage: node .claude/scripts/hooks.mjs <hook-type> [args...]
7
+ *
8
+ * Hook types:
9
+ * pre-edit --file <path>
10
+ * post-edit --file <path> --success <bool>
11
+ * pre-command --command <cmd>
12
+ * post-command --command <cmd> --success <bool>
13
+ * pre-task --description <desc>
14
+ * post-task --task-id <id> --success <bool>
15
+ * session-start
16
+ * session-restore --session-id <id>
17
+ * route --task <prompt>
18
+ * index-guidance [--file <path>] [--force]
19
+ * daemon-start
20
+ */
21
+
22
+ import { spawn } from 'child_process';
23
+ import { existsSync, appendFileSync } from 'fs';
24
+ import { resolve, dirname } from 'path';
25
+ import { fileURLToPath } from 'url';
26
+
27
+ const __filename = fileURLToPath(import.meta.url);
28
+ const __dirname = dirname(__filename);
29
+ const projectRoot = resolve(__dirname, '../..');
30
+ const logFile = resolve(projectRoot, '.swarm/hooks.log');
31
+
32
+ // Parse command line args
33
+ const args = process.argv.slice(2);
34
+ const hookType = args[0];
35
+
36
+ // Simple log function - writes to .swarm/hooks.log
37
+ function log(level, message) {
38
+ const timestamp = new Date().toISOString();
39
+ const line = `[${timestamp}] [${level.toUpperCase()}] [${hookType || 'unknown'}] ${message}\n`;
40
+
41
+ // Always log errors to stderr so they're visible in Claude
42
+ if (level === 'error') {
43
+ console.error(`[hook:${hookType}] ${message}`);
44
+ }
45
+
46
+ // Also append to log file for history
47
+ try {
48
+ appendFileSync(logFile, line);
49
+ } catch {
50
+ // Can't write log - that's fine, don't fail
51
+ }
52
+ }
53
+
54
+ // Helper to get arg value
55
+ function getArg(name) {
56
+ const idx = args.indexOf(`--${name}`);
57
+ if (idx !== -1 && args[idx + 1]) {
58
+ return args[idx + 1];
59
+ }
60
+ // Also check environment variables (Claude sets these)
61
+ const envName = `TOOL_INPUT_${name}`.replace(/-/g, '_');
62
+ return process.env[envName] || process.env[name.toUpperCase()] || null;
63
+ }
64
+
65
+ // Helper to check if arg/flag exists
66
+ function hasArg(name) {
67
+ return args.includes(`--${name}`);
68
+ }
69
+
70
+ // Get the local CLI path (preferred - no network/extraction overhead)
71
+ function getLocalCliPath() {
72
+ const localCli = resolve(projectRoot, 'node_modules/moflo/src/@claude-flow/cli/bin/cli.js');
73
+ return existsSync(localCli) ? localCli : null;
74
+ }
75
+
76
+ // Check if running on Windows
77
+ const isWindows = process.platform === 'win32';
78
+
79
+ // Run a command and return promise with exit code
80
+ function runCommand(cmd, cmdArgs, options = {}) {
81
+ return new Promise((resolve) => {
82
+ let stderr = '';
83
+
84
+ // Use windowsHide: true directly - no PowerShell wrapper needed
85
+ // The wrapper can actually cause MORE flashes as PowerShell starts
86
+ const proc = spawn(cmd, cmdArgs, {
87
+ stdio: options.silent ? ['ignore', 'ignore', 'pipe'] : 'inherit',
88
+ shell: false,
89
+ cwd: projectRoot,
90
+ env: { ...process.env, ...options.env },
91
+ detached: options.background || false,
92
+ windowsHide: true // This is sufficient on Windows when shell: false
93
+ });
94
+
95
+ // Capture stderr even in silent mode
96
+ if (proc.stderr) {
97
+ proc.stderr.on('data', (data) => {
98
+ stderr += data.toString();
99
+ });
100
+ }
101
+
102
+ proc.on('close', (code) => {
103
+ if (code !== 0 && stderr) {
104
+ log('error', `Command failed (exit ${code}): ${cmd} ${cmdArgs.join(' ')}`);
105
+ if (stderr.trim()) {
106
+ log('error', ` stderr: ${stderr.trim().substring(0, 200)}`);
107
+ }
108
+ }
109
+ resolve(code || 0);
110
+ });
111
+
112
+ proc.on('error', (err) => {
113
+ log('error', `Command error: ${cmd} - ${err.message}`);
114
+ resolve(1);
115
+ });
116
+ });
117
+ }
118
+
119
+ // Show Windows toast notification (works on native Windows and WSL)
120
+ async function showWindowsToast(title, message) {
121
+ // PowerShell script to show toast notification
122
+ const psScript = `
123
+ [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
124
+ $template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)
125
+ $text = $template.GetElementsByTagName('text')
126
+ $text.Item(0).AppendChild($template.CreateTextNode('${title.replace(/'/g, "''")}')) | Out-Null
127
+ $text.Item(1).AppendChild($template.CreateTextNode('${message.replace(/'/g, "''")}')) | Out-Null
128
+ $toast = [Windows.UI.Notifications.ToastNotification]::new($template)
129
+ [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Claude Code').Show($toast)
130
+ `.trim();
131
+
132
+ // Encode script as base64 for -EncodedCommand (avoids shell escaping issues)
133
+ const encodedScript = Buffer.from(psScript, 'utf16le').toString('base64');
134
+
135
+ try {
136
+ // Detect environment and use appropriate PowerShell command
137
+ const isWSL = process.platform === 'linux' && existsSync('/proc/version') &&
138
+ (await import('fs')).readFileSync('/proc/version', 'utf-8').toLowerCase().includes('microsoft');
139
+
140
+ if (process.platform === 'win32') {
141
+ // Native Windows - use powershell with encoded command (avoids cmd.exe escaping issues)
142
+ await runCommand('powershell', ['-NoProfile', '-WindowStyle', 'Hidden', '-EncodedCommand', encodedScript], { silent: true });
143
+ log('debug', 'Toast notification sent via PowerShell');
144
+ } else if (isWSL) {
145
+ // WSL - use powershell.exe to call Windows PowerShell
146
+ await runCommand('powershell.exe', ['-NoProfile', '-WindowStyle', 'Hidden', '-EncodedCommand', encodedScript], { silent: true });
147
+ log('debug', 'Toast notification sent via powershell.exe (WSL)');
148
+ } else {
149
+ // Linux/Mac - no Windows toast available, just log
150
+ log('debug', 'Windows toast not available on this platform');
151
+ }
152
+ } catch (err) {
153
+ // Toast notifications are nice-to-have, don't fail the hook
154
+ log('debug', `Toast notification failed: ${err.message}`);
155
+ }
156
+ }
157
+
158
+ // Run claude-flow CLI command using local installation
159
+ async function runClaudeFlow(subcommand, cliArgs = []) {
160
+ const localCli = getLocalCliPath();
161
+
162
+ if (localCli) {
163
+ // Use local CLI (fastest, no network/extraction)
164
+ const fullArgs = [localCli, subcommand, ...cliArgs];
165
+ const exitCode = await runCommand('node', fullArgs, { silent: true });
166
+
167
+ if (exitCode !== 0) {
168
+ log('warn', `claude-flow ${subcommand} exited with code ${exitCode}`);
169
+ }
170
+
171
+ return exitCode;
172
+ } else {
173
+ log('warn', 'Local CLI not found. Install with: npm install @claude-flow/cli');
174
+ return 1;
175
+ }
176
+ }
177
+
178
+ // Main hook dispatcher
179
+ async function main() {
180
+ if (!hookType) {
181
+ console.error('Usage: node hooks.mjs <hook-type> [args...]');
182
+ process.exit(1);
183
+ }
184
+
185
+ try {
186
+ switch (hookType) {
187
+ case 'pre-edit': {
188
+ const file = getArg('file') || process.env.TOOL_INPUT_file_path;
189
+ if (file) {
190
+ await runClaudeFlow('hooks', ['pre-edit', '--file', file]);
191
+ }
192
+ break;
193
+ }
194
+
195
+ case 'post-edit': {
196
+ const file = getArg('file') || process.env.TOOL_INPUT_file_path;
197
+ const success = getArg('success') || process.env.TOOL_SUCCESS || 'true';
198
+ if (file) {
199
+ await runClaudeFlow('hooks', ['post-edit', '--file', file, '--success', success]);
200
+
201
+ // Check if this is a guidance file that needs indexing (run in background)
202
+ if (file.includes('.claude/guidance/') || file.includes('.claude/skills/cl/')) {
203
+ runIndexGuidanceBackground(file);
204
+ }
205
+ }
206
+ break;
207
+ }
208
+
209
+ case 'pre-command': {
210
+ const command = getArg('command') || process.env.TOOL_INPUT_command;
211
+ if (command) {
212
+ await runClaudeFlow('hooks', ['pre-command', '--command', command]);
213
+ }
214
+ break;
215
+ }
216
+
217
+ case 'post-command': {
218
+ const command = getArg('command') || process.env.TOOL_INPUT_command;
219
+ const success = getArg('success') || process.env.TOOL_SUCCESS || 'true';
220
+ if (command) {
221
+ await runClaudeFlow('hooks', ['post-command', '--command', command, '--success', success]);
222
+ }
223
+ break;
224
+ }
225
+
226
+ case 'pre-task': {
227
+ const description = getArg('description') || process.env.TOOL_INPUT_prompt;
228
+ if (description) {
229
+ const taskId = `task-${Date.now()}`;
230
+ await runClaudeFlow('hooks', ['pre-task', '--task-id', taskId, '--description', description]);
231
+ }
232
+ break;
233
+ }
234
+
235
+ case 'pre-research': {
236
+ // Memory-first gate: remind to search memory before exploring codebase
237
+ // This fires on Glob/Grep to catch research-style queries
238
+ const pattern = process.env.TOOL_INPUT_pattern || getArg('pattern');
239
+ const query = process.env.TOOL_INPUT_query || getArg('query');
240
+ const searchTerm = pattern || query;
241
+
242
+ // Only remind if this looks like a research query (not a specific path lookup)
243
+ if (searchTerm && !searchTerm.includes('/') && !searchTerm.match(/\.(ts|tsx|js|json|md)$/)) {
244
+ console.log('[MEMORY GATE] Did you search memory first? Run: memory search --query "[topic]" --namespace guidance');
245
+ }
246
+ break;
247
+ }
248
+
249
+ case 'post-task': {
250
+ const taskId = getArg('task-id') || process.env.TOOL_RESULT_agent_id;
251
+ const success = getArg('success') || process.env.TOOL_SUCCESS || 'true';
252
+ if (taskId) {
253
+ await runClaudeFlow('hooks', ['post-task', '--task-id', taskId, '--success', success]);
254
+ }
255
+ break;
256
+ }
257
+
258
+ case 'session-start': {
259
+ // All startup tasks run in background (non-blocking)
260
+ // Start daemon quietly in background
261
+ runDaemonStartBackground();
262
+ // Index guidance files in background
263
+ runIndexGuidanceBackground();
264
+ // Generate structural code map in background
265
+ runCodeMapBackground();
266
+ // Run pretrain in background to extract patterns from repository
267
+ runBackgroundPretrain();
268
+ // Force HNSW rebuild to ensure all processes use identical fresh index
269
+ // This fixes agent search result mismatches (0.61 vs 0.81 similarity)
270
+ runHNSWRebuildBackground();
271
+ // Neural patterns now loaded by moflo core routing — no external patching.
272
+ break;
273
+ }
274
+
275
+ case 'session-restore': {
276
+ const sessionId = getArg('session-id') || process.env.SESSION_ID;
277
+ if (sessionId) {
278
+ await runClaudeFlow('hooks', ['session-restore', '--session-id', sessionId]);
279
+ }
280
+ break;
281
+ }
282
+
283
+ case 'route': {
284
+ const task = getArg('task') || process.env.PROMPT;
285
+ if (task) {
286
+ // Check for /cl command and output gate reminder
287
+ if (task.includes('/cl') || task.match(/^cl\s/)) {
288
+ const hasHelpFlag = task.includes('-h') || task.includes('--help');
289
+ const hasNakedFlag = task.includes('-n') || task.includes('--naked');
290
+
291
+ if (!hasHelpFlag && !hasNakedFlag) {
292
+ // Output visible reminder - this appears in Claude's context
293
+ console.log('[SWARM GATE] /cl detected. Required order: TaskList() → TaskCreate() → swarm init → Task(run_in_background)');
294
+ console.log('[SWARM GATE] Do NOT call GitHub/Grep/Read until tasks are created.');
295
+ }
296
+ }
297
+ await runClaudeFlow('hooks', ['route', '--task', task]);
298
+ }
299
+ break;
300
+ }
301
+
302
+ case 'index-guidance': {
303
+ const file = getArg('file');
304
+ await runIndexGuidance(file);
305
+ break;
306
+ }
307
+
308
+ case 'daemon-start': {
309
+ await runClaudeFlow('daemon', ['start', '--quiet']);
310
+ break;
311
+ }
312
+
313
+ case 'session-end': {
314
+ // Run ReasoningBank and MicroLoRA training in background on session end
315
+ log('info', 'Session ending - starting background learning...');
316
+
317
+ // Run session-end hook (persists state)
318
+ await runClaudeFlow('hooks', ['session-end', '--persist-state', 'true']);
319
+
320
+ // Start background training (non-blocking)
321
+ runBackgroundTraining();
322
+ break;
323
+ }
324
+
325
+
326
+ case 'semantic-search': {
327
+ // Semantic search using embeddings
328
+ const query = getArg('query') || args[1];
329
+ const searchLimit = getArg('limit') || '5';
330
+ const threshold = getArg('threshold') || '0.3';
331
+ const searchScript = resolve(projectRoot, '.claude/scripts/semantic-search.mjs');
332
+
333
+ if (query && existsSync(searchScript)) {
334
+ const searchArgs = [searchScript, query, '--limit', searchLimit, '--threshold', threshold];
335
+ if (getArg('namespace')) searchArgs.push('--namespace', getArg('namespace'));
336
+ if (hasArg('json')) searchArgs.push('--json');
337
+ // semantic-search.mjs uses better-sqlite3
338
+ await runCommand('node', searchArgs, { silent: false });
339
+ } else if (!query) {
340
+ console.log('Usage: node .claude/scripts/hooks.mjs semantic-search --query "your query"');
341
+ } else {
342
+ log('error', 'Semantic search script not found');
343
+ }
344
+ break;
345
+ }
346
+
347
+ case 'notification': {
348
+ // Handle notification hook - show Windows toast if possible
349
+ const message = process.env.NOTIFICATION_MESSAGE || getArg('message') || 'Claude Code needs your attention';
350
+ const title = getArg('title') || 'Claude Code';
351
+ await showWindowsToast(title, message);
352
+ log('info', 'Notification hook triggered');
353
+ break;
354
+ }
355
+
356
+ default:
357
+ // Unknown hook type - just pass through to claude-flow
358
+ log('info', `Passing through unknown hook: ${hookType}`);
359
+ await runClaudeFlow('hooks', args);
360
+ }
361
+ } catch (err) {
362
+ // Log the error but don't block Claude
363
+ log('error', `Hook exception: ${err.message}`);
364
+ process.exit(0);
365
+ }
366
+
367
+ process.exit(0);
368
+ }
369
+
370
+ // Run the guidance indexer (blocking - used for specific file updates)
371
+ async function runIndexGuidance(specificFile = null) {
372
+ const indexScript = resolve(projectRoot, '.claude/scripts/index-guidance.mjs');
373
+
374
+ if (existsSync(indexScript)) {
375
+ const indexArgs = specificFile ? ['--file', specificFile] : [];
376
+ if (hasArg('force')) indexArgs.push('--force');
377
+ // index-guidance.mjs uses better-sqlite3
378
+ const code = await runCommand('node', [indexScript, ...indexArgs], { silent: true });
379
+ if (code !== 0) {
380
+ log('warn', `index-guidance.mjs exited with code ${code}`);
381
+ }
382
+ return code;
383
+ }
384
+
385
+ log('warn', 'Guidance indexer not found');
386
+ return 0;
387
+ }
388
+
389
+ // Spawn a background process that's truly windowless on Windows
390
+ function spawnWindowless(cmd, args, description) {
391
+ const proc = spawn(cmd, args, {
392
+ cwd: projectRoot,
393
+ stdio: 'ignore',
394
+ detached: true,
395
+ shell: false,
396
+ windowsHide: true
397
+ });
398
+
399
+ proc.unref();
400
+ log('info', `Started ${description} (PID: ${proc.pid})`);
401
+ return proc;
402
+ }
403
+
404
+ // Resolve a moflo npm bin script, falling back to local .claude/scripts/ copy
405
+ function resolveBinOrLocal(binName, localScript) {
406
+ // 1. npm bin from moflo package (always up to date with published version)
407
+ const mofloScript = resolve(projectRoot, 'node_modules/moflo/bin', localScript);
408
+ if (existsSync(mofloScript)) return mofloScript;
409
+
410
+ // 2. npm bin from .bin (symlinked by npm install)
411
+ const npmBin = resolve(projectRoot, 'node_modules/.bin', binName);
412
+ if (existsSync(npmBin)) return npmBin;
413
+
414
+ // 3. Local .claude/scripts/ copy (may be stale but better than nothing)
415
+ const localPath = resolve(projectRoot, '.claude/scripts', localScript);
416
+ if (existsSync(localPath)) return localPath;
417
+
418
+ return null;
419
+ }
420
+
421
+ // Run the guidance indexer in background (non-blocking - used for session start and file changes)
422
+ function runIndexGuidanceBackground(specificFile = null) {
423
+ const indexScript = resolveBinOrLocal('flo-index', 'index-guidance.mjs');
424
+
425
+ if (!indexScript) {
426
+ log('warn', 'Guidance indexer not found (checked npm bin + .claude/scripts/)');
427
+ return;
428
+ }
429
+
430
+ const indexArgs = [indexScript];
431
+ if (specificFile) indexArgs.push('--file', specificFile);
432
+
433
+ const desc = specificFile ? `background indexing file: ${specificFile}` : 'background indexing (full)';
434
+ spawnWindowless('node', indexArgs, desc);
435
+ }
436
+
437
+ // Run structural code map generator in background (non-blocking)
438
+ function runCodeMapBackground() {
439
+ const codeMapScript = resolveBinOrLocal('flo-codemap', 'generate-code-map.mjs');
440
+
441
+ if (!codeMapScript) {
442
+ log('warn', 'Code map generator not found (checked npm bin + .claude/scripts/)');
443
+ return;
444
+ }
445
+
446
+ spawnWindowless('node', [codeMapScript], 'background code map generation');
447
+ }
448
+
449
+ // Run ReasoningBank + MicroLoRA training + EWC++ consolidation in background (non-blocking)
450
+ function runBackgroundTraining() {
451
+ const localCli = getLocalCliPath();
452
+ if (!localCli) {
453
+ log('warn', 'Local CLI not found, skipping background training');
454
+ return;
455
+ }
456
+
457
+ // Pattern types to train with MicroLoRA
458
+ const patternTypes = ['coordination', 'routing', 'debugging'];
459
+
460
+ for (const ptype of patternTypes) {
461
+ spawnWindowless('node', [localCli, 'neural', 'train', '--pattern-type', ptype, '--epochs', '2'], `MicroLoRA training: ${ptype}`);
462
+ }
463
+
464
+ // Run pretrain to update ReasoningBank
465
+ spawnWindowless('node', [localCli, 'hooks', 'pretrain'], 'ReasoningBank pretrain');
466
+
467
+ // Run EWC++ memory consolidation (prevents catastrophic forgetting)
468
+ spawnWindowless('node', [localCli, 'hooks', 'worker', 'dispatch', '--trigger', 'consolidate', '--background'], 'EWC++ consolidation');
469
+
470
+ // Run neural optimize (Int8 quantization, memory compression)
471
+ spawnWindowless('node', [localCli, 'neural', 'optimize'], 'neural optimize');
472
+ }
473
+
474
+ // Run daemon start in background (non-blocking)
475
+ function runDaemonStartBackground() {
476
+ const localCli = getLocalCliPath();
477
+ if (!localCli) {
478
+ log('warn', 'Local CLI not found, skipping daemon start');
479
+ return;
480
+ }
481
+
482
+ spawnWindowless('node', [localCli, 'daemon', 'start', '--quiet'], 'daemon');
483
+ }
484
+
485
+ // Run pretrain in background on session start (non-blocking)
486
+ function runBackgroundPretrain() {
487
+ const localCli = getLocalCliPath();
488
+ if (!localCli) {
489
+ log('warn', 'Local CLI not found, skipping background pretrain');
490
+ return;
491
+ }
492
+
493
+ spawnWindowless('node', [localCli, 'hooks', 'pretrain'], 'background pretrain');
494
+ }
495
+
496
+ // Force HNSW rebuild in background to ensure all processes use identical fresh index
497
+ // This fixes the issue where spawned agents return different search results than CLI/MCP
498
+ function runHNSWRebuildBackground() {
499
+ const localCli = getLocalCliPath();
500
+ if (!localCli) {
501
+ log('warn', 'Local CLI not found, skipping HNSW rebuild');
502
+ return;
503
+ }
504
+
505
+ spawnWindowless('node', [localCli, 'memory', 'rebuild', '--force'], 'HNSW rebuild');
506
+ }
507
+
508
+ // Neural pattern application — now handled by moflo core routing (learned patterns
509
+ // loaded from routing-outcomes.json by hooks-tools.ts getSemanticRouter).
510
+ // No external patch script needed.
511
+
512
+ main().catch((err) => {
513
+ log('error', `Unhandled exception: ${err.message}`);
514
+ process.exit(0);
515
+ });
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Fast session-start launcher — single hook that replaces all SessionStart entries.
4
+ *
5
+ * Spawns background tasks via spawn(detached + unref) and exits immediately.
6
+ *
7
+ * Invoked by: node .claude/scripts/session-start-launcher.mjs
8
+ */
9
+
10
+ import { spawn } from 'child_process';
11
+ import { existsSync } from 'fs';
12
+ import { resolve, dirname } from 'path';
13
+ import { fileURLToPath } from 'url';
14
+
15
+ const __dirname = dirname(fileURLToPath(import.meta.url));
16
+ const projectRoot = resolve(__dirname, '../..');
17
+
18
+ // ── 1. Helper: fire-and-forget a background process ─────────────────────────
19
+ function fireAndForget(cmd, args, label) {
20
+ try {
21
+ const proc = spawn(cmd, args, {
22
+ cwd: projectRoot,
23
+ stdio: 'ignore', // Don't hold stdio pipes open
24
+ detached: true, // New process group
25
+ shell: false,
26
+ windowsHide: true // No console popup on Windows
27
+ });
28
+ proc.unref(); // Let this process exit without waiting
29
+ } catch {
30
+ // If spawn fails (e.g. node not found), don't block startup
31
+ }
32
+ }
33
+
34
+ // ── 2. Reset workflow state for new session ──────────────────────────────────
35
+ import { writeFileSync, mkdirSync } from 'fs';
36
+ const stateDir = resolve(projectRoot, '.claude');
37
+ const stateFile = resolve(stateDir, 'workflow-state.json');
38
+ try {
39
+ if (!existsSync(stateDir)) mkdirSync(stateDir, { recursive: true });
40
+ writeFileSync(stateFile, JSON.stringify({
41
+ tasksCreated: false,
42
+ taskCount: 0,
43
+ memorySearched: false,
44
+ sessionStart: new Date().toISOString()
45
+ }, null, 2));
46
+ } catch {
47
+ // Non-fatal - workflow gate will use defaults
48
+ }
49
+
50
+ // ── 3. Spawn background tasks ───────────────────────────────────────────────
51
+ const localCli = resolve(projectRoot, 'node_modules/moflo/src/@claude-flow/cli/bin/cli.js');
52
+ const hasLocalCli = existsSync(localCli);
53
+
54
+ // hooks.mjs session-start (daemon, indexer, pretrain, HNSW, neural patterns)
55
+ const hooksScript = resolve(projectRoot, '.claude/scripts/hooks.mjs');
56
+ if (existsSync(hooksScript)) {
57
+ fireAndForget('node', [hooksScript, 'session-start'], 'hooks session-start');
58
+ }
59
+
60
+ // Patches are now baked into moflo@4.0.0 source — no runtime patching needed.
61
+
62
+ // ── 4. Done — exit immediately ──────────────────────────────────────────────
63
+ process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moflo",
3
- "version": "4.6.10",
3
+ "version": "4.6.12",
4
4
  "description": "MoFlo — AI agent orchestration for Claude Code. Forked from ruflo/claude-flow with patches applied to source, plus feature-level orchestration.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -56,7 +56,8 @@
56
56
  "security:audit": "npm audit --audit-level high",
57
57
  "security:fix": "npm audit fix",
58
58
  "security:test": "npm run test:security",
59
- "moflo:security": "npm run security:audit && npm run security:test"
59
+ "moflo:security": "npm run security:audit && npm run security:test",
60
+ "version": "node -e \"const v=require('./package.json').version;const p='src/@claude-flow/cli/package.json';const j=JSON.parse(require('fs').readFileSync(p));j.version=v;require('fs').writeFileSync(p,JSON.stringify(j,null,2)+'\\n')\" && git add src/@claude-flow/cli/package.json"
60
61
  },
61
62
  "dependencies": {
62
63
  "@ruvector/learning-wasm": "^0.1.29",
@@ -29,6 +29,9 @@ export function generateMCPConfig(options) {
29
29
  const npmEnv = {
30
30
  npm_config_update_notifier: 'false',
31
31
  };
32
+ // When toolDefer is true, emit "deferred" so Claude Code loads schemas on
33
+ // demand via ToolSearch instead of putting 150+ schemas into context at startup.
34
+ const deferProps = config.toolDefer ? { toolDefer: 'deferred' } : {};
32
35
  // Claude Flow MCP server (core)
33
36
  if (config.claudeFlow) {
34
37
  mcpServers['claude-flow'] = createMCPServerEntry(['@claude-flow/cli@latest', 'mcp', 'start'], {
@@ -38,15 +41,15 @@ export function generateMCPConfig(options) {
38
41
  CLAUDE_FLOW_TOPOLOGY: options.runtime.topology,
39
42
  CLAUDE_FLOW_MAX_AGENTS: String(options.runtime.maxAgents),
40
43
  CLAUDE_FLOW_MEMORY_BACKEND: options.runtime.memoryBackend,
41
- }, { autoStart: config.autoStart });
44
+ }, { autoStart: config.autoStart, ...deferProps });
42
45
  }
43
46
  // Ruv-Swarm MCP server (enhanced coordination)
44
47
  if (config.ruvSwarm) {
45
- mcpServers['ruv-swarm'] = createMCPServerEntry(['ruv-swarm', 'mcp', 'start'], { ...npmEnv }, { optional: true });
48
+ mcpServers['ruv-swarm'] = createMCPServerEntry(['ruv-swarm', 'mcp', 'start'], { ...npmEnv }, { optional: true, ...deferProps });
46
49
  }
47
50
  // Flow Nexus MCP server (cloud features)
48
51
  if (config.flowNexus) {
49
- mcpServers['flow-nexus'] = createMCPServerEntry(['flow-nexus@latest', 'mcp', 'start'], { ...npmEnv }, { optional: true, requiresAuth: true });
52
+ mcpServers['flow-nexus'] = createMCPServerEntry(['flow-nexus@latest', 'mcp', 'start'], { ...npmEnv }, { optional: true, requiresAuth: true, ...deferProps });
50
53
  }
51
54
  return { mcpServers };
52
55
  }
@@ -20,9 +20,9 @@ import * as path from 'path';
20
20
  async function runWizard(root) {
21
21
  const { confirm, input } = await import('../prompt.js');
22
22
  // Detect project structure
23
- const guidanceCandidates = ['.claude/guidance', 'docs/guides', 'docs'];
23
+ const guidanceCandidates = ['.claude/guidance', 'docs/guides', 'docs', 'architecture', 'adr', '.cursor/rules'];
24
24
  const detectedGuidance = guidanceCandidates.filter(d => fs.existsSync(path.join(root, d)));
25
- const srcCandidates = ['src', 'packages', 'lib', 'app', 'apps', 'services'];
25
+ const srcCandidates = ['src', 'packages', 'lib', 'app', 'apps', 'services', 'server', 'client'];
26
26
  const detectedSrc = srcCandidates.filter(d => fs.existsSync(path.join(root, d)));
27
27
  // Ask questions
28
28
  const guidance = await confirm({
@@ -67,11 +67,11 @@ async function runWizard(root) {
67
67
  * Get default answers (--yes mode).
68
68
  */
69
69
  function defaultAnswers(root) {
70
- const guidanceCandidates = ['.claude/guidance', 'docs/guides', 'docs'];
70
+ const guidanceCandidates = ['.claude/guidance', 'docs/guides', 'docs', 'architecture', 'adr', '.cursor/rules'];
71
71
  const guidanceDirs = guidanceCandidates.filter(d => fs.existsSync(path.join(root, d)));
72
72
  if (guidanceDirs.length === 0)
73
73
  guidanceDirs.push('.claude/guidance');
74
- const srcCandidates = ['src', 'packages', 'lib', 'app', 'apps', 'services'];
74
+ const srcCandidates = ['src', 'packages', 'lib', 'app', 'apps', 'services', 'server', 'client'];
75
75
  const srcDirs = srcCandidates.filter(d => fs.existsSync(path.join(root, d)));
76
76
  if (srcDirs.length === 0)
77
77
  srcDirs.push('src');
@@ -180,15 +180,22 @@ hooks:
180
180
  session_restore: true # Restore session state on start
181
181
  notification: true # Hook into Claude Code notifications
182
182
 
183
+ # MCP server options
184
+ mcp:
185
+ tool_defer: deferred # Defer 150+ tool schemas; loaded on demand via ToolSearch
186
+ auto_start: false # Auto-start MCP server on session begin
187
+
183
188
  # Status line display (shown at bottom of Claude Code)
184
- # mode: "single-line" (concise, default) or "dashboard" (full multi-line)
189
+ # mode: "compact" (default), "single-line", or "dashboard" (full multi-line)
185
190
  status_line:
186
191
  enabled: true
187
- mode: single-line
192
+ mode: compact
188
193
  branding: "MoFlo V4"
189
194
  show_git: true
190
- show_model: true
191
195
  show_session: true
196
+ show_swarm: true
197
+ show_agentdb: true
198
+ show_mcp: true
192
199
 
193
200
  # Model preferences (haiku, sonnet, opus)
194
201
  models:
@@ -401,419 +408,23 @@ function generateSkill(root, force) {
401
408
  if (!fs.existsSync(skillDir)) {
402
409
  fs.mkdirSync(skillDir, { recursive: true });
403
410
  }
404
- const skillContent = `---
405
- name: flo
406
- description: MoFlo ticket workflow - analyze and execute GitHub issues
407
- arguments: "[options] <issue-number>"
408
- ---
409
-
410
- # /flo - MoFlo Ticket Workflow
411
-
412
- Research, enhance, and execute GitHub issues automatically.
413
-
414
- **Arguments:** $ARGUMENTS
415
-
416
- ## Usage
417
-
418
- \`\`\`
419
- /flo <issue-number> # Full workflow with SWARM (default)
420
- /flo -e <issue-number> # Enhance only: research and update ticket, then STOP
421
- /flo --enhance <issue-number> # Same as -e
422
- /flo -r <issue-number> # Research only: analyze issue, output findings
423
- /flo --research <issue-number> # Same as -r
424
- \`\`\`
425
-
426
- Also available as \`/fl\` (shorthand alias).
427
-
428
- ### Execution Mode (how work is done)
429
-
430
- \`\`\`
431
- /flo 123 # SWARM mode (default) - multi-agent coordination
432
- /flo -sw 123 # SWARM mode (explicit)
433
- /flo --swarm 123 # Same as -sw
434
- /flo -hv 123 # HIVE-MIND mode - consensus-based coordination
435
- /flo --hive 123 # Same as -hv
436
- /flo -n 123 # NAKED mode - single Claude, no agents
437
- /flo --naked 123 # Same as -n
438
- \`\`\`
439
-
440
- ### Epic Handling
441
-
442
- \`\`\`
443
- /flo 42 # If #42 is an epic, processes all stories sequentially
444
- \`\`\`
445
-
446
- **Epic Detection:** Issues with \`epic\` label or containing \`## Stories\` / \`## Tasks\` sections are automatically detected as epics.
447
-
448
- **Sequential Processing:** When an epic is selected:
449
- 1. List all child stories/tasks (from checklist or linked issues)
450
- 2. Process each story **one at a time** in order
451
- 3. Each story goes through the full workflow (research -> enhance -> implement -> test -> PR)
452
- 4. After each story's PR is created, move to the next story
453
- 5. Continue until all stories are complete
454
-
455
- ### Combined Examples
456
-
457
- \`\`\`
458
- /flo 123 # Swarm + full workflow (default) - includes ALL tests
459
- /flo 42 # If #42 is epic, processes stories sequentially
460
- /flo -e 123 # Swarm + enhance only (no implementation)
461
- /flo -hv -e 123 # Hive-mind + enhance only
462
- /flo -n -r 123 # Naked + research only
463
- /flo --swarm --enhance 123 # Explicit swarm + enhance only
464
- /flo -n 123 # Naked + full workflow (still runs all tests)
465
- \`\`\`
466
-
467
- ## SWARM IS MANDATORY BY DEFAULT
468
-
469
- Even if a task "looks simple", you MUST use swarm coordination unless
470
- the user explicitly passes -n/--naked. "Simple" is a trap. Tasks have
471
- hidden complexity. Swarm catches it.
472
-
473
- THE ONLY WAY TO SKIP SWARM: User passes -n or --naked explicitly.
474
-
475
- ## COMPREHENSIVE TESTING REQUIREMENT
476
-
477
- ALL tests MUST pass BEFORE PR creation - NO EXCEPTIONS.
478
- - Unit Tests: MANDATORY for all new/modified code
479
- - Integration Tests: MANDATORY for API endpoints and services
480
- - E2E Tests: MANDATORY for user-facing features
481
- PR CANNOT BE CREATED until all relevant tests pass.
482
-
483
- ## Workflow Overview
484
-
485
- \`\`\`
486
- Research -> Enhance -> Execute -> Testing -> Simplify -> PR+Done
487
-
488
- Research: Fetch issue, search memory, read guidance, find files
489
- Enhance: Update GitHub issue with tech analysis, affected files, impl plan
490
- Execute: Assign self, create branch, implement changes
491
- Testing: Unit + Integration + E2E tests (ALL MUST PASS - gate)
492
- Simplify: Run /simplify on changed code (gate - must run before PR)
493
- PR+Done: Create PR, update issue status, store learnings
494
- \`\`\`
495
-
496
- ### Workflow Gates
497
-
498
- | Gate | Requirement | Blocked Action |
499
- |------|-------------|----------------|
500
- | **Testing Gate** | Unit + Integration + E2E must pass | PR creation |
501
- | **Simplification Gate** | /simplify must run on changed files | PR creation |
502
-
503
- ### Execution Mode (applies to all phases)
504
-
505
- | Mode | Description |
506
- |------|-------------|
507
- | **SWARM** (default) | Multi-agent via Task tool: researcher, coder, tester, reviewer |
508
- | **HIVE-MIND** (-hv) | Consensus-based coordination for architecture decisions |
509
- | **NAKED** (-n) | Single Claude, no agent spawning. Only when user explicitly requests. |
510
-
511
- ## Phase 1: Research (-r or default first step)
512
-
513
- ### 1.1 Fetch Issue Details
514
- \`\`\`bash
515
- gh issue view <issue-number> --json number,title,body,labels,state,assignees,comments,milestone
516
- \`\`\`
517
-
518
- ### 1.2 Check Enhancement Status
519
- Look for \`## Implementation Plan\` marker in issue body.
520
- - **If present**: Issue already enhanced, skip to execute or confirm
521
- - **If absent**: Proceed with research and enhancement
522
-
523
- ### 1.3 Search Memory FIRST
524
- ALWAYS search memory BEFORE reading guidance or docs files.
525
- Memory has file paths, context, and patterns - often all you need.
526
- Only read guidance files if memory search returns zero relevant results.
527
-
528
- \`\`\`bash
529
- npx flo memory search --query "<issue title keywords>" --namespace patterns
530
- npx flo memory search --query "<domain keywords>" --namespace guidance
531
- \`\`\`
532
-
533
- Or via MCP: \`mcp__claude-flow__memory_search\`
534
-
535
- ### 1.4 Read Guidance Docs (ONLY if memory insufficient)
536
- **Only if memory search returned < 3 relevant results**, read guidance files:
537
- - Bug -> testing patterns, error handling
538
- - Feature -> domain model, architecture
539
- - UI -> frontend patterns, components
540
-
541
- ### 1.5 Research Codebase
542
- Use Task tool with Explore agent to find:
543
- - Affected files and their current state
544
- - Related code and dependencies
545
- - Existing patterns to follow
546
- - Test coverage gaps
547
-
548
- ## Phase 2: Enhance (-e includes research + enhancement)
549
-
550
- ### 2.1 Build Enhancement
551
- Compile research into structured enhancement:
552
-
553
- **Technical Analysis** - Root cause (bugs) or approach (features), impact, risk factors
554
-
555
- **Affected Files** - Files to modify (with line numbers), new files, deletions
556
-
557
- **Implementation Plan** - Numbered steps with clear actions, dependencies, decision points
558
-
559
- **Test Plan** - Unit tests to add/update, integration tests needed, manual testing checklist
560
-
561
- **Estimates** - Complexity (Low/Medium/High), scope (# files, # new tests)
562
-
563
- ### 2.2 Update GitHub Issue
564
- \`\`\`bash
565
- gh issue edit <issue-number> --body "<original body + Technical Analysis + Affected Files + Implementation Plan + Test Plan + Estimates>"
566
- \`\`\`
567
-
568
- ### 2.3 Add Enhancement Comment
569
- \`\`\`bash
570
- gh issue comment <issue-number> --body "Issue enhanced with implementation plan. Ready for execution."
571
- \`\`\`
572
-
573
- ## Phase 3: Execute (default, runs automatically after enhance)
574
-
575
- ### 3.1 Assign Issue and Update Status
576
- \`\`\`bash
577
- gh issue edit <issue-number> --add-assignee @me
578
- gh issue edit <issue-number> --add-label "in-progress"
579
- \`\`\`
580
-
581
- ### 3.2 Create Branch
582
- \`\`\`bash
583
- git checkout main && git pull origin main
584
- git checkout -b <type>/<issue-number>-<short-desc>
585
- \`\`\`
586
- Types: \`feature/\`, \`fix/\`, \`refactor/\`, \`docs/\`
587
-
588
- ### 3.3 Implement
589
- Follow the implementation plan from the enhanced issue. No prompts - execute all steps.
590
-
591
- ## Phase 4: Testing (MANDATORY GATE)
592
-
593
- This is NOT optional. ALL applicable test types must pass for the change type.
594
- WORKFLOW STOPS HERE until tests pass. No shortcuts. No exceptions.
595
-
596
- ### 4.1 Write and Run Tests
597
- Write unit, integration, and E2E tests as appropriate for the change type.
598
- Use the project's existing test runner and patterns.
599
-
600
- ### 4.2 Test Auto-Fix Loop
601
- If any tests fail, enter the auto-fix loop (max 3 retries OR 10 minutes):
602
- 1. Run all tests
603
- 2. If ALL pass -> proceed to simplification
604
- 3. If ANY fail: analyze failure, fix test or implementation code, retry
605
- 4. If retries exhausted -> STOP and report to user
606
-
607
- ## Phase 4.5: Code Simplification (MANDATORY)
608
-
609
- The built-in /simplify command reviews ALL changed code for:
610
- - Reuse opportunities and code quality
611
- - Efficiency improvements
612
- - Consistency with existing codebase patterns
613
- - Preserves ALL functionality - no behavior changes
614
-
615
- If /simplify makes changes -> re-run tests to confirm nothing broke.
616
- If re-tests fail -> revert changes, proceed with original code.
617
-
618
- ## Phase 5: Commit and PR (only after tests pass)
619
-
620
- ### 5.1 Commit
621
- \`\`\`bash
622
- git add <specific files>
623
- git commit -m "type(scope): description
624
-
625
- Closes #<issue-number>
626
-
627
- Co-Authored-By: Claude <noreply@anthropic.com>"
628
- \`\`\`
629
-
630
- ### 5.2 Create PR
631
- \`\`\`bash
632
- git push -u origin <branch-name>
633
- gh pr create --title "type(scope): description" --body "## Summary
634
- <brief description>
635
-
636
- ## Changes
637
- <bullet list>
638
-
639
- ## Testing
640
- - [x] Unit tests pass
641
- - [x] Integration tests pass
642
- - [x] E2E tests pass
643
- - [ ] Manual testing done
644
-
645
- Closes #<issue-number>"
646
- \`\`\`
647
-
648
- ### 5.3 Update Issue Status
649
- \`\`\`bash
650
- gh issue edit <issue-number> --remove-label "in-progress" --add-label "ready-for-review"
651
- gh issue comment <issue-number> --body "PR created: <pr-url>"
652
- \`\`\`
653
-
654
- ## Epic Handling
655
-
656
- ### Detecting Epics
657
-
658
- An issue is an **epic** if:
659
- 1. It has the \`epic\` label, OR
660
- 2. Its body contains \`## Stories\` or \`## Tasks\` sections, OR
661
- 3. It has linked child issues (via \`- [ ] #123\` checklist format)
662
-
663
- ### Epic Processing Flow
664
-
665
- 1. DETECT EPIC - Check labels, parse body for ## Stories / ## Tasks, extract issue references
666
- 2. LIST ALL STORIES - Extract from checklist, order top-to-bottom as listed
667
- 3. SEQUENTIAL PROCESSING - For each story: run full /flo workflow, wait for PR, update checklist
668
- 4. COMPLETION - All stories have PRs, epic marked as ready-for-review
669
-
670
- ONE STORY AT A TIME - NO PARALLEL STORY EXECUTION.
671
- Each story must complete (PR created) before starting next.
672
-
673
- ### Epic Detection Code
674
-
675
- \`\`\`javascript
676
- function isEpic(issue) {
677
- if (issue.labels?.some(l => l.name === 'epic')) return true;
678
- if (issue.body?.includes('## Stories') || issue.body?.includes('## Tasks')) return true;
679
- const linkedIssuePattern = /- \\[[ x]\\] #\\d+/;
680
- if (linkedIssuePattern.test(issue.body)) return true;
681
- return false;
682
- }
683
-
684
- function extractStories(epicBody) {
685
- const stories = [];
686
- const pattern = /- \\[[ ]\\] #(\\d+)/g;
687
- let match;
688
- while ((match = pattern.exec(epicBody)) !== null) {
689
- stories.push(parseInt(match[1]));
690
- }
691
- return stories;
692
- }
693
- \`\`\`
694
-
695
- ## Parse Arguments
696
-
697
- \`\`\`javascript
698
- const args = "$ARGUMENTS".trim().split(/\\s+/);
699
- let workflowMode = "full"; // full, enhance, research
700
- let execMode = "swarm"; // swarm (default), hive, naked
701
- let issueNumber = null;
702
-
703
- for (let i = 0; i < args.length; i++) {
704
- const arg = args[i];
705
-
706
- // Workflow mode (what to do)
707
- if (arg === "-e" || arg === "--enhance") {
708
- workflowMode = "enhance";
709
- } else if (arg === "-r" || arg === "--research") {
710
- workflowMode = "research";
711
- }
712
-
713
- // Execution mode (how to do it)
714
- else if (arg === "-sw" || arg === "--swarm") {
715
- execMode = "swarm";
716
- } else if (arg === "-hv" || arg === "--hive") {
717
- execMode = "hive";
718
- } else if (arg === "-n" || arg === "--naked") {
719
- execMode = "naked";
720
- }
721
-
722
- // Issue number
723
- else if (/^\\d+$/.test(arg)) {
724
- issueNumber = arg;
725
- }
726
- }
727
-
728
- if (!issueNumber) {
729
- throw new Error("Issue number required. Usage: /flo <issue-number>");
730
- }
731
-
732
- // Log execution mode to prevent silent skipping
733
- console.log("Execution mode: " + execMode.toUpperCase());
734
- if (execMode === "swarm") {
735
- console.log("SWARM MODE: Will spawn agents via Task tool. Do NOT skip this.");
736
- }
737
- console.log("TESTING: Unit + Integration + E2E tests REQUIRED before PR.");
738
- console.log("SIMPLIFY: /simplify command runs on changed code before PR.");
739
- \`\`\`
740
-
741
- ## Execution Flow
742
-
743
- ### Workflow Modes (what to do)
744
-
745
- | Mode | Command | Steps | Stops After |
746
- |------|---------|-------|-------------|
747
- | **Full** (default) | \`/flo 123\` | Research -> Enhance -> Implement -> Test -> Simplify -> PR | PR created |
748
- | **Epic** | \`/flo 42\` (epic) | For each story: Full workflow sequentially | All story PRs created |
749
- | **Enhance** | \`/flo -e 123\` | Research -> Enhance | Issue updated |
750
- | **Research** | \`/flo -r 123\` | Research | Findings output |
751
-
752
- ### Execution Modes (how to do it)
753
-
754
- | Mode | Flag | Description | When to Use |
755
- |------|------|-------------|-------------|
756
- | **Swarm** (DEFAULT) | \`-sw\`, \`--swarm\` | Multi-agent via Task tool | Always, unless explicitly overridden |
757
- | **Hive-Mind** | \`-hv\`, \`--hive\` | Consensus-based coordination | Architecture decisions, tradeoffs |
758
- | **Naked** | \`-n\`, \`--naked\` | Single Claude, no agents | User explicitly wants simple mode |
759
-
760
- ## Execution Mode Details
761
-
762
- ### SWARM Mode (Default) - ALWAYS USE UNLESS TOLD OTHERWISE
763
-
764
- You MUST use the Task tool to spawn agents. No exceptions.
765
-
766
- **Swarm spawns these agents via Task tool:**
767
- - \`researcher\` - Analyzes issue, searches memory, finds patterns
768
- - \`coder\` - Implements changes following plan
769
- - \`tester\` - Writes and runs tests
770
- - \`/simplify\` - Built-in command that reviews changed code before PR
771
- - \`reviewer\` - Reviews code before PR
772
-
773
- **Swarm execution pattern:**
774
- \`\`\`javascript
775
- // 1. Create task list FIRST
776
- TaskCreate({ subject: "Research issue #123", ... })
777
- TaskCreate({ subject: "Implement changes", ... })
778
- TaskCreate({ subject: "Test implementation", ... })
779
- TaskCreate({ subject: "Run /simplify on changed files", ... })
780
- TaskCreate({ subject: "Review and PR", ... })
781
-
782
- // 2. Init swarm
783
- Bash("npx flo swarm init --topology hierarchical --max-agents 8 --strategy specialized")
784
-
785
- // 3. Spawn agents with Task tool (run_in_background: true)
786
- Task({ prompt: "...", subagent_type: "researcher", run_in_background: true })
787
- Task({ prompt: "...", subagent_type: "coder", run_in_background: true })
788
-
789
- // 4. Wait for results, synthesize, continue
790
- \`\`\`
791
-
792
- ### HIVE-MIND Mode (-hv, --hive)
793
-
794
- Use for consensus-based decisions:
795
- - Architecture choices
796
- - Approach tradeoffs
797
- - Design decisions with multiple valid options
798
-
799
- ### NAKED Mode (-n, --naked)
800
-
801
- **Only when user explicitly requests.** Single Claude execution without agents.
802
- - Still uses Task tool for tracking
803
- - Still creates tasks for visibility
804
- - Just doesn't spawn multiple agents
805
-
806
- ---
807
-
808
- **Full mode executes without prompts.** It will:
809
- 1. Research the issue and codebase
810
- 2. Enhance the GitHub issue with implementation plan
811
- 3. Assign issue to self, add "in-progress" label
812
- 4. Create branch, implement, test
813
- 5. Run /simplify on changed code, re-test if changes made
814
- 6. Commit, create PR, update issue status
815
- 7. Store learnings
816
- `;
411
+ // Copy static SKILL.md from moflo package instead of generating it
412
+ let skillContent = '';
413
+ const staticSkillCandidates = [
414
+ // Installed via npm
415
+ path.join(root, 'node_modules', 'moflo', '.claude', 'skills', 'flo', 'SKILL.md'),
416
+ // Running from moflo repo itself
417
+ path.join(path.dirname(path.dirname(path.dirname(path.dirname(path.dirname(__dirname))))), '.claude', 'skills', 'flo', 'SKILL.md'),
418
+ ];
419
+ for (const candidate of staticSkillCandidates) {
420
+ if (fs.existsSync(candidate)) {
421
+ skillContent = fs.readFileSync(candidate, 'utf-8');
422
+ break;
423
+ }
424
+ }
425
+ if (!skillContent) {
426
+ return { name: '.claude/skills/flo/', status: 'failed', detail: 'Could not find SKILL.md in moflo package' };
427
+ }
817
428
  fs.writeFileSync(skillFile, skillContent, 'utf-8');
818
429
  // Create /fl alias (same content)
819
430
  if (!fs.existsSync(aliasDir)) {
@@ -156,6 +156,8 @@ export interface MCPConfig {
156
156
  autoStart: boolean;
157
157
  /** Server port */
158
158
  port: number;
159
+ /** Defer tool schema loading — schemas loaded on demand via ToolSearch */
160
+ toolDefer: boolean;
159
161
  }
160
162
  /**
161
163
  * Runtime configuration (.claude-flow/)
@@ -118,6 +118,7 @@ export const DEFAULT_INIT_OPTIONS = {
118
118
  flowNexus: false,
119
119
  autoStart: false,
120
120
  port: 3000,
121
+ toolDefer: true,
121
122
  },
122
123
  runtime: {
123
124
  topology: 'hierarchical-mesh',
@@ -245,6 +246,7 @@ export const FULL_INIT_OPTIONS = {
245
246
  flowNexus: true,
246
247
  autoStart: false,
247
248
  port: 3000,
249
+ toolDefer: true,
248
250
  },
249
251
  embeddings: {
250
252
  enabled: true,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moflo/cli",
3
- "version": "4.6.10",
3
+ "version": "4.6.12",
4
4
  "type": "module",
5
5
  "description": "MoFlo CLI — AI agent orchestration with specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",