moflo 4.8.11 → 4.8.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.
@@ -1,341 +1,83 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * Claude Flow Hook Handler (Cross-Platform)
4
- * Dispatches hook events to the appropriate helper modules.
5
- */
6
-
7
- const path = require('path');
8
- const fs = require('fs');
9
-
10
- const helpersDir = __dirname;
11
-
12
- function safeRequire(modulePath) {
13
- try {
14
- if (fs.existsSync(modulePath)) {
15
- const origLog = console.log;
16
- const origError = console.error;
17
- console.log = () => {};
18
- console.error = () => {};
19
- try {
20
- const mod = require(modulePath);
21
- return mod;
22
- } finally {
23
- console.log = origLog;
24
- console.error = origError;
25
- }
26
- }
27
- } catch (e) {
28
- // silently fail
29
- }
30
- return null;
31
- }
32
-
33
- const router = safeRequire(path.join(helpersDir, 'router.cjs'));
34
- const session = safeRequire(path.join(helpersDir, 'session.cjs'));
35
- const memory = safeRequire(path.join(helpersDir, 'memory.cjs'));
36
- const intelligence = safeRequire(path.join(helpersDir, 'intelligence.cjs'));
37
-
38
- const [,, command, ...args] = process.argv;
39
-
40
- // Read stdin — Claude Code sends hook data as JSON via stdin
41
- // Uses a timeout to prevent hanging when stdin is in an ambiguous state
42
- // (not TTY, not a proper pipe) which happens with Claude Code hook invocations.
43
- async function readStdin() {
44
- if (process.stdin.isTTY) return '';
45
- return new Promise((resolve) => {
46
- let data = '';
47
- const timer = setTimeout(() => {
2
+ 'use strict';
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+
6
+ var PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
7
+ var METRICS_FILE = path.join(PROJECT_DIR, '.claude-flow', 'metrics', 'learning.json');
8
+ var command = process.argv[2];
9
+
10
+ // Read stdin (Claude Code sends hook data as JSON)
11
+ function readStdin() {
12
+ if (process.stdin.isTTY) return Promise.resolve('');
13
+ return new Promise(function(resolve) {
14
+ var data = '';
15
+ var timer = setTimeout(function() {
48
16
  process.stdin.removeAllListeners();
49
17
  process.stdin.pause();
50
18
  resolve(data);
51
19
  }, 500);
52
20
  process.stdin.setEncoding('utf8');
53
- process.stdin.on('data', (chunk) => { data += chunk; });
54
- process.stdin.on('end', () => { clearTimeout(timer); resolve(data); });
55
- process.stdin.on('error', () => { clearTimeout(timer); resolve(data); });
21
+ process.stdin.on('data', function(chunk) { data += chunk; });
22
+ process.stdin.on('end', function() { clearTimeout(timer); resolve(data); });
23
+ process.stdin.on('error', function() { clearTimeout(timer); resolve(data); });
56
24
  process.stdin.resume();
57
25
  });
58
26
  }
59
27
 
60
- async function main() {
61
- let stdinData = '';
62
- try { stdinData = await readStdin(); } catch (e) { /* ignore stdin errors */ }
28
+ function bumpMetric(key) {
29
+ try {
30
+ var metrics = {};
31
+ if (fs.existsSync(METRICS_FILE)) metrics = JSON.parse(fs.readFileSync(METRICS_FILE, 'utf-8'));
32
+ metrics[key] = (metrics[key] || 0) + 1;
33
+ metrics.lastUpdated = new Date().toISOString();
34
+ var dir = path.dirname(METRICS_FILE);
35
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
36
+ fs.writeFileSync(METRICS_FILE, JSON.stringify(metrics, null, 2));
37
+ } catch (e) { /* non-fatal */ }
38
+ }
63
39
 
64
- let hookInput = {};
65
- if (stdinData.trim()) {
66
- try { hookInput = JSON.parse(stdinData); } catch (e) { /* ignore parse errors */ }
40
+ readStdin().then(function(stdinData) {
41
+ var hookInput = {};
42
+ if (stdinData && stdinData.trim()) {
43
+ try { hookInput = JSON.parse(stdinData); } catch (e) { /* ignore */ }
67
44
  }
68
45
 
69
- // Merge stdin data into prompt resolution: prefer stdin fields, then env vars.
70
- // NEVER fall back to argv args — shell glob expansion of braces in bash output
71
- // creates junk files (#1342). Use env vars or stdin only.
72
- const prompt = hookInput.prompt || hookInput.command || hookInput.toolInput
73
- || process.env.PROMPT || process.env.TOOL_INPUT_command || '';
74
-
75
- const handlers = {
76
- 'route': () => {
77
- if (intelligence && intelligence.getContext) {
78
- try {
79
- const ctx = intelligence.getContext(prompt);
80
- if (ctx) console.log(ctx);
81
- } catch (e) { /* non-fatal */ }
82
- }
83
- if (router && router.routeTask) {
84
- const result = router.routeTask(prompt);
85
- var output = [];
86
- output.push('[INFO] Routing task: ' + (prompt.substring(0, 80) || '(no prompt)'));
87
- output.push('');
88
- output.push('+------------------- Primary Recommendation -------------------+');
89
- output.push('| Agent: ' + result.agent.padEnd(53) + '|');
90
- output.push('| Confidence: ' + (result.confidence * 100).toFixed(1) + '%' + ' '.repeat(44) + '|');
91
- output.push('| Reason: ' + result.reason.substring(0, 53).padEnd(53) + '|');
92
- output.push('+--------------------------------------------------------------+');
93
- console.log(output.join('\n'));
94
- } else {
95
- console.log('[INFO] Router not available, using default routing');
96
- }
97
- },
98
-
99
- 'pre-bash': () => {
100
- var cmd = (hookInput.command || prompt).toLowerCase();
101
- var dangerous = ['rm -rf /', 'format c:', 'del /s /q c:\\', ':(){:|:&};:'];
102
- for (var i = 0; i < dangerous.length; i++) {
103
- if (cmd.includes(dangerous[i])) {
104
- console.error('[BLOCKED] Dangerous command detected: ' + dangerous[i]);
105
- process.exit(1);
106
- }
107
- }
108
- console.log('[OK] Command validated');
109
- },
110
-
111
- 'post-edit': () => {
112
- if (session && session.metric) {
113
- try { session.metric('edits'); } catch (e) { /* no active session */ }
114
- }
115
- if (intelligence && intelligence.recordEdit) {
116
- try {
117
- var file = hookInput.file_path || (hookInput.toolInput && hookInput.toolInput.file_path)
118
- || process.env.TOOL_INPUT_file_path || args[0] || '';
119
- intelligence.recordEdit(file);
120
- } catch (e) { /* non-fatal */ }
121
- }
122
- console.log('[OK] Edit recorded');
123
- },
124
-
125
- 'session-restore': async () => {
126
- if (session) {
127
- var existing = session.restore && session.restore();
128
- if (!existing) {
129
- session.start && session.start();
130
- }
131
- } else {
132
- console.log('No session to restore');
133
- console.log('Session started: session-' + Date.now());
134
- }
135
- if (intelligence && intelligence.init) {
136
- try {
137
- var result = intelligence.init();
138
- if (result && result.nodes > 0) {
139
- console.log('[INTELLIGENCE] Loaded ' + result.nodes + ' patterns, ' + result.edges + ' edges');
140
- }
141
- } catch (e) { /* non-fatal */ }
142
- }
143
-
144
- // Auto-index guidance, code map, and patterns on session start
145
- // Delegates to shared ProcessManager for dedup + PID tracking + cleanup.
146
- try {
147
- var projectDir = path.resolve(path.dirname(helpersDir), '..');
148
-
149
- // Dynamic import of ESM process-manager from CJS context
150
- var pmMod = await import(
151
- 'file:///' + path.join(projectDir, 'node_modules', 'moflo', 'bin', 'lib', 'process-manager.mjs').replace(/\\/g, '/')
152
- ).catch(function() {
153
- // Fallback: try local bin/ (for moflo repo itself)
154
- return import(
155
- 'file:///' + path.join(projectDir, 'bin', 'lib', 'process-manager.mjs').replace(/\\/g, '/')
156
- );
157
- }).catch(function() { return null; });
158
-
159
- if (!pmMod) return; // No process manager available
160
-
161
- var pm = pmMod.createProcessManager(projectDir);
162
-
163
- // Kill stale background processes from previous session
164
- pm.killAll();
165
-
166
- // Guard: prevent concurrent session-restores from spawning duplicates
167
- if (pm.isLocked()) return;
168
- pm.acquireLock();
169
-
170
- // Read moflo.yaml auto_index flags (default: both true)
171
- var autoGuidance = true;
172
- var autoCodeMap = true;
173
- var mofloConfigPath = path.join(projectDir, 'moflo.yaml');
174
- var mofloJsonPath = path.join(projectDir, 'moflo.config.json');
175
-
176
- if (fs.existsSync(mofloConfigPath)) {
177
- try {
178
- var content = fs.readFileSync(mofloConfigPath, 'utf-8');
179
- if (/auto_index:\s*\n\s+guidance:\s*false/i.test(content)) autoGuidance = false;
180
- if (/auto_index:\s*\n(?:\s+guidance:\s*\w+\n)?\s+code_map:\s*false/i.test(content)) autoCodeMap = false;
181
- } catch (e) { /* ignore */ }
182
- } else if (fs.existsSync(mofloJsonPath)) {
183
- try {
184
- var config = JSON.parse(fs.readFileSync(mofloJsonPath, 'utf-8'));
185
- var ai = config.auto_index || config.autoIndex || {};
186
- if (ai.guidance === false) autoGuidance = false;
187
- if (ai.code_map === false || ai.codeMap === false) autoCodeMap = false;
188
- } catch (e) { /* ignore */ }
189
- }
190
-
191
- // Helper: find a moflo bin script by filename
192
- function findMofloScript(scriptName) {
193
- var candidates = [
194
- path.join(projectDir, 'bin', scriptName),
195
- path.join(projectDir, 'node_modules', 'moflo', 'bin', scriptName),
196
- ];
197
- for (var i = 0; i < candidates.length; i++) {
198
- if (fs.existsSync(candidates[i])) return candidates[i];
199
- }
200
- try {
201
- var resolved = require.resolve('moflo/bin/' + scriptName, { paths: [projectDir] });
202
- if (fs.existsSync(resolved)) return resolved;
203
- } catch (e) { /* not installed */ }
204
- return null;
205
- }
206
-
207
- // 1. Index guidance docs (with embeddings for semantic search)
208
- if (autoGuidance) {
209
- var guidanceScript = findMofloScript('index-guidance.mjs');
210
- if (guidanceScript) pm.spawn('node', [guidanceScript], 'index-guidance');
211
- }
212
-
213
- // 2. Generate code map (structural index of source files)
214
- if (autoCodeMap) {
215
- var codeMapScript = findMofloScript('generate-code-map.mjs');
216
- if (codeMapScript) pm.spawn('node', [codeMapScript], 'generate-code-map');
217
- }
218
-
219
- // 3. Start learning service (pattern research on codebase)
220
- var learnScript = findMofloScript('../.claude/helpers/learning-service.mjs');
221
- if (!learnScript) learnScript = findMofloScript('learning-service.mjs');
222
- if (!learnScript) {
223
- var localLearn = path.join(projectDir, '.claude', 'helpers', 'learning-service.mjs');
224
- if (fs.existsSync(localLearn)) learnScript = localLearn;
225
- }
226
- if (!learnScript) {
227
- var nmLearn = path.join(projectDir, 'node_modules', 'moflo', '.claude', 'helpers', 'learning-service.mjs');
228
- if (fs.existsSync(nmLearn)) learnScript = nmLearn;
229
- }
230
- if (learnScript) pm.spawn('node', [learnScript], 'learning-service');
231
-
232
- } catch (e) { /* non-fatal: session-start indexing is best-effort */ }
233
- },
234
-
235
- 'session-end': () => {
236
- // Kill all tracked background processes using shared sync helper.
237
- // Must be SYNCHRONOUS — process.exit(0) fires in finally{} so async would never resolve.
238
- var projectDir = path.resolve(path.dirname(helpersDir), '..');
239
- try {
240
- var cleanup = require(path.join(projectDir, 'node_modules', 'moflo', 'bin', 'lib', 'registry-cleanup.cjs'));
241
- var killed = cleanup.killTrackedSync(projectDir);
242
- if (killed > 0) console.log('[CLEANUP] Killed ' + killed + ' background process(es)');
243
- } catch (e) {
244
- // Fallback: try local bin/ (for moflo repo itself)
245
- try {
246
- var cleanup2 = require(path.join(projectDir, 'bin', 'lib', 'registry-cleanup.cjs'));
247
- var killed2 = cleanup2.killTrackedSync(projectDir);
248
- if (killed2 > 0) console.log('[CLEANUP] Killed ' + killed2 + ' background process(es)');
249
- } catch (e2) { /* non-fatal */ }
250
- }
251
-
252
- if (intelligence && intelligence.consolidate) {
253
- try {
254
- var result = intelligence.consolidate();
255
- if (result && result.entries > 0) {
256
- var msg = '[INTELLIGENCE] Consolidated: ' + result.entries + ' entries, ' + result.edges + ' edges';
257
- if (result.newEntries > 0) msg += ', ' + result.newEntries + ' new';
258
- msg += ', PageRank recomputed';
259
- console.log(msg);
260
- }
261
- } catch (e) { /* non-fatal */ }
262
- }
263
- if (session && session.end) {
264
- session.end();
265
- } else {
266
- console.log('[OK] Session ended');
267
- }
268
- },
269
-
270
- 'pre-task': () => {
271
- if (session && session.metric) {
272
- try { session.metric('tasks'); } catch (e) { /* no active session */ }
273
- }
274
- if (router && router.routeTask && prompt) {
275
- var result = router.routeTask(prompt);
276
- console.log('[INFO] Task routed to: ' + result.agent + ' (confidence: ' + result.confidence + ')');
277
- } else {
46
+ switch (command) {
47
+ case 'route': {
48
+ var prompt = hookInput.prompt || hookInput.command || process.env.PROMPT || '';
49
+ if (prompt) console.log('[INFO] Routing: ' + prompt.substring(0, 80));
50
+ else console.log('[INFO] Ready');
51
+ break;
52
+ }
53
+ case 'pre-edit':
54
+ case 'post-edit':
55
+ bumpMetric('edits');
56
+ console.log('[OK] Edit recorded');
57
+ break;
58
+ case 'pre-task':
59
+ bumpMetric('tasks');
278
60
  console.log('[OK] Task started');
279
- }
280
- },
281
-
282
- 'post-task': () => {
283
- if (intelligence && intelligence.feedback) {
61
+ break;
62
+ case 'post-task':
63
+ bumpMetric('tasksCompleted');
64
+ console.log('[OK] Task completed');
65
+ break;
66
+ case 'session-end': {
67
+ // Kill tracked background processes via shared sync helper
284
68
  try {
285
- intelligence.feedback(true);
286
- } catch (e) { /* non-fatal */ }
287
- }
288
- console.log('[OK] Task completed');
289
- },
290
-
291
- 'compact-manual': () => {
292
- console.log('PreCompact Guidance:');
293
- console.log('IMPORTANT: Review CLAUDE.md in project root for:');
294
- console.log(' - Available agents and concurrent usage patterns');
295
- console.log(' - Swarm coordination strategies (hierarchical, mesh, adaptive)');
296
- console.log(' - Critical concurrent execution rules (1 MESSAGE = ALL OPERATIONS)');
297
- console.log('Ready for compact operation');
298
- },
299
-
300
- 'compact-auto': () => {
301
- console.log('Auto-Compact Guidance (Context Window Full):');
302
- console.log('CRITICAL: Before compacting, ensure you understand:');
303
- console.log(' - All agents available in .claude/agents/ directory');
304
- console.log(' - Concurrent execution patterns from CLAUDE.md');
305
- console.log(' - Swarm coordination strategies for complex tasks');
306
- console.log('Apply GOLDEN RULE: Always batch operations in single messages');
307
- console.log('Auto-compact proceeding with full agent context');
308
- },
309
-
310
- 'status': () => {
311
- console.log('[OK] Status check');
312
- },
313
-
314
- 'stats': () => {
315
- if (intelligence && intelligence.stats) {
316
- intelligence.stats(args.includes('--json'));
317
- } else {
318
- console.log('[WARN] Intelligence module not available. Run session-restore first.');
319
- }
320
- },
321
- };
322
-
323
- if (command && handlers[command]) {
324
- try {
325
- await handlers[command]();
326
- } catch (e) {
327
- console.log('[WARN] Hook ' + command + ' encountered an error: ' + e.message);
328
- }
329
- } else if (command) {
330
- console.log('[OK] Hook: ' + command);
331
- } else {
332
- console.log('Usage: hook-handler.cjs <route|pre-bash|post-edit|session-restore|session-end|pre-task|post-task|compact-manual|compact-auto|status|stats>');
69
+ var cleanup = require('./lib/registry-cleanup.cjs');
70
+ var killed = cleanup.killTrackedSync(PROJECT_DIR);
71
+ if (killed > 0) console.log('[CLEANUP] Killed ' + killed + ' background process(es)');
72
+ } catch (e) { /* non-fatal: cleanup module not available */ }
73
+ console.log('[OK] Session ended');
74
+ break;
75
+ }
76
+ case 'notification':
77
+ // Silent just acknowledge
78
+ break;
79
+ default:
80
+ if (command) console.log('[OK] Hook: ' + command);
81
+ break;
333
82
  }
334
- }
335
-
336
- main().catch(function(e) {
337
- console.log('[WARN] Hook handler error: ' + e.message);
338
- }).finally(function() {
339
- // Ensure clean exit for Claude Code hooks
340
- process.exit(0);
341
83
  });
@@ -0,0 +1,16 @@
1
+ #!/bin/bash
2
+ # Claude Flow Post-Commit Hook
3
+ # Records commit metrics and trains patterns
4
+
5
+ COMMIT_HASH=$(git rev-parse HEAD)
6
+ COMMIT_MSG=$(git log -1 --pretty=%B)
7
+
8
+ echo "📊 Recording commit metrics..."
9
+
10
+ # Notify claude-flow of commit
11
+ npx moflo hooks notify \
12
+ --message "Commit: $COMMIT_MSG" \
13
+ --level info \
14
+ --metadata '{"hash": "'$COMMIT_HASH'"}' 2>/dev/null || true
15
+
16
+ echo "✅ Commit recorded"
@@ -0,0 +1,26 @@
1
+ #!/bin/bash
2
+ # Claude Flow Pre-Commit Hook
3
+ # Validates code quality before commit
4
+
5
+ set -e
6
+
7
+ echo "🔍 Running Claude Flow pre-commit checks..."
8
+
9
+ # Get staged files
10
+ STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)
11
+
12
+ # Run validation for each staged file
13
+ for FILE in $STAGED_FILES; do
14
+ if [[ "$FILE" =~ \.(ts|js|tsx|jsx)$ ]]; then
15
+ echo " Validating: $FILE"
16
+ npx moflo hooks pre-edit --file "$FILE" --validate-syntax 2>/dev/null || true
17
+ fi
18
+ done
19
+
20
+ # Run tests if available
21
+ if [ -f "package.json" ] && grep -q '"test"' package.json; then
22
+ echo "🧪 Running tests..."
23
+ npm test --if-present 2>/dev/null || echo " Tests skipped or failed"
24
+ fi
25
+
26
+ echo "✅ Pre-commit checks complete"
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ import { execSync } from 'child_process';
3
+ import { resolve } from 'path';
4
+
5
+ // Read stdin JSON from Claude Code
6
+ var stdinData = '';
7
+ try {
8
+ stdinData = await new Promise(function(res) {
9
+ var data = '';
10
+ var timeout = setTimeout(function() { res(data); }, 500);
11
+ process.stdin.setEncoding('utf-8');
12
+ process.stdin.on('data', function(chunk) { data += chunk; });
13
+ process.stdin.on('end', function() { clearTimeout(timeout); res(data); });
14
+ process.stdin.on('error', function() { clearTimeout(timeout); res(''); });
15
+ if (process.stdin.isTTY) { clearTimeout(timeout); res(''); }
16
+ });
17
+ } catch (e) { /* no stdin */ }
18
+
19
+ var hookContext = {};
20
+ try { if (stdinData.trim()) hookContext = JSON.parse(stdinData); } catch (e) {}
21
+
22
+ var userPrompt = hookContext.user_prompt || hookContext.prompt || '';
23
+ var env = Object.assign({}, process.env, { CLAUDE_USER_PROMPT: userPrompt });
24
+
25
+ // Run prompt-reminder via gate.cjs
26
+ var projectDir = (env.CLAUDE_PROJECT_DIR || process.cwd()).replace(/^\/([a-z])\//i, '$1:/');
27
+ var gateScript = resolve(projectDir, '.claude/helpers/gate.cjs');
28
+ var output = '';
29
+ try {
30
+ output = execSync('node "' + gateScript + '" prompt-reminder', {
31
+ env: env, encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe']
32
+ });
33
+ } catch (err) { output = (err && err.stdout) || ''; }
34
+
35
+ // Classify prompt for namespace hint
36
+ var lower = userPrompt.toLowerCase();
37
+
38
+ var KNOWLEDGE_ONLY = /\b(knowledge|remember|recall)\b|we (decid|agree|chose|said)/;
39
+ var EXPLICIT_NS = [
40
+ { pattern: /\b(pattern|convention|best practice|style|coding rule)\b/, ns: 'patterns', label: 'code patterns and conventions' },
41
+ { pattern: /\b(code.?map|file structure|project structure|directory)\b/, ns: 'code-map', label: 'codebase navigation' },
42
+ ];
43
+ var PATTERN_HINTS = [/\b(template|example|similar to|how do we|how should)\b/];
44
+ var DOMAIN_HINTS = [
45
+ /\b(guidance|guide|docs|documentation|rules|how-to)\b/,
46
+ /\b(architecture|design|domain|tenant|migrat|schema|deploy)/,
47
+ /\b(rule|requirement|constraint|compliance)\b/,
48
+ ];
49
+ var NAV_PATTERNS = [
50
+ /\b(find|where|which file|look up|locate|endpoint|route|url|path)\b/,
51
+ /\b(class|function|method|component|service|entity|module)\b/,
52
+ ];
53
+
54
+ var nsHint = '';
55
+ if (KNOWLEDGE_ONLY.test(lower)) {
56
+ nsHint = 'Memory namespace hint: use "knowledge" for user-directed project decisions.';
57
+ } else {
58
+ var found = EXPLICIT_NS.find(function(e) { return e.pattern.test(lower); });
59
+ if (found) {
60
+ nsHint = 'Memory namespace hint: use "' + found.ns + '" for ' + found.label + '.';
61
+ } else if (DOMAIN_HINTS.some(function(p) { return p.test(lower); })) {
62
+ nsHint = 'Memory namespace hint: search "guidance" and "knowledge" for domain rules and project decisions.';
63
+ } else if (PATTERN_HINTS.some(function(p) { return p.test(lower); })) {
64
+ nsHint = 'Memory namespace hint: use "patterns" for code patterns and conventions.';
65
+ } else if (NAV_PATTERNS.some(function(p) { return p.test(lower); })) {
66
+ nsHint = 'Memory namespace hint: use "code-map" for codebase navigation.';
67
+ }
68
+ }
69
+
70
+ var parts = [output.trim(), nsHint].filter(Boolean);
71
+ if (parts.length) process.stdout.write(parts.join('\n') + '\n');
72
+ process.exit(0);