@@ -1798,7 +1798,16 @@ int cbm_remove_junie_mcp(const char *config_path) {
17981798#define CMM_HOOK_MATCHER "Grep|Glob|Read"
17991799/* Basename only; the full command path is resolved at install time via
18001800 * cbm_resolve_hook_command so $CLAUDE_CONFIG_DIR is honored. */
1801+ #ifdef _WIN32
1802+ /* #929: extensionless bash shims under %USERPROFILE%\\.claude\\hooks trigger
1803+ * the "How do you want to open this file?" dialog when editors (Cursor) scan
1804+ * the hooks dir, and cannot execute without bash anyway. Windows installs
1805+ * .cmd scripts; the extensionless legacy files are removed on upgrade. */
1806+ #define CMM_HOOK_GATE_SCRIPT "cbm-code-discovery-gate.cmd"
1807+ #else
18011808#define CMM_HOOK_GATE_SCRIPT "cbm-code-discovery-gate"
1809+ #endif
1810+ #define CMM_HOOK_GATE_SCRIPT_LEGACY "cbm-code-discovery-gate"
18021811/* Hard backstop in settings.json; the binary also self-bounds with an
18031812 * in-process deadline well under this. */
18041813#define CMM_HOOK_TIMEOUT_SEC 5
@@ -2046,6 +2055,20 @@ int cbm_remove_claude_hooks(const char *settings_path) {
20462055 * a missing/old/hung binary results in a silent exit 0 (issue #362/#288).
20472056 * The legacy filename `cbm-code-discovery-gate` is retained so existing
20482057 * settings.json entries and uninstall keep working with zero migration. */
2058+ /* #929 (Windows): remove the pre-.cmd extensionless twin so upgrades stop
2059+ * triggering the Open-With dialog. POSIX keeps the extensionless name, where
2060+ * legacy == current — never unlink there. */
2061+ static void cbm_remove_legacy_hook_script (const char * hooks_dir , const char * legacy_name ) {
2062+ #ifdef _WIN32
2063+ char legacy_path [CLI_BUF_1K ];
2064+ snprintf (legacy_path , sizeof (legacy_path ), "%s/%s" , hooks_dir , legacy_name );
2065+ cbm_unlink (legacy_path );
2066+ #else
2067+ (void )hooks_dir ;
2068+ (void )legacy_name ;
2069+ #endif
2070+ }
2071+
20492072void cbm_install_hook_gate_script (const char * home , const char * binary_path ) {
20502073 if (!home || !binary_path ) {
20512074 return ;
@@ -2066,13 +2089,25 @@ void cbm_install_hook_gate_script(const char *home, const char *binary_path) {
20662089 snprintf (hooks_dir , sizeof (hooks_dir ), "%s/hooks" , config_dir );
20672090 cbm_mkdir_p (hooks_dir , CLI_OCTAL_PERM );
20682091
2092+ cbm_remove_legacy_hook_script (hooks_dir , CMM_HOOK_GATE_SCRIPT_LEGACY );
20692093 char script_path [CLI_BUF_1K ];
20702094 snprintf (script_path , sizeof (script_path ), "%s/" CMM_HOOK_GATE_SCRIPT , hooks_dir );
20712095
20722096 FILE * f = fopen (script_path , "w" );
20732097 if (!f ) {
20742098 return ;
20752099 }
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 Never blocks a tool call - it only adds graph context.\r\n"
2105+ "REM Any failure is silent (exit 0, no output).\r\n"
2106+ "if not exist \"%s\" exit /b 0\r\n"
2107+ "\"%s\" hook-augment 2>NUL\r\n"
2108+ "exit /b 0\r\n" ,
2109+ binary_path , binary_path );
2110+ #else
20762111 (void )fprintf (f ,
20772112 "#!/usr/bin/env bash\n"
20782113 "# codebase-memory-mcp search augmenter (Claude Code PreToolUse).\n"
@@ -2084,6 +2119,7 @@ void cbm_install_hook_gate_script(const char *home, const char *binary_path) {
20842119 "\"$BIN\" hook-augment 2>/dev/null\n"
20852120 "exit 0\n" ,
20862121 binary_path );
2122+ #endif
20872123 /* fchmod before close to avoid TOCTOU race (CodeQL cpp/toctou-race-condition) */
20882124#ifndef _WIN32
20892125 fchmod (fileno (f ), CLI_OCTAL_PERM );
@@ -2095,7 +2131,12 @@ void cbm_install_hook_gate_script(const char *home, const char *binary_path) {
20952131}
20962132
20972133/* SessionStart hook: remind agent to use MCP tools on every context reset. */
2134+ #ifdef _WIN32
2135+ #define CMM_SESSION_REMINDER_SCRIPT "cbm-session-reminder.cmd"
2136+ #else
20982137#define CMM_SESSION_REMINDER_SCRIPT "cbm-session-reminder"
2138+ #endif
2139+ #define CMM_SESSION_REMINDER_SCRIPT_LEGACY "cbm-session-reminder"
20992140
21002141static void cbm_install_session_reminder_script (const char * home ) {
21012142 if (!home ) {
@@ -2110,13 +2151,33 @@ static void cbm_install_session_reminder_script(const char *home) {
21102151 snprintf (hooks_dir , sizeof (hooks_dir ), "%s/hooks" , config_dir );
21112152 cbm_mkdir_p (hooks_dir , CLI_OCTAL_PERM );
21122153
2154+ cbm_remove_legacy_hook_script (hooks_dir , CMM_SESSION_REMINDER_SCRIPT_LEGACY );
21132155 char script_path [CLI_BUF_1K ];
21142156 snprintf (script_path , sizeof (script_path ), "%s/" CMM_SESSION_REMINDER_SCRIPT , hooks_dir );
21152157
21162158 FILE * f = fopen (script_path , "w" );
21172159 if (!f ) {
21182160 return ;
21192161 }
2162+ #ifdef _WIN32
2163+ /* cmd variant: echo per line; `|` must be caret-escaped in cmd. */
2164+ (void )fprintf (
2165+ f ,
2166+ "@echo off\r\n"
2167+ "REM SessionStart hook: remind agent to use codebase-memory-mcp tools.\r\n"
2168+ "echo CRITICAL - Code Discovery Protocol:\r\n"
2169+ "echo 1. ALWAYS use codebase-memory-mcp tools FIRST for ANY code exploration:\r\n"
2170+ "echo - search_graph(name_pattern/label/qn_pattern) to find functions/classes/routes\r\n"
2171+ "echo - trace_path(function_name, mode=calls^|data_flow^|cross_service) for call "
2172+ "chains\r\n"
2173+ "echo - get_code_snippet(qualified_name) for exact symbol source (precise ranges)\r\n"
2174+ "echo - query_graph(query) for complex Cypher patterns\r\n"
2175+ "echo - get_architecture(aspects) for project structure\r\n"
2176+ "echo - search_code(pattern) for text search (graph-augmented grep)\r\n"
2177+ "echo 2. Use Grep/Glob/Read freely for text, configs, non-code files, and\r\n"
2178+ "echo always Read a file before editing it.\r\n"
2179+ "echo 3. If a project is not indexed yet, run index_repository FIRST.\r\n" );
2180+ #else
21202181 (void )fprintf (
21212182 f , "#!/usr/bin/env bash\n"
21222183 "# SessionStart hook: remind agent to use codebase-memory-mcp tools.\n"
@@ -2134,6 +2195,7 @@ static void cbm_install_session_reminder_script(const char *home) {
21342195 " always Read a file before editing it.\n"
21352196 "3. If a project is not indexed yet, run index_repository FIRST.\n"
21362197 "REMINDER\n" );
2198+ #endif
21372199#ifndef _WIN32
21382200 fchmod (fileno (f ), CLI_OCTAL_PERM );
21392201#endif
@@ -2180,7 +2242,12 @@ static int cbm_remove_session_hooks(const char *settings_path) {
21802242 * The text is a leaner variant of the SessionStart protocol: it omits the
21812243 * "run index_repository first" step, since the parent session has already
21822244 * indexed the project. Matcher "*" fires for every agent type. */
2245+ #ifdef _WIN32
2246+ #define CMM_SUBAGENT_REMINDER_SCRIPT "cbm-subagent-reminder.cmd"
2247+ #else
21832248#define CMM_SUBAGENT_REMINDER_SCRIPT "cbm-subagent-reminder"
2249+ #endif
2250+ #define CMM_SUBAGENT_REMINDER_SCRIPT_LEGACY "cbm-subagent-reminder"
21842251
21852252static void cbm_install_subagent_reminder_script (const char * home ) {
21862253 if (!home ) {
@@ -2195,6 +2262,7 @@ static void cbm_install_subagent_reminder_script(const char *home) {
21952262 snprintf (hooks_dir , sizeof (hooks_dir ), "%s/hooks" , config_dir );
21962263 cbm_mkdir_p (hooks_dir , CLI_OCTAL_PERM );
21972264
2265+ cbm_remove_legacy_hook_script (hooks_dir , CMM_SUBAGENT_REMINDER_SCRIPT_LEGACY );
21982266 char script_path [CLI_BUF_1K ];
21992267 snprintf (script_path , sizeof (script_path ), "%s/" CMM_SUBAGENT_REMINDER_SCRIPT , hooks_dir );
22002268
@@ -2205,6 +2273,17 @@ static void cbm_install_subagent_reminder_script(const char *home) {
22052273 /* The additionalContext value is a single line with no embedded quotes,
22062274 * backslashes, or newlines, so the JSON below is valid as written — no
22072275 * runtime escaping (and no python3/jq dependency) is required. */
2276+ #ifdef _WIN32
2277+ /* cmd variant: one echo with the JSON verbatim (quotes are literal in
2278+ * cmd echo; no cmd metacharacters appear in the payload). */
2279+ (void )fprintf (f , "@echo off\r\n"
2280+ "REM SubagentStart hook: tell subagents to use codebase-memory-mcp tools.\r\n"
2281+ "echo {\"hookSpecificOutput\":{\"hookEventName\":\"SubagentStart\","
2282+ "\"additionalContext\":\"Code discovery: prefer codebase-memory-mcp tools "
2283+ "(search_graph, trace_path, get_code_snippet, query_graph, get_architecture, "
2284+ "search_code) over grep/file-read for navigating code. Use Grep/Glob/Read for "
2285+ "text, configs, and non-code files.\"}}\r\n" );
2286+ #else
22082287 (void )fprintf (f ,
22092288 "#!/usr/bin/env bash\n"
22102289 "# SubagentStart hook: tell subagents to use codebase-memory-mcp tools.\n"
@@ -2217,6 +2296,7 @@ static void cbm_install_subagent_reminder_script(const char *home) {
22172296 "search_code) over grep/file-read for navigating code. Use Grep/Glob/Read for "
22182297 "text, configs, and non-code files.\"}}\n"
22192298 "REMINDER\n" );
2299+ #endif
22202300#ifndef _WIN32
22212301 fchmod (fileno (f ), CLI_OCTAL_PERM );
22222302#endif
0 commit comments