v4.7.0: brewtools:agent-router (experimental) + hook timeout units are seconds

This commit is contained in:
kochetkov-ma
2026-08-05 21:32:11 +01:00
parent caf4720cf5
commit b0261422c2
27 changed files with 2693 additions and 52 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "brewcode",
"version": "4.6.0",
"version": "4.7.0",
"description": "Brewcode - full-featured development platform for Claude Code: infinite focus tasks, prompt optimization, skill/agent creation, quorum reviews, rules management",
"author": {
"name": "Maksim Kochetkov",
+1 -1
View File
@@ -4,7 +4,7 @@
| Field | Value |
|-------|-------|
| Version | 4.6.0 |
| Version | 4.7.0 |
| Skills | 9 |
| Agents | 10 |
| Hooks | 2 |
+2 -2
View File
@@ -7,7 +7,7 @@
"type": "command",
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/hooks/forced-eval.mjs"],
"timeout": 1000
"timeout": 2
}
]
}
@@ -19,7 +19,7 @@
"type": "command",
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/hooks/session-start.mjs"],
"timeout": 3000
"timeout": 3
}
]
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "claude-plugin-brewcode",
"version": "4.6.0",
"version": "4.7.0",
"description": "Infinite task execution with automatic handoff for Claude Code",
"keywords": [
"claude-code",
@@ -36,6 +36,6 @@
},
"claude-plugin": {
"name": "brewcode",
"version": "4.6.0"
"version": "4.7.0"
}
}
+11 -11
View File
@@ -188,14 +188,14 @@ process. The matcher is exactly `Explore` — no other subagent type is touched.
{
"hooks": {
"SessionStart": [
{ "hooks": [ { "type": "command", "command": "node", "args": ["<absdir>/semble-session.mjs"], "timeout": 5000 } ] }
{ "hooks": [ { "type": "command", "command": "node", "args": ["<absdir>/semble-session.mjs"], "timeout": 5 } ] }
],
"PreToolUse": [
{ "matcher": "Bash", "hooks": [ { "type": "command", "command": "node", "args": ["<absdir>/semble-reminder.mjs"], "timeout": 5000 } ] },
{ "matcher": "Grep", "hooks": [ { "type": "command", "command": "node", "args": ["<absdir>/semble-reminder.mjs"], "timeout": 5000 } ] }
{ "matcher": "Bash", "hooks": [ { "type": "command", "command": "node", "args": ["<absdir>/semble-reminder.mjs"], "timeout": 5 } ] },
{ "matcher": "Grep", "hooks": [ { "type": "command", "command": "node", "args": ["<absdir>/semble-reminder.mjs"], "timeout": 5 } ] }
],
"SubagentStart": [
{ "matcher": "Explore", "hooks": [ { "type": "command", "command": "node", "args": ["<absdir>/semble-explore.mjs"], "timeout": 5000 } ] }
{ "matcher": "Explore", "hooks": [ { "type": "command", "command": "node", "args": ["<absdir>/semble-explore.mjs"], "timeout": 5 } ] }
]
},
"permissions": {
@@ -204,9 +204,9 @@ process. The matcher is exactly `Explore` — no other subagent type is touched.
}
```
`timeout` is in MILLISECONDS and is **not optional**: an entry without it
inherits Claude Code's 60 s default, so a hung `node` on the `Bash` matcher would
stall every tool call in the session for a minute. 5000 is ~80x the measured
`timeout` is in SECONDS and is **not optional**: an entry without it
inherits Claude Code's 600 s default, so a hung `node` on the `Bash` matcher would
stall the tool call for 10 minutes. 5 s is ~80x the measured
runtime of these hooks.
The marker for all semble entries is `args` containing a path whose basename is
@@ -243,10 +243,10 @@ SETTINGS="$PWD/.claude/settings.json" HOOKS_DIR="$PWD/.claude/hooks" node -e '
const fs=require("fs"), path=require("path");
const f=process.env.SETTINGS, dir=process.env.HOOKS_DIR;
const marks=["semble-session.mjs","semble-reminder.mjs","semble-explore.mjs"];
const want=[["SessionStart",null,"semble-session.mjs",5000],
["PreToolUse","Bash","semble-reminder.mjs",5000],
["PreToolUse","Grep","semble-reminder.mjs",5000],
["SubagentStart","Explore","semble-explore.mjs",5000]];
const want=[["SessionStart",null,"semble-session.mjs",5],
["PreToolUse","Bash","semble-reminder.mjs",5],
["PreToolUse","Grep","semble-reminder.mjs",5],
["SubagentStart","Explore","semble-explore.mjs",5]];
const tools=["mcp__semble_code__search","mcp__semble_code__find_related"];
let s={};
if(fs.existsSync(f)){
@@ -327,10 +327,10 @@ const fs=require("fs"), path=require("path");
const f=process.env.SG_SETTINGS, dir=process.env.SG_HOOKS;
const doHooks=process.env.SG_DO_HOOKS==="1", doPerms=process.env.SG_DO_PERMS==="1";
const marks=["semble-session.mjs","semble-reminder.mjs","semble-explore.mjs"];
const want=[["SessionStart",null,"semble-session.mjs",5000],
["PreToolUse","Bash","semble-reminder.mjs",5000],
["PreToolUse","Grep","semble-reminder.mjs",5000],
["SubagentStart","Explore","semble-explore.mjs",5000]];
const want=[["SessionStart",null,"semble-session.mjs",5],
["PreToolUse","Bash","semble-reminder.mjs",5],
["PreToolUse","Grep","semble-reminder.mjs",5],
["SubagentStart","Explore","semble-explore.mjs",5]];
const tools=[process.env.SG_SEARCH,process.env.SG_RELATED];
let s={};
if(fs.existsSync(f)){
+8 -8
View File
@@ -261,14 +261,14 @@ const REMIND_OK = (cwd) => ({
const s = readSettings(p);
const { session, reminder: rem, explore: exp } = semblePaths(p);
check('A1.sessionEntry', s.hooks.SessionStart, [
{ hooks: [{ type: 'command', command: 'node', args: [session], timeout: 5000 }] },
], 'SessionStart entry has the exact contract shape with an explicit 5000 ms timeout');
{ hooks: [{ type: 'command', command: 'node', args: [session], timeout: 5 }] },
], 'SessionStart entry has the exact contract shape with an explicit 5 s timeout');
check('A1.preToolUse', s.hooks.PreToolUse, [
{ hooks: [{ type: 'command', command: 'node', args: [rem], timeout: 5000 }], matcher: 'Bash' },
{ hooks: [{ type: 'command', command: 'node', args: [rem], timeout: 5000 }], matcher: 'Grep' },
{ hooks: [{ type: 'command', command: 'node', args: [rem], timeout: 5 }], matcher: 'Bash' },
{ hooks: [{ type: 'command', command: 'node', args: [rem], timeout: 5 }], matcher: 'Grep' },
], 'the reminder is registered once under Bash and once under Grep');
check('A1.subagentStart', s.hooks.SubagentStart, [
{ hooks: [{ type: 'command', command: 'node', args: [exp], timeout: 5000 }], matcher: 'Explore' },
{ hooks: [{ type: 'command', command: 'node', args: [exp], timeout: 5 }], matcher: 'Explore' },
], 'the explore hook is registered once under SubagentStart with matcher Explore');
check('A1.perm', s.permissions.allow, ['mcp__semble_code__search', 'mcp__semble_code__find_related'],
'both MCP tool names land in permissions.allow');
@@ -358,10 +358,10 @@ const FOREIGN = {
const seed = {
hooks: {
SessionStart: [
{ hooks: [{ type: 'command', command: 'node', args: [staleDir + '/semble-session.mjs'], timeout: 5000 }] },
{ hooks: [{ type: 'command', command: 'node', args: [staleDir + '/semble-session.mjs'], timeout: 5 }] },
],
PreToolUse: [
{ matcher: 'Bash', hooks: [{ type: 'command', command: 'node', args: [staleDir + '/semble-reminder.mjs'], timeout: 5000 }] },
{ matcher: 'Bash', hooks: [{ type: 'command', command: 'node', args: [staleDir + '/semble-reminder.mjs'], timeout: 5 }] },
{ matcher: 'Write', hooks: [{ type: 'command', command: 'node', args: ['/opt/foreign/other.mjs'], timeout: 2000 }] },
],
},
@@ -955,7 +955,7 @@ const PRE_MD = '# CLAUDE.md\n\n## Overview\n\nproject text\n';
const FOREIGN_HOOK = { type: 'command', command: 'node', args: ['/opt/foreign/guard.mjs'], timeout: 3000 };
{
const staleDir = '/old/elsewhere/.claude/hooks';
const staleHook = { type: 'command', command: 'node', args: [staleDir + '/semble-reminder.mjs'], timeout: 5000 };
const staleHook = { type: 'command', command: 'node', args: [staleDir + '/semble-reminder.mjs'], timeout: 5 };
const seed = { hooks: { PreToolUse: [{ matcher: 'Bash', hooks: [FOREIGN_HOOK, staleHook] }] } };
const p = freshProject({ settings: JSON.stringify(seed, null, 2) + '\n' });
const r = guidance(p, ['install', '--part', 'hooks', '--json']);