2025-12-09 17:31:15 +09:00
|
|
|
import type { ClaudeHooksConfig, HookMatcher } from "../hooks/claude-code-hooks/types"
|
|
|
|
|
|
2026-02-06 12:48:28 +05:30
|
|
|
/**
|
|
|
|
|
* Escape all regex special characters EXCEPT asterisk (*).
|
|
|
|
|
* Asterisk is preserved for glob-to-regex conversion.
|
|
|
|
|
*/
|
|
|
|
|
function escapeRegexExceptAsterisk(str: string): string {
|
|
|
|
|
// Escape all regex special chars except * (which we convert to .* for glob matching)
|
|
|
|
|
return str.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-18 14:19:00 +09:00
|
|
|
const regexCache = new Map<string, RegExp>()
|
|
|
|
|
|
2025-12-09 17:31:15 +09:00
|
|
|
export function matchesToolMatcher(toolName: string, matcher: string): boolean {
|
|
|
|
|
if (!matcher) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
const patterns = matcher.split("|").map((p) => p.trim())
|
|
|
|
|
return patterns.some((p) => {
|
|
|
|
|
if (p.includes("*")) {
|
2026-02-06 12:48:28 +05:30
|
|
|
// First escape regex special chars (except *), then convert * to .*
|
2026-03-18 14:19:00 +09:00
|
|
|
let regex = regexCache.get(p)
|
|
|
|
|
if (!regex) {
|
|
|
|
|
const escaped = escapeRegexExceptAsterisk(p)
|
|
|
|
|
regex = new RegExp(`^${escaped.replace(/\*/g, ".*")}$`, "i")
|
|
|
|
|
regexCache.set(p, regex)
|
|
|
|
|
}
|
2025-12-09 17:31:15 +09:00
|
|
|
return regex.test(toolName)
|
|
|
|
|
}
|
|
|
|
|
return p.toLowerCase() === toolName.toLowerCase()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function findMatchingHooks(
|
|
|
|
|
config: ClaudeHooksConfig,
|
|
|
|
|
eventName: keyof ClaudeHooksConfig,
|
|
|
|
|
toolName?: string
|
|
|
|
|
): HookMatcher[] {
|
|
|
|
|
const hookMatchers = config[eventName]
|
|
|
|
|
if (!hookMatchers) return []
|
|
|
|
|
|
|
|
|
|
return hookMatchers.filter((hookMatcher) => {
|
|
|
|
|
if (!toolName) return true
|
|
|
|
|
return matchesToolMatcher(toolName, hookMatcher.matcher)
|
|
|
|
|
})
|
|
|
|
|
}
|