diff --git a/src/ai_dev_tools/git/inspect.py b/src/ai_dev_tools/git/inspect.py index c8d91fd..9275632 100644 --- a/src/ai_dev_tools/git/inspect.py +++ b/src/ai_dev_tools/git/inspect.py @@ -22,7 +22,7 @@ def inspect_git( command="git inspect" if detailed else "git status", project_root=settings.project_root ) status_cmd = run_command( - ["git", "status", "--porcelain=v2", "--branch", "-z"], + ["git", "status", "--porcelain=v2", "--branch", "--untracked-files=all", "-z"], settings.project_root, 30, ) diff --git a/tests/integration/test_git.py b/tests/integration/test_git.py index 367eef6..bca5508 100644 --- a/tests/integration/test_git.py +++ b/tests/integration/test_git.py @@ -114,7 +114,14 @@ def spy_run(command: list[str], root: Path, timeout_seconds: int = 300): # type report = inspect_git(tmp_path, detailed=False, write_reports=False) assert len(git_commands) == 1 - assert git_commands[0] == ["git", "status", "--porcelain=v2", "--branch", "-z"] + assert git_commands[0] == [ + "git", + "status", + "--porcelain=v2", + "--branch", + "--untracked-files=all", + "-z", + ] assert report.summary["untracked_files"] == ["test.txt"] assert "DIRTY" in report.summary["states"] @@ -217,4 +224,21 @@ def test_git_states_and_large_files(tmp_path: Path) -> None: assert res == ["big.bin"] +def test_git_inspect_expands_nested_untracked_files(tmp_path: Path) -> None: + run_command(["git", "init", "-b", "main"], tmp_path, 30) + nested = tmp_path / "src" / "new package" + nested.mkdir(parents=True) + source = nested / "service.py" + source.write_text("def calculate():\n return 1\n", encoding="utf-8") + (tmp_path / ".gitignore").write_text("ignored/\n", encoding="utf-8") + ignored = tmp_path / "ignored" + ignored.mkdir() + (ignored / "secret.py").write_text("VALUE = 1\n", encoding="utf-8") + + report = inspect_git(tmp_path, detailed=True, write_reports=False) + assert "src/new package/service.py" in report.summary["untracked_files"] + assert "src/new package/service.py" in report.summary["changed_files"] + assert "src/" not in report.summary["changed_files"] + assert all(not path.startswith("ignored/") for path in report.summary["changed_files"]) + assert any(symbol["name"] == "calculate" for symbol in report.summary["changed_symbols"]) diff --git a/tests/unit/test_context_builder.py b/tests/unit/test_context_builder.py index 7489093..1e98428 100644 --- a/tests/unit/test_context_builder.py +++ b/tests/unit/test_context_builder.py @@ -357,3 +357,22 @@ def test_context_hierarchical_refinement_reports_rounds_and_limits(tmp_path: Pat assert refinement["added_count"] == 2 selected = {item["path"] for item in report.summary["selected_files"]} assert {"app.py", "helper.py", "leaf.py"} <= selected + + +def test_changed_only_context_includes_nested_untracked_source(tmp_path: Path) -> None: + from ai_dev_tools.utils.subprocess import run_command + + run_command(["git", "init", "-b", "main"], tmp_path, 30) + (tmp_path / "src" / "new_package").mkdir(parents=True) + (tmp_path / "src" / "new_package" / "service.py").write_text( + "def calculate():\n return 1\n", encoding="utf-8" + ) + + report = build_context( + tmp_path, + ContextOptions(changed_only=True, explain=True, task="review new service"), + ) + + selected = {item["path"]: item for item in report.summary["selected_files"]} + assert "src/new_package/service.py" in selected + assert selected["src/new_package/service.py"]["reason_code"] == "CHANGED_FILE"