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
1 change: 0 additions & 1 deletion .github/scripts/create-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@ if git rev-parse "$VERSION" > /dev/null 2>&1; then
fi

# Get current commit
CURRENT_SHA=$(git rev-parse HEAD)
CURRENT_SHA_SHORT=$(git rev-parse --short HEAD)

echo ""
Expand Down
140 changes: 26 additions & 114 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
name: Lint

on:
pull_request:
types: [opened, synchronize, reopened]
workflow_call:
outputs:
passed:
description: 'Whether all lint checks passed'
value: ${{ jobs.aggregate.outputs.passed }}
workflow_dispatch: # Allow manual testing

concurrency:
group: lint-${{ github.head_ref || github.ref }}
cancel-in-progress: true

jobs:
python:
name: Python
Expand All @@ -26,52 +32,20 @@ jobs:
- name: Install dependencies
run: uv sync --extra dev

- name: Ruff check
id: ruff-check
continue-on-error: true
run: .venv/bin/ruff check .

- name: Ruff format
id: ruff-format
continue-on-error: true
run: .venv/bin/ruff format --check .

- name: Mypy
id: mypy
continue-on-error: true
run: .venv/bin/mypy gateway shared sandbox --exclude 'gateway/tests/'

- name: Check results
if: always()
run: |
failed=""
[ "${{ steps.ruff-check.outcome }}" = "failure" ] && failed="$failed ruff-check"
[ "${{ steps.ruff-format.outcome }}" = "failure" ] && failed="$failed ruff-format"
[ "${{ steps.mypy.outcome }}" = "failure" ] && failed="$failed mypy"
if [ -n "$failed" ]; then
echo "::error::Failed checks:$failed"
exit 1
fi
- name: Lint Python
run: make lint-python

