Skip to content
Merged
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
20 changes: 17 additions & 3 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
72 changes: 31 additions & 41 deletions codeflash/discovery/functions_to_optimize.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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}")
Expand Down Expand Up @@ -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:
Expand All @@ -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 []
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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]:
Expand Down Expand Up @@ -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."""
Expand All @@ -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():
Expand All @@ -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
):
Expand Down Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion packages/codeflash/scripts/postinstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not 3.13?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aider has had it in 3.12 for a while, probably just to be safe

stdio: 'inherit',
shell: true,
});
Expand Down
Loading