@@ -9747,6 +9747,36 @@ bool cbm_search_code_file_pattern_can_prefilter(const char *file_pattern) {
97479747 return true;
97489748}
97499749
9750+ bool cbm_search_code_windows_path_matches_prefilter(const char *path, const char *file_pattern) {
9751+ if (!path || !cbm_search_code_file_pattern_can_prefilter(file_pattern)) {
9752+ return false;
9753+ }
9754+
9755+ const char *suffix = file_pattern + 1;
9756+ size_t path_len = strlen(path);
9757+ size_t suffix_len = strlen(suffix);
9758+ if (path_len < suffix_len) {
9759+ return false;
9760+ }
9761+
9762+ const unsigned char *candidate = (const unsigned char *)path + path_len - suffix_len;
9763+ const unsigned char *expected = (const unsigned char *)suffix;
9764+ for (size_t i = 0; i < suffix_len; i++) {
9765+ unsigned char left = candidate[i];
9766+ unsigned char right = expected[i];
9767+ if (left >= 'A' && left <= 'Z') {
9768+ left = (unsigned char)(left - 'A' + 'a');
9769+ }
9770+ if (right >= 'A' && right <= 'Z') {
9771+ right = (unsigned char)(right - 'A' + 'a');
9772+ }
9773+ if (left != right) {
9774+ return false;
9775+ }
9776+ }
9777+ return true;
9778+ }
9779+
97509780/* Build the grep/search command string based on scoped vs recursive mode.
97519781 * On Windows, uses PowerShell Select-String with tab-delimited output.
97529782 * On POSIX, uses grep with colon-delimited output. */
@@ -9767,30 +9797,16 @@ void cbm_search_code_build_grep_cmd(char *cmd, size_t cmd_sz, bool use_regex, bo
97679797 const char *sm = use_regex ? "" : " -SimpleMatch";
97689798 if (scoped) {
97699799 if (file_pattern) {
9770- if (cbm_search_code_file_pattern_can_prefilter(file_pattern)) {
9771- snprintf(
9772- cmd, cmd_sz,
9773- "powershell -Command \"" CBM_PS_UTF8_PRELUDE
9774- "$pat = Get-Content -Encoding UTF8 -LiteralPath '%s'; "
9775- "Get-Content -Encoding UTF8 -LiteralPath '%s'"
9776- " | Where-Object { $_ -like '%s' }"
9777- " | ForEach-Object { Select-String -LiteralPath $_ -Pattern $pat%s "
9778- "-ErrorAction SilentlyContinue }"
9779- " | Where-Object { $_.Path -like '*%s' }"
9780- " | ForEach-Object { $_.Path + [char]9 + $_.LineNumber + [char]9 + $_.Line }\"",
9781- tmpfile, filelist, file_pattern, sm, file_pattern);
9782- } else {
9783- snprintf(
9784- cmd, cmd_sz,
9785- "powershell -Command \"" CBM_PS_UTF8_PRELUDE
9786- "$pat = Get-Content -Encoding UTF8 -LiteralPath '%s'; "
9787- "Get-Content -Encoding UTF8 -LiteralPath '%s' | ForEach-Object { Select-String "
9788- "-LiteralPath $_ -Pattern $pat%s "
9789- "-ErrorAction SilentlyContinue }"
9790- " | Where-Object { $_.Path -like '*%s' }"
9791- " | ForEach-Object { $_.Path + [char]9 + $_.LineNumber + [char]9 + $_.Line }\"",
9792- tmpfile, filelist, sm, file_pattern);
9793- }
9800+ snprintf(
9801+ cmd, cmd_sz,
9802+ "powershell -Command \"" CBM_PS_UTF8_PRELUDE
9803+ "$pat = Get-Content -Encoding UTF8 -LiteralPath '%s'; "
9804+ "Get-Content -Encoding UTF8 -LiteralPath '%s' | ForEach-Object { Select-String "
9805+ "-LiteralPath $_ -Pattern $pat%s "
9806+ "-ErrorAction SilentlyContinue }"
9807+ " | Where-Object { $_.Path -like '*%s' }"
9808+ " | ForEach-Object { $_.Path + [char]9 + $_.LineNumber + [char]9 + $_.Line }\"",
9809+ tmpfile, filelist, sm, file_pattern);
97949810 } else {
97959811 snprintf(
97969812 cmd, cmd_sz,
@@ -10433,8 +10449,8 @@ static void classify_all_grep_hits(grep_match_t *gm, int gm_count, cbm_store_t *
1043310449 * created inside the private scratch directory; this function never opens or
1043410450 * closes it, so the list is never reachable through a predictable pathname. */
1043510451static bool write_scoped_filelist(cbm_mcp_server_t *srv, const char *project, const char *root_path,
10436- FILE *fl, bool has_path_filter, cbm_regex_t *path_regex ,
10437- int *out_written) {
10452+ FILE *fl, const char *file_pattern, bool has_path_filter ,
10453+ cbm_regex_t *path_regex, int *out_written) {
1043810454 *out_written = 0;
1043910455 cbm_store_t *pre_store = resolve_store(srv, project);
1044010456 if (!pre_store) {
@@ -10471,6 +10487,12 @@ static bool write_scoped_filelist(cbm_mcp_server_t *srv, const char *project, co
1047110487 continue;
1047210488 }
1047310489 }
10490+ #ifdef _WIN32
10491+ if (cbm_search_code_file_pattern_can_prefilter(file_pattern) &&
10492+ !cbm_search_code_windows_path_matches_prefilter(indexed_files[fi], file_pattern)) {
10493+ continue;
10494+ }
10495+ #endif
1047410496 size_t root_len = strlen(root_path);
1047510497 size_t file_len = strlen(indexed_files[fi]);
1047610498 if (root_len > SIZE_MAX - file_len - 2) {
@@ -10954,8 +10976,9 @@ static char *handle_search_code(cbm_mcp_server_t *srv, const char *args) {
1095410976
1095510977 uint64_t scope_t0 = metrics.include_phase_timings ? cbm_now_ms() : 0;
1095610978 if (!scan_cancellation_latched && !scan_deadline_latched) {
10957- scoped = write_scoped_filelist(srv, project, root_path, scratch.filelist, has_path_filter,
10958- has_path_filter ? &path_regex : NULL, &scoped_written);
10979+ scoped = write_scoped_filelist(srv, project, root_path, scratch.filelist, file_pattern,
10980+ has_path_filter, has_path_filter ? &path_regex : NULL,
10981+ &scoped_written);
1095910982 }
1096010983 /* Close before grep runs: this is what flushes the records the helper wrote
1096110984 * through the descriptor. Clearing the field hands ownership to
0 commit comments