Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion cycode/cli/apps/mcp/mcp_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import uuid
from typing import Annotated, Any, Optional

import anyio
import typer
from pathvalidate import sanitize_filepath
from pydantic import Field
Expand Down Expand Up @@ -65,6 +66,7 @@ def _get_current_executable() -> str:
return 'cycode'


# ruff: disable[ASYNC109]
async def _run_cycode_command(*args: str, timeout: int = _DEFAULT_RUN_COMMAND_TIMEOUT) -> dict[str, Any]:
"""Run a cycode command asynchronously and return the parsed result.

Expand Down Expand Up @@ -109,6 +111,9 @@ async def _run_cycode_command(*args: str, timeout: int = _DEFAULT_RUN_COMMAND_TI
return {'error': f'Failed to run command: {e!s}'}


# ruff: enable[ASYNC109]


def _sanitize_file_path(file_path: str) -> str:
"""Sanitize file path to prevent path traversal and other security issues.

Expand Down Expand Up @@ -238,7 +243,7 @@ async def _cycode_scan_tool(

try:
if paths:
missing = [p for p in paths if not os.path.exists(p)]
missing = [p for p in paths if not await anyio.Path(p).exists()]
if missing:
return json.dumps({'error': f'Paths not found on disk: {missing}'}, indent=2)

Expand Down
70 changes: 27 additions & 43 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pydantic = ">=2.11.5,<3.0.0"
pathvalidate = ">=3.3.1,<4.0.0"
tomli-w = ">=1.0.0,<2.0.0"
tomli = {version = ">=2.0.0,<3.0.0", python = "<3.11"}
anyio = ">=4.0.0, <4.13.0"

[tool.poetry.group.test.dependencies]
mock = ">=4.0.3,<4.1.0"
Expand All @@ -65,7 +66,7 @@ pyinstaller = {version=">=6.0.0,<7.0.0", python=">=3.9,<3.15"}
dunamai = ">=1.26.1,<1.27.0"

[tool.poetry.group.dev.dependencies]
ruff = "0.11.7"
ruff = "0.15.14"

[tool.pytest.ini_options]
log_cli = true
Expand Down
32 changes: 16 additions & 16 deletions tests/cli/files_collector/test_commit_range_documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def test_returns_head_when_repository_has_commits(self) -> None:

def test_returns_empty_tree_hash_when_repository_has_no_commits(self) -> None:
"""Test that an empty tree hash is returned when the repository has no commits."""
with temporary_git_repository() as (temp_dir, repo):
with temporary_git_repository() as (_temp_dir, repo):
result = get_safe_head_reference_for_diff(repo)
expected_empty_tree_hash = consts.GIT_EMPTY_TREE_OBJECT
assert result == expected_empty_tree_hash
Expand Down Expand Up @@ -343,7 +343,7 @@ def test_diff_with_bare_repository(self) -> None:

def test_diff_with_no_paths(self) -> None:
"""Test behavior when the diff has neither a_path nor b_path."""
with temporary_git_repository() as (temp_dir, repo):
with temporary_git_repository() as (_temp_dir, repo):

class MockDiff:
def __init__(self) -> None:
Expand Down Expand Up @@ -409,7 +409,7 @@ class TestGetDefaultBranchesForMergeBase:
def test_environment_variable_override(self) -> None:
"""Test that the environment variable takes precedence."""
with (
temporary_git_repository() as (temp_dir, repo),
temporary_git_repository() as (_temp_dir, repo),
patch.dict(os.environ, {consts.CYCODE_DEFAULT_BRANCH_ENV_VAR_NAME: 'custom-main'}),
):
branches = _get_default_branches_for_merge_base(repo)
Expand All @@ -418,7 +418,7 @@ def test_environment_variable_override(self) -> None:

def test_git_symbolic_ref_success(self) -> None:
"""Test getting default branch via git symbolic-ref."""
with temporary_git_repository() as (temp_dir, repo):
with temporary_git_repository() as (_temp_dir, _repo):
# Create a mock repo with a git interface that returns origin/main
mock_repo = Mock()
mock_repo.git.symbolic_ref.return_value = 'refs/remotes/origin/main'
Expand All @@ -429,7 +429,7 @@ def test_git_symbolic_ref_success(self) -> None:

def test_git_symbolic_ref_with_master(self) -> None:
"""Test getting default branch via git symbolic-ref when it's master."""
with temporary_git_repository() as (temp_dir, repo):
with temporary_git_repository() as (_temp_dir, _repo):
# Create a mock repo with a git interface that returns origin/master
mock_repo = Mock()
mock_repo.git.symbolic_ref.return_value = 'refs/remotes/origin/master'
Expand All @@ -440,7 +440,7 @@ def test_git_symbolic_ref_with_master(self) -> None:

def test_git_remote_show_fallback(self) -> None:
"""Test fallback to git remote show when symbolic-ref fails."""
with temporary_git_repository() as (temp_dir, repo):
with temporary_git_repository() as (_temp_dir, _repo):
# Create a mock repo where symbolic-ref fails but the remote show succeeds
mock_repo = Mock()
mock_repo.git.symbolic_ref.side_effect = Exception('symbolic-ref failed')
Expand All @@ -459,7 +459,7 @@ def test_git_remote_show_fallback(self) -> None:

def test_both_git_methods_fail_fallback_to_hardcoded(self) -> None:
"""Test fallback to hardcoded branches when both Git methods fail."""
with temporary_git_repository() as (temp_dir, repo):
with temporary_git_repository() as (_temp_dir, _repo):
# Create a mock repo where both Git methods fail
mock_repo = Mock()
mock_repo.git.symbolic_ref.side_effect = Exception('symbolic-ref failed')
Expand All @@ -474,7 +474,7 @@ def test_both_git_methods_fail_fallback_to_hardcoded(self) -> None:

def test_no_duplicates_in_branch_list(self) -> None:
"""Test that duplicate branches are not added to the list."""
with temporary_git_repository() as (temp_dir, repo):
with temporary_git_repository() as (_temp_dir, _repo):
# Create a mock repo that returns main (which is also in fallback list)
mock_repo = Mock()
mock_repo.git.symbolic_ref.return_value = 'refs/remotes/origin/main'
Expand All @@ -486,7 +486,7 @@ def test_no_duplicates_in_branch_list(self) -> None:

def test_env_var_plus_git_detection(self) -> None:
"""Test combination of environment variable and git detection."""
with temporary_git_repository() as (temp_dir, repo):
with temporary_git_repository() as (_temp_dir, _repo):
mock_repo = Mock()
mock_repo.git.symbolic_ref.return_value = 'refs/remotes/origin/develop'

Expand All @@ -500,7 +500,7 @@ def test_env_var_plus_git_detection(self) -> None:

def test_malformed_symbolic_ref_response(self) -> None:
"""Test handling of malformed symbolic-ref response."""
with temporary_git_repository() as (temp_dir, repo):
with temporary_git_repository() as (_temp_dir, _repo):
# Create a mock repo that returns a malformed response
mock_repo = Mock()
mock_repo.git.symbolic_ref.return_value = 'malformed-response'
Expand Down Expand Up @@ -845,39 +845,39 @@ def _make_linear_history(self, repo: Repo, base_dir: str) -> tuple[str, str, str
def test_two_dot_linear_history(self) -> None:
"""For 'A..C', expect (A,C) in linear history."""
with temporary_git_repository() as (temp_dir, repo):
a, b, c = self._make_linear_history(repo, temp_dir)
a, _b, c = self._make_linear_history(repo, temp_dir)

parsed_from, parsed_to, separator = parse_commit_range(f'{a}..{c}', temp_dir)
assert (parsed_from, parsed_to, separator) == (a, c, '..')

def test_three_dot_linear_history(self) -> None:
"""For 'A...C' in linear history, expect (A,C)."""
with temporary_git_repository() as (temp_dir, repo):
a, b, c = self._make_linear_history(repo, temp_dir)
a, _b, c = self._make_linear_history(repo, temp_dir)

parsed_from, parsed_to, separator = parse_commit_range(f'{a}...{c}', temp_dir)
assert (parsed_from, parsed_to, separator) == (a, c, '...')

def test_open_right_linear_history(self) -> None:
"""For 'A..', expect (A,HEAD=C)."""
with temporary_git_repository() as (temp_dir, repo):
a, b, c = self._make_linear_history(repo, temp_dir)
a, _b, c = self._make_linear_history(repo, temp_dir)

parsed_from, parsed_to, separator = parse_commit_range(f'{a}..', temp_dir)
assert (parsed_from, parsed_to, separator) == (a, c, '..')

def test_open_left_linear_history(self) -> None:
"""For '..C' where HEAD==C, expect (HEAD=C,C)."""
with temporary_git_repository() as (temp_dir, repo):
a, b, c = self._make_linear_history(repo, temp_dir)
_a, _b, c = self._make_linear_history(repo, temp_dir)

parsed_from, parsed_to, separator = parse_commit_range(f'..{c}', temp_dir)
assert (parsed_from, parsed_to, separator) == (c, c, '..')

def test_single_commit_spec(self) -> None:
"""For 'A', expect (A,HEAD=C)."""
with temporary_git_repository() as (temp_dir, repo):
a, b, c = self._make_linear_history(repo, temp_dir)
a, _b, c = self._make_linear_history(repo, temp_dir)

parsed_from, parsed_to, separator = parse_commit_range(a, temp_dir)
assert (parsed_from, parsed_to, separator) == (a, c, '..')
Expand Down Expand Up @@ -935,7 +935,7 @@ def test_parse_all_for_empty_remote_scenario_with_two_commits(self) -> None:

def test_parse_all_with_empty_repository_returns_none(self) -> None:
"""Test that '--all' returns None when repository has no commits."""
with temporary_git_repository() as (temp_dir, repo):
with temporary_git_repository() as (temp_dir, _repo):
# Empty repository with no commits
parsed_from, parsed_to, separator = parse_commit_range('--all', temp_dir)
# Should return None, None, None when HEAD doesn't exist
Expand Down
Loading