feat: regex tool name matching for hooks (#660)
This commit is contained in:
@@ -147,8 +147,10 @@ export async function loadHooks(
|
||||
* Check if a tool name matches a matcher pattern
|
||||
* Patterns:
|
||||
* - "*" or "": matches all tools
|
||||
* - "ToolName": exact match
|
||||
* - "Tool1|Tool2|Tool3": matches any of the listed tools
|
||||
* - "ToolName": exact match (simple alphanumeric strings)
|
||||
* - "Edit|Write": regex alternation, matches Edit or Write
|
||||
* - "Notebook.*": regex pattern, matches Notebook, NotebookEdit, etc.
|
||||
* - Any valid regex pattern is supported (case-sensitive)
|
||||
*/
|
||||
export function matchesTool(pattern: string, toolName: string): boolean {
|
||||
// Empty or "*" matches everything
|
||||
@@ -156,14 +158,14 @@ export function matchesTool(pattern: string, toolName: string): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for pipe-separated list
|
||||
if (pattern.includes("|")) {
|
||||
const tools = pattern.split("|").map((t) => t.trim());
|
||||
return tools.includes(toolName);
|
||||
// Treat pattern as regex (anchored to match full tool name)
|
||||
try {
|
||||
const regex = new RegExp(`^(?:${pattern})$`);
|
||||
return regex.test(toolName);
|
||||
} catch {
|
||||
// Invalid regex, fall back to exact match
|
||||
return pattern === toolName;
|
||||
}
|
||||
|
||||
// Exact match
|
||||
return pattern === toolName;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -168,6 +168,34 @@ describe("Hooks Loader", () => {
|
||||
expect(matchesTool("Edit|Write", "Bash")).toBe(false);
|
||||
expect(matchesTool("Edit|Write|Read", "Read")).toBe(true);
|
||||
});
|
||||
|
||||
test("regex patterns work", () => {
|
||||
// .* suffix pattern
|
||||
expect(matchesTool("Notebook.*", "Notebook")).toBe(true);
|
||||
expect(matchesTool("Notebook.*", "NotebookEdit")).toBe(true);
|
||||
expect(matchesTool("Notebook.*", "NotebookRead")).toBe(true);
|
||||
expect(matchesTool("Notebook.*", "Edit")).toBe(false);
|
||||
|
||||
// Prefix pattern
|
||||
expect(matchesTool(".*Edit", "NotebookEdit")).toBe(true);
|
||||
expect(matchesTool(".*Edit", "Edit")).toBe(true);
|
||||
expect(matchesTool(".*Edit", "Write")).toBe(false);
|
||||
|
||||
// Character class
|
||||
expect(matchesTool("Task|Bash", "Task")).toBe(true);
|
||||
expect(matchesTool("Task|Bash", "Bash")).toBe(true);
|
||||
|
||||
// More complex patterns
|
||||
expect(matchesTool("Web.*", "WebFetch")).toBe(true);
|
||||
expect(matchesTool("Web.*", "WebSearch")).toBe(true);
|
||||
expect(matchesTool("Web.*", "Bash")).toBe(false);
|
||||
});
|
||||
|
||||
test("invalid regex falls back to exact match", () => {
|
||||
// Unclosed bracket is invalid regex
|
||||
expect(matchesTool("[invalid", "[invalid")).toBe(true);
|
||||
expect(matchesTool("[invalid", "invalid")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getMatchingHooks", () => {
|
||||
|
||||
Reference in New Issue
Block a user