@@ -2120,7 +2120,7 @@ TEST(tool_search_graph_includes_node_properties) {
21202120 ASSERT_NOT_NULL (strstr (inner , "func HandleRequest" )); /* its value */
21212121 ASSERT_NOT_NULL (strstr (inner , "\"base_classes\"" ));
21222122 ASSERT_NOT_NULL (strstr (inner , "[\"HandlerBase\",\"Audited\"]" ));
2123- ASSERT_NULL (strstr (inner , "is_exported" )); /* blob never spills */
2123+ ASSERT_NULL (strstr (inner , "is_exported" )); /* blob never spills */
21242124 free (inner );
21252125 free (resp );
21262126
@@ -2680,15 +2680,13 @@ TEST(tool_check_index_coverage_accepts_truncated_ignored_catalog_for_fresh_path_
26802680 ((int64_t )source_stat .st_mtimespec .tv_sec * (int64_t )CBM_NSEC_PER_SEC ) +
26812681 (int64_t )source_stat .st_mtimespec .tv_nsec ;
26822682#elif defined(_WIN32 )
2683- int64_t source_mtime_ns =
2684- (int64_t )source_stat .st_mtime * (int64_t )CBM_NSEC_PER_SEC ;
2683+ int64_t source_mtime_ns = (int64_t )source_stat .st_mtime * (int64_t )CBM_NSEC_PER_SEC ;
26852684#else
2686- int64_t source_mtime_ns =
2687- ((int64_t )source_stat .st_mtim .tv_sec * (int64_t )CBM_NSEC_PER_SEC ) +
2688- (int64_t )source_stat .st_mtim .tv_nsec ;
2685+ int64_t source_mtime_ns = ((int64_t )source_stat .st_mtim .tv_sec * (int64_t )CBM_NSEC_PER_SEC ) +
2686+ (int64_t )source_stat .st_mtim .tv_nsec ;
26892687#endif
2690- ASSERT_EQ (cbm_store_upsert_file_hash (store , "test-project" , "main.go" , "" ,
2691- source_mtime_ns , source_stat .st_size ),
2688+ ASSERT_EQ (cbm_store_upsert_file_hash (store , "test-project" , "main.go" , "" , source_mtime_ns ,
2689+ source_stat .st_size ),
26922690 CBM_STORE_OK );
26932691 cbm_project_t project = {0 };
26942692 ASSERT_EQ (cbm_store_get_project (store , "test-project" , & project ), CBM_STORE_OK );
@@ -2702,8 +2700,7 @@ TEST(tool_check_index_coverage_accepts_truncated_ignored_catalog_for_fresh_path_
27022700 .coverage_version = 1 ,
27032701 .hash_records_complete = true,
27042702 };
2705- ASSERT_EQ (cbm_store_coverage_replace_ex (store , "test-project" , NULL , 0 , & meta ),
2706- CBM_STORE_OK );
2703+ ASSERT_EQ (cbm_store_coverage_replace_ex (store , "test-project" , NULL , 0 , & meta ), CBM_STORE_OK );
27072704 cbm_project_free_fields (& project );
27082705
27092706 char * response = cbm_mcp_handle_tool (
@@ -4351,12 +4348,12 @@ TEST(search_code_full_preserves_utf8_source) {
43514348 .end_line = 2 };
43524349 ASSERT_GT (cbm_store_upsert_node (st , & section ), 0 );
43534350
4354- char * resp = cbm_mcp_server_handle (
4355- srv , "{\"jsonrpc\":\"2.0\",\"id\":97,\"method\":\"tools/call\","
4356- "\"params\":{\"name\":\"search_code\",\"arguments\":{"
4357- "\"project\":\"utf8-search\",\"pattern\":\"accounting-design\","
4358- "\"file_pattern\":\"*.md\",\"path_filter\":\"^design/\","
4359- "\"mode\":\"full\",\"format\":\"json\",\"limit\":5}}}" );
4351+ char * resp =
4352+ cbm_mcp_server_handle ( srv , "{\"jsonrpc\":\"2.0\",\"id\":97,\"method\":\"tools/call\","
4353+ "\"params\":{\"name\":\"search_code\",\"arguments\":{"
4354+ "\"project\":\"utf8-search\",\"pattern\":\"accounting-design\","
4355+ "\"file_pattern\":\"*.md\",\"path_filter\":\"^design/\","
4356+ "\"mode\":\"full\",\"format\":\"json\",\"limit\":5}}}" );
43604357 ASSERT_NOT_NULL (resp );
43614358 char * inner = extract_text_content (resp );
43624359 ASSERT_NOT_NULL (inner );
@@ -4408,11 +4405,11 @@ TEST(search_code_raw_match_preserves_utf8_content) {
44084405 .end_line = 1 };
44094406 ASSERT_GT (cbm_store_upsert_node (st , & node ), 0 );
44104407
4411- char * resp = cbm_mcp_server_handle (
4412- srv , "{\"jsonrpc\":\"2.0\",\"id\":98,\"method\":\"tools/call\","
4413- "\"params\":{\"name\":\"search_code\",\"arguments\":{"
4414- "\"project\":\"test-project\",\"pattern\":\"raw-\","
4415- "\"file_pattern\":\"*.md\",\"format\":\"json\",\"limit\":5}}}" );
4408+ char * resp =
4409+ cbm_mcp_server_handle ( srv , "{\"jsonrpc\":\"2.0\",\"id\":98,\"method\":\"tools/call\","
4410+ "\"params\":{\"name\":\"search_code\",\"arguments\":{"
4411+ "\"project\":\"test-project\",\"pattern\":\"raw-\","
4412+ "\"file_pattern\":\"*.md\",\"format\":\"json\",\"limit\":5}}}" );
44164413 ASSERT_NOT_NULL (resp );
44174414 char * inner = extract_text_content (resp );
44184415 ASSERT_NOT_NULL (inner );
@@ -4459,11 +4456,11 @@ TEST(search_code_context_preserves_utf8_context) {
44594456 .end_line = 3 };
44604457 ASSERT_GT (cbm_store_upsert_node (st , & node ), 0 );
44614458
4462- char * resp = cbm_mcp_server_handle (
4463- srv , "{\"jsonrpc\":\"2.0\",\"id\":99,\"method\":\"tools/call\","
4464- "\"params\":{\"name\":\"search_code\",\"arguments\":{"
4465- "\"project\":\"test-project\",\"pattern\":\"context-needle\","
4466- "\"format\":\"json\",\"context\":1,\"limit\":5}}}" );
4459+ char * resp =
4460+ cbm_mcp_server_handle ( srv , "{\"jsonrpc\":\"2.0\",\"id\":99,\"method\":\"tools/call\","
4461+ "\"params\":{\"name\":\"search_code\",\"arguments\":{"
4462+ "\"project\":\"test-project\",\"pattern\":\"context-needle\","
4463+ "\"format\":\"json\",\"context\":1,\"limit\":5}}}" );
44674464 ASSERT_NOT_NULL (resp );
44684465 char * inner = extract_text_content (resp );
44694466 ASSERT_NOT_NULL (inner );
@@ -4507,8 +4504,8 @@ TEST(search_code_invalid_utf8_still_returns_valid_json) {
45074504 char invalid_path [512 ];
45084505 snprintf (invalid_path , sizeof (invalid_path ), "%s/project/invalid.md" , tmp );
45094506 static const unsigned char invalid_source [] = {
4510- 'i' , 'n' , 'v' , 'a' , 'l' , 'i' , 'd' , '-' , 'n' , 'e' , 'e' , 'd' , 'l' , 'e' , '\n ' ,
4511- 'c' , 'o' , 'n' , 't' , 'e' , 'x' , 't' , ' ' , 0xFF , '\n' ,
4507+ 'i' , 'n' , 'v' , 'a' , 'l' , 'i' , 'd' , '-' , 'n' , 'e' , 'e' , 'd' , 'l ' ,
4508+ 'e' , '\n' , ' c' , 'o' , 'n' , 't' , 'e' , 'x' , 't' , ' ' , 0xFF , '\n' ,
45124509 };
45134510 FILE * fp = cbm_fopen (invalid_path , "wb" );
45144511 ASSERT_NOT_NULL (fp );
@@ -4526,11 +4523,11 @@ TEST(search_code_invalid_utf8_still_returns_valid_json) {
45264523 .end_line = 2 };
45274524 ASSERT_GT (cbm_store_upsert_node (st , & node ), 0 );
45284525
4529- char * resp = cbm_mcp_server_handle (
4530- srv , "{\"jsonrpc\":\"2.0\",\"id\":100,\"method\":\"tools/call\","
4531- "\"params\":{\"name\":\"search_code\",\"arguments\":{"
4532- "\"project\":\"test-project\",\"pattern\":\"invalid-needle\","
4533- "\"mode\":\"full\",\"format\":\"json\",\"limit\":5}}}" );
4526+ char * resp =
4527+ cbm_mcp_server_handle ( srv , "{\"jsonrpc\":\"2.0\",\"id\":100,\"method\":\"tools/call\","
4528+ "\"params\":{\"name\":\"search_code\",\"arguments\":{"
4529+ "\"project\":\"test-project\",\"pattern\":\"invalid-needle\","
4530+ "\"mode\":\"full\",\"format\":\"json\",\"limit\":5}}}" );
45344531 ASSERT_NOT_NULL (resp );
45354532 char * inner = extract_text_content (resp );
45364533 ASSERT_NOT_NULL (inner );
@@ -4921,6 +4918,35 @@ TEST(search_code_windows_prefilter_precedes_content_scan) {
49214918#endif
49224919}
49234920
4921+ /* Windows raw scans must pin the PowerShell pipe to UTF-8: PS 5.1 otherwise
4922+ * emits stdout in the console OEM codepage and non-ASCII content degrades to
4923+ * '?' depending on the inherited console CP (seen as test_mcp raw-Русский
4924+ * mojibake on CI). Every builder variant must carry the prelude. */
4925+ TEST (search_code_windows_scan_pins_utf8_output ) {
4926+ #ifdef _WIN32
4927+ static const char prelude [] =
4928+ "powershell -Command \"[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; " ;
4929+ char command [CBM_SZ_4K ];
4930+ struct {
4931+ bool scoped ;
4932+ const char * file_pattern ;
4933+ } cases [] = {{true, "*.go" }, /* scoped + prefilterable pattern */
4934+ {true, "*x*z*.go" }, /* scoped + non-prefilterable pattern */
4935+ {true, NULL }, /* scoped, no pattern */
4936+ {false, "*.go" }, /* unscoped + pattern */
4937+ {false, NULL }}; /* unscoped, no pattern */
4938+ for (size_t i = 0 ; i < sizeof (cases ) / sizeof (cases [0 ]); i ++ ) {
4939+ cbm_search_code_build_grep_cmd (command , sizeof (command ), false, cases [i ].scoped ,
4940+ cases [i ].file_pattern , "C:/tmp/pattern" , "C:/tmp/filelist" ,
4941+ "C:/tmp/root" );
4942+ ASSERT_TRUE (strncmp (command , prelude , sizeof (prelude ) - 1 ) == 0 );
4943+ }
4944+ PASS ();
4945+ #else
4946+ SKIP_PLATFORM ("PowerShell scan encoding applies on Windows" );
4947+ #endif
4948+ }
4949+
49244950/* issue #283: search_code with regex=true and a syntactically invalid pattern
49254951 * must return an explicit error, not an empty result indistinguishable from a
49264952 * legitimate no-match. */
@@ -8769,10 +8795,10 @@ TEST(tool_resolve_store_by_internal_name_issue704) {
87698795 ASSERT_NULL (strstr (list , "ghost704" )); /* 0-byte ghost filtered (RED before) */
87708796 free (list );
87718797
8772- char * page = cbm_mcp_server_handle (
8773- srv , "{\"jsonrpc\":\"2.0\",\"id\":11,\"method\":\"tools/call\","
8774- "\"params\":{\"name\":\"list_projects\","
8775- "\"arguments\":{\"offset\":0,\"limit\":1}}}" );
8798+ char * page =
8799+ cbm_mcp_server_handle ( srv , "{\"jsonrpc\":\"2.0\",\"id\":11,\"method\":\"tools/call\","
8800+ "\"params\":{\"name\":\"list_projects\","
8801+ "\"arguments\":{\"offset\":0,\"limit\":1}}}" );
87768802 ASSERT_NOT_NULL (page );
87778803 ASSERT_NOT_NULL (strstr (page , "\\\"total\\\":3" ));
87788804 ASSERT_NOT_NULL (strstr (page , "\\\"limit\\\":1" ));
@@ -11223,6 +11249,7 @@ SUITE(mcp) {
1122311249 RUN_TEST (search_code_path_filter_matches_nothing );
1122411250 RUN_TEST (search_code_file_pattern_prefilter_boundaries );
1122511251 RUN_TEST (search_code_windows_prefilter_precedes_content_scan );
11252+ RUN_TEST (search_code_windows_scan_pins_utf8_output );
1122611253 RUN_TEST (search_code_invalid_regex_errors_issue283 );
1122711254 RUN_TEST (search_code_literal_pipe_warns_issue282 );
1122811255 RUN_TEST (search_code_reports_phase_timings_only_in_debug_mode );
0 commit comments