Skip to content

Commit e02071d

Browse files
authored
Merge branch 'main' into gpu-flag
2 parents 805e612 + 1c0d8da commit e02071d

File tree

13 files changed

+269
-288
lines changed

13 files changed

+269
-288
lines changed

.github/workflows/claude-code-review.yml

Lines changed: 0 additions & 59 deletions
This file was deleted.

.github/workflows/claude.yml

Lines changed: 152 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
name: Claude Code
22

33
on:
4+
pull_request:
5+
types: [opened, synchronize, ready_for_review, reopened]
46
issue_comment:
57
types: [created]
68
pull_request_review_comment:
@@ -11,19 +13,154 @@ on:
1113
types: [submitted]
1214

1315
jobs:
14-
claude:
16+
# Automatic PR review (can fix linting issues and push)
17+
# Blocked for fork PRs to prevent malicious code execution
18+
pr-review:
1519
if: |
16-
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
17-
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
18-
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
19-
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
20+
github.event_name == 'pull_request' &&
21+
github.actor != 'claude[bot]' &&
22+
github.event.pull_request.head.repo.full_name == github.repository
2023
runs-on: ubuntu-latest
2124
permissions:
2225
contents: write
2326
pull-requests: write
2427
issues: read
2528
id-token: write
26-
actions: read # Required for Claude to read CI results on PRs
29+
actions: read
30+
steps:
31+
- name: Checkout repository
32+
uses: actions/checkout@v4
33+
with:
34+
fetch-depth: 0
35+
ref: ${{ github.event.pull_request.head.ref }}
36+
37+
- name: Install uv
38+
uses: astral-sh/setup-uv@v6
39+
40+
- name: Install dependencies
41+
run: |
42+
uv venv --seed
43+
uv sync
44+
45+
- name: Run Claude Code
46+
id: claude
47+
uses: anthropics/claude-code-action@v1
48+
with:
49+
use_foundry: "true"
50+
use_sticky_comment: true
51+
allowed_bots: "claude[bot]"
52+
prompt: |
53+
REPO: ${{ github.repository }}
54+
PR NUMBER: ${{ github.event.pull_request.number }}
55+
EVENT: ${{ github.event.action }}
56+
57+
## STEP 1: Run pre-commit checks and fix issues
58+
59+
First, run `uv run prek run --from-ref origin/main` to check for linting/formatting issues on files changed in this PR.
60+
61+
If there are any issues:
62+
- For SAFE auto-fixable issues (formatting, import sorting, trailing whitespace, etc.), run `uv run prek run --from-ref origin/main` again to auto-fix them
63+
- Stage the fixed files with `git add`
64+
- Commit with message "style: auto-fix linting issues"
65+
- Push the changes with `git push`
66+
67+
Do NOT attempt to fix:
68+
- Type errors that require logic changes
69+
- Complex refactoring suggestions
70+
- Anything that could change behavior
71+
72+
## STEP 2: Review the PR
73+
74+
${{ github.event.action == 'synchronize' && 'This is a RE-REVIEW after new commits. First, get the list of changed files in this latest push using `gh pr diff`. Review ONLY the changed files. Check ALL existing review comments and resolve ones that are now fixed.' || 'This is the INITIAL REVIEW.' }}
75+
76+
Review this PR focusing ONLY on:
77+
1. Critical bugs or logic errors
78+
2. Security vulnerabilities
79+
3. Breaking API changes
80+
4. Test failures (methods with typos that wont run)
81+
82+
IMPORTANT:
83+
- First check existing review comments using `gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments`. For each existing comment, check if the issue still exists in the current code.
84+
- If an issue is fixed, use `gh api --method PATCH repos/${{ github.repository }}/pulls/comments/COMMENT_ID -f body="✅ Fixed in latest commit"` to resolve it.
85+
- Only create NEW inline comments for HIGH-PRIORITY issues found in changed files.
86+
- Limit to 5-7 NEW comments maximum per review.
87+
- Use CLAUDE.md for project-specific guidance.
88+
- Use `gh pr comment` for summary-level feedback.
89+
- Use `mcp__github_inline_comment__create_inline_comment` sparingly for critical code issues only.
90+
91+
## STEP 3: Coverage analysis
92+
93+
Analyze test coverage for changed files:
94+
95+
1. Get the list of Python files changed in this PR (excluding tests):
96+
`git diff --name-only origin/main...HEAD -- '*.py' | grep -v test`
97+
98+
2. Run tests with coverage on the PR branch:
99+
`uv run coverage run -m pytest tests/ -q --tb=no`
100+
`uv run coverage json -o coverage-pr.json`
101+
102+
3. Get coverage for changed files only:
103+
`uv run coverage report --include="<changed_files_comma_separated>"`
104+
105+
4. Compare with main branch coverage:
106+
- Checkout main: `git checkout origin/main`
107+
- Run coverage: `uv run coverage run -m pytest tests/ -q --tb=no && uv run coverage json -o coverage-main.json`
108+
- Checkout back: `git checkout -`
109+
110+
5. Analyze the diff to identify:
111+
- NEW FILES: Files that don't exist on main (require good test coverage)
112+
- MODIFIED FILES: Files with changes (changes must be covered by tests)
113+
114+
6. Report in PR comment with a markdown table:
115+
- Coverage % for each changed file (PR vs main)
116+
- Overall coverage change
117+
- For NEW files: Flag if coverage is below 75%
118+
- For MODIFIED files: Flag if the changed lines are not covered by tests
119+
- Flag if overall coverage decreased
120+
121+
Coverage requirements:
122+
- New implementations/files: Must have ≥75% test coverage
123+
- Modified code: Changed lines should be exercised by existing or new tests
124+
- No coverage regressions: Overall coverage should not decrease
125+
claude_args: '--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*),Bash(gh issue view:*),Bash(gh issue list:*),Bash(gh api:*),Bash(uv run prek *),Bash(uv run coverage *),Bash(uv run pytest *),Bash(git status*),Bash(git add *),Bash(git commit *),Bash(git push*),Bash(git diff *),Bash(git checkout *),Read,Glob,Grep"'
126+
additional_permissions: |
127+
actions: read
128+
env:
129+
ANTHROPIC_FOUNDRY_API_KEY: ${{ secrets.AZURE_ANTHROPIC_API_KEY }}
130+
ANTHROPIC_FOUNDRY_BASE_URL: ${{ secrets.AZURE_ANTHROPIC_ENDPOINT }}
131+
132+
# @claude mentions (can edit and push) - restricted to maintainers only
133+
claude-mention:
134+
if: |
135+
(
136+
github.event_name == 'issue_comment' &&
137+
contains(github.event.comment.body, '@claude') &&
138+
(github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'COLLABORATOR')
139+
) ||
140+
(
141+
github.event_name == 'pull_request_review_comment' &&
142+
contains(github.event.comment.body, '@claude') &&
143+
(github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'COLLABORATOR') &&
144+
github.event.pull_request.head.repo.full_name == github.repository
145+
) ||
146+
(
147+
github.event_name == 'pull_request_review' &&
148+
contains(github.event.review.body, '@claude') &&
149+
(github.event.review.author_association == 'OWNER' || github.event.review.author_association == 'MEMBER' || github.event.review.author_association == 'COLLABORATOR') &&
150+
github.event.pull_request.head.repo.full_name == github.repository
151+
) ||
152+
(
153+
github.event_name == 'issues' &&
154+
(contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) &&
155+
(github.event.issue.author_association == 'OWNER' || github.event.issue.author_association == 'MEMBER' || github.event.issue.author_association == 'COLLABORATOR')
156+
)
157+
runs-on: ubuntu-latest
158+
permissions:
159+
contents: write
160+
pull-requests: write
161+
issues: read
162+
id-token: write
163+
actions: read
27164
steps:
28165
- name: Get PR head ref
29166
id: pr-ref
@@ -44,15 +181,22 @@ jobs:
44181
fetch-depth: 0
45182
ref: ${{ steps.pr-ref.outputs.ref }}
46183

