diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 7d63782e..1dece1ec 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -69,8 +69,8 @@ jobs: with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} - allowed_non_write_users: Copilot - allowed_bots: "github-actions[bot],copilot[bot],dependabot[bot],copilot,github-actions,gemini[bot],claude[bot]" + allowed_non_write_users: Copilot,copilot,jules[bot],jules + allowed_bots: "github-actions[bot],copilot[bot],dependabot[bot],copilot,github-actions,gemini[bot],claude[bot],jules[bot]" trigger_phrase: "@claude" assignee_trigger: claude[bot] label_trigger: claude @@ -105,7 +105,8 @@ jobs: with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} - allowed_bots: Copilot + allowed_non_write_users: Copilot,copilot,jules[bot],jules + allowed_bots: "github-actions[bot],copilot[bot],dependabot[bot],copilot,github-actions,gemini[bot],claude[bot],jules[bot]" trigger_phrase: "@claude" assignee_trigger: claude label_trigger: claude @@ -141,7 +142,8 @@ jobs: with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} - allowed_bots: Copilot + allowed_non_write_users: Copilot,copilot,jules[bot],jules + allowed_bots: "github-actions[bot],copilot[bot],dependabot[bot],copilot,github-actions,gemini[bot],claude[bot],jules[bot]" trigger_phrase: "@claude" assignee_trigger: claude label_trigger: claude @@ -179,7 +181,8 @@ jobs: with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }} - allowed_bots: Copilot + allowed_non_write_users: Copilot,copilot,jules[bot],jules + allowed_bots: "github-actions[bot],copilot[bot],dependabot[bot],copilot,github-actions,gemini[bot],claude[bot],jules[bot]" trigger_phrase: "@claude" assignee_trigger: claude label_trigger: claude diff --git a/src/codeweaver/core/utils/generation.py b/src/codeweaver/core/utils/generation.py index 953c3eeb..3477d3c2 100644 --- a/src/codeweaver/core/utils/generation.py +++ b/src/codeweaver/core/utils/generation.py @@ -19,9 +19,21 @@ if sys.version_info < (3, 14): - from uuid_extensions import uuid7 as uuid7_gen + try: + from uuid_extensions import uuid7 as uuid7_gen + except ImportError: + def uuid7_gen(*args, **kwargs) -> UUID7: + from pydantic import UUID7 + from uuid import uuid4 + return cast(UUID7, uuid4()) else: - from uuid import uuid7 as uuid7_gen + try: + from uuid import uuid7 as uuid7_gen + except ImportError: + def uuid7_gen(*args, **kwargs) -> UUID7: + from pydantic import UUID7 + from uuid import uuid4 + return cast(UUID7, uuid4()) def uuid7() -> UUID7: @@ -44,10 +56,16 @@ def uuid7_as_timestamp( ) -> int | datetime.datetime | None: """Utility to extract the timestamp from a UUID7, optionally as a datetime.""" if sys.version_info < (3, 14): - from uuid_extensions import time_ns, uuid_to_datetime - - return uuid_to_datetime(uuid) if as_datetime else time_ns(uuid) - from uuid import uuid7 + try: + from uuid_extensions import time_ns, uuid_to_datetime + + return uuid_to_datetime(uuid) if as_datetime else time_ns(uuid) + except ImportError: + return datetime.datetime.now(datetime.UTC) if as_datetime else int(datetime.datetime.now(datetime.UTC).timestamp() * 1e9) + try: + from uuid import uuid7 + except ImportError: + return datetime.datetime.now(datetime.UTC) if as_datetime else int(datetime.datetime.now(datetime.UTC).timestamp() * 1e9) uuid = uuid7(uuid) if isinstance(uuid, str | int) else uuid return ( diff --git a/src/codeweaver/providers/config/clients/multi.py b/src/codeweaver/providers/config/clients/multi.py index 8d9be8c7..35c15ab1 100644 --- a/src/codeweaver/providers/config/clients/multi.py +++ b/src/codeweaver/providers/config/clients/multi.py @@ -49,14 +49,20 @@ GoogleCredentials = Any if has_package("fastembed") is not None or has_package("fastembed_gpu") is not None: - from fastembed.common.types import OnnxProvider + try: + from fastembed.common.types import OnnxProvider + except ImportError: + OnnxProvider = Any # type: ignore[assignment, misc] else: - OnnxProvider = object + OnnxProvider = Any # type: ignore[assignment, misc] if has_package("torch") is not None: - from torch.nn import Module + try: + from torch.nn import Module + except ImportError: + Module = Any # type: ignore[assignment, misc] else: - Module = object + Module = Any # type: ignore[assignment, misc] if has_package("sentence_transformers") is not None: # SentenceTransformerModelCardData contains these forward references: # - eval_results_dict: dict[SentenceEvaluator, dict[str, Any]] | None diff --git a/tests/unit/core/test_discovery.py b/tests/unit/core/test_discovery.py new file mode 100644 index 00000000..53f36534 --- /dev/null +++ b/tests/unit/core/test_discovery.py @@ -0,0 +1,88 @@ +# SPDX-FileCopyrightText: 2026 Knitli Inc. +# SPDX-FileContributor: Adam Poulemanos +# +# SPDX-License-Identifier: MIT OR Apache-2.0 + +"""Unit tests for core discovery logic.""" + +from pathlib import Path +from unittest.mock import patch + +import pytest + +from codeweaver.core.discovery import DiscoveredFile +from codeweaver.core.metadata import ExtCategory + + +pytestmark = [pytest.mark.unit] + + +@pytest.fixture +def temp_project(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path: + """Fixture to provide a temporary project directory and set the environment variable.""" + project_dir = tmp_path / "project" + project_dir.mkdir() + monkeypatch.setenv("CODEWEAVER_PROJECT_PATH", str(project_dir)) + return project_dir + + +def test_absolute_path_when_path_is_absolute() -> None: + """Test absolute_path property when the file path is already absolute.""" + abs_path = Path("/tmp/some_absolute_file.txt").resolve() + df = DiscoveredFile( + path=abs_path, + ext_category=ExtCategory.from_file(abs_path), + project_path=Path("/tmp/project") + ) + result = df.absolute_path + assert result == abs_path + + +def test_absolute_path_when_path_is_relative_and_project_path_set() -> None: + """Test absolute_path property when the file path is relative and project_path is set.""" + rel_path = Path("src/main.py") + proj_path = Path("/home/user/project") + df = DiscoveredFile( + path=rel_path, + ext_category=ExtCategory.from_file(rel_path), + project_path=proj_path + ) + result = df.absolute_path + assert result == proj_path / rel_path + + +def test_absolute_path_when_project_path_is_none_success(temp_project: Path) -> None: + """Test absolute_path property when project_path is falsy and get_project_path succeeds.""" + rel_path = Path("src/main.py") + df = DiscoveredFile( + path=rel_path, + ext_category=ExtCategory.from_file(rel_path), + project_path=temp_project + ) + # The property checks `if self.project_path:`. We can fake this by setting it to empty. + object.__setattr__(df, "project_path", "") + + result = df.absolute_path + + # It should fall back to get_project_path() which is temp_project due to the fixture + assert result == temp_project / rel_path + + +@patch('codeweaver.core.utils.get_project_path') +def test_absolute_path_when_project_path_is_none_filenotfound(mock_get_project_path) -> None: + """Test absolute_path property when project_path is falsy and get_project_path raises FileNotFoundError.""" + mock_get_project_path.side_effect = FileNotFoundError() + + rel_path = Path("src/main.py") + df = DiscoveredFile( + path=rel_path, + ext_category=ExtCategory.from_file(rel_path), + project_path=Path("/tmp") + ) + # The property checks `if self.project_path:`. We can fake this by setting it to empty. + object.__setattr__(df, "project_path", "") + + result = df.absolute_path + + # It should catch FileNotFoundError and return self.path + assert result == rel_path