Skip to content

Commit 2f37f61

Browse files
perf(mcp): filter suffix globs while building search scope
Signed-off-by: Ertan <ertan.kucukoglu@gmail.com>
1 parent 5fbab7b commit 2f37f61

3 files changed

Lines changed: 60 additions & 33 deletions

File tree

‎src/mcp/mcp.c‎

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -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. */
1043510451
static 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

‎src/mcp/mcp_internal.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ bool cbm_detect_node_in_hunks(const cbm_node_t *node, const cbm_changed_hunk_t *
6767
* PowerShell -like contract. Exposed for
6868
* direct boundary tests only. */
6969
bool cbm_search_code_file_pattern_can_prefilter(const char *file_pattern);
70+
bool cbm_search_code_windows_path_matches_prefilter(const char *path, const char *file_pattern);
7071

7172
/* Internal command builder exposed so tests can pin the PowerShell pipeline
7273
* ordering without

‎tests/test_mcp.c‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5345,22 +5345,25 @@ TEST(search_code_file_pattern_prefilter_boundaries) {
53455345
ASSERT_FALSE(cbm_search_code_file_pattern_can_prefilter("src\\*.pas"));
53465346
ASSERT_FALSE(cbm_search_code_file_pattern_can_prefilter("*.c++"));
53475347
ASSERT_FALSE(cbm_search_code_file_pattern_can_prefilter("*R&D*.go"));
5348+
5349+
ASSERT_TRUE(cbm_search_code_windows_path_matches_prefilter("src/UnitMain.PAS", "*.pas"));
5350+
ASSERT_TRUE(cbm_search_code_windows_path_matches_prefilter("types/index.D.TS", "*.d.ts"));
5351+
ASSERT_FALSE(cbm_search_code_windows_path_matches_prefilter("src/UnitMain.pas.bak", "*.pas"));
5352+
ASSERT_FALSE(cbm_search_code_windows_path_matches_prefilter("types/index.ts", "*.d.ts"));
53485353
PASS();
53495354
}
53505355

5351-
TEST(search_code_windows_prefilter_precedes_content_scan) {
5356+
TEST(search_code_windows_scope_prefilter_removes_pipeline_filter) {
53525357
#ifdef _WIN32
53535358
char command[CBM_SZ_4K];
53545359
cbm_search_code_build_grep_cmd(command, sizeof(command), false, true, "*.go", "C:/tmp/pattern",
53555360
"C:/tmp/filelist", "C:/tmp/root");
53565361

5357-
const char *prefilter = strstr(command, "Where-Object { $_ -like '*.go' }");
53585362
const char *content_scan = strstr(command, "ForEach-Object { Select-String");
53595363
const char *postfilter = strstr(command, "Where-Object { $_.Path -like '**.go' }");
5360-
ASSERT_NOT_NULL(prefilter);
5364+
ASSERT_NULL(strstr(command, "Where-Object { $_ -like '*.go' }"));
53615365
ASSERT_NOT_NULL(content_scan);
53625366
ASSERT_NOT_NULL(postfilter);
5363-
ASSERT_TRUE(prefilter < content_scan);
53645367
ASSERT_TRUE(content_scan < postfilter);
53655368

53665369
cbm_search_code_build_grep_cmd(command, sizeof(command), false, true, "*handler*.go",
@@ -13904,7 +13907,7 @@ SUITE(mcp) {
1390413907
RUN_TEST(search_code_path_filter_prefilter_keeps_matches);
1390513908
RUN_TEST(search_code_path_filter_matches_nothing);
1390613909
RUN_TEST(search_code_file_pattern_prefilter_boundaries);
13907-
RUN_TEST(search_code_windows_prefilter_precedes_content_scan);
13910+
RUN_TEST(search_code_windows_scope_prefilter_removes_pipeline_filter);
1390813911
RUN_TEST(search_code_cancel_cleans_supervised_scan);
1390913912
RUN_TEST(search_code_output_limit_fails_closed_and_cleans_scan);
1391013913
RUN_TEST(search_code_scan_deadline_fails_closed_and_resets);

0 commit comments

Comments
 (0)