moflo 4.6.11 → 4.7.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 (35) hide show
  1. package/.claude/helpers/hook-handler.cjs +35 -6
  2. package/.claude/settings.json +4 -4
  3. package/.claude/workflow-state.json +5 -0
  4. package/README.md +11 -1
  5. package/bin/hooks.mjs +519 -0
  6. package/bin/session-start-launcher.mjs +63 -0
  7. package/bin/setup-project.mjs +1 -1
  8. package/package.json +3 -2
  9. package/src/@claude-flow/cli/README.md +452 -7536
  10. package/src/@claude-flow/cli/dist/src/commands/doctor.js +1 -1
  11. package/src/@claude-flow/cli/dist/src/commands/embeddings.js +4 -4
  12. package/src/@claude-flow/cli/dist/src/commands/init.js +35 -8
  13. package/src/@claude-flow/cli/dist/src/commands/swarm.js +2 -2
  14. package/src/@claude-flow/cli/dist/src/init/claudemd-generator.js +316 -294
  15. package/src/@claude-flow/cli/dist/src/init/executor.js +461 -465
  16. package/src/@claude-flow/cli/dist/src/init/helpers-generator.d.ts +0 -36
  17. package/src/@claude-flow/cli/dist/src/init/helpers-generator.js +146 -1124
  18. package/src/@claude-flow/cli/dist/src/init/index.d.ts +1 -1
  19. package/src/@claude-flow/cli/dist/src/init/index.js +1 -1
  20. package/src/@claude-flow/cli/dist/src/init/mcp-generator.js +6 -3
  21. package/src/@claude-flow/cli/dist/src/init/moflo-init.js +108 -424
  22. package/src/@claude-flow/cli/dist/src/init/settings-generator.js +50 -120
  23. package/src/@claude-flow/cli/dist/src/init/types.d.ts +2 -0
  24. package/src/@claude-flow/cli/dist/src/init/types.js +2 -0
  25. package/src/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +275 -32
  26. package/src/@claude-flow/cli/dist/src/plugins/store/discovery.js +4 -204
  27. package/src/@claude-flow/cli/dist/src/plugins/tests/standalone-test.js +4 -4
  28. package/src/@claude-flow/cli/dist/src/runtime/headless.d.ts +3 -3
  29. package/src/@claude-flow/cli/dist/src/runtime/headless.js +31 -31
  30. package/src/@claude-flow/cli/dist/src/services/agentic-flow-bridge.d.ts +3 -3
  31. package/src/@claude-flow/cli/dist/src/services/agentic-flow-bridge.js +3 -1
  32. package/src/@claude-flow/cli/dist/src/services/headless-worker-executor.js +14 -0
  33. package/src/@claude-flow/cli/dist/src/services/workflow-gate.js +21 -1
  34. package/src/@claude-flow/cli/dist/src/transfer/store/tests/standalone-test.js +4 -4
  35. package/src/@claude-flow/cli/package.json +1 -1
@@ -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
  },
@@ -165,10 +165,10 @@
165
165
  },
166
166
  "attribution": {
167
167
  "commit": "Co-Authored-By: moflo <noreply@motailz.com>",
168
- "pr": "\ud83e\udd16 Generated with [claude-flow](https://github.com/eric-cielo/moflo)"
168
+ "pr": "\ud83e\udd16 Generated with [moflo](https://github.com/eric-cielo/moflo)"
169
169
  },
