55Supports multiple IDEs: Cursor, Claude Code (future).
66"""
77
8+ import copy
89import json
910from pathlib import Path
1011from typing import Optional
1112
13+ import yaml
14+
1215from cycode .cli .apps .ai_guardrails .consts import (
13- CYCODE_SCAN_PROMPT_COMMAND ,
1416 DEFAULT_IDE ,
1517 IDE_CONFIGS ,
1618 AIIDEType ,
19+ PolicyMode ,
1720 get_hooks_config ,
1821)
22+ from cycode .cli .apps .ai_guardrails .scan .consts import DEFAULT_POLICY , POLICY_FILE_NAME
1923from cycode .logger import get_logger
2024
2125logger = get_logger ('AI Guardrails Hooks' )
@@ -58,6 +62,13 @@ def save_hooks_file(hooks_path: Path, hooks_config: dict) -> bool:
5862 return False
5963
6064
65+ _CYCODE_COMMAND_MARKERS = ('cycode ai-guardrails' ,)
66+
67+
68+ def _is_cycode_command (command : str ) -> bool :
69+ return any (marker in command for marker in _CYCODE_COMMAND_MARKERS )
70+
71+
6172def is_cycode_hook_entry (entry : dict ) -> bool :
6273 """Check if a hook entry is from cycode-cli.
6374
@@ -68,22 +79,66 @@ def is_cycode_hook_entry(entry: dict) -> bool:
6879 """
6980 # Check Cursor format (flat command)
7081 command = entry .get ('command' , '' )
71- if CYCODE_SCAN_PROMPT_COMMAND in command :
82+ if _is_cycode_command ( command ) :
7283 return True
7384
7485 # Check Claude Code format (nested hooks array)
7586 hooks = entry .get ('hooks' , [])
7687 for hook in hooks :
7788 if isinstance (hook , dict ):
7889 hook_command = hook .get ('command' , '' )
79- if CYCODE_SCAN_PROMPT_COMMAND in hook_command :
90+ if _is_cycode_command ( hook_command ) :
8091 return True
8192
8293 return False
8394
8495
96+ def _load_policy (policy_path : Path ) -> dict :
97+ """Load existing policy file merged with defaults, or return defaults if not found."""
98+ if not policy_path .exists ():
99+ return copy .deepcopy (DEFAULT_POLICY )
100+ try :
101+ existing = yaml .safe_load (policy_path .read_text (encoding = 'utf-8' )) or {}
102+ except Exception :
103+ existing = {}
104+ return {** copy .deepcopy (DEFAULT_POLICY ), ** existing }
105+
106+
107+ def create_policy_file (scope : str , mode : PolicyMode , repo_path : Optional [Path ] = None ) -> tuple [bool , str ]:
108+ """Create or update the ai-guardrails.yaml policy file.
109+
110+ If the file already exists, only the mode field is updated.
111+ If it doesn't exist, a new file is created from the default policy.
112+
113+ Args:
114+ scope: 'user' for user-level, 'repo' for repository-level
115+ mode: The policy mode to set
116+ repo_path: Repository path (required if scope is 'repo')
117+
118+ Returns:
119+ Tuple of (success, message)
120+ """
121+ config_dir = repo_path / '.cycode' if scope == 'repo' and repo_path else Path .home () / '.cycode'
122+ policy_path = config_dir / POLICY_FILE_NAME
123+
124+ policy = _load_policy (policy_path )
125+
126+ policy ['mode' ] = mode .value
127+
128+ try :
129+ config_dir .mkdir (parents = True , exist_ok = True )
130+ policy_path .write_text (yaml .dump (policy , default_flow_style = False , sort_keys = False ), encoding = 'utf-8' )
131+ return True , f'AI guardrails policy ({ mode .value } mode) set: { policy_path } '
132+ except Exception as e :
133+ logger .error ('Failed to create policy file' , exc_info = e )
134+ return False , f'Failed to create policy file: { policy_path } '
135+
136+
85137def install_hooks (
86- scope : str = 'user' , repo_path : Optional [Path ] = None , ide : AIIDEType = DEFAULT_IDE
138+ scope : str = 'user' ,
139+ repo_path : Optional [Path ] = None ,
140+ ide : AIIDEType = DEFAULT_IDE ,
141+ report_mode : bool = False ,
87142) -> tuple [bool , str ]:
88143 """
89144 Install Cycode AI guardrails hooks.
@@ -92,6 +147,7 @@ def install_hooks(
92147 scope: 'user' for user-level hooks, 'repo' for repository-level hooks
93148 repo_path: Repository path (required if scope is 'repo')
94149 ide: The AI IDE type (default: Cursor)
150+ report_mode: If True, install hooks in async mode (non-blocking)
95151
96152 Returns:
97153 Tuple of (success, message)
@@ -104,7 +160,7 @@ def install_hooks(
104160 existing .setdefault ('hooks' , {})
105161
106162 # Get IDE-specific hooks configuration
107- hooks_config = get_hooks_config (ide )
163+ hooks_config = get_hooks_config (ide , async_mode = report_mode )
108164
109165 # Add/update Cycode hooks
110166 for event , entries in hooks_config ['hooks' ].items ():
0 commit comments