diff --git a/ccw/src/tools/smart-search.ts b/ccw/src/tools/smart-search.ts index 1f7dcc69..b4e4cc7b 100644 --- a/ccw/src/tools/smart-search.ts +++ b/ccw/src/tools/smart-search.ts @@ -2189,10 +2189,10 @@ async function executeCodexLensV2Bridge( ): Promise { return new Promise((resolve) => { const args = [ + '--db-path', dbPath, 'search', '--query', query, '--top-k', String(topK), - '--db-path', dbPath, ]; execFile('codexlens-search', args, { @@ -2293,10 +2293,12 @@ async function executeCodexLensV2Bridge( async function executeV2BridgeCommand( subcommand: string, args: string[], - options?: { timeout?: number }, + options?: { timeout?: number; dbPath?: string }, ): Promise { return new Promise((resolve) => { - const fullArgs = [subcommand, ...args]; + // --db-path is a global arg and must come BEFORE the subcommand + const globalArgs = options?.dbPath ? ['--db-path', options.dbPath] : []; + const fullArgs = [...globalArgs, subcommand, ...args]; execFile('codexlens-search', fullArgs, { encoding: 'utf-8', timeout: options?.timeout ?? EXEC_TIMEOUTS.PROCESS_SPAWN, @@ -2333,14 +2335,13 @@ async function executeInitActionV2(params: Params): Promise { const dbPath = join(scope.workingDirectory, '.codexlens'); // Step 1: init empty index - const initResult = await executeV2BridgeCommand('init', ['--db-path', dbPath]); + const initResult = await executeV2BridgeCommand('init', [], { dbPath }); if (!initResult.success) return initResult; // Step 2: sync all files const syncResult = await executeV2BridgeCommand('sync', [ '--root', scope.workingDirectory, - '--db-path', dbPath, - ], { timeout: 1800000 }); // 30 min for large codebases + ], { timeout: 1800000, dbPath }); // 30 min for large codebases return { success: syncResult.success, @@ -2361,7 +2362,7 @@ async function executeStatusActionV2(params: Params): Promise { const scope = resolveSearchScope(path); const dbPath = join(scope.workingDirectory, '.codexlens'); - return executeV2BridgeCommand('status', ['--db-path', dbPath]); + return executeV2BridgeCommand('status', [], { dbPath }); } /** @@ -2374,8 +2375,7 @@ async function executeUpdateActionV2(params: Params): Promise { return executeV2BridgeCommand('sync', [ '--root', scope.workingDirectory, - '--db-path', dbPath, - ], { timeout: 600000 }); // 10 min + ], { timeout: 600000, dbPath }); // 10 min } /** @@ -2389,9 +2389,8 @@ async function executeWatchActionV2(params: Params): Promise { // Watch runs indefinitely — start it with a short initial timeout to confirm startup const result = await executeV2BridgeCommand('watch', [ '--root', scope.workingDirectory, - '--db-path', dbPath, '--debounce-ms', debounce.toString(), - ], { timeout: 5000 }); + ], { timeout: 5000, dbPath }); return { success: true, @@ -3607,7 +3606,7 @@ export async function executeInitWithProgress( } // Step 1: init empty index - const initResult = await executeV2BridgeCommand('init', ['--db-path', dbPath]); + const initResult = await executeV2BridgeCommand('init', [], { dbPath }); if (!initResult.success) return initResult; if (onProgress) { @@ -3617,8 +3616,7 @@ export async function executeInitWithProgress( // Step 2: sync all files const syncResult = await executeV2BridgeCommand('sync', [ '--root', scope.workingDirectory, - '--db-path', dbPath, - ], { timeout: 1800000 }); + ], { timeout: 1800000, dbPath }); if (onProgress) { onProgress({ stage: 'complete', message: 'Index build complete', percent: 100 } as ProgressInfo); diff --git a/codex-lens-v2/src/codexlens_search/bridge.py b/codex-lens-v2/src/codexlens_search/bridge.py index 9b98d24b..0fe94834 100644 --- a/codex-lens-v2/src/codexlens_search/bridge.py +++ b/codex-lens-v2/src/codexlens_search/bridge.py @@ -23,9 +23,18 @@ log = logging.getLogger("codexlens_search.bridge") # Helpers # --------------------------------------------------------------------------- +def _ensure_utf8_stdio() -> None: + """Force UTF-8 encoding on stdout/stderr (Windows defaults to GBK/cp936).""" + if sys.platform == "win32": + for stream_name in ("stdout", "stderr"): + stream = getattr(sys, stream_name) + if hasattr(stream, "reconfigure"): + stream.reconfigure(encoding="utf-8", errors="replace") + + def _json_output(data: dict | list) -> None: """Print JSON to stdout with flush.""" - print(json.dumps(data, ensure_ascii=False), flush=True) + print(json.dumps(data, ensure_ascii=True), flush=True) def _error_exit(message: str, code: int = 1) -> None: @@ -363,6 +372,7 @@ def _build_parser() -> argparse.ArgumentParser: def main() -> None: """CLI entry point.""" + _ensure_utf8_stdio() parser = _build_parser() args = parser.parse_args() diff --git a/codex-lens-v2/tests/unit/test_bridge.py b/codex-lens-v2/tests/unit/test_bridge.py index 8f8eb479..5dba4460 100644 --- a/codex-lens-v2/tests/unit/test_bridge.py +++ b/codex-lens-v2/tests/unit/test_bridge.py @@ -91,7 +91,8 @@ class TestJsonHelpers: def test_json_output_unicode(self, capsys): _json_output({"msg": "中文测试"}) out = capsys.readouterr().out.strip() - assert "中文测试" in out + parsed = json.loads(out) + assert parsed["msg"] == "中文测试" def test_error_exit(self): with pytest.raises(SystemExit) as exc_info: