From 890d2fefafef460f2150d8113f0baba3895c9ca9 Mon Sep 17 00:00:00 2001 From: longcoding Date: Thu, 23 Jul 2026 17:31:24 +0800 Subject: [PATCH] fix(shell): index tracked vendor files --- src/kimi_cli/utils/file_filter.py | 17 ++++++++++++--- tests/ui_and_conv/test_file_completer.py | 27 ++++++++++++------------ tests/utils/test_file_filter.py | 2 +- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/kimi_cli/utils/file_filter.py b/src/kimi_cli/utils/file_filter.py index 64ac127724..8db17cabc1 100644 --- a/src/kimi_cli/utils/file_filter.py +++ b/src/kimi_cli/utils/file_filter.py @@ -145,7 +145,12 @@ def git_index_mtime(root: Path) -> float | None: return None -def _parse_ls_files_output(stdout: str, *, filter_ignored: bool = True) -> list[str]: +def _parse_ls_files_output( + stdout: str, + *, + filter_ignored: bool = True, + allowed_ignored_names: frozenset[str] = frozenset(), +) -> list[str]: """Parse NUL-delimited ``git ls-files -z`` output into paths with synthesised dirs. When *filter_ignored* is *True*, paths whose segments match @@ -168,7 +173,7 @@ def _parse_ls_files_output(stdout: str, *, filter_ignored: bool = True) -> list[ if prefix in ignored_prefixes: skip = True break - if is_ignored(part): + if is_ignored(part) and part not in allowed_ignored_names: ignored_prefixes.add(prefix) skip = True break @@ -244,7 +249,13 @@ def list_files_git( return None deleted = _git_deleted_files(root, scope) - paths = _parse_ls_files_output(result.stdout) + # ``vendor`` often contains checked-in project sources (for example Go + # dependencies), so honor those explicit index entries. Other generated + # directories remain filtered, as does the untracked pass below. + paths = _parse_ls_files_output( + result.stdout, + allowed_ignored_names=frozenset(("vendor",)), + ) if deleted: paths = [p for p in paths if p.endswith("/") or p not in deleted] diff --git a/tests/ui_and_conv/test_file_completer.py b/tests/ui_and_conv/test_file_completer.py index 9df041ee44..e4dd822895 100644 --- a/tests/ui_and_conv/test_file_completer.py +++ b/tests/ui_and_conv/test_file_completer.py @@ -160,13 +160,8 @@ def _init_git_repo(work_dir: Path) -> None: subprocess.run(cmd, cwd=work_dir, capture_output=True, check=True) -def test_tracked_ignored_dirs_filtered_in_git_mode(tmp_path: Path): - """Tracked ``node_modules/`` and ``vendor/`` must still be filtered. - - Regression test: ``git ls-files`` returns all tracked paths, so - directories in ``_IGNORED_NAMES`` were surfacing in completion when - they happened to be committed. - """ +def test_tracked_ignored_dirs_are_indexed_without_scanning_untracked_tree(tmp_path: Path): + """Tracked vendor files are explicit project inputs; untracked ones stay filtered.""" (tmp_path / "src").mkdir() (tmp_path / "src" / "app.py").write_text("# app") nm = tmp_path / "node_modules" / "pkg" @@ -181,14 +176,20 @@ def test_tracked_ignored_dirs_filtered_in_git_mode(tmp_path: Path): completer = LocalFileMentionCompleter(tmp_path) texts = _completion_texts(completer, "@nod") - assert not any("node_modules" in t for t in texts), ( - f"node_modules should be filtered even if tracked, got: {texts}" - ) + assert not any("node_modules" in text for text in texts) texts = _completion_texts(completer, "@ven") - assert not any("vendor" in t for t in texts), ( - f"vendor should be filtered even if tracked, got: {texts}" - ) + assert "vendor/dep.py" in texts + + # Adding a large untracked dependency subtree must not expand candidates. + untracked = vendor / "generated" + untracked.mkdir() + for i in range(100): + (untracked / f"generated_{i}.py").write_text("# generated") + + completer = LocalFileMentionCompleter(tmp_path) + texts = _completion_texts(completer, "@generated") + assert not any("generated_" in text for text in texts) def test_unstaged_rename_hides_deleted_path(tmp_path: Path): diff --git a/tests/utils/test_file_filter.py b/tests/utils/test_file_filter.py index a5fd2fbc88..8987271414 100644 --- a/tests/utils/test_file_filter.py +++ b/tests/utils/test_file_filter.py @@ -95,7 +95,7 @@ class TestIgnoredDirFiltering: """Tracked ignored dirs must not leak into git results.""" @pytest.mark.parametrize( - "dirname", ["node_modules", "vendor", "__pycache__", ".vscode", "dist"] + "dirname", ["node_modules", "__pycache__", ".vscode", "dist"] ) def test_tracked_ignored_dir_filtered(self, tmp_path: Path, dirname: str) -> None: (tmp_path / "keep.py").write_text("")