170
170
  "claudeFlow": {
171
- "version": "3.5.2",
171
+ "version": "4.7.1",
172
172
  "enabled": true,
173
173
  "modelPreferences": {
174
174
  "default": "claude-opus-4-6",
@@ -275,9 +275,9 @@
275
275
  },
276
276
  "mcpServers": {
277
277
  "claude-flow": {
278
- "command": "node",
278
+ "command": "npx",
279
279
  "args": [
280
- "/workspaces/claude-flow/v3/@claude-flow/cli/bin/cli.js",
280
+ "moflo",
281
281
  "mcp",
282
282
  "start"
283
283
  ]
@@ -0,0 +1,5 @@
1
+ {
2
+ "tasksCreated": false,
3
+ "taskCount": 0,
4
+ "memorySearched": true
5
+ }
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/eric-cielo/moflo/main/docs/moflo.png?v=4" alt="MoFlo" />
2
+ <img src="https://raw.githubusercontent.com/eric-cielo/moflo/main/docs/Moflo_md.png?v=5" alt="MoFlo" />
3
3
  </p>
4
4
 
5
5
  # MoFlo
@@ -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,519 @@
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, 'bin/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 indexCandidates = [
373
+ resolve(dirname(fileURLToPath(import.meta.url)), 'index-guidance.mjs'),
374
+ resolve(projectRoot, '.claude/scripts/index-guidance.mjs'),
375
+ ];
376
+ const indexScript = indexCandidates.find(p => existsSync(p));
377
+
378
+ if (indexScript) {
379
+ const indexArgs = specificFile ? ['--file', specificFile] : [];
380
+ if (hasArg('force')) indexArgs.push('--force');
381
+ // index-guidance.mjs uses better-sqlite3
382
+ const code = await runCommand('node', [indexScript, ...indexArgs], { silent: true });
383
+ if (code !== 0) {
384
+ log('warn', `index-guidance.mjs exited with code ${code}`);
385
+ }
386
+ return code;
387
+ }
388
+
389
+ log('warn', 'Guidance indexer not found');
390
+ return 0;
391
+ }
392
+
393
+ // Spawn a background process that's truly windowless on Windows
394
+ function spawnWindowless(cmd, args, description) {
395
+ const proc = spawn(cmd, args, {
396
+ cwd: projectRoot,
397
+ stdio: 'ignore',
398
+ detached: true,
399
+ shell: false,
400
+ windowsHide: true
401
+ });
402
+
403
+ proc.unref();
404
+ log('info', `Started ${description} (PID: ${proc.pid})`);
405
+ return proc;
406
+ }
407
+
408
+ // Resolve a moflo npm bin script, falling back to local .claude/scripts/ copy
409
+ function resolveBinOrLocal(binName, localScript) {
410
+ // 1. npm bin from moflo package (always up to date with published version)
411
+ const mofloScript = resolve(projectRoot, 'node_modules/moflo/bin', localScript);
412
+ if (existsSync(mofloScript)) return mofloScript;
413
+
414
+ // 2. npm bin from .bin (symlinked by npm install)
415
+ const npmBin = resolve(projectRoot, 'node_modules/.bin', binName);
416
+ if (existsSync(npmBin)) return npmBin;
417
+
418
+ // 3. Local .claude/scripts/ copy (may be stale but better than nothing)
419
+ const localPath = resolve(projectRoot, '.claude/scripts', localScript);
420
+ if (existsSync(localPath)) return localPath;
421
+
422
+ return null;
423
+ }
424
+
425
+ // Run the guidance indexer in background (non-blocking - used for session start and file changes)
426
+ function runIndexGuidanceBackground(specificFile = null) {
427
+ const indexScript = resolveBinOrLocal('flo-index', 'index-guidance.mjs');
428
+
429
+ if (!indexScript) {
430
+ log('warn', 'Guidance indexer not found (checked npm bin + .claude/scripts/)');
431
+ return;
432
+ }
433
+
434
+ const indexArgs = [indexScript];
435
+ if (specificFile) indexArgs.push('--file', specificFile);
436
+
437
+ const desc = specificFile ? `background indexing file: ${specificFile}` : 'background indexing (full)';
438
+ spawnWindowless('node', indexArgs, desc);
439
+ }
440
+
441
+ // Run structural code map generator in background (non-blocking)
442
+ function runCodeMapBackground() {
443
+ const codeMapScript = resolveBinOrLocal('flo-codemap', 'generate-code-map.mjs');
444
+
445
+ if (!codeMapScript) {
446
+ log('warn', 'Code map generator not found (checked npm bin + .claude/scripts/)');
447
+ return;
448
+ }
449
+
450
+ spawnWindowless('node', [codeMapScript], 'background code map generation');
451
+ }
452
+
453
+ // Run ReasoningBank + MicroLoRA training + EWC++ consolidation in background (non-blocking)
454
+ function runBackgroundTraining() {
455
+ const localCli = getLocalCliPath();
456
+ if (!localCli) {
457
+ log('warn', 'Local CLI not found, skipping background training');
458
+ return;
459
+ }
460
+
461
+ // Pattern types to train with MicroLoRA
462
+ const patternTypes = ['coordination', 'routing', 'debugging'];
463
+
464
+ for (const ptype of patternTypes) {
465
+ spawnWindowless('node', [localCli, 'neural', 'train', '--pattern-type', ptype, '--epochs', '2'], `MicroLoRA training: ${ptype}`);
466
+ }
467
+
468
+ // Run pretrain to update ReasoningBank
469
+ spawnWindowless('node', [localCli, 'hooks', 'pretrain'], 'ReasoningBank pretrain');
470
+
471
+ // Run EWC++ memory consolidation (prevents catastrophic forgetting)
472
+ spawnWindowless('node', [localCli, 'hooks', 'worker', 'dispatch', '--trigger', 'consolidate', '--background'], 'EWC++ consolidation');
473
+
474
+ // Run neural optimize (Int8 quantization, memory compression)
475
+ spawnWindowless('node', [localCli, 'neural', 'optimize'], 'neural optimize');
476
+ }
477
+
478
+ // Run daemon start in background (non-blocking)
479
+ function runDaemonStartBackground() {
480
+ const localCli = getLocalCliPath();
481
+ if (!localCli) {
482
+ log('warn', 'Local CLI not found, skipping daemon start');
483
+ return;
484
+ }
485
+
486
+ spawnWindowless('node', [localCli, 'daemon', 'start', '--quiet'], 'daemon');
487
+ }
488
+
489
+ // Run pretrain in background on session start (non-blocking)
490
+ function runBackgroundPretrain() {
491
+ const localCli = getLocalCliPath();
492
+ if (!localCli) {
493
+ log('warn', 'Local CLI not found, skipping background pretrain');
494
+ return;
495
+ }
496
+
497
+ spawnWindowless('node', [localCli, 'hooks', 'pretrain'], 'background pretrain');
498
+ }
499
+
500
+ // Force HNSW rebuild in background to ensure all processes use identical fresh index
501
+ // This fixes the issue where spawned agents return different search results than CLI/MCP
502
+ function runHNSWRebuildBackground() {
503
+ const localCli = getLocalCliPath();
504
+ if (!localCli) {
505
+ log('warn', 'Local CLI not found, skipping HNSW rebuild');
506
+ return;
507
+ }
508
+
509
+ spawnWindowless('node', [localCli, 'memory', 'rebuild', '--force'], 'HNSW rebuild');
510
+ }
511
+
512
+ // Neural pattern application — now handled by moflo core routing (learned patterns
513
+ // loaded from routing-outcomes.json by hooks-tools.ts getSemanticRouter).
514
+ // No external patch script needed.
515
+
516
+ main().catch((err) => {
517
+ log('error', `Unhandled exception: ${err.message}`);
518
+ process.exit(0);
519
+ });