claude-flow 3.21.1 → 3.23.0

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 (40) hide show
  1. package/.claude/helpers/.helpers-version +1 -0
  2. package/.claude/helpers/auto-memory-hook.mjs +59 -274
  3. package/.claude/helpers/helpers.manifest.json +12 -0
  4. package/.claude/helpers/hook-handler.cjs +132 -112
  5. package/.claude/helpers/intelligence.cjs +992 -196
  6. package/package.json +1 -1
  7. package/v3/@claude-flow/cli/dist/src/commands/daemon.js +23 -2
  8. package/v3/@claude-flow/cli/dist/src/commands/memory-backup.d.ts +11 -0
  9. package/v3/@claude-flow/cli/dist/src/commands/memory-backup.js +46 -0
  10. package/v3/@claude-flow/cli/dist/src/commands/memory-distill.d.ts +27 -0
  11. package/v3/@claude-flow/cli/dist/src/commands/memory-distill.js +374 -0
  12. package/v3/@claude-flow/cli/dist/src/commands/memory.js +5 -2
  13. package/v3/@claude-flow/cli/dist/src/index.d.ts +1 -1
  14. package/v3/@claude-flow/cli/dist/src/index.js +22 -1
  15. package/v3/@claude-flow/cli/dist/src/init/executor.js +20 -0
  16. package/v3/@claude-flow/cli/dist/src/init/helper-refresh.d.ts +19 -0
  17. package/v3/@claude-flow/cli/dist/src/init/helper-refresh.js +175 -0
  18. package/v3/@claude-flow/cli/dist/src/init/helper-signing.d.ts +30 -0
  19. package/v3/@claude-flow/cli/dist/src/init/helper-signing.js +60 -0
  20. package/v3/@claude-flow/cli/dist/src/init/helpers-generator.d.ts +16 -0
  21. package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +63 -6
  22. package/v3/@claude-flow/cli/dist/src/init/statusline-generator.js +18 -7
  23. package/v3/@claude-flow/cli/dist/src/mcp-client.js +13 -0
  24. package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-speculate-tools.js +9 -2
  25. package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-intent-tools.d.ts +162 -0
  26. package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-intent-tools.js +548 -0
  27. package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-tools.d.ts +9 -1
  28. package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-tools.js +4 -1
  29. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +67 -47
  30. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +71 -0
  31. package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +330 -2
  32. package/v3/@claude-flow/cli/dist/src/services/distill-tuning.d.ts +111 -0
  33. package/v3/@claude-flow/cli/dist/src/services/distill-tuning.js +510 -0
  34. package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +24 -0
  35. package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +94 -0
  36. package/v3/@claude-flow/cli/dist/src/services/memory-distillation.d.ts +41 -0
  37. package/v3/@claude-flow/cli/dist/src/services/memory-distillation.js +277 -0
  38. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +31 -2
  39. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +159 -6
  40. package/v3/@claude-flow/cli/package.json +5 -2
@@ -2,6 +2,15 @@
2
2
  /**
3
3
  * Claude Flow Hook Handler (Cross-Platform)
4
4
  * Dispatches hook events to the appropriate helper modules.
5
+ *
6
+ * Usage: node hook-handler.cjs <command> [args...]
7
+ *
8
+ * Commands:
9
+ * route - Route a task to optimal agent (reads PROMPT from env/stdin)
10
+ * pre-bash - Validate command safety before execution
11
+ * post-edit - Record edit outcome for learning
12
+ * session-restore - Restore previous session state
13
+ * session-end - End session and persist state
5
14
  */
6
15
 
7
16
  const path = require('path');
@@ -9,6 +18,9 @@ const fs = require('fs');
9
18
 
10
19
  const helpersDir = __dirname;
11
20
 
