From 7191d19f2501b58f6da3254792a999ac9cfcc82a Mon Sep 17 00:00:00 2001 From: johnhuang316 <134570882+johnhuang316@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:31:26 +0800 Subject: [PATCH 1/2] fix(indexing): relax shallow find_files matching --- .../indexing/shallow_index_manager.py | 49 +++++++++++++++++-- tests/indexing/test_shallow_index_lenient.py | 41 ++++++++++++++++ 2 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 tests/indexing/test_shallow_index_lenient.py diff --git a/src/code_index_mcp/indexing/shallow_index_manager.py b/src/code_index_mcp/indexing/shallow_index_manager.py index 530c593..48ad4b4 100644 --- a/src/code_index_mcp/indexing/shallow_index_manager.py +++ b/src/code_index_mcp/indexing/shallow_index_manager.py @@ -107,14 +107,43 @@ def find_files(self, pattern: str = "*") -> List[str]: if not isinstance(pattern, str): return [] norm = (pattern.strip() or "*").replace('\\\\','/').replace('\\','/') - regex = self._compile_glob_regex(norm) files = self._file_list or [] + + # Fast path: wildcard all if norm == "*": return list(files) - return [f for f in files if regex.match(f) is not None] + + # 1) Exact, case-sensitive + exact_regex = self._compile_glob_regex(norm) + exact_hits = [f for f in files if exact_regex.match(f) is not None] + if exact_hits or '/' in norm: + return exact_hits + + # 2) Recursive **/ fallback (case-sensitive) + recursive_pattern = f"**/{norm}" + rec_regex = self._compile_glob_regex(recursive_pattern) + rec_hits = [f for f in files if rec_regex.match(f) is not None] + if rec_hits: + return self._dedupe_preserve_order(exact_hits + rec_hits) + + # 3) Case-insensitive (root only) + ci_regex = self._compile_glob_regex(norm, ignore_case=True) + ci_hits = [f for f in files if ci_regex.match(f) is not None] + if ci_hits: + return self._dedupe_preserve_order(exact_hits + rec_hits + ci_hits) + + # 4) Case-insensitive recursive + rec_ci_regex = self._compile_glob_regex(recursive_pattern, ignore_case=True) + rec_ci_hits = [f for f in files if rec_ci_regex.match(f) is not None] + if rec_ci_hits: + return self._dedupe_preserve_order( + exact_hits + rec_hits + ci_hits + rec_ci_hits + ) + + return [] @staticmethod - def _compile_glob_regex(pattern: str) -> re.Pattern: + def _compile_glob_regex(pattern: str, ignore_case: bool = False) -> re.Pattern: i = 0 out = [] special = ".^$+{}[]|()" @@ -134,7 +163,18 @@ def _compile_glob_regex(pattern: str) -> re.Pattern: else: out.append(c) i += 1 - return re.compile('^' + ''.join(out) + '$') + flags = re.IGNORECASE if ignore_case else 0 + return re.compile('^' + ''.join(out) + '$', flags=flags) + + @staticmethod + def _dedupe_preserve_order(items: List[str]) -> List[str]: + seen = set() + result = [] + for item in items: + if item not in seen: + seen.add(item) + result.append(item) + return result def cleanup(self) -> None: with self._lock: @@ -152,4 +192,3 @@ def cleanup(self) -> None: def get_shallow_index_manager() -> ShallowIndexManager: return _shallow_manager - diff --git a/tests/indexing/test_shallow_index_lenient.py b/tests/indexing/test_shallow_index_lenient.py new file mode 100644 index 0000000..9cb4f79 --- /dev/null +++ b/tests/indexing/test_shallow_index_lenient.py @@ -0,0 +1,41 @@ +from pathlib import Path + +import pytest + +from code_index_mcp.indexing.shallow_index_manager import ShallowIndexManager + + +@pytest.fixture() +def temp_manager(tmp_path): + project = Path("test/sample-projects/python/user_management").resolve() + m = ShallowIndexManager() + assert m.set_project_path(str(project)) + assert m.build_index() + assert m.load_index() + yield m + m.cleanup() + + +def test_simple_filename_triggers_recursive(temp_manager): + res = temp_manager.find_files("user.py") + assert "models/user.py" in res + + +def test_case_insensitive(temp_manager): + res = temp_manager.find_files("USER.PY") + assert "models/user.py" in [p.lower() for p in res] + + +def test_pattern_with_slash_not_lenient(temp_manager): + res = temp_manager.find_files("models/user.py") + assert res == ["models/user.py"] + + +def test_wildcard_all_unchanged(temp_manager): + res = temp_manager.find_files("*") + # sample project has 12 files + assert len(res) == 12 + + +def test_non_string_returns_empty(temp_manager): + assert temp_manager.find_files(None) == [] # type: ignore[arg-type] From 819e308f634e7b5aa5c2408d89ce6c964c3606f5 Mon Sep 17 00:00:00 2001 From: johnhuang316 <134570882+johnhuang316@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:34:50 +0800 Subject: [PATCH 2/2] chore(release): v2.9.2 --- pyproject.toml | 2 +- src/code_index_mcp/__init__.py | 2 +- uv.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6449b8e..5aadd22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "code-index-mcp" -version = "2.9.1" +version = "2.9.2" description = "Code indexing and analysis tools for LLMs using MCP" readme = "README.md" requires-python = ">=3.10" diff --git a/src/code_index_mcp/__init__.py b/src/code_index_mcp/__init__.py index c78e51b..bc2daa7 100644 --- a/src/code_index_mcp/__init__.py +++ b/src/code_index_mcp/__init__.py @@ -3,4 +3,4 @@ A Model Context Protocol server for code indexing, searching, and analysis. """ -__version__ = "2.9.1" +__version__ = "2.9.2" diff --git a/uv.lock b/uv.lock index d0f64ad..5417f7e 100644 --- a/uv.lock +++ b/uv.lock @@ -140,7 +140,7 @@ wheels = [ [[package]] name = "code-index-mcp" -version = "2.9.1" +version = "2.9.2" source = { editable = "." } dependencies = [ { name = "mcp" },