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.
- package/.claude/helpers/.helpers-version +1 -0
- package/.claude/helpers/auto-memory-hook.mjs +59 -274
- package/.claude/helpers/helpers.manifest.json +12 -0
- package/.claude/helpers/hook-handler.cjs +132 -112
- package/.claude/helpers/intelligence.cjs +992 -196
- package/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/daemon.js +23 -2
- package/v3/@claude-flow/cli/dist/src/commands/memory-backup.d.ts +11 -0
- package/v3/@claude-flow/cli/dist/src/commands/memory-backup.js +46 -0
- package/v3/@claude-flow/cli/dist/src/commands/memory-distill.d.ts +27 -0
- package/v3/@claude-flow/cli/dist/src/commands/memory-distill.js +374 -0
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +5 -2
- package/v3/@claude-flow/cli/dist/src/index.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/index.js +22 -1
- package/v3/@claude-flow/cli/dist/src/init/executor.js +20 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-refresh.d.ts +19 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-refresh.js +175 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-signing.d.ts +30 -0
- package/v3/@claude-flow/cli/dist/src/init/helper-signing.js +60 -0
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.d.ts +16 -0
- package/v3/@claude-flow/cli/dist/src/init/helpers-generator.js +63 -6
- package/v3/@claude-flow/cli/dist/src/init/statusline-generator.js +18 -7
- package/v3/@claude-flow/cli/dist/src/mcp-client.js +13 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agenticow-speculate-tools.js +9 -2
- package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-intent-tools.d.ts +162 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-intent-tools.js +548 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-tools.d.ts +9 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/browser-tools.js +4 -1
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +67 -47
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +71 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +330 -2
- package/v3/@claude-flow/cli/dist/src/services/distill-tuning.d.ts +111 -0
- package/v3/@claude-flow/cli/dist/src/services/distill-tuning.js +510 -0
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +24 -0
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +94 -0
- package/v3/@claude-flow/cli/dist/src/services/memory-distillation.d.ts +41 -0
- package/v3/@claude-flow/cli/dist/src/services/memory-distillation.js +277 -0
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +31 -2
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +159 -6
- 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.
|
|
34
|
-
const session = safeRequire(path.join(helpersDir, 'session.
|
|
35
|
-
const memory = safeRequire(path.join(helpersDir, 'memory.
|
|
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
|
-
|
|
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
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
timer = setTimeout(
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
135
|
-
output
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
232
|
+
const initResult = await runWithTimeout(() => intelligence.init(), 'intelligence.init()');
|
|
189
233
|
if (initResult && initResult.nodes > 0) {
|
|
190
|
-
console.log(
|
|
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
|
-
|
|
242
|
+
const consResult = await runWithTimeout(() => intelligence.consolidate(), 'intelligence.consolidate()');
|
|
199
243
|
if (consResult && consResult.entries > 0) {
|
|
200
|
-
|
|
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
|
-
|
|
219
|
-
console.log(
|
|
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(
|
|
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
|
-
|
|
288
|
+
// Execute the handler
|
|
289
|
+
if (command && handlers[command]) {
|
|
267
290
|
try {
|
|
268
291
|
await Promise.resolve(handlers[command]());
|
|
269
292
|
} catch (e) {
|
|
270
|
-
|
|
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
|
-
|
|
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|
|
|
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
|
-
//
|
|
280
|
-
//
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
+
});
|