diff --git a/hooks/hooks.json b/hooks/hooks.json index 35d79fd5a..f1c82b515 100644 --- a/hooks/hooks.json +++ b/hooks/hooks.json @@ -36,7 +36,7 @@ "id": "pre:edit-write:suggest-compact" }, { - "matcher": "*", + "matcher": ".*", "hooks": [ { "type": "command", @@ -73,7 +73,7 @@ "id": "pre:config-protection" }, { - "matcher": "*", + "matcher": ".*", "hooks": [ { "type": "command", @@ -98,7 +98,7 @@ ], "PreCompact": [ { - "matcher": "*", + "matcher": ".*", "hooks": [ { "type": "command", @@ -111,7 +111,7 @@ ], "SessionStart": [ { - "matcher": "*", + "matcher": ".*", "hooks": [ { "type": "command", @@ -122,7 +122,7 @@ "id": "session:start" }, { - "matcher": "*", + "matcher": ".*", "hooks": [ { "type": "command", @@ -135,7 +135,7 @@ ], "PostToolUse": [ { - "matcher": "*", + "matcher": ".*", "hooks": [ { "type": "command", @@ -147,7 +147,7 @@ "id": "post:dispatcher:sync" }, { - "matcher": "*", + "matcher": ".*", "hooks": [ { "type": "command", @@ -162,7 +162,7 @@ ], "PostToolUseFailure": [ { - "matcher": "*", + "matcher": ".*", "hooks": [ { "type": "command", @@ -186,7 +186,7 @@ ], "Stop": [ { - "matcher": "*", + "matcher": ".*", "hooks": [ { "type": "command", @@ -197,7 +197,7 @@ "id": "stop:plan-canvas-pending" }, { - "matcher": "*", + "matcher": ".*", "hooks": [ { "type": "command", @@ -209,7 +209,7 @@ "id": "stop:format-typecheck" }, { - "matcher": "*", + "matcher": ".*", "hooks": [ { "type": "command", @@ -220,7 +220,7 @@ "id": "stop:check-console-log" }, { - "matcher": "*", + "matcher": ".*", "hooks": [ { "type": "command", @@ -233,7 +233,7 @@ "id": "stop:session-end" }, { - "matcher": "*", + "matcher": ".*", "hooks": [ { "type": "command", @@ -246,7 +246,7 @@ "id": "stop:evaluate-session" }, { - "matcher": "*", + "matcher": ".*", "hooks": [ { "type": "command", @@ -259,7 +259,7 @@ "id": "stop:cost-tracker" }, { - "matcher": "*", + "matcher": ".*", "hooks": [ { "type": "command", @@ -274,7 +274,7 @@ ], "SessionEnd": [ { - "matcher": "*", + "matcher": ".*", "hooks": [ { "type": "command", diff --git a/tests/hooks/hooks.test.js b/tests/hooks/hooks.test.js index 746aa88f7..ce3411b15 100644 --- a/tests/hooks/hooks.test.js +++ b/tests/hooks/hooks.test.js @@ -2585,7 +2585,7 @@ async function runTests() { ['post:dispatcher:sync', 'post:dispatcher:async'], 'PostToolUse should have one sync and one async dispatcher' ); - assert.ok(postEntries.every(entry => entry.matcher === '*')); + assert.ok(postEntries.every(entry => entry.matcher === '.*')); const preCommand = Array.isArray(preBash[0].hooks[0].command) ? preBash[0].hooks[0].command.join(' ') : preBash[0].hooks[0].command; @@ -2599,6 +2599,22 @@ async function runTests() { passed++; else failed++; + if ( + test('all string hook matchers are valid regular expressions', () => { + const hooksPath = path.join(__dirname, '..', '..', 'hooks', 'hooks.json'); + const hooks = JSON.parse(fs.readFileSync(hooksPath, 'utf8')); + + for (const [eventName, hookArray] of Object.entries(hooks.hooks)) { + for (const entry of hookArray) { + if (typeof entry.matcher !== 'string') continue; + assert.doesNotThrow(() => new RegExp(entry.matcher), `${eventName}/${entry.id || 'hook'} should use a valid regex matcher`); + } + } + }) + ) + passed++; + else failed++; + if ( test('SessionEnd marker hook is async and cleanup-safe', () => { const hooksPath = path.join(__dirname, '..', '..', 'hooks', 'hooks.json');