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.
- package/.claude/helpers/hook-handler.cjs +35 -6
- package/.claude/settings.json +4 -4
- package/.claude/workflow-state.json +5 -0
- package/README.md +11 -1
- package/bin/hooks.mjs +519 -0
- package/bin/session-start-launcher.mjs +63 -0
- package/bin/setup-project.mjs +1 -1
- package/package.json +3 -2
- package/src/@claude-flow/cli/README.md +452 -7536
- package/src/@claude-flow/cli/dist/src/commands/doctor.js +1 -1
- package/src/@claude-flow/cli/dist/src/commands/embeddings.js +4 -4
- package/src/@claude-flow/cli/dist/src/commands/init.js +35 -8
- package/src/@claude-flow/cli/dist/src/commands/swarm.js +2 -2
- package/src/@claude-flow/cli/dist/src/init/claudemd-generator.js +316 -294
- package/src/@claude-flow/cli/dist/src/init/executor.js +461 -465
- package/src/@claude-flow/cli/dist/src/init/helpers-generator.d.ts +0 -36
- package/src/@claude-flow/cli/dist/src/init/helpers-generator.js +146 -1124
- package/src/@claude-flow/cli/dist/src/init/index.d.ts +1 -1
- package/src/@claude-flow/cli/dist/src/init/index.js +1 -1
- package/src/@claude-flow/cli/dist/src/init/mcp-generator.js +6 -3
- package/src/@claude-flow/cli/dist/src/init/moflo-init.js +108 -424
- package/src/@claude-flow/cli/dist/src/init/settings-generator.js +50 -120
- package/src/@claude-flow/cli/dist/src/init/types.d.ts +2 -0
- package/src/@claude-flow/cli/dist/src/init/types.js +2 -0
- package/src/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +275 -32
- package/src/@claude-flow/cli/dist/src/plugins/store/discovery.js +4 -204
- package/src/@claude-flow/cli/dist/src/plugins/tests/standalone-test.js +4 -4
- package/src/@claude-flow/cli/dist/src/runtime/headless.d.ts +3 -3
- package/src/@claude-flow/cli/dist/src/runtime/headless.js +31 -31
- package/src/@claude-flow/cli/dist/src/services/agentic-flow-bridge.d.ts +3 -3
- package/src/@claude-flow/cli/dist/src/services/agentic-flow-bridge.js +3 -1
- package/src/@claude-flow/cli/dist/src/services/headless-worker-executor.js +14 -0
- package/src/@claude-flow/cli/dist/src/services/workflow-gate.js +21 -1
- package/src/@claude-flow/cli/dist/src/transfer/store/tests/standalone-test.js +4 -4
- 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
|
-
|
|
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
|
-
})
|
|
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
|
},
|
package/.claude/settings.json
CHANGED
|
@@ -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 [
|
|
168
|
+
"pr": "\ud83e\udd16 Generated with [moflo](https://github.com/eric-cielo/moflo)"
|
|
169
169
|
},
|
|
170
170
|
"claudeFlow": {
|
|
171
|
-
"version": "
|
|
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": "
|
|
278
|
+
"command": "npx",
|
|
279
279
|
"args": [
|
|
280
|
-
"
|
|
280
|
+
"moflo",
|
|
281
281
|
"mcp",
|
|
282
282
|
"start"
|
|
283
283
|
]
|
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/
|
|
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
|
+
});
|