21
+ // Safe require with stdout suppression - the helper modules have CLI
22
+ // sections that run unconditionally on require(), so we mute console
23
+ // during the require to prevent noisy output.
12
24
  function safeRequire(modulePath) {
13
25
  try {
14
26
  if (fs.existsSync(modulePath)) {
@@ -30,51 +42,43 @@ function safeRequire(modulePath) {
30
42
  return null;
31
43
  }
32
44
 
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'));
45
+ const router = safeRequire(path.join(helpersDir, 'router.js'));
46
+ const session = safeRequire(path.join(helpersDir, 'session.js'));
47
+ const memory = safeRequire(path.join(helpersDir, 'memory.js'));
36
48
  const intelligence = safeRequire(path.join(helpersDir, 'intelligence.cjs'));
37
49
 
38
50
  // ── Intelligence timeout protection (fixes #1530, #1531) ───────────────────
39
- var INTELLIGENCE_TIMEOUT_MS = 3000;
40
- // Race the (possibly-async) work against a real timeout.
51
+ const INTELLIGENCE_TIMEOUT_MS = 3000;
52
+ // Race the (possibly-async) work against a real timeout. The previous version
53
+ // called fn() and clearTimeout(timer) immediately, so an async fn returned a
54
+ // pending promise that resolved THROUGH the race — the timeout protected
55
+ // nothing. This settles on whichever finishes first, then clears the timer.
41
56
  //
42
- // The previous implementation called `fn()` and then `clearTimeout(timer)`
43
- // immediately but if `fn()` returns a *pending* promise (async work), the
44
- // timer was cancelled before the work settled and the promise resolved with the
45
- // still-pending promise, so the timeout protected nothing. This version only
46
- // settles when the work resolves OR the timeout fires, whichever comes first,
47
- // and clears the timer afterwards.
48
- //
49
- // LIMITATION (be honest): a *synchronous* CPU-bound `fn` (e.g. the current
50
- // intelligence.init(), which does blocking fs reads) cannot be interrupted by
51
- // any in-process timer — the event loop is blocked, so the timeout callback
52
- // can't run until `fn` already returned. The real guard for that case lives in
53
- // intelligence.cjs (MAX_DATA_FILE_SIZE / MAX_GRAPH_NODES caps). This util only
54
- // bounds work that yields to the event loop (async I/O, dynamic import, etc.).
57
+ // LIMITATION: a synchronous blocking fn (the current intelligence.init() does
58
+ // blocking fs reads) cannot be interrupted by any in-process timer the event
59
+ // loop is blocked. The real guard for that case is the readJSON file-size
60
+ // limit in intelligence.cjs. This util only bounds work that yields (async I/O).
55
61
  function runWithTimeout(fn, label) {
56
- var timer;
57
- var timeout = new Promise(function(resolve) {
58
- timer = setTimeout(function() {
62
+ let timer;
63
+ const timeout = new Promise((resolve) => {
64
+ timer = setTimeout(() => {
59
65
  process.stderr.write("[WARN] " + label + " timed out after " + INTELLIGENCE_TIMEOUT_MS + "ms, skipping\n");
60
66
  resolve(null);
61
67
  }, INTELLIGENCE_TIMEOUT_MS);
62
68
  });
63
- // Promise.resolve().then(fn) turns a synchronous throw into a rejection so it
64
- // is handled here rather than escaping the Promise constructor.
65
- var work = Promise.resolve().then(fn).catch(function() { return null; });
66
- return Promise.race([work, timeout]).then(function(result) {
69
+ const work = Promise.resolve().then(fn).catch(() => null);
70
+ return Promise.race([work, timeout]).then((result) => {
67
71
  clearTimeout(timer);
68
72
  return result;
69
73
  });
70
74
  }
71
75
 
72
76
 
77
+ // Get the command from argv
73
78
  const [,, command, ...args] = process.argv;
74
79
 
75
- // Read stdin — Claude Code sends hook data as JSON via stdin
76
- // Uses a timeout to prevent hanging when stdin is in an ambiguous state
77
- // (not TTY, not a proper pipe) which happens with Claude Code hook invocations.
80
+ // Read stdin with timeout — Claude Code sends hook data as JSON via stdin.
81
+ // Timeout prevents hanging when stdin is not properly closed (common on Windows).
78
82
  async function readStdin() {
79
83
  if (process.stdin.isTTY) return '';
80
84
  return new Promise((resolve) => {
@@ -94,11 +98,11 @@ async function readStdin() {
94
98
 
95
99
  async function main() {
96
100
  // Global safety timeout: hooks must NEVER hang (#1530, #1531)
97
- var safetyTimer = setTimeout(function() {
101
+ const safetyTimer = setTimeout(() => {
98
102
  process.stderr.write("[WARN] Hook handler global timeout (5s), forcing exit\n");
99
103
  process.exit(0);
100
104
  }, 5000);
101
- safetyTimer.unref();
105
+ safetyTimer.unref(); // don't keep process alive just for this timer
102
106
 
103
107
  let stdinData = '';
104
108
  try { stdinData = await readStdin(); } catch (e) { /* ignore stdin errors */ }
@@ -108,21 +112,40 @@ async function main() {
108
112
  try { hookInput = JSON.parse(stdinData); } catch (e) { /* ignore parse errors */ }
109
113
  }
110
114
 
111
- // Merge stdin data into prompt resolution: prefer stdin fields, then env vars.
112
- // NEVER fall back to argv args — shell glob expansion of braces in bash output
113
- // creates junk files (#1342). Use env vars or stdin only.
114
115
  // Normalize snake_case/camelCase: Claude Code sends tool_input/tool_name (snake_case)
115
- var toolInput = hookInput.toolInput || hookInput.tool_input || {};
116
- var toolName = hookInput.toolName || hookInput.tool_name || '';
116
+ const toolInput = hookInput.toolInput || hookInput.tool_input || {};
117
+ const toolName = hookInput.toolName || hookInput.tool_name || '';
118
+
119
+ // Merge stdin data into prompt resolution: prefer stdin fields, then env, then argv.
120
+ // `toolInput` is an object (e.g. {command:"ls"}) — it's truthy but not a string,
121
+ // so falling back to it directly bound `prompt` to the object and tripped
122
+ // `.toLowerCase()` / `.substring()` on every Bash hook (#1944). Use the
123
+ // `.command` field instead, which is the actual string the hook needs.
124
+ const prompt = hookInput.prompt || hookInput.command || toolInput.command
125
+ || process.env.PROMPT || process.env.TOOL_INPUT_command || args.join(' ') || '';
117
126
 
118
- // `toolInput` is an object (e.g. {command:"ls"}) falling back to it
119
- // directly bound `prompt` to the object and tripped `.toLowerCase()` /
120
- // `.substring()` on every Bash hook (#1944). Use the `.command` field.
121
- var prompt = hookInput.prompt || hookInput.command || toolInput.command
122
- || process.env.PROMPT || process.env.TOOL_INPUT_command || '';
127
+ // ADR-174: capture FAILURES so the learning substrate has negative examples.
128
+ // Claude Code's PostToolUse payload carries the tool result; a failed
129
+ // Write/Edit/Bash surfaces as tool_response.is_error / an error string /
130
+ // a non-zero exit code. Conservative only a positive error signal counts
131
+ // as failure (mirrors isToolFailure() in helpers-generator.ts).
132
+ const toolFailed = (function (hi) {
133
+ if (!hi || typeof hi !== 'object') return false;
134
+ const tr = hi.tool_response != null ? hi.tool_response : (hi.toolResponse != null ? hi.toolResponse : hi.result);
135
+ if (tr == null) return false;
136
+ if (typeof tr === 'string') return /\b(error|failed|failure|exception|not found|no such|permission denied|traceback)\b/i.test(tr);
137
+ if (typeof tr === 'object') {
138
+ if (tr.is_error === true || tr.isError === true || tr.success === false || tr.error != null) return true;
139
+ const code = tr.exit_code != null ? tr.exit_code : (tr.exitCode != null ? tr.exitCode : tr.code);
140
+ if (typeof code === 'number' && code !== 0) return true;
141
+ if (Array.isArray(tr.content) && tr.is_error === true) return true;
142
+ }
143
+ return false;
144
+ })(hookInput);
123
145
 
124
146
  const handlers = {
125
147
  'route': () => {
148
+ // Inject ranked intelligence context before routing
126
149
  if (intelligence && intelligence.getContext) {
127
150
  try {
128
151
  const ctx = intelligence.getContext(prompt);
@@ -131,14 +154,16 @@ const handlers = {
131
154
  }
132
155
  if (router && router.routeTask) {
133
156
  const result = router.routeTask(prompt);
134
- var output = [];
135
- output.push('[INFO] Routing task: ' + (prompt.substring(0, 80) || '(no prompt)'));
136
- output.push('');
137
- output.push('+------------------- Primary Recommendation -------------------+');
138
- output.push('| Agent: ' + result.agent.padEnd(53) + '|');
139
- output.push('| Confidence: ' + (result.confidence * 100).toFixed(1) + '%' + ' '.repeat(44) + '|');
140
- output.push('| Reason: ' + result.reason.substring(0, 53).padEnd(53) + '|');
141
- output.push('+--------------------------------------------------------------+');
157
+ // Format output for Claude Code hook consumption — real data only
158
+ const output = [
159
+ `[INFO] Routing task: ${prompt.substring(0, 80) || '(no prompt)'}`,
160
+ '',
161
+ '+------------------- Primary Recommendation -------------------+',
162
+ `| Agent: ${result.agent.padEnd(53)}|`,
163
+ `| Confidence: ${(result.confidence * 100).toFixed(1)}%${' '.repeat(44)}|`,
164
+ `| Reason: ${(result.reason || '').substring(0, 53).padEnd(53)}|`,
165
+ '+--------------------------------------------------------------+',
166
+ ];
142
167
  console.log(output.join('\n'));
143
168
  } else {
144
169
  console.log('[INFO] Router not available, using default routing');
@@ -146,14 +171,16 @@ const handlers = {
146
171
  },
147
172
 
148
173
  'pre-bash': () => {
174
+ // Basic command safety check — prefer stdin command data from Claude Code.
149
175
  // String() wrap is belt-and-suspenders for #2017: even if a future regression
150
176
  // re-binds `prompt` or `hookInput.command` to a non-string, `.toLowerCase()`
151
- // can no longer throw a TypeError that the global try/catch would swallow.
152
- var cmd = String(hookInput.command || toolInput.command || prompt || '').toLowerCase();
153
- var dangerous = ['rm -rf /', 'format c:', 'del /s /q c:\\', ':(){:|:&};:'];
154
- for (var i = 0; i < dangerous.length; i++) {
155
- if (cmd.includes(dangerous[i])) {
156
- console.error('[BLOCKED] Dangerous command detected: ' + dangerous[i]);
177
+ // can no longer throw a TypeError that the global try/catch would swallow
178
+ // (silently exiting 0 and letting the dangerous command through).
179
+ const cmd = String(hookInput.command || toolInput.command || prompt || '').toLowerCase();
180
+ const dangerous = ['rm -rf /', 'format c:', 'del /s /q c:\\', ':(){:|:&};:'];
181
+ for (const d of dangerous) {
182
+ if (cmd.includes(d)) {
183
+ console.error(`[BLOCKED] Dangerous command detected: ${d}`);
157
184
  process.exit(1);
158
185
  }
159
186
  }
@@ -161,46 +188,60 @@ const handlers = {
161
188
  },
162
189
 
163
190
  'post-edit': () => {
191
+ // Record edit for session metrics
164
192
  if (session && session.metric) {
165
193
  try { session.metric('edits'); } catch (e) { /* no active session */ }
166
194
  }
195
+ // Record edit for intelligence consolidation — prefer stdin data from Claude Code
167
196
  if (intelligence && intelligence.recordEdit) {
168
197
  try {
169
- var file = hookInput.file_path || toolInput.file_path
198
+ const file = hookInput.file_path || toolInput.file_path
170
199
  || process.env.TOOL_INPUT_file_path || args[0] || '';
171
- intelligence.recordEdit(file);
200
+ intelligence.recordEdit(file, !toolFailed);
172
201
  } catch (e) { /* non-fatal */ }
173
202
  }
174
- console.log('[OK] Edit recorded');
203
+ console.log(toolFailed ? '[LEARN] Edit FAILURE recorded' : '[OK] Edit recorded');
175
204
  },
176
205
 
177
206
  'session-restore': async () => {
178
207
  if (session) {
179
- var existing = session.restore && session.restore();
208
+ // Try restore first, fall back to start
209
+ const existing = session.restore && session.restore();
180
210
  if (!existing) {
181
211
  session.start && session.start();
182
212
  }
183
213
  } else {
184
- console.log('[OK] Session restored: session-' + Date.now());
214
+ // Minimal session restore output
215
+ const sessionId = `session-${Date.now()}`;
216
+ console.log(`[INFO] Restoring session: %SESSION_ID%`);
217
+ console.log('');
218
+ console.log(`[OK] Session restored from %SESSION_ID%`);
219
+ console.log(`New session ID: ${sessionId}`);
220
+ console.log('');
221
+ console.log('Restored State');
222
+ console.log('+----------------+-------+');
223
+ console.log('| Item | Count |');
224
+ console.log('+----------------+-------+');
225
+ console.log('| Tasks | 0 |');
226
+ console.log('| Agents | 0 |');
227
+ console.log('| Memory Entries | 0 |');
228
+ console.log('+----------------+-------+');
185
229
  }
186
- // Initialize intelligence (with timeout — #1530)
230
+ // Initialize intelligence graph after session restore (with timeout — #1530)
187
231
  if (intelligence && intelligence.init) {
188
- var initResult = await runWithTimeout(function() { return intelligence.init(); }, 'intelligence.init()');
232
+ const initResult = await runWithTimeout(() => intelligence.init(), 'intelligence.init()');
189
233
  if (initResult && initResult.nodes > 0) {
190
- console.log('[INTELLIGENCE] Loaded ' + initResult.nodes + ' patterns, ' + initResult.edges + ' edges');
234
+ console.log(`[INTELLIGENCE] Loaded ${initResult.nodes} patterns, ${initResult.edges} edges`);
191
235
  }
192
236
  }
193
237
  },
194
238
 
195
239
  'session-end': async () => {
196
- // Consolidate intelligence (with timeout — #1530)
240
+ // Consolidate intelligence before ending session (with timeout — #1530)
197
241
  if (intelligence && intelligence.consolidate) {
198
- var consResult = await runWithTimeout(function() { return intelligence.consolidate(); }, 'intelligence.consolidate()');
242
+ const consResult = await runWithTimeout(() => intelligence.consolidate(), 'intelligence.consolidate()');
199
243
  if (consResult && consResult.entries > 0) {
200
- var msg = '[INTELLIGENCE] Consolidated: ' + consResult.entries + ' entries, ' + consResult.edges + ' edges';
201
- if (consResult.newEntries > 0) msg += ', ' + consResult.newEntries + ' new';
202
- msg += ', PageRank recomputed';
203
- console.log(msg);
244
+ console.log(`[INTELLIGENCE] Consolidated: ${consResult.entries} entries, ${consResult.edges} edges${consResult.newEntries > 0 ? `, ${consResult.newEntries} new` : ''}, PageRank recomputed`);
204
245
  }
205
246
  }
206
247
  if (session && session.end) {
@@ -214,44 +255,25 @@ const handlers = {
214
255
  if (session && session.metric) {
215
256
  try { session.metric('tasks'); } catch (e) { /* no active session */ }
216
257
  }
258
+ // Route the task if router is available
217
259
  if (router && router.routeTask && prompt) {
218
- var result = router.routeTask(prompt);
219
- console.log('[INFO] Task routed to: ' + result.agent + ' (confidence: ' + result.confidence + ')');
260
+ const result = router.routeTask(prompt);
261
+ console.log(`[INFO] Task routed to: ${result.agent} (confidence: ${result.confidence})`);
220
262
  } else {
221
263
  console.log('[OK] Task started');
222
264
  }
223
265
  },
224
266
 
225
267
  'post-task': () => {
268
+ // ADR-174: feed the REAL outcome (feedback() boosts confidence on success,
269
+ // decays it on failure) instead of a hardcoded true — no more all-positive
270
+ // signal that the substrate can't learn from.
226
271
  if (intelligence && intelligence.feedback) {
227
272
  try {
228
- intelligence.feedback(true);
273
+ intelligence.feedback(!toolFailed);
229
274
  } catch (e) { /* non-fatal */ }
230
275
  }
231
- console.log('[OK] Task completed');
232
- },
233
-
234
- 'compact-manual': () => {
235
- console.log('PreCompact Guidance:');
236
- console.log('IMPORTANT: Review CLAUDE.md in project root for:');
237
- console.log(' - Available agents and concurrent usage patterns');
238
- console.log(' - Swarm coordination strategies (hierarchical, mesh, adaptive)');
239
- console.log(' - Critical concurrent execution rules (1 MESSAGE = ALL OPERATIONS)');
240
- console.log('Ready for compact operation');
241
- },
242
-
243
- 'compact-auto': () => {
244
- console.log('Auto-Compact Guidance (Context Window Full):');
245
- console.log('CRITICAL: Before compacting, ensure you understand:');
246
- console.log(' - All agents available in .claude/agents/ directory');
247
- console.log(' - Concurrent execution patterns from CLAUDE.md');
248
- console.log(' - Swarm coordination strategies for complex tasks');
249
- console.log('Apply GOLDEN RULE: Always batch operations in single messages');
250
- console.log('Auto-compact proceeding with full agent context');
251
- },
252
-
253
- 'status': () => {
254
- console.log('[OK] Status check');
276
+ console.log(toolFailed ? '[LEARN] Task FAILURE recorded' : '[OK] Task completed');
255
277
  },
256
278
 
257
279
  'stats': () => {
@@ -263,29 +285,27 @@ const handlers = {
263
285
  },
264
286
  };
265
287
 
266
- if (command && handlers[command]) {
288
+ // Execute the handler
289
+ if (command && handlers[command]) {
267
290
  try {
268
291
  await Promise.resolve(handlers[command]());
269
292
  } catch (e) {
270
- console.log('[WARN] Hook ' + command + ' encountered an error: ' + e.message);
293
+ // Hooks should never crash Claude Code - fail silently
294
+ console.log(`[WARN] Hook ${command} encountered an error: ${e.message}`);
271
295
  }
272
296
  } else if (command) {
273
- console.log('[OK] Hook: ' + command);
297
+ // Unknown command - pass through without error
298
+ console.log(`[OK] Hook: ${command}`);
274
299
  } else {
275
- 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>');
300
+ console.log('Usage: hook-handler.cjs <route|pre-bash|post-edit|session-restore|session-end|pre-task|post-task|stats>');
276
301
  }
277
302
  }
278
303
 
279
- // Only dispatch hooks when run directly (node hook-handler.cjs ...). When
280
- // require()'d by a test, just expose the internals so they can be unit-tested
281
- // without triggering stdin reads / process.exit.
282
- if (require.main === module) {
283
- main().catch(function(e) {
284
- console.log('[WARN] Hook handler error: ' + e.message);
285
- }).finally(function() {
286
- // Ensure clean exit for Claude Code hooks
287
- process.exit(0);
288
- });
289
- }
290
-
291
- module.exports = { runWithTimeout: runWithTimeout, INTELLIGENCE_TIMEOUT_MS: INTELLIGENCE_TIMEOUT_MS };
304
+ // Hooks must ALWAYS exit 0 Claude Code treats non-zero as "hook error"
305
+ // and skips all subsequent hooks for the event.
306
+ process.exitCode = 0;
307
+ main().catch((e) => {
308
+ try { console.log(`[WARN] Hook handler error: ${e.message}`); } catch (_) {}
309
+ }).finally(() => {
310
+ process.exit(0);
311
+ });