diff --git a/strix/interface/utils.py b/strix/interface/utils.py index c267b77..3559fa9 100644 --- a/strix/interface/utils.py +++ b/strix/interface/utils.py @@ -1018,6 +1018,7 @@ def resolve_diff_scope_context( repo_scopes: list[RepoDiffScope] = [] skipped_non_git: list[str] = [] + skipped_diff_scope: list[str] = [] for source in local_sources: source_path = source.get("source_path") if not source_path: @@ -1025,9 +1026,23 @@ def resolve_diff_scope_context( if not _is_git_repo(Path(source_path)): skipped_non_git.append(source_path) continue - repo_scopes.append(_resolve_repo_diff_scope(source, diff_base, env_map)) + try: + repo_scopes.append(_resolve_repo_diff_scope(source, diff_base, env_map)) + except ValueError as e: + if scope_mode == "auto": + skipped_diff_scope.append(f"{source_path} (diff-scope skipped: {e})") + continue + raise if not repo_scopes: + if scope_mode == "auto": + metadata: dict[str, Any] = {"active": False, "mode": scope_mode} + if skipped_non_git: + metadata["skipped_non_git_sources"] = skipped_non_git + if skipped_diff_scope: + metadata["skipped_diff_scope_sources"] = skipped_diff_scope + return DiffScopeResult(active=False, mode=scope_mode, metadata=metadata) + raise ValueError( "Diff-scope is active, but no Git repositories were found. " "Use --scope-mode full to disable diff-scope for this run." @@ -1044,6 +1059,8 @@ def resolve_diff_scope_context( } if skipped_non_git: metadata["skipped_non_git_sources"] = skipped_non_git + if skipped_diff_scope: + metadata["skipped_diff_scope_sources"] = skipped_diff_scope return DiffScopeResult( active=True, diff --git a/tests/interface/test_diff_scope.py b/tests/interface/test_diff_scope.py index a1796e3..9fe7dd6 100644 --- a/tests/interface/test_diff_scope.py +++ b/tests/interface/test_diff_scope.py @@ -1,6 +1,8 @@ import importlib.util from pathlib import Path +import pytest + def _load_utils_module(): module_path = Path(__file__).resolve().parents[2] / "strix" / "interface" / "utils.py" @@ -96,3 +98,56 @@ def test_resolve_base_ref_falls_back_to_remote_main(monkeypatch) -> None: assert base_ref == "refs/remotes/origin/main" assert "refs/remotes/origin/main" in calls assert "origin/main" not in calls + + +def test_resolve_diff_scope_context_auto_degrades_when_repo_scope_resolution_fails( + monkeypatch, +) -> None: + source = {"source_path": "/tmp/repo", "workspace_subdir": "repo"} + + monkeypatch.setattr(utils, "_should_activate_auto_scope", lambda *_args, **_kwargs: True) + monkeypatch.setattr(utils, "_is_git_repo", lambda _repo_path: True) + monkeypatch.setattr( + utils, + "_resolve_repo_diff_scope", + lambda *_args, **_kwargs: (_ for _ in ()).throw(ValueError("shallow history")), + ) + + result = utils.resolve_diff_scope_context( + local_sources=[source], + scope_mode="auto", + diff_base=None, + non_interactive=True, + env={}, + ) + + assert result.active is False + assert result.mode == "auto" + assert result.metadata["active"] is False + assert result.metadata["mode"] == "auto" + assert "skipped_diff_scope_sources" in result.metadata + assert result.metadata["skipped_diff_scope_sources"] == [ + "/tmp/repo (diff-scope skipped: shallow history)" + ] + + +def test_resolve_diff_scope_context_diff_mode_still_raises_on_repo_scope_resolution_failure( + monkeypatch, +) -> None: + source = {"source_path": "/tmp/repo", "workspace_subdir": "repo"} + + monkeypatch.setattr(utils, "_is_git_repo", lambda _repo_path: True) + monkeypatch.setattr( + utils, + "_resolve_repo_diff_scope", + lambda *_args, **_kwargs: (_ for _ in ()).throw(ValueError("shallow history")), + ) + + with pytest.raises(ValueError, match="shallow history"): + utils.resolve_diff_scope_context( + local_sources=[source], + scope_mode="diff", + diff_base=None, + non_interactive=True, + env={}, + )