@@ -1148,20 +1148,46 @@ static void cbm_claude_user_root(const char *home_dir, char *out, size_t out_sz)
11481148 }
11491149}
11501150
1151+ #ifdef _WIN32
1152+ #define CMM_HOOK_SCRIPT_EXT ".cmd"
1153+ #else
1154+ #define CMM_HOOK_SCRIPT_EXT ""
1155+ #endif
1156+
1157+ /* Append the platform hook script extension (e.g. ".cmd" on Windows). */
1158+ static void cbm_hook_script_filename (const char * script_name , char * out , size_t out_sz ) {
1159+ if (out_sz == 0 ) {
1160+ return ;
1161+ }
1162+ snprintf (out , out_sz , "%s%s" , script_name , CMM_HOOK_SCRIPT_EXT );
1163+ }
1164+
11511165/* Build the hook command string written into Claude Code's settings.json.
1152- * Honors $CLAUDE_CONFIG_DIR. When CLAUDE_CONFIG_DIR is unset, preserves the
1153- * legacy tilde-expanded form so settings.json stays portable across HOME values. */
1166+ * Honors $CLAUDE_CONFIG_DIR. On Unix, when CLAUDE_CONFIG_DIR is unset, preserves
1167+ * the legacy tilde-expanded form so settings.json stays portable across HOME values.
1168+ * On Windows, writes an absolute path to a .cmd wrapper — extensionless bash shims
1169+ * trigger the "Open with" dialog when Cursor/Claude Code spawn hook commands. */
11541170static void cbm_resolve_hook_command (const char * script_name , char * out , size_t out_sz ) {
11551171 if (out_sz == 0 ) {
11561172 return ;
11571173 }
11581174 out [0 ] = '\0' ;
11591175 char env_buf [CLI_BUF_1K ];
1176+ char filename [CLI_BUF_256 ];
1177+ cbm_hook_script_filename (script_name , filename , sizeof (filename ));
11601178 const char * env = cbm_safe_getenv ("CLAUDE_CONFIG_DIR" , env_buf , sizeof (env_buf ), NULL );
11611179 if (env && env [0 ]) {
1162- snprintf (out , out_sz , "%s/hooks/%s" , env , script_name );
1180+ snprintf (out , out_sz , "%s/hooks/%s" , env , filename );
11631181 } else {
1164- snprintf (out , out_sz , "~/.claude/hooks/%s" , script_name );
1182+ #ifdef _WIN32
1183+ const char * home = cbm_get_home_dir ();
1184+ if (!home || !home [0 ]) {
1185+ return ;
1186+ }
1187+ snprintf (out , out_sz , "%s/.claude/hooks/%s" , home , filename );
1188+ #else
1189+ snprintf (out , out_sz , "~/.claude/hooks/%s" , filename );
1190+ #endif
11651191 }
11661192}
11671193
@@ -2063,12 +2089,25 @@ void cbm_install_hook_gate_script(const char *home, const char *binary_path) {
20632089 cbm_mkdir_p (hooks_dir , CLI_OCTAL_PERM );
20642090
20652091 char script_path [CLI_BUF_1K ];
2066- snprintf (script_path , sizeof (script_path ), "%s/" CMM_HOOK_GATE_SCRIPT , hooks_dir );
2092+ char script_name [CLI_BUF_256 ];
2093+ cbm_hook_script_filename (CMM_HOOK_GATE_SCRIPT , script_name , sizeof (script_name ));
2094+ snprintf (script_path , sizeof (script_path ), "%s/%s" , hooks_dir , script_name );
20672095
20682096 FILE * f = fopen (script_path , "w" );
20692097 if (!f ) {
20702098 return ;
20712099 }
2100+ #ifdef _WIN32
2101+ (void )fprintf (f ,
2102+ "@echo off\r\n"
2103+ "REM codebase-memory-mcp search augmenter (Claude Code PreToolUse).\r\n"
2104+ "REM NOTE: the legacy basename is kept for zero-migration upgrades.\r\n"
2105+ "REM Despite the name this NEVER blocks a tool call - it only adds\r\n"
2106+ "REM graph context. Any failure is silent (exit 0, no output).\r\n"
2107+ "\"%s\" hook-augment 2>nul\r\n"
2108+ "exit /b 0\r\n" ,
2109+ binary_path );
2110+ #else
20722111 (void )fprintf (f ,
20732112 "#!/usr/bin/env bash\n"
20742113 "# codebase-memory-mcp search augmenter (Claude Code PreToolUse).\n"
@@ -2080,6 +2119,7 @@ void cbm_install_hook_gate_script(const char *home, const char *binary_path) {
20802119 "\"$BIN\" hook-augment 2>/dev/null\n"
20812120 "exit 0\n" ,
20822121 binary_path );
2122+ #endif
20832123 /* fchmod before close to avoid TOCTOU race (CodeQL cpp/toctou-race-condition) */
20842124#ifndef _WIN32
20852125 fchmod (fileno (f ), CLI_OCTAL_PERM );
@@ -2107,12 +2147,34 @@ static void cbm_install_session_reminder_script(const char *home) {
21072147 cbm_mkdir_p (hooks_dir , CLI_OCTAL_PERM );
21082148
21092149 char script_path [CLI_BUF_1K ];
2110- snprintf (script_path , sizeof (script_path ), "%s/" CMM_SESSION_REMINDER_SCRIPT , hooks_dir );
2150+ char script_name [CLI_BUF_256 ];
2151+ cbm_hook_script_filename (CMM_SESSION_REMINDER_SCRIPT , script_name , sizeof (script_name ));
2152+ snprintf (script_path , sizeof (script_path ), "%s/%s" , hooks_dir , script_name );
21112153
21122154 FILE * f = fopen (script_path , "w" );
21132155 if (!f ) {
21142156 return ;
21152157 }
2158+ #ifdef _WIN32
2159+ (void )fprintf (
2160+ f , "@echo off\r\n"
2161+ "REM SessionStart hook: remind agent to use codebase-memory-mcp tools.\r\n"
2162+ "echo CRITICAL - Code Discovery Protocol:\r\n"
2163+ "echo 1. ALWAYS use codebase-memory-mcp tools FIRST for ANY code exploration:\r\n"
2164+ "echo - search_graph(name_pattern/label/qn_pattern) to find "
2165+ "functions/classes/routes\r\n"
2166+ "echo - trace_path(function_name, mode=calls^|data_flow^|cross_service) for "
2167+ "call chains\r\n"
2168+ "echo - get_code_snippet(qualified_name) for exact symbol source (precise "
2169+ "ranges)\r\n"
2170+ "echo - query_graph(query) for complex Cypher patterns\r\n"
2171+ "echo - get_architecture(aspects) for project structure\r\n"
2172+ "echo - search_code(pattern) for text search (graph-augmented grep)\r\n"
2173+ "echo 2. Use Grep/Glob/Read freely for text, configs, non-code files, and\r\n"
2174+ "echo always Read a file before editing it.\r\n"
2175+ "echo 3. If a project is not indexed yet, run index_repository FIRST.\r\n"
2176+ "exit /b 0\r\n" );
2177+ #else
21162178 (void )fprintf (
21172179 f , "#!/usr/bin/env bash\n"
21182180 "# SessionStart hook: remind agent to use codebase-memory-mcp tools.\n"
@@ -2130,6 +2192,7 @@ static void cbm_install_session_reminder_script(const char *home) {
21302192 " always Read a file before editing it.\n"
21312193 "3. If a project is not indexed yet, run index_repository FIRST.\n"
21322194 "REMINDER\n" );
2195+ #endif
21332196#ifndef _WIN32
21342197 fchmod (fileno (f ), CLI_OCTAL_PERM );
21352198#endif
@@ -2192,15 +2255,25 @@ static void cbm_install_subagent_reminder_script(const char *home) {
21922255 cbm_mkdir_p (hooks_dir , CLI_OCTAL_PERM );
21932256
21942257 char script_path [CLI_BUF_1K ];
2195- snprintf (script_path , sizeof (script_path ), "%s/" CMM_SUBAGENT_REMINDER_SCRIPT , hooks_dir );
2258+ char script_name [CLI_BUF_256 ];
2259+ cbm_hook_script_filename (CMM_SUBAGENT_REMINDER_SCRIPT , script_name , sizeof (script_name ));
2260+ snprintf (script_path , sizeof (script_path ), "%s/%s" , hooks_dir , script_name );
21962261
21972262 FILE * f = fopen (script_path , "w" );
21982263 if (!f ) {
21992264 return ;
22002265 }
2201- /* The additionalContext value is a single line with no embedded quotes,
2202- * backslashes, or newlines, so the JSON below is valid as written — no
2203- * runtime escaping (and no python3/jq dependency) is required. */
2266+ #ifdef _WIN32
2267+ (void )fprintf (
2268+ f , "@echo off\r\n"
2269+ "REM SubagentStart hook: tell subagents to use codebase-memory-mcp tools.\r\n"
2270+ "echo {\"hookSpecificOutput\":{\"hookEventName\":\"SubagentStart\","
2271+ "\"additionalContext\":\"Code discovery: prefer codebase-memory-mcp tools "
2272+ "(search_graph, trace_path, get_code_snippet, query_graph, get_architecture, "
2273+ "search_code) over grep/file-read for navigating code. Use Grep/Glob/Read for "
2274+ "text, configs, and non-code files.\"}}\r\n"
2275+ "exit /b 0\r\n" );
2276+ #else
22042277 (void )fprintf (f ,
22052278 "#!/usr/bin/env bash\n"
22062279 "# SubagentStart hook: tell subagents to use codebase-memory-mcp tools.\n"
@@ -2213,6 +2286,7 @@ static void cbm_install_subagent_reminder_script(const char *home) {
22132286 "search_code) over grep/file-read for navigating code. Use Grep/Glob/Read for "
22142287 "text, configs, and non-code files.\"}}\n"
22152288 "REMINDER\n" );
2289+ #endif
22162290#ifndef _WIN32
22172291 fchmod (fileno (f ), CLI_OCTAL_PERM );
22182292#endif
0 commit comments