@@ -1096,27 +1096,47 @@ def _install_codex_hook(project_dir: Path) -> None:
10961096 }
10971097 }
10981098
1099- pre_tool = existing . setdefault ( "hooks" , {}). setdefault ( "PreToolUse" , [] )
1100- existing [ " hooks" ][ "PreToolUse" ] = [ h for h in pre_tool if "graphify" not in str ( h )]
1101- existing [ " hooks" ][ " PreToolUse"] .extend (hook_entry ["hooks" ]["PreToolUse" ])
1099+ _remove_graphify_hook_entries ( existing )
1100+ hooks = existing . setdefault ( "hooks" , {})
1101+ hooks . setdefault ( " PreToolUse", []) .extend (hook_entry ["hooks" ]["PreToolUse" ])
11021102 hooks_path .write_text (json .dumps (existing , indent = 2 ), encoding = "utf-8" )
11031103 print (f" .codex/hooks.json -> PreToolUse hook registered ({ graphify_exe } hook-check)" )
11041104
11051105
1106+ def _remove_graphify_hook_entries (settings : dict ) -> None :
1107+ """Remove graphify hook entries while preserving unrelated hooks."""
1108+ hooks = settings .get ("hooks" )
1109+ if not isinstance (hooks , dict ):
1110+ settings ["hooks" ] = {}
1111+ return
1112+
1113+ for event , event_hooks in list (hooks .items ()):
1114+ if not isinstance (event_hooks , list ):
1115+ if "graphify" in str (event_hooks ):
1116+ hooks .pop (event , None )
1117+ continue
1118+ filtered = [h for h in event_hooks if "graphify" not in str (h )]
1119+ if filtered :
1120+ hooks [event ] = filtered
1121+ else :
1122+ hooks .pop (event , None )
1123+
1124+ if not hooks :
1125+ settings .pop ("hooks" , None )
1126+
1127+
11061128def _uninstall_codex_hook (project_dir : Path ) -> None :
1107- """Remove graphify PreToolUse hook from .codex/hooks.json."""
1129+ """Remove graphify hooks from .codex/hooks.json."""
11081130 hooks_path = project_dir / ".codex" / "hooks.json"
11091131 if not hooks_path .exists ():
11101132 return
11111133 try :
11121134 existing = json .loads (hooks_path .read_text (encoding = "utf-8" ))
11131135 except json .JSONDecodeError :
11141136 return
1115- pre_tool = existing .get ("hooks" , {}).get ("PreToolUse" , [])
1116- filtered = [h for h in pre_tool if "graphify" not in str (h )]
1117- existing ["hooks" ]["PreToolUse" ] = filtered
1137+ _remove_graphify_hook_entries (existing )
11181138 hooks_path .write_text (json .dumps (existing , indent = 2 ), encoding = "utf-8" )
1119- print (f" .codex/hooks.json -> PreToolUse hook removed" )
1139+ print (f" .codex/hooks.json -> graphify hooks removed" )
11201140
11211141
11221142def _agents_install (project_dir : Path , platform : str ) -> None :
@@ -1209,24 +1229,18 @@ def claude_install(project_dir: Path | None = None) -> None:
12091229
12101230def _remove_graphify_claude_hooks (settings : dict ) -> None :
12111231 """Remove graphify Claude hooks while preserving unrelated hooks."""
1212- hooks = settings .get ("hooks" )
1213- if not isinstance (hooks , dict ):
1214- settings ["hooks" ] = {}
1215- return
1232+ _remove_graphify_hook_entries (settings )
12161233
1217- for event in ("UserPromptSubmit" , "PreToolUse" ):
1218- event_hooks = hooks .get (event )
1219- if not isinstance (event_hooks , list ):
1220- hooks .pop (event , None )
1221- continue
1222- filtered = [h for h in event_hooks if "graphify" not in str (h )]
1223- if filtered :
1224- hooks [event ] = filtered
1225- else :
1226- hooks .pop (event , None )
12271234
1228- if not hooks :
1229- settings .pop ("hooks" , None )
1235+ def _remove_graphify_claude_hook_scripts (claude_dir : Path ) -> list [Path ]:
1236+ """Remove generated graphify Claude hook scripts."""
1237+ removed = []
1238+ for name in ("graphify-guard.py" , "graphify-hook.cjs" ):
1239+ hook_path = claude_dir / "hooks" / name
1240+ if hook_path .exists ():
1241+ hook_path .unlink ()
1242+ removed .append (hook_path )
1243+ return removed
12301244
12311245
12321246def _load_json_object (path : Path ) -> dict :
@@ -1251,6 +1265,7 @@ def _install_claude_hook(project_dir: Path) -> None:
12511265 settings_path = claude_dir / "settings.json"
12521266 hooks_dir = claude_dir / "hooks"
12531267 hooks_dir .mkdir (parents = True , exist_ok = True )
1268+ _remove_graphify_claude_hook_scripts (claude_dir )
12541269
12551270 guard_path = hooks_dir / "graphify-guard.py"
12561271 guard_path .write_text (_GRAPHIFY_GUARD_SCRIPT , encoding = "utf-8" )
@@ -1285,10 +1300,8 @@ def _uninstall_claude_hook(project_dir: Path) -> None:
12851300 settings_path .write_text (json .dumps (settings , indent = 2 ), encoding = "utf-8" )
12861301 print (" .claude/settings.json -> graphify hooks removed" )
12871302
1288- guard_path = claude_dir / "hooks" / "graphify-guard.py"
1289- if guard_path .exists ():
1290- guard_path .unlink ()
1291- print (" .claude/hooks/graphify-guard.py -> removed" )
1303+ for hook_path in _remove_graphify_claude_hook_scripts (claude_dir ):
1304+ print (f" { hook_path .relative_to (claude_dir .parent )} -> removed" )
12921305
12931306def claude_uninstall (project_dir : Path | None = None ) -> None :
12941307 """Remove the graphify section from the local CLAUDE.md."""
@@ -1382,7 +1395,7 @@ def main() -> None:
13821395 # Skip during install/uninstall (hook writes trigger a fresh check anyway).
13831396 # Deduplicate paths so platforms sharing the same install dir don't warn twice.
13841397 if not any (arg in ("install" , "uninstall" ) for arg in sys .argv ) and (
1385- len (sys .argv ) < 2 or sys .argv [1 ] not in ("skill" , "setup" )
1398+ len (sys .argv ) < 2 or sys .argv [1 ] not in ("skill" , "setup" , "hook-check" )
13861399 ):
13871400 for skill_dst in {_platform_skill_destination (platform_name ) for platform_name in _PLATFORM_CONFIG }:
13881401 _check_skill_version (skill_dst )
0 commit comments