diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 24acf1ffd..37fa45506 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -19,16 +19,30 @@ jobs: (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) runs-on: ubuntu-latest permissions: - contents: read - pull-requests: read + contents: write + pull-requests: write issues: read id-token: write actions: read # Required for Claude to read CI results on PRs steps: + - name: Get PR head ref + id: pr-ref + env: + GH_TOKEN: ${{ github.token }} + run: | + # For issue_comment events, we need to fetch the PR info + if [ "${{ github.event_name }}" = "issue_comment" ]; then + PR_REF=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }} --jq '.head.ref') + echo "ref=$PR_REF" >> $GITHUB_OUTPUT + else + echo "ref=${{ github.event.pull_request.head.ref || github.head_ref }}" >> $GITHUB_OUTPUT + fi + - name: Checkout repository uses: actions/checkout@v4 with: - fetch-depth: 1 + fetch-depth: 0 + ref: ${{ steps.pr-ref.outputs.ref }} - name: Run Claude Code id: claude diff --git a/codeflash/discovery/functions_to_optimize.py b/codeflash/discovery/functions_to_optimize.py index 0042ebc47..b3e384dc0 100644 --- a/codeflash/discovery/functions_to_optimize.py +++ b/codeflash/discovery/functions_to_optimize.py @@ -41,6 +41,8 @@ from codeflash.models.models import CodeOptimizationContext from codeflash.verification.verification_utils import TestConfig +import contextlib + from rich.text import Text _property_id = "property" @@ -595,9 +597,10 @@ def get_all_replay_test_functions( except Exception as e: logger.warning(f"Error parsing replay test file {replay_test_file}: {e}") - if not trace_file_path: + if trace_file_path is None: logger.error("Could not find trace_file_path in replay test files.") exit_with_message("Could not find trace_file_path in replay test files.") + raise AssertionError("Unreachable") # exit_with_message never returns if not trace_file_path.exists(): logger.error(f"Trace file not found: {trace_file_path}") @@ -652,7 +655,7 @@ def get_all_replay_test_functions( if filtered_list: filtered_valid_functions[file_path] = filtered_list - return filtered_valid_functions, trace_file_path + return dict(filtered_valid_functions), trace_file_path def is_git_repo(file_path: str) -> bool: @@ -664,11 +667,13 @@ def is_git_repo(file_path: str) -> bool: @cache -def ignored_submodule_paths(module_root: str) -> list[str]: +def ignored_submodule_paths(module_root: str) -> list[Path]: if is_git_repo(module_root): git_repo = git.Repo(module_root, search_parent_directories=True) try: - return [Path(git_repo.working_tree_dir, submodule.path).resolve() for submodule in git_repo.submodules] + working_dir = git_repo.working_tree_dir + if working_dir is not None: + return [Path(working_dir, submodule.path).resolve() for submodule in git_repo.submodules] except Exception as e: logger.warning(f"Error getting submodule paths: {e}") return [] @@ -682,7 +687,7 @@ def __init__( self.class_name = class_name self.function_name = function_or_method_name self.is_top_level = False - self.function_has_args = None + self.function_has_args: bool | None = None self.line_no = line_no self.is_staticmethod = False self.is_classmethod = False @@ -796,31 +801,28 @@ def was_function_previously_optimized( # Check optimization status if repository info is provided # already_optimized_count = 0 - try: + + # Check optimization status if repository info is provided + # already_optimized_count = 0 + owner = None + repo = None + with contextlib.suppress(git.exc.InvalidGitRepositoryError): owner, repo = get_repo_owner_and_name() - except git.exc.InvalidGitRepositoryError: - logger.warning("No git repository found") - owner, repo = None, None + pr_number = get_pr_number() if not owner or not repo or pr_number is None or getattr(args, "no_pr", False): return False - code_contexts = [] - func_hash = code_context.hashing_code_context_hash - # Use a unique path identifier that includes function info - code_contexts.append( + code_contexts = [ { - "file_path": function_to_optimize.file_path, + "file_path": str(function_to_optimize.file_path), "function_name": function_to_optimize.qualified_name, "code_hash": func_hash, } - ) - - if not code_contexts: - return False + ] try: result = is_function_being_optimized_again(owner, repo, pr_number, code_contexts) @@ -839,7 +841,7 @@ def filter_functions( ignore_paths: list[Path], project_root: Path, module_root: Path, - previous_checkpoint_functions: dict[Path, dict[str, Any]] | None = None, + previous_checkpoint_functions: dict[str, dict[str, Any]] | None = None, *, disable_logs: bool = False, ) -> tuple[dict[Path, list[FunctionToOptimize]], int]: @@ -868,24 +870,13 @@ def filter_functions( # Check if tests_root overlaps with module_root or project_root # In this case, we need to use file pattern matching instead of directory matching - tests_root_overlaps_source = ( - tests_root_str == module_root_str - or tests_root_str == project_root_str - or module_root_str.startswith(tests_root_str + os.sep) + tests_root_overlaps_source = tests_root_str in (module_root_str, project_root_str) or module_root_str.startswith( + tests_root_str + os.sep ) # Test file patterns for when tests_root overlaps with source - test_file_name_patterns = ( - ".test.", - ".spec.", - "_test.", - "_spec.", - ) - test_dir_patterns = ( - os.sep + "test" + os.sep, - os.sep + "tests" + os.sep, - os.sep + "__tests__" + os.sep, - ) + test_file_name_patterns = (".test.", ".spec.", "_test.", "_spec.") + test_dir_patterns = (os.sep + "test" + os.sep, os.sep + "tests" + os.sep, os.sep + "__tests__" + os.sep) def is_test_file(file_path_normalized: str) -> bool: """Check if a file is a test file based on patterns.""" @@ -899,11 +890,10 @@ def is_test_file(file_path_normalized: str) -> bool: # to avoid false positives from parent directories relative_path = file_lower if project_root_str and file_lower.startswith(project_root_str.lower()): - relative_path = file_lower[len(project_root_str):] + relative_path = file_lower[len(project_root_str) :] return any(pattern in relative_path for pattern in test_dir_patterns) - else: - # Use directory-based filtering when tests are in a separate directory - return file_path_normalized.startswith(tests_root_str + os.sep) + # Use directory-based filtering when tests are in a separate directory + return file_path_normalized.startswith(tests_root_str + os.sep) # We desperately need Python 3.10+ only support to make this code readable with structural pattern matching for file_path_path, functions in modified_functions.items(): @@ -913,12 +903,12 @@ def is_test_file(file_path_normalized: str) -> bool: if is_test_file(file_path_normalized): test_functions_removed_count += len(_functions) continue - if file_path in ignore_paths or any( + if file_path_path in ignore_paths or any( file_path_normalized.startswith(os.path.normcase(str(ignore_path)) + os.sep) for ignore_path in ignore_paths ): ignore_paths_removed_count += 1 continue - if file_path in submodule_paths or any( + if file_path_path in submodule_paths or any( file_path_normalized.startswith(os.path.normcase(str(submodule_path)) + os.sep) for submodule_path in submodule_paths ): @@ -1010,7 +1000,7 @@ def filter_files_optimized(file_path: Path, tests_root: Path, ignore_paths: list def function_has_return_statement(function_node: FunctionDef | AsyncFunctionDef) -> bool: # Custom DFS, return True as soon as a Return node is found - stack = [function_node] + stack: list[ast.AST] = [function_node] while stack: node = stack.pop() if isinstance(node, ast.Return): diff --git a/packages/codeflash/scripts/postinstall.js b/packages/codeflash/scripts/postinstall.js index 261cbea25..4dafbd713 100644 --- a/packages/codeflash/scripts/postinstall.js +++ b/packages/codeflash/scripts/postinstall.js @@ -115,7 +115,7 @@ function installCodeflash(uvBin) { try { // Use uv tool install to install codeflash in an isolated environment // This avoids conflicts with any existing Python environments - execSync(`"${uvBin}" tool install codeflash --force`, { + execSync(`"${uvBin}" tool install --force --python python3.12 codeflash`, { stdio: 'inherit', shell: true, });