184+
- name: Install uv
185+
uses: astral-sh/setup-uv@v6
186+
187+
- name: Install dependencies
188+
run: |
189+
uv venv --seed
190+
uv sync
191+
47192
- name: Run Claude Code
48193
id: claude
49194
uses: anthropics/claude-code-action@v1
50195
with:
51196
use_foundry: "true"
52-
claude_args: '--allowedTools "Read,Edit,Write,Glob,Grep,Bash(git status*),Bash(git diff*),Bash(git add *),Bash(git commit *),Bash(git push*),Bash(git log*),Bash(uv run pre-commit *),Bash(uv run ruff *),Bash(uv run pytest *),Bash(uv run mypy *),Bash(uv run coverage *),Bash(gh pr comment*),Bash(gh pr view*),Bash(gh pr diff*)"'
197+
claude_args: '--allowedTools "Read,Edit,Write,Glob,Grep,Bash(git status*),Bash(git diff*),Bash(git add *),Bash(git commit *),Bash(git push*),Bash(git log*),Bash(git merge*),Bash(git fetch*),Bash(git checkout*),Bash(git branch*),Bash(uv run prek *),Bash(prek *),Bash(uv run ruff *),Bash(uv run pytest *),Bash(uv run mypy *),Bash(uv run coverage *),Bash(gh pr comment*),Bash(gh pr view*),Bash(gh pr diff*),Bash(gh pr merge*),Bash(gh pr close*)"'
53198
additional_permissions: |
54199
actions: read
55200
env:
56201
ANTHROPIC_FOUNDRY_API_KEY: ${{ secrets.AZURE_ANTHROPIC_API_KEY }}
57202
ANTHROPIC_FOUNDRY_BASE_URL: ${{ secrets.AZURE_ANTHROPIC_ENDPOINT }}
58-

.github/workflows/pre-commit.yaml

Lines changed: 0 additions & 19 deletions
This file was deleted.

