mirror of
https://github.com/codeflash-ai/codeflash.git
synced 2026-05-04 18:25:17 +00:00
AUTHOR_ASSOCIATION can be COLLABORATOR (repo-level access) or OWNER, not just MEMBER (org-level). Accept all three.
537 lines
20 KiB
YAML
537 lines
20 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- 'codeflash/**'
|
|
- 'codeflash-benchmark/**'
|
|
- 'codeflash-java-runtime/**'
|
|
- 'tests/**'
|
|
- 'packages/**'
|
|
- 'pyproject.toml'
|
|
- 'uv.lock'
|
|
- 'mypy_allowlist.txt'
|
|
- '.github/workflows/ci.yaml'
|
|
- '.github/actions/**'
|
|
pull_request:
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref_name }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# ---------------------------------------------------------------------------
|
|
# Linked issue check — every PR must reference an issue or discussion.
|
|
# Skipped on push to main and workflow_dispatch.
|
|
# ---------------------------------------------------------------------------
|
|
check-linked-issue:
|
|
if: github.event_name == 'pull_request'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
pull-requests: read
|
|
steps:
|
|
- name: Check PR body for linked issue or discussion
|
|
env:
|
|
PR_BODY: ${{ github.event.pull_request.body }}
|
|
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
|
|
AUTHOR_ASSOCIATION: ${{ github.event.pull_request.author_association }}
|
|
run: |
|
|
# Skip for bots (dependabot, renovate, github-actions)
|
|
if [[ "$PR_AUTHOR" == *"[bot]"* || "$PR_AUTHOR" == "dependabot" ]]; then
|
|
echo "Bot PR — skipping linked issue check."
|
|
exit 0
|
|
fi
|
|
|
|
# Skip for org members and collaborators
|
|
if [[ "$AUTHOR_ASSOCIATION" == "MEMBER" || "$AUTHOR_ASSOCIATION" == "COLLABORATOR" || "$AUTHOR_ASSOCIATION" == "OWNER" ]]; then
|
|
echo "Org member ($PR_AUTHOR, $AUTHOR_ASSOCIATION) — skipping linked issue check."
|
|
exit 0
|
|
fi
|
|
|
|
if [ -z "$PR_BODY" ]; then
|
|
echo "::error::PR body is empty. Every PR must link an issue or discussion."
|
|
echo "Use 'Closes #<number>', 'Fixes #<number>', 'Relates to #<number>', or include a discussion URL."
|
|
exit 1
|
|
fi
|
|
|
|
# Match: #123, GH-123, org/repo#123, Closes/Fixes/Relates/Resolves #123,
|
|
# or a github.com URL to an issue or discussion
|
|
if echo "$PR_BODY" | grep -qiP '(close[sd]?|fix(e[sd])?|relate[sd]?\s+to|resolve[sd]?)\s+#\d+'; then
|
|
echo "Found linked issue keyword."
|
|
exit 0
|
|
fi
|
|
if echo "$PR_BODY" | grep -qP '#\d+'; then
|
|
echo "Found issue reference."
|
|
exit 0
|
|
fi
|
|
if echo "$PR_BODY" | grep -qiP 'github\.com/[^\s]+/(issues|discussions)/\d+'; then
|
|
echo "Found GitHub issue/discussion URL."
|
|
exit 0
|
|
fi
|
|
if echo "$PR_BODY" | grep -qiP 'CF-#?\d+'; then
|
|
echo "Found Linear ticket reference."
|
|
exit 0
|
|
fi
|
|
|
|
echo "::error::No linked issue or discussion found in PR body."
|
|
echo "Every PR must reference an issue or discussion. See CONTRIBUTING.md for details."
|
|
echo "Use 'Closes #<number>', 'Fixes #<number>', 'Relates to #<number>', or include a discussion URL."
|
|
exit 1
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Change detection — decides which downstream jobs actually run.
|
|
# On push/workflow_dispatch every flag is true so all jobs execute.
|
|
# On pull_request we diff against the merge base.
|
|
# ---------------------------------------------------------------------------
|
|
determine-changes:
|
|
uses: codeflash-ai/github-workflows/.github/workflows/determine-changes.yml@main
|
|
with:
|
|
path-filters: |
|
|
{
|
|
"unit_tests": ["codeflash/", "codeflash-benchmark/", "tests/", "packages/", "pyproject.toml", "uv.lock"],
|
|
"type_check": ["codeflash/", "pyproject.toml", "uv.lock", "mypy_allowlist.txt"],
|
|
"e2e": ["codeflash/*.py", "codeflash/api/", "codeflash/benchmarking/", "codeflash/cli_cmds/", "codeflash/code_utils/", "codeflash/discovery/", "codeflash/github/", "codeflash/languages/python/", "codeflash/languages/*.py", "codeflash/lsp/", "codeflash/models/", "codeflash/optimization/", "codeflash/picklepatch/", "codeflash/result/", "codeflash/setup/", "codeflash/telemetry/", "codeflash/tracing/", "codeflash/verification/", "tests/", "pyproject.toml", "uv.lock"],
|
|
"e2e_js": ["codeflash/languages/javascript/", "codeflash/languages/base.py", "codeflash/languages/registry.py", "codeflash/optimization/", "codeflash/verification/", "packages/", "code_to_optimize/js/", "tests/scripts/end_to_end_test_js*"],
|
|
"e2e_java": ["codeflash/languages/java/", "codeflash/languages/base.py", "codeflash/languages/registry.py", "codeflash/optimization/", "codeflash/verification/", "codeflash-java-runtime/", "code_to_optimize/java/", "tests/scripts/end_to_end_test_java*", "tests/test_languages/fixtures/java_tracer_e2e/"]
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Unit tests — 6 Linux + 1 Windows matrix
|
|
# ---------------------------------------------------------------------------
|
|
unit-tests:
|
|
needs: determine-changes
|
|
if: fromJSON(needs.determine-changes.outputs.flags).unit_tests == 'true'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- os: ubuntu-latest
|
|
python-version: "3.9"
|
|
- os: ubuntu-latest
|
|
python-version: "3.10"
|
|
- os: ubuntu-latest
|
|
python-version: "3.11"
|
|
- os: ubuntu-latest
|
|
python-version: "3.12"
|
|
- os: ubuntu-latest
|
|
python-version: "3.13"
|
|
- os: ubuntu-latest
|
|
python-version: "3.14"
|
|
- os: windows-latest
|
|
python-version: "3.13"
|
|
runs-on: ${{ matrix.os }}
|
|
env:
|
|
PYTHONIOENCODING: utf-8
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 1
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Install uv
|
|
uses: astral-sh/setup-uv@v8.1.0
|
|
with:
|
|
python-version: ${{ matrix.python-version }}
|
|
enable-cache: true
|
|
|
|
- name: Install dependencies
|
|
shell: bash
|
|
run: |
|
|
if [[ "${{ matrix.python-version }}" == "3.9" || "${{ matrix.python-version }}" == "3.13" ]]; then
|
|
uv sync --group tests
|
|
else
|
|
uv sync
|
|
fi
|
|
|
|
- name: Unit tests
|
|
run: uv run pytest tests/
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Coverage — single run on ubuntu/py3.13 to enforce the coverage floor.
|
|
# ---------------------------------------------------------------------------
|
|
coverage:
|
|
needs: determine-changes
|
|
if: fromJSON(needs.determine-changes.outputs.flags).unit_tests == 'true'
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
PYTHONIOENCODING: utf-8
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 1
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Install uv
|
|
uses: astral-sh/setup-uv@v8.1.0
|
|
with:
|
|
python-version: "3.13"
|
|
enable-cache: true
|
|
|
|
- name: Install dependencies
|
|
run: uv sync
|
|
|
|
- name: Run tests with coverage
|
|
run: uv run pytest tests/ --ignore=tests/test_tracer.py --cov=codeflash --cov-report=xml:coverage.xml --cov-report=term-missing --cov-config=.coveragerc
|
|
|
|
- name: Check coverage floor
|
|
run: uv run coverage report --fail-under=58
|
|
|
|
- name: Upload coverage report
|
|
if: always()
|
|
uses: actions/upload-artifact@v7
|
|
with:
|
|
name: coverage-report
|
|
path: coverage.xml
|
|
retention-days: 30
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Mypy type checking
|
|
# ---------------------------------------------------------------------------
|
|
type-check:
|
|
needs: determine-changes
|
|
if: fromJSON(needs.determine-changes.outputs.flags).type_check == 'true'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 1
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Install uv
|
|
uses: astral-sh/setup-uv@v8.1.0
|
|
with:
|
|
enable-cache: true
|
|
|
|
- name: Install dependencies
|
|
run: uv sync
|
|
|
|
- name: Run mypy
|
|
run: uv run mypy --non-interactive --config-file pyproject.toml @mypy_allowlist.txt
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lint (prek) — pull_request only
|
|
# ---------------------------------------------------------------------------
|
|
prek:
|
|
needs: determine-changes
|
|
if: >-
|
|
github.event_name == 'pull_request'
|
|
&& (fromJSON(needs.determine-changes.outputs.flags).e2e == 'true'
|
|
|| fromJSON(needs.determine-changes.outputs.flags).e2e_js == 'true')
|
|
uses: codeflash-ai/github-workflows/.github/workflows/prek-lint.yml@main
|
|
permissions:
|
|
contents: write
|
|
with:
|
|
auto-fix: true
|
|
checkout-ref: ${{ github.head_ref }}
|
|
restore-paths: "codeflash/version.py codeflash-benchmark/codeflash_benchmark/version.py"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# E2E tests — only on pull_request and workflow_dispatch (not push to main)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# --- Standard Python E2Es (9 tests) ---
|
|
e2e-python:
|
|
needs: determine-changes
|
|
if: >-
|
|
fromJSON(needs.determine-changes.outputs.flags).e2e == 'true'
|
|
&& github.event_name != 'push'
|
|
&& github.actor != 'dependabot[bot]'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- name: tracer-replay
|
|
script: end_to_end_test_tracer_replay.py
|
|
expected_improvement: 10
|
|
- name: bubble-sort-pytest-nogit
|
|
script: end_to_end_test_bubblesort_pytest.py
|
|
expected_improvement: 70
|
|
remove_git: true
|
|
- name: bubble-sort-unittest
|
|
script: end_to_end_test_bubblesort_unittest.py
|
|
expected_improvement: 40
|
|
- name: futurehouse-structure
|
|
script: end_to_end_test_futurehouse.py
|
|
expected_improvement: 5
|
|
- name: topological-sort
|
|
script: end_to_end_test_topological_sort_worktree.py
|
|
expected_improvement: 5
|
|
- name: async-optimization
|
|
script: end_to_end_test_async.py
|
|
expected_improvement: 10
|
|
- name: benchmark-bubble-sort
|
|
script: end_to_end_test_benchmark_sort.py
|
|
expected_improvement: 5
|
|
- name: coverage-e2e
|
|
script: end_to_end_test_coverage.py
|
|
extra_deps: black
|
|
- name: init-optimization
|
|
script: end_to_end_test_init_optimization.py
|
|
expected_improvement: 10
|
|
environment: ${{ ((github.event_name == 'workflow_dispatch' && github.actor != 'misrasaurabh1' && github.actor != 'KRRT7') || (contains(toJSON(github.event.pull_request.files.*.filename), '.github/workflows/') && github.event.pull_request.user.login != 'misrasaurabh1' && github.event.pull_request.user.login != 'KRRT7')) && 'external-trusted-contributors' || '' }}
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
CODEFLASH_AIS_SERVER: prod
|
|
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
|
|
CODEFLASH_API_KEY: ${{ secrets.CODEFLASH_API_KEY }}
|
|
MAX_RETRIES: 3
|
|
RETRY_DELAY: 5
|
|
CODEFLASH_END_TO_END: 1
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ github.event.pull_request.head.ref || '' }}
|
|
repository: ${{ github.event.pull_request.head.repo.full_name || '' }}
|
|
fetch-depth: 0
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Validate PR
|
|
if: github.event_name == 'pull_request'
|
|
uses: ./.github/actions/validate-pr
|
|
with:
|
|
base_sha: ${{ github.event.pull_request.base.sha }}
|
|
head_sha: ${{ github.event.pull_request.head.sha }}
|
|
author: ${{ github.event.pull_request.user.login }}
|
|
pr_state: ${{ github.event.pull_request.state }}
|
|
|
|
- name: Install uv
|
|
uses: astral-sh/setup-uv@v8.1.0
|
|
with:
|
|
python-version: 3.11.6
|
|
enable-cache: true
|
|
|
|
- name: Install dependencies
|
|
run: uv sync
|
|
|
|
- name: Install extra dependencies
|
|
if: matrix.extra_deps
|
|
run: uv add ${{ matrix.extra_deps }}
|
|
|
|
- name: Set test configuration
|
|
if: matrix.expected_improvement
|
|
run: |
|
|
echo "COLUMNS=110" >> "$GITHUB_ENV"
|
|
echo "EXPECTED_IMPROVEMENT_PCT=${{ matrix.expected_improvement }}" >> "$GITHUB_ENV"
|
|
|
|
- name: Remove .git
|
|
if: matrix.remove_git
|
|
run: |
|
|
if [ -d ".git" ]; then
|
|
echo ".git directory exists!"
|
|
sudo rm -rf .git
|
|
if [ -d ".git" ]; then
|
|
echo ".git directory still exists after removal attempt!"
|
|
exit 1
|
|
else
|
|
echo ".git directory successfully removed."
|
|
fi
|
|
else
|
|
echo ".git directory does not exist. Nothing to remove."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Run E2E test
|
|
run: uv run python tests/scripts/${{ matrix.script }}
|
|
|
|
# --- JS E2Es (3 tests, need Node.js + packages/) ---
|
|
e2e-js:
|
|
needs: determine-changes
|
|
if: >-
|
|
fromJSON(needs.determine-changes.outputs.flags).e2e_js == 'true'
|
|
&& github.event_name != 'push'
|
|
&& github.actor != 'dependabot[bot]'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- name: js-cjs-function
|
|
script: end_to_end_test_js_cjs_function.py
|
|
js_project_dir: code_to_optimize/js/code_to_optimize_js
|
|
expected_improvement: 50
|
|
- name: js-esm-async
|
|
script: end_to_end_test_js_esm_async.py
|
|
js_project_dir: code_to_optimize/js/code_to_optimize_js_esm
|
|
expected_improvement: 10
|
|
allow_failure: true
|
|
- name: js-ts-class
|
|
script: end_to_end_test_js_ts_class.py
|
|
js_project_dir: code_to_optimize/js/code_to_optimize_ts
|
|
expected_improvement: 30
|
|
continue-on-error: ${{ matrix.allow_failure || false }}
|
|
environment: ${{ ((github.event_name == 'workflow_dispatch' && github.actor != 'misrasaurabh1' && github.actor != 'KRRT7') || (contains(toJSON(github.event.pull_request.files.*.filename), '.github/workflows/') && github.event.pull_request.user.login != 'misrasaurabh1' && github.event.pull_request.user.login != 'KRRT7')) && 'external-trusted-contributors' || '' }}
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
CODEFLASH_AIS_SERVER: prod
|
|
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
|
|
CODEFLASH_API_KEY: ${{ secrets.CODEFLASH_API_KEY }}
|
|
COLUMNS: 110
|
|
MAX_RETRIES: 3
|
|
RETRY_DELAY: 5
|
|
EXPECTED_IMPROVEMENT_PCT: ${{ matrix.expected_improvement }}
|
|
CODEFLASH_END_TO_END: 1
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ github.event.pull_request.head.ref || '' }}
|
|
repository: ${{ github.event.pull_request.head.repo.full_name || '' }}
|
|
fetch-depth: 0
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Validate PR
|
|
if: github.event_name == 'pull_request'
|
|
uses: ./.github/actions/validate-pr
|
|
with:
|
|
base_sha: ${{ github.event.pull_request.base.sha }}
|
|
head_sha: ${{ github.event.pull_request.head.sha }}
|
|
author: ${{ github.event.pull_request.user.login }}
|
|
pr_state: ${{ github.event.pull_request.state }}
|
|
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@v6
|
|
with:
|
|
node-version: '20'
|
|
cache: 'npm'
|
|
cache-dependency-path: |
|
|
packages/codeflash/package-lock.json
|
|
code_to_optimize/js/*/package-lock.json
|
|
|
|
- name: Install codeflash npm package dependencies
|
|
run: |
|
|
cd packages/codeflash
|
|
npm install
|
|
|
|
- name: Install JS test project dependencies
|
|
run: |
|
|
cd ${{ matrix.js_project_dir }}
|
|
npm install
|
|
|
|
- name: Install uv
|
|
uses: astral-sh/setup-uv@v8.1.0
|
|
with:
|
|
python-version: 3.11.6
|
|
enable-cache: true
|
|
|
|
- name: Install dependencies
|
|
run: uv sync
|
|
|
|
- name: Run E2E test
|
|
run: uv run python tests/scripts/${{ matrix.script }}
|
|
|
|
# --- Java E2Es (3 tests, need JDK + Maven) ---
|
|
e2e-java:
|
|
needs: determine-changes
|
|
if: >-
|
|
fromJSON(needs.determine-changes.outputs.flags).e2e_java == 'true'
|
|
&& github.event_name != 'push'
|
|
&& github.actor != 'dependabot[bot]'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- name: java-fibonacci-nogit
|
|
script: end_to_end_test_java_fibonacci.py
|
|
expected_improvement: 70
|
|
remove_git: true
|
|
- name: java-tracer
|
|
script: end_to_end_test_java_tracer.py
|
|
expected_improvement: 10
|
|
- name: java-void-optimization-nogit
|
|
script: end_to_end_test_java_void_optimization.py
|
|
expected_improvement: 70
|
|
remove_git: true
|
|
environment: ${{ ((github.event_name == 'workflow_dispatch' && github.actor != 'misrasaurabh1' && github.actor != 'KRRT7') || (contains(toJSON(github.event.pull_request.files.*.filename), '.github/workflows/') && github.event.pull_request.user.login != 'misrasaurabh1' && github.event.pull_request.user.login != 'KRRT7')) && 'external-trusted-contributors' || '' }}
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
CODEFLASH_AIS_SERVER: prod
|
|
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
|
|
CODEFLASH_API_KEY: ${{ secrets.CODEFLASH_API_KEY }}
|
|
COLUMNS: 110
|
|
MAX_RETRIES: 3
|
|
RETRY_DELAY: 5
|
|
EXPECTED_IMPROVEMENT_PCT: ${{ matrix.expected_improvement }}
|
|
CODEFLASH_END_TO_END: 1
|
|
CODEFLASH_LOOPING_TIME: 5
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ github.event.pull_request.head.ref || '' }}
|
|
repository: ${{ github.event.pull_request.head.repo.full_name || '' }}
|
|
fetch-depth: 0
|
|
token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Validate PR
|
|
if: github.event_name == 'pull_request'
|
|
uses: ./.github/actions/validate-pr
|
|
with:
|
|
base_sha: ${{ github.event.pull_request.base.sha }}
|
|
head_sha: ${{ github.event.pull_request.head.sha }}
|
|
author: ${{ github.event.pull_request.user.login }}
|
|
pr_state: ${{ github.event.pull_request.state }}
|
|
|
|
- name: Set up JDK 11
|
|
uses: actions/setup-java@v5
|
|
with:
|
|
java-version: '11'
|
|
distribution: 'temurin'
|
|
cache: maven
|
|
|
|
- name: Install uv
|
|
uses: astral-sh/setup-uv@v8.1.0
|
|
with:
|
|
python-version: 3.11.6
|
|
enable-cache: true
|
|
|
|
- name: Install dependencies
|
|
run: uv sync
|
|
|
|
- name: Cache codeflash-runtime JAR
|
|
id: runtime-jar-cache
|
|
uses: actions/cache@v5
|
|
with:
|
|
path: ~/.m2/repository/io/codeflash
|
|
key: codeflash-runtime-${{ hashFiles('codeflash-java-runtime/pom.xml', 'codeflash-java-runtime/src/**') }}
|
|
|
|
- name: Build and install codeflash-runtime JAR
|
|
if: steps.runtime-jar-cache.outputs.cache-hit != 'true'
|
|
run: |
|
|
cd codeflash-java-runtime
|
|
mvn install -q -DskipTests
|
|
|
|
- name: Remove .git
|
|
if: matrix.remove_git
|
|
run: |
|
|
if [ -d ".git" ]; then
|
|
sudo rm -rf .git
|
|
echo ".git directory removed."
|
|
else
|
|
echo ".git directory does not exist."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Run E2E test
|
|
run: uv run python tests/scripts/${{ matrix.script }}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Gate job — the ONLY required check in the GitHub ruleset.
|
|
# Accepts "success" and "skipped" (job skipped by change detection).
|
|
# Rejects "failure" and "cancelled".
|
|
# ---------------------------------------------------------------------------
|
|
required-checks-passed:
|
|
name: required checks passed
|
|
if: always()
|
|
needs:
|
|
- check-linked-issue
|
|
- unit-tests
|
|
- coverage
|
|
- type-check
|
|
- prek
|
|
- e2e-python
|
|
- e2e-js
|
|
- e2e-java
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: codeflash-ai/github-workflows/.github/actions/required-checks-gate@main
|
|
with:
|
|
needs-json: ${{ toJSON(needs) }}
|