shell:
name: Shell
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Shellcheck
run: |
sudo apt-get update && sudo apt-get install -y shellcheck
SHELL_FILES=$(find . -name "*.sh" -not -path "./.venv/*" -not -path "./.git/*")
# Also include extensionless scripts in sandbox/scripts/
if [ -d "sandbox/scripts" ]; then
for f in sandbox/scripts/*; do
[ -f "$f" ] && SHELL_FILES="$SHELL_FILES"$'\n'"$f"
done
fi
if [ -n "$SHELL_FILES" ]; then
echo "$SHELL_FILES" | sort -u | xargs shellcheck --severity=warning
fi
- name: Install shellcheck
run: sudo apt-get update && sudo apt-get install -y shellcheck

- name: Lint Shell
run: make lint-shell

yaml:
name: YAML
Expand All @@ -90,16 +64,16 @@ jobs:
- name: Install dependencies
run: uv sync --extra dev

- name: Yamllint
run: .venv/bin/yamllint -c .yamllint.yaml .
- name: Lint YAML
run: make lint-yaml

docker:
name: Docker
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Hadolint
- name: Install hadolint
run: |
ARCH=$(uname -m)
case "$ARCH" in
Expand All @@ -110,16 +84,17 @@ jobs:
wget -qO /usr/local/bin/hadolint \
"https://github.com/hadolint/hadolint/releases/download/v2.12.0/hadolint-Linux-${HADOLINT_ARCH}"
chmod +x /usr/local/bin/hadolint
hadolint --config .hadolint.yaml gateway/Dockerfile
hadolint --config .hadolint.yaml sandbox/Dockerfile

- name: Lint Docker
run: make lint-docker

actions:
name: Actions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Actionlint
- name: Install actionlint
run: |
ACTIONLINT_VERSION=1.7.10
ARCH=$(uname -m)
Expand All @@ -131,7 +106,10 @@ jobs:
wget -qO actionlint.tar.gz \
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_${ACTIONLINT_ARCH}.tar.gz"
tar xzf actionlint.tar.gz actionlint
./actionlint
mv actionlint /usr/local/bin/

- name: Lint Actions
run: make lint-actions

custom-checks:
name: Custom Checks
Expand All @@ -150,74 +128,8 @@ jobs:
- name: Install dependencies
run: uv sync --extra dev

- name: Check container paths
id: container-paths
continue-on-error: true
run: .venv/bin/python scripts/check-container-paths.py

- name: Check container-host boundary
id: container-host-boundary
continue-on-error: true
run: .venv/bin/python scripts/check-container-host-boundary.py

- name: Check gh CLI usage
id: gh-cli-usage
continue-on-error: true
run: .venv/bin/python scripts/check-gh-cli-usage.py

- name: Check Claude imports
id: claude-imports
continue-on-error: true
run: .venv/bin/python scripts/check-claude-imports.py

- name: Check docker and claude invocations
id: docker-claude-invocations
continue-on-error: true
run: .venv/bin/python scripts/check-docker-and-claude-invocations.py

- name: Check workflow secret safety
id: workflow-secrets
continue-on-error: true
run: .venv/bin/python scripts/check-workflow-secrets.py

- name: Check hardcoded ports
id: hardcoded-ports
continue-on-error: true
run: .venv/bin/python scripts/check-hardcoded-ports.py

- name: Check reviewer job names
id: reviewer-job-names
continue-on-error: true
run: .venv/bin/python scripts/check-reviewer-job-names.py

- name: Check LLM API boundary
id: llm-api-boundary
continue-on-error: true
run: .venv/bin/python scripts/check-llm-api-calls.py

- name: Check model alias usage
id: model-alias
continue-on-error: true
run: .venv/bin/python scripts/check-model-versions.py

- name: Check results
if: always()
run: |
failed=""
[ "${{ steps.container-paths.outcome }}" = "failure" ] && failed="$failed container-paths"
[ "${{ steps.container-host-boundary.outcome }}" = "failure" ] && failed="$failed container-host-boundary"
[ "${{ steps.gh-cli-usage.outcome }}" = "failure" ] && failed="$failed gh-cli-usage"
[ "${{ steps.claude-imports.outcome }}" = "failure" ] && failed="$failed claude-imports"
[ "${{ steps.docker-claude-invocations.outcome }}" = "failure" ] && failed="$failed docker-claude-invocations"
[ "${{ steps.workflow-secrets.outcome }}" = "failure" ] && failed="$failed workflow-secrets"
[ "${{ steps.hardcoded-ports.outcome }}" = "failure" ] && failed="$failed hardcoded-ports"
[ "${{ steps.reviewer-job-names.outcome }}" = "failure" ] && failed="$failed reviewer-job-names"
[ "${{ steps.llm-api-boundary.outcome }}" = "failure" ] && failed="$failed llm-api-boundary"
[ "${{ steps.model-alias.outcome }}" = "failure" ] && failed="$failed model-alias"
if [ -n "$failed" ]; then
echo "::error::Failed checks:$failed"
exit 1
fi
- name: Custom Checks
run: make lint-custom

aggregate:
name: Aggregate Lint Results
Expand Down
50 changes: 26 additions & 24 deletions .github/workflows/on-review-feedback.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,30 +115,32 @@ jobs:
id: resolve
run: |
# Use inputs if called as reusable workflow, otherwise use repository variables
if [[ "${{ github.event_name }}" == "workflow_call" ]]; then
echo "bot_username=${{ inputs.bot_username }}" >> "$GITHUB_OUTPUT"
echo "branch_prefix=${{ inputs.branch_prefix }}" >> "$GITHUB_OUTPUT"
echo "reviewer_username=${{ inputs.reviewer_username || '' }}" >> "$GITHUB_OUTPUT"
echo "authorized_users=${{ inputs.authorized_users || 'jwbron' }}" >> "$GITHUB_OUTPUT"
else
echo "bot_username=${{ vars.EGG_BOT_USERNAME }}" >> "$GITHUB_OUTPUT"
echo "branch_prefix=${{ vars.EGG_BRANCH_PREFIX }}" >> "$GITHUB_OUTPUT"
echo "reviewer_username=${{ vars.EGG_REVIEWER_USERNAME || '' }}" >> "$GITHUB_OUTPUT"
echo "authorized_users=${{ vars.EGG_AUTHORIZED_USERS || 'jwbron' }}" >> "$GITHUB_OUTPUT"
fi
echo "max_feedback_rounds=${{ inputs.max_feedback_rounds || '5' }}" >> "$GITHUB_OUTPUT"

# Determine PR number based on event type
if [[ "${{ github.event_name }}" == "workflow_call" ]]; then
echo "pr_number=${{ inputs.pr_number }}" >> "$GITHUB_OUTPUT"
echo "should_check_trigger=false" >> "$GITHUB_OUTPUT"
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "pr_number=${{ github.event.inputs.pr_number }}" >> "$GITHUB_OUTPUT"
echo "should_check_trigger=false" >> "$GITHUB_OUTPUT"
else
echo "pr_number=${{ github.event.pull_request.number || github.event.issue.number }}" >> "$GITHUB_OUTPUT"
echo "should_check_trigger=true" >> "$GITHUB_OUTPUT"
fi
{
if [[ "${{ github.event_name }}" == "workflow_call" ]]; then
echo "bot_username=${{ inputs.bot_username }}"
echo "branch_prefix=${{ inputs.branch_prefix }}"
echo "reviewer_username=${{ inputs.reviewer_username || '' }}"
echo "authorized_users=${{ inputs.authorized_users || 'jwbron' }}"
else
echo "bot_username=${{ vars.EGG_BOT_USERNAME }}"
echo "branch_prefix=${{ vars.EGG_BRANCH_PREFIX }}"
echo "reviewer_username=${{ vars.EGG_REVIEWER_USERNAME || '' }}"
echo "authorized_users=${{ vars.EGG_AUTHORIZED_USERS || 'jwbron' }}"
fi
echo "max_feedback_rounds=${{ inputs.max_feedback_rounds || '5' }}"

# Determine PR number based on event type
if [[ "${{ github.event_name }}" == "workflow_call" ]]; then
echo "pr_number=${{ inputs.pr_number }}"
echo "should_check_trigger=false"
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
echo "pr_number=${{ github.event.inputs.pr_number }}"
echo "should_check_trigger=false"
else
echo "pr_number=${{ github.event.pull_request.number || github.event.issue.number }}"
echo "should_check_trigger=true"
fi
} >> "$GITHUB_OUTPUT"

# For event-triggered runs, check if the trigger matches our bot
check-trigger:
Expand Down
15 changes: 9 additions & 6 deletions .github/workflows/release-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,20 @@ jobs:
MINOR="${BASH_REMATCH[2]}"
PRERELEASE="${BASH_REMATCH[4]}"

echo "major_tag=v${MAJOR}" >> "$GITHUB_OUTPUT"
echo "minor_tag=v${MAJOR}.${MINOR}" >> "$GITHUB_OUTPUT"

if [[ -n "$PRERELEASE" ]]; then
IS_PRERELEASE="true"
fi
fi

echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "is_release=$IS_RELEASE" >> "$GITHUB_OUTPUT"
echo "is_prerelease=$IS_PRERELEASE" >> "$GITHUB_OUTPUT"
{
if [[ "${IS_RELEASE}" == "true" && -n "${MAJOR:-}" ]]; then
echo "major_tag=v${MAJOR}"
echo "minor_tag=v${MAJOR}.${MINOR}"
fi
echo "tag=$TAG"
echo "is_release=$IS_RELEASE"
echo "is_prerelease=$IS_PRERELEASE"
} >> "$GITHUB_OUTPUT"

- name: Generate image tags
id: tags
Expand Down
15 changes: 9 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
name: Test

on:
pull_request:
types: [opened, synchronize, reopened]
workflow_call:
outputs:
passed:
description: 'Whether all tests passed'
value: ${{ jobs.aggregate.outputs.passed }}
workflow_dispatch: # Allow manual testing

concurrency:
group: test-${{ github.head_ref || github.ref }}
cancel-in-progress: true

jobs:
unit:
name: Unit Tests
Expand All @@ -28,10 +34,7 @@ jobs:

- name: Run unit tests
run: |
PYTHONPATH=shared:gateway:orchestrator .venv/bin/pytest tests/ gateway/tests/ orchestrator/tests/ -v \
--cov=gateway --cov=shared --cov=sandbox \
--cov-report=term-missing \
--cov-fail-under=80
make test PYTEST_ARGS="--cov=gateway --cov=shared --cov=sandbox --cov-report=term-missing --cov-fail-under=80"

security:
name: Security Scan
Expand All @@ -50,8 +53,8 @@ jobs:
- name: Install dependencies
run: uv sync --extra dev

- name: Run bandit
run: .venv/bin/bandit -r gateway shared sandbox orchestrator -ll -c pyproject.toml
- name: Run security scan
run: make security

aggregate:
name: Aggregate Test Results
Expand Down
Loading
Loading