.github/workflows/prek.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: Lint
2+
on: [pull_request]
3+
4+
concurrency:
5+
group: ${{ github.workflow }}-${{ github.ref_name }}
6+
cancel-in-progress: true
7+
8+
jobs:
9+
prek:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
with:
14+
fetch-depth: 0
15+
- uses: astral-sh/setup-uv@v6
16+
- uses: j178/prek-action@v1
17+
with:
18+
extra-args: '--from-ref origin/${{ github.base_ref }} --to-ref ${{ github.sha }}'

codeflash/code_utils/code_extractor.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1429,7 +1429,9 @@ def _collect_numerical_imports(tree: ast.Module) -> tuple[set[str], set[str]]:
14291429
numerical_names: set[str] = set()
14301430
modules_used: set[str] = set()
14311431

1432-
for node in ast.walk(tree):
1432+
stack: list[ast.AST] = [tree]
1433+
while stack:
1434+
node = stack.pop()
14331435
if isinstance(node, ast.Import):
14341436
for alias in node.names:
14351437
# import numpy or import numpy as np
@@ -1451,6 +1453,8 @@ def _collect_numerical_imports(tree: ast.Module) -> tuple[set[str], set[str]]:
14511453
name = alias.asname or alias.name
14521454
numerical_names.add(name)
14531455
modules_used.add(module_root)
1456+
else:
1457+
stack.extend(ast.iter_child_nodes(node))
14541458

14551459
return numerical_names, modules_used
14561460

codeflash/context/code_context_extractor.py

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,11 @@ def get_external_base_class_inits(code_context: CodeStringsMarkdown, project_roo
746746
return CodeStringsMarkdown(code_strings=[])
747747

748748
imported_names: dict[str, str] = {}
749-
external_bases: list[tuple[str, str]] = []
749+
# Use a set to deduplicate external base entries to avoid repeated expensive checks/imports.
750+
external_bases_set: set[tuple[str, str]] = set()
751+
# Local cache to avoid repeated _is_project_module calls for the same module_name.
752+
is_project_cache: dict[str, bool] = {}
753+
750754
for node in ast.walk(tree):
751755
if isinstance(node, ast.ImportFrom) and node.module:
752756
for alias in node.names:
@@ -763,21 +767,31 @@ def get_external_base_class_inits(code_context: CodeStringsMarkdown, project_roo
763767

764768
if base_name and base_name in imported_names:
765769
module_name = imported_names[base_name]
766-
if not _is_project_module(module_name, project_root_path):
767-
external_bases.append((base_name, module_name))
768-
769-
if not external_bases:
770+
# Check cache first to avoid repeated expensive checks.
771+
cached = is_project_cache.get(module_name)
772+
if cached is None:
773+
is_project = _is_project_module(module_name, project_root_path)
774+
is_project_cache[module_name] = is_project
775+
else:
776+
is_project = cached
777+
778+
if not is_project:
779+
external_bases_set.add((base_name, module_name))
780+
781+
if not external_bases_set:
770782
return CodeStringsMarkdown(code_strings=[])
771783

772784
code_strings: list[CodeString] = []
773-
extracted: set[tuple[str, str]] = set()
774-
775-
for base_name, module_name in external_bases:
776-
if (module_name, base_name) in extracted:
777-
continue
785+
# Cache imported modules to avoid repeated importlib.import_module calls.
786+
imported_module_cache: dict[str, object] = {}
778787

788+
for base_name, module_name in external_bases_set:
779789
try:
780-
module = importlib.import_module(module_name)
790+
module = imported_module_cache.get(module_name)
791+
if module is None:
792+
module = importlib.import_module(module_name)
793+
imported_module_cache[module_name] = module
794+
781795
base_class = getattr(module, base_name, None)
782796
if base_class is None:
783797
continue
@@ -799,7 +813,6 @@ def get_external_base_class_inits(code_context: CodeStringsMarkdown, project_roo
799813

800814
class_source = f"class {base_name}:\n" + textwrap.indent(init_source, " ")
801815
code_strings.append(CodeString(code=class_source, file_path=class_file))
802-
extracted.add((module_name, base_name))
803816

804817
except (ImportError, ModuleNotFoundError, AttributeError):
805818
logger.debug(f"Failed to extract __init__ for {module_name}.{base_name}")
@@ -854,12 +867,13 @@ def extract_imports_for_class(module_tree: ast.Module, class_node: ast.ClassDef,
854867
needed_names.add(decorator.func.value.id)
855868

856869
# Get type annotation names from class body (for dataclass fields)
857-
for item in ast.walk(class_node):
870+
for item in class_node.body:
858871
if isinstance(item, ast.AnnAssign) and item.annotation:
859872
collect_names_from_annotation(item.annotation, needed_names)
860873
# Also check for field() calls which are common in dataclasses
861-
if isinstance(item, ast.Call) and isinstance(item.func, ast.Name):
862-
needed_names.add(item.func.id)
874+
elif isinstance(item, ast.Assign) and isinstance(item.value, ast.Call):
875+
if isinstance(item.value.func, ast.Name):
876+
needed_names.add(item.value.func.id)
863877

864878
# Find imports that provide these names
865879
import_lines: list[str] = []

0 commit comments

Comments
 (0)