1- """Constants for AI guardrails hooks management.
1+ """Shared constants and policy/mode enums for AI guardrails."""
22
3- Currently supports:
4- - Cursor
5- - Claude Code
6- """
7-
8- import platform
9- from copy import deepcopy
103from enum import Enum
11- from pathlib import Path
12- from typing import NamedTuple
13-
14-
15- class AIIDEType (str , Enum ):
16- """Supported AI IDE types."""
17-
18- CURSOR = 'cursor'
19- CLAUDE_CODE = 'claude-code'
204
215
226class PolicyMode (str , Enum ):
@@ -33,123 +17,7 @@ class InstallMode(str, Enum):
3317 BLOCK = 'block'
3418
3519
36- class IDEConfig (NamedTuple ):
37- """Configuration for an AI IDE."""
38-
39- name : str
40- hooks_dir : Path
41- repo_hooks_subdir : str # Subdirectory in repo for hooks (e.g., '.cursor')
42- hooks_file_name : str
43- hook_events : list [str ] # List of supported hook event names for this IDE
44-
45-
46- def _get_cursor_hooks_dir () -> Path :
47- """Get Cursor hooks directory based on platform."""
48- if platform .system () == 'Darwin' :
49- return Path .home () / '.cursor'
50- if platform .system () == 'Windows' :
51- return Path .home () / 'AppData' / 'Roaming' / 'Cursor'
52- # Linux
53- return Path .home () / '.config' / 'Cursor'
54-
55-
56- def _get_claude_code_hooks_dir () -> Path :
57- """Get Claude Code hooks directory.
58-
59- Claude Code uses ~/.claude on all platforms.
60- """
61- return Path .home () / '.claude'
62-
63-
64- # IDE-specific configurations
65- IDE_CONFIGS : dict [AIIDEType , IDEConfig ] = {
66- AIIDEType .CURSOR : IDEConfig (
67- name = 'Cursor' ,
68- hooks_dir = _get_cursor_hooks_dir (),
69- repo_hooks_subdir = '.cursor' ,
70- hooks_file_name = 'hooks.json' ,
71- hook_events = ['beforeSubmitPrompt' , 'beforeReadFile' , 'beforeMCPExecution' ],
72- ),
73- AIIDEType .CLAUDE_CODE : IDEConfig (
74- name = 'Claude Code' ,
75- hooks_dir = _get_claude_code_hooks_dir (),
76- repo_hooks_subdir = '.claude' ,
77- hooks_file_name = 'settings.json' ,
78- hook_events = ['UserPromptSubmit' , 'PreToolUse:Read' , 'PreToolUse:mcp' ],
79- ),
80- }
81-
82- # Default IDE
83- DEFAULT_IDE = AIIDEType .CURSOR
84-
85- # Command used in hooks
20+ # Base CLI commands invoked from installed hooks. IDE classes append --ide flags
21+ # (and any other suffix) on top of these.
8622CYCODE_SCAN_PROMPT_COMMAND = 'cycode ai-guardrails scan'
8723CYCODE_SESSION_START_COMMAND = 'cycode ai-guardrails session-start'
88-
89-
90- def _get_cursor_hooks_config (async_mode : bool = False ) -> dict :
91- """Get Cursor-specific hooks configuration."""
92- config = IDE_CONFIGS [AIIDEType .CURSOR ]
93- command = f'{ CYCODE_SCAN_PROMPT_COMMAND } &' if async_mode else CYCODE_SCAN_PROMPT_COMMAND
94- hooks = {event : [{'command' : command }] for event in config .hook_events }
95- hooks ['sessionStart' ] = [{'command' : f'{ CYCODE_SESSION_START_COMMAND } --ide cursor' }]
96-
97- return {
98- 'version' : 1 ,
99- 'hooks' : hooks ,
100- }
101-
102-
103- def _get_claude_code_hooks_config (async_mode : bool = False ) -> dict :
104- """Get Claude Code-specific hooks configuration.
105-
106- Claude Code uses a different hook format with nested structure:
107- - hooks are arrays of objects with 'hooks' containing command arrays
108- - PreToolUse uses 'matcher' field to specify which tools to intercept
109- """
110- command = f'{ CYCODE_SCAN_PROMPT_COMMAND } --ide claude-code'
111-
112- hook_entry = {'type' : 'command' , 'command' : command }
113- if async_mode :
114- hook_entry ['async' ] = True
115- hook_entry ['timeout' ] = 20
116-
117- return {
118- 'hooks' : {
119- 'SessionStart' : [
120- {
121- 'hooks' : [{'type' : 'command' , 'command' : f'{ CYCODE_SESSION_START_COMMAND } --ide claude-code' }],
122- }
123- ],
124- 'UserPromptSubmit' : [
125- {
126- 'hooks' : [deepcopy (hook_entry )],
127- }
128- ],
129- 'PreToolUse' : [
130- {
131- 'matcher' : 'Read' ,
132- 'hooks' : [deepcopy (hook_entry )],
133- },
134- {
135- 'matcher' : 'mcp__.*' ,
136- 'hooks' : [deepcopy (hook_entry )],
137- },
138- ],
139- },
140- }
141-
142-
143- def get_hooks_config (ide : AIIDEType , async_mode : bool = False ) -> dict :
144- """Get the hooks configuration for a specific IDE.
145-
146- Args:
147- ide: The AI IDE type
148- async_mode: If True, hooks run asynchronously (non-blocking)
149-
150- Returns:
151- Dict with hooks configuration for the specified IDE
152- """
153- if ide == AIIDEType .CLAUDE_CODE :
154- return _get_claude_code_hooks_config (async_mode = async_mode )
155- return _get_cursor_hooks_config (async_mode = async_mode )
0 commit comments