CI #6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [master, main] | |
| pull_request: | |
| branches: [master, main] | |
| schedule: | |
| - cron: '31 6 * * 1' | |
| workflow_dispatch: | |
| inputs: | |
| expected_sha: | |
| description: Exact 40-character commit selected by --ref (manual runs always force full CI) | |
| required: true | |
| type: string | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ci-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CARGO_TERM_COLOR: always | |
| CARGO_INCREMENTAL: 0 | |
| RUSTFLAGS: -Dwarnings | |
| jobs: | |
| changes: | |
| name: Change detection | |
| runs-on: ubuntu-latest | |
| outputs: | |
| heavy: ${{ steps.detect.outputs.heavy }} | |
| workflow: ${{ steps.detect.outputs.workflow }} | |
| mobile: ${{ steps.detect.outputs.mobile }} | |
| actions: ${{ steps.detect.outputs.actions }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Detect executable changes | |
| id: detect | |
| shell: bash | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| BASE_REF: ${{ github.base_ref }} | |
| BEFORE_SHA: ${{ github.event.before }} | |
| EXPECTED_SHA: ${{ inputs.expected_sha }} | |
| run: | | |
| set -euo pipefail | |
| if [[ "${EVENT_NAME}" == "workflow_dispatch" ]]; then | |
| if [[ "${#EXPECTED_SHA}" -ne 40 || "${EXPECTED_SHA}" =~ [^0-9a-fA-F] ]]; then | |
| echo "::error::expected_sha must be a full 40-character commit SHA." >&2 | |
| exit 1 | |
| fi | |
| actual="$(git rev-parse HEAD)" | |
| expected_normalized="$(printf '%s' "${EXPECTED_SHA}" | tr '[:upper:]' '[:lower:]')" | |
| if [[ "${actual}" != "${expected_normalized}" ]]; then | |
| echo "::error::Dispatch resolved to ${actual}, not requested ${EXPECTED_SHA}." >&2 | |
| exit 1 | |
| fi | |
| echo "Manual exact-head dispatch: forcing heavy, workflow, mobile, and action gates." | |
| echo "heavy=true" >> "${GITHUB_OUTPUT}" | |
| echo "workflow=true" >> "${GITHUB_OUTPUT}" | |
| echo "mobile=true" >> "${GITHUB_OUTPUT}" | |
| echo "actions=true" >> "${GITHUB_OUTPUT}" | |
| exit 0 | |
| fi | |
| if [[ "${EVENT_NAME}" == "schedule" ]]; then | |
| echo "heavy=true" >> "${GITHUB_OUTPUT}" | |
| echo "workflow=true" >> "${GITHUB_OUTPUT}" | |
| echo "mobile=true" >> "${GITHUB_OUTPUT}" | |
| echo "actions=true" >> "${GITHUB_OUTPUT}" | |
| exit 0 | |
| fi | |
| base="" | |
| if [[ "${EVENT_NAME}" == "pull_request" && -n "${BASE_REF}" ]]; then | |
| git fetch --no-tags origin "${BASE_REF}:refs/remotes/origin/${BASE_REF}" --depth=1 | |
| base="origin/${BASE_REF}" | |
| elif [[ -n "${BEFORE_SHA}" && "${BEFORE_SHA}" != "0000000000000000000000000000000000000000" ]]; then | |
| base="${BEFORE_SHA}" | |
| fi | |
| if [[ -z "${base}" ]]; then | |
| echo "heavy=true" >> "${GITHUB_OUTPUT}" | |
| echo "workflow=true" >> "${GITHUB_OUTPUT}" | |
| echo "mobile=true" >> "${GITHUB_OUTPUT}" | |
| echo "actions=true" >> "${GITHUB_OUTPUT}" | |
| exit 0 | |
| fi | |
| mapfile -t changed < <(git diff --name-only "${base}" "${GITHUB_SHA}" | sort) | |
| heavy=false | |
| workflow=false | |
| mobile=false | |
| actions=false | |
| for path in "${changed[@]}"; do | |
| # Heavy classification. ORDER MATTERS: must-stay-heavy inputs are | |
| # matched BEFORE any light entry so a script that only a | |
| # heavy-gated job exercises can never be misclassified as light. | |
| # Anything unrecognized falls through to the default-heavy `*)` | |
| # arm (fail-safe default-heavy). Light-classified scripts below | |
| # are exercised by ALWAYS-on jobs/steps that run regardless of | |
| # `heavy` (check-versions.sh / check-ohos-deps.sh via Version | |
| # drift, check-coauthor-trailers.py via Lint), so no coverage is | |
| # lost. | |
| case "${path}" in | |
| scripts/release/npm-wrapper-smoke.js|scripts/mobile-smoke.sh|scripts/check-provider-registry.py) | |
| heavy=true | |
| ;; | |
| docs/*|*.md|.github/PULL_REQUEST_TEMPLATE.md|.github/ISSUE_TEMPLATE/*|.github/scripts/agent-task-metadata.test.sh|.github/workflows/agent-task-labels.yml|.github/workflows/auto-tag.yml|.github/workflows/stale.yml|.github/workflows/triage.yml|scripts/release/check-versions.sh|scripts/release/check-ohos-deps.sh|scripts/release/install-dogfood.sh|scripts/release/install-dogfood.test.sh|scripts/release/prepare-release.sh|scripts/release/prepare-release.test.sh|scripts/check-coauthor-trailers.py) | |
| ;; | |
| *) | |
| heavy=true | |
| ;; | |
| esac | |
| case "${path}" in | |
| crates/workflow/*|workflows/rlm_cache_change.star|.github/workflows/ci.yml) | |
| workflow=true | |
| ;; | |
| esac | |
| # Mobile runtime surface: the `codewhale-tui serve --mobile` | |
| # HTTP/SSE stack that scripts/mobile-smoke.sh exercises. Pull | |
| # requests run the smoke only when one of these changes; every | |
| # push to main still runs it unconditionally as the pre-release | |
| # safety net for anything this filter misses. | |
| case "${path}" in | |
| crates/app-server/*|crates/tui/src/runtime_api*|crates/tui/src/runtime_mobile.html|crates/tui/src/runtime_threads*|crates/tui/src/main.rs|scripts/mobile-smoke.sh|.github/workflows/ci.yml|Cargo.lock|Cargo.toml) | |
| mobile=true | |
| ;; | |
| esac | |
| case "${path}" in | |
| .github/workflows/*|.github/actionlint.yml) | |
| actions=true | |
| ;; | |
| esac | |
| done | |
| echo "heavy=${heavy}" >> "${GITHUB_OUTPUT}" | |
| echo "workflow=${workflow}" >> "${GITHUB_OUTPUT}" | |
| echo "mobile=${mobile}" >> "${GITHUB_OUTPUT}" | |
| echo "actions=${actions}" >> "${GITHUB_OUTPUT}" | |
| echo "Heavy Rust CI required: ${heavy}" | |
| echo "Workflow RLM cache CI required: ${workflow}" | |
| echo "Mobile runtime smoke required (PRs): ${mobile}" | |
| echo "Workflow lint required: ${actions}" | |
| printf 'Changed files:\n' | |
| printf ' %s\n' "${changed[@]}" | |
| versions: | |
| name: Version drift | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 20 | |
| - name: Check version drift | |
| run: ./scripts/release/check-versions.sh | |
| - name: Check OHOS dependency graph | |
| run: ./scripts/release/check-ohos-deps.sh | |
| - name: Check release helper contracts | |
| run: | | |
| bash .github/scripts/agent-task-metadata.test.sh | |
| bash scripts/release/generate-release-body.test.sh | |
| bash scripts/release/install-dogfood.test.sh | |
| bash scripts/release/prepare-release.test.sh | |
| bash scripts/release/require-release-tag-checkout.test.sh | |
| bash scripts/release/verify-remote-tag.test.sh | |
| bash .github/scripts/update-homebrew-tap.test.sh | |
| node .github/scripts/release-workflows.test.js | |
| node --test scripts/release/assemble-release-assets.test.js | |
| node --test scripts/release/ensure-release-assets-absent.test.js | |
| - name: Run runtime web client tests | |
| # crates/tui/tests/runtime_web_client.test.mjs exercises the embedded | |
| # web client's event/snapshot state machine; it ran nowhere before. | |
| run: node --test crates/tui/tests/runtime_web_client.test.mjs | |
| integrations: | |
| name: Integrations | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| - name: Run chat-bridge suites | |
| # All four bridges + bridge-core ship dependency-free node --test | |
| # suites that no workflow ran. weixin has no lockfile by design | |
| # (zero deps); npm test works without npm ci everywhere here. | |
| run: | | |
| set -euo pipefail | |
| for bridge in bridge-core feishu-bridge telegram-bridge wecom-bridge weixin-bridge; do | |
| echo "== ${bridge}" | |
| (cd "integrations/${bridge}" && npm test) | |
| done | |
| lint: | |
| name: Lint | |
| needs: changes | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - uses: dtolnay/rust-toolchain@master | |
| if: needs.changes.outputs.heavy == 'true' | |
| with: | |
| toolchain: stable | |
| components: rustfmt, clippy | |
| - uses: mozilla-actions/sccache-action@v0.0.10 | |
| id: sccache | |
| # Cache bootstrap failures (e.g. GitHub 504s fetching the sccache | |
| # binary) degrade to an uncached build instead of failing product CI. | |
| continue-on-error: true | |
| if: needs.changes.outputs.heavy == 'true' | |
| - name: Enable sccache | |
| if: needs.changes.outputs.heavy == 'true' && steps.sccache.outcome == 'success' | |
| shell: bash | |
| run: | | |
| echo "SCCACHE_GHA_ENABLED=true" >> "${GITHUB_ENV}" | |
| echo "RUSTC_WRAPPER=sccache" >> "${GITHUB_ENV}" | |
| echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1" >> "${GITHUB_ENV}" | |
| - name: Install Linux system dependencies | |
| if: needs.changes.outputs.heavy == 'true' | |
| run: | | |
| for i in 1 2 3 4 5; do | |
| sudo apt-get update && break | |
| echo "apt-get update failed (attempt $i); retrying in 15s" | |
| sleep 15 | |
| done | |
| sudo apt-get install -y libdbus-1-dev pkg-config | |
| - uses: Swatinem/rust-cache@v2 | |
| if: needs.changes.outputs.heavy == 'true' | |
| with: | |
| cache-bin: false | |
| # PRs restore the cache seeded by main but skip the expensive | |
| # post-job save; sccache covers PR-specific compilation deltas. | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Check formatting | |
| if: needs.changes.outputs.heavy == 'true' | |
| run: cargo fmt --all -- --check | |
| - name: Run clippy | |
| if: needs.changes.outputs.heavy == 'true' | |
| run: | | |
| cargo clippy --workspace --all-features --locked -- \ | |
| -D warnings \ | |
| -A clippy::uninlined_format_args \ | |
| -A clippy::too_many_arguments \ | |
| -A clippy::unnecessary_map_or \ | |
| -A clippy::collapsible_if \ | |
| -A clippy::assertions_on_constants | |
| - name: sccache stats | |
| if: needs.changes.outputs.heavy == 'true' && steps.sccache.outcome == 'success' | |
| continue-on-error: true | |
| shell: bash | |
| run: sccache --show-stats | |
| - name: Check provider registry drift | |
| if: needs.changes.outputs.heavy == 'true' | |
| run: python3 scripts/check-provider-registry.py | |
| # Clippy above runs without `--all-targets`, so it cannot see dead code | |
| # that only tests keep alive. This ratchet covers that blind spot by | |
| # refusing to let the `#[allow(dead_code)]` total rise (#4785). | |
| - name: Check dead-code budget | |
| if: needs.changes.outputs.heavy == 'true' | |
| run: python3 scripts/check-dead-code-budget.py | |
| - name: Test runtime-contract measurement harness | |
| if: needs.changes.outputs.heavy == 'true' | |
| run: | | |
| python3 scripts/test_measure_runtime_contract.py | |
| python3 scripts/test_check_runtime_contract_budget.py | |
| # The offline runtime-contract measurement needs the full locked graph, | |
| # dev-dependencies included (e.g. wiremock -> assert-json-diff), but | |
| # clippy above builds no test targets and the rust-cache registry key | |
| # derives from Cargo.lock, so any lock-changing PR (every dependabot | |
| # bump) restores an empty cache and the hermetic `cargo test --offline` | |
| # dies with "failed to download ... --offline was specified" before a | |
| # single budget is measured. Fetch the locked graph once here so the | |
| # measurement below is deterministic on every branch. | |
| - name: Fetch locked dependency graph for offline measurement | |
| if: needs.changes.outputs.heavy == 'true' | |
| run: cargo fetch --locked | |
| # Provider-free local measurement. The checker forces Cargo offline and | |
| # the measurement script runs only locked, ignored Rust metric tests. | |
| - name: Check runtime-contract budget | |
| if: needs.changes.outputs.heavy == 'true' | |
| run: python3 scripts/check-runtime-contract-budget.py | |
| # Provider-free paused-consumer measurement of the production | |
| # persistence request channel. RSS is sampled only on macOS; every host | |
| # enforces the accepted/retained request and payload contract. | |
| - name: Test persistence-backlog measurement and checker harnesses | |
| if: needs.changes.outputs.heavy == 'true' | |
| run: | | |
| python3 scripts/test_measure_persistence_backlog.py | |
| python3 scripts/test_check_persistence_backlog_budget.py | |
| - name: Check persistence-backlog budget | |
| if: needs.changes.outputs.heavy == 'true' | |
| run: python3 scripts/check-persistence-backlog-budget.py | |
| # Source-only ownership ratchet. Deletion and line-neutral consolidation | |
| # pass; new packages/binaries/thousand-line module paths, a larger maximum | |
| # module, or aggregate owned Rust growth require reviewed updates. | |
| - name: Check source-structure budget | |
| if: needs.changes.outputs.heavy == 'true' | |
| run: python3 scripts/check-source-structure-budget.py | |
| - name: Check README translations stay in sync | |
| if: github.event_name != 'schedule' | |
| run: python3 scripts/check-readme-translations.py | |
| - name: Check README locale link symmetry | |
| if: github.event_name != 'schedule' | |
| run: bash scripts/check-readme-locales.sh | |
| - name: Check TUI locale pack parity | |
| if: github.event_name != 'schedule' | |
| run: python3 scripts/check-tui-locale-parity.py | |
| - name: Check website locale dictionary parity | |
| if: github.event_name != 'schedule' | |
| run: node web/scripts/check-locales.mjs | |
| - name: Check harvested contributor credit | |
| if: github.event_name != 'schedule' | |
| shell: bash | |
| run: | | |
| if [[ "${{ github.event_name }}" == "pull_request" ]]; then | |
| git fetch --no-tags origin "${{ github.base_ref }}" | |
| RANGE="origin/${{ github.base_ref }}..HEAD" | |
| elif [[ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]]; then | |
| RANGE="${{ github.event.before }}..${{ github.sha }}" | |
| else | |
| RANGE="HEAD~1..HEAD" | |
| fi | |
| python3 scripts/check-coauthor-trailers.py \ | |
| --author-map .github/AUTHOR_MAP \ | |
| --range "$RANGE" \ | |
| --check-authors | |
| - name: Skip Rust lint for light change | |
| if: needs.changes.outputs.heavy != 'true' | |
| run: echo "No executable Rust changes detected; preserving required Lint context." | |
| - name: Linux clippy location | |
| if: needs.changes.outputs.heavy == 'true' | |
| run: echo "Linux clippy/test gates run on CNB for mirrored fix/*, rebrand/*, work/v*, and main branches." | |
| workflow-rlm-cache: | |
| name: Workflow RLM cache | |
| needs: changes | |
| if: needs.changes.outputs.workflow == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: mozilla-actions/sccache-action@v0.0.10 | |
| id: sccache | |
| continue-on-error: true | |
| - name: Enable sccache | |
| if: steps.sccache.outcome == 'success' | |
| shell: bash | |
| run: | | |
| echo "SCCACHE_GHA_ENABLED=true" >> "${GITHUB_ENV}" | |
| echo "RUSTC_WRAPPER=sccache" >> "${GITHUB_ENV}" | |
| echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1" >> "${GITHUB_ENV}" | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| cache-bin: false | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Run RLM cache workflow mock/replay tests | |
| run: cargo test -p codewhale-workflow --locked rlm_cache_change | |
| test: | |
| name: Test | |
| needs: changes | |
| # Required contexts "Test (ubuntu-latest)" / "Test (macos-latest)" / | |
| # "Test (windows-latest)" derive from job name + matrix.os and are | |
| # independent of runs-on. For light changes the macOS/Windows legs only | |
| # echo a skip line, so run them on ubuntu instead of queueing for scarce | |
| # macOS/Windows runners. Heavy changes use the real matrix OS as before. | |
| # The ternary is safe: matrix.os is always a non-empty literal, so | |
| # runs-on can never evaluate to empty. | |
| runs-on: ${{ needs.changes.outputs.heavy == 'true' && matrix.os || 'ubuntu-latest' }} | |
| strategy: | |
| # A failure on one desktop platform must not erase evidence from the | |
| # other one. We need both conclusions to diagnose and release safely. | |
| fail-fast: false | |
| matrix: | |
| # Linux workspace tests moved to CNB; GitHub keeps the platform | |
| # coverage CNB cannot provide. | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| steps: | |
| - name: Skip tests for light change | |
| if: needs.changes.outputs.heavy != 'true' | |
| run: echo "No executable Rust changes detected; preserving required Test context." | |
| - uses: actions/checkout@v7 | |
| if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') | |
| - name: Test Windows installer PATH helper | |
| if: needs.changes.outputs.heavy == 'true' && matrix.os == 'windows-latest' | |
| shell: pwsh | |
| run: ./scripts/installer/update-user-path.tests.ps1 | |
| - name: Install NSIS for Windows installer regression | |
| if: needs.changes.outputs.heavy == 'true' && matrix.os == 'windows-latest' | |
| shell: pwsh | |
| run: choco install nsis -y --no-progress | |
| - name: Test Windows installer PATH regression | |
| if: needs.changes.outputs.heavy == 'true' && matrix.os == 'windows-latest' | |
| shell: pwsh | |
| run: ./scripts/installer/installer-path-regression.tests.ps1 -AllowUserPathMutation | |
| - uses: dtolnay/rust-toolchain@stable | |
| if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') | |
| - uses: mozilla-actions/sccache-action@v0.0.10 | |
| id: sccache | |
| continue-on-error: true | |
| if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') | |
| - name: Enable sccache | |
| if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') && steps.sccache.outcome == 'success' | |
| shell: bash | |
| run: | | |
| echo "SCCACHE_GHA_ENABLED=true" >> "${GITHUB_ENV}" | |
| echo "RUSTC_WRAPPER=sccache" >> "${GITHUB_ENV}" | |
| echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1" >> "${GITHUB_ENV}" | |
| - name: Install Linux system dependencies | |
| if: needs.changes.outputs.heavy == 'true' && matrix.os == 'ubuntu-latest' && github.event_name == 'workflow_dispatch' | |
| run: | | |
| for i in 1 2 3 4 5; do | |
| sudo apt-get update && break | |
| echo "apt-get update failed (attempt $i); retrying in 15s" | |
| sleep 15 | |
| done | |
| sudo apt-get install -y libdbus-1-dev pkg-config | |
| - uses: Swatinem/rust-cache@v2 | |
| if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') | |
| with: | |
| cache-bin: false | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Run tests | |
| if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') | |
| run: cargo test --workspace --all-features --locked | |
| env: | |
| # Give test threads the stack the product gives itself. main.rs runs | |
| # the owner thread and every tokio worker at | |
| # CODEWHALE_MAIN_STACK_BYTES (16 MiB) because the engine and | |
| # runtime-thread futures are genuinely deep. `#[tokio::test]` builds | |
| # its own runtime and never sees that, so tests ran the same code on | |
| # ~2 MiB (~1 MiB on Windows) — a configuration that never ships. | |
| # That gap is what aborted the whole Windows test binary with | |
| # STATUS_STACK_OVERFLOW in start_turn_accepts_dynamic_tools_and_ | |
| # environment_id, masking every other Windows result (78afd8d3d4 | |
| # Box::pin'd that one frame; the mismatch itself remained). std reads | |
| # this for any thread spawned without an explicit size, which covers | |
| # both libtest's per-test threads and tokio's workers. | |
| RUST_MIN_STACK: '16777216' | |
| # The Ubuntu lint lane validates non-RSS backlog fields. Run the same | |
| # source-bound measurement on macOS so loss or growth of RSS evidence | |
| # fails closed instead of becoming an unsupported-field skip. | |
| - name: Check persistence-backlog RSS budget | |
| if: needs.changes.outputs.heavy == 'true' && matrix.os == 'macos-latest' | |
| run: python3 scripts/check-persistence-backlog-budget.py | |
| - name: Run isolated Skills Manager PTY acceptance | |
| # This real-PTY scenario is deterministic in a fresh process (10/10 | |
| # locally) but can inherit event starvation after the full qa_pty | |
| # module suite on loaded Linux runners. Keep the assertion intact and | |
| # run it separately on Unix after the workspace suite has released its | |
| # PTYs. | |
| if: needs.changes.outputs.heavy == 'true' && matrix.os != 'windows-latest' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') | |
| run: cargo test -p codewhale-tui --test pty qa_pty::skills_opens_manager_owned_then_compatible -- --ignored --exact | |
| - name: Lockfile drift guard | |
| if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') | |
| run: git diff --exit-code -- Cargo.lock | |
| - name: Run Offline Eval Harness | |
| # The eval harness is OS-independent prompt/composition checking; | |
| # running it once (on the faster macOS leg, warm from the test build) | |
| # instead of once per desktop OS keeps the coverage while taking | |
| # ~2min off the Windows critical path. | |
| if: needs.changes.outputs.heavy == 'true' && matrix.os == 'macos-latest' | |
| run: cargo run -p codewhale-tui --all-features -- eval | |
| - name: sccache stats | |
| if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') && steps.sccache.outcome == 'success' | |
| continue-on-error: true | |
| shell: bash | |
| run: sccache --show-stats | |
| - name: Linux test location | |
| if: needs.changes.outputs.heavy == 'true' && matrix.os == 'ubuntu-latest' && github.event_name != 'workflow_dispatch' | |
| run: echo "Linux workspace tests run on CNB for mirrored first-party branches." | |
| npm-wrapper-smoke: | |
| name: npm wrapper smoke | |
| needs: changes | |
| if: github.event_name != 'schedule' | |
| # Same ternary rationale as the Test job: light legs only echo, so keep | |
| # them off macOS/Windows runners. On pull_request the matrix is | |
| # ubuntu-only, so the required "npm wrapper smoke (ubuntu-latest)" | |
| # context is unaffected. | |
| runs-on: ${{ needs.changes.outputs.heavy == 'true' && matrix.os || 'ubuntu-latest' }} | |
| strategy: | |
| matrix: | |
| os: ${{ fromJSON(github.event_name == 'pull_request' && '["ubuntu-latest"]' || '["ubuntu-latest","macos-latest","windows-latest"]') }} | |
| steps: | |
| - name: Skip npm wrapper smoke for light change | |
| if: needs.changes.outputs.heavy != 'true' | |
| run: echo "No executable Rust changes detected; preserving required npm wrapper smoke context." | |
| - uses: actions/checkout@v7 | |
| if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') | |
| - uses: dtolnay/rust-toolchain@stable | |
| if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') | |
| - uses: mozilla-actions/sccache-action@v0.0.10 | |
| id: sccache | |
| continue-on-error: true | |
| if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') | |
| - name: Enable sccache | |
| if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') && steps.sccache.outcome == 'success' | |
| shell: bash | |
| run: | | |
| echo "SCCACHE_GHA_ENABLED=true" >> "${GITHUB_ENV}" | |
| echo "RUSTC_WRAPPER=sccache" >> "${GITHUB_ENV}" | |
| echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1" >> "${GITHUB_ENV}" | |
| - uses: actions/setup-node@v7 | |
| if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') | |
| with: | |
| node-version: 20 | |
| - name: Install Linux system dependencies | |
| if: needs.changes.outputs.heavy == 'true' && matrix.os == 'ubuntu-latest' && github.event_name == 'workflow_dispatch' | |
| run: | | |
| for i in 1 2 3 4 5; do | |
| sudo apt-get update && break | |
| echo "apt-get update failed (attempt $i); retrying in 15s" | |
| sleep 15 | |
| done | |
| sudo apt-get install -y libdbus-1-dev pkg-config | |
| - uses: Swatinem/rust-cache@v2 | |
| if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') | |
| with: | |
| cache-bin: false | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Build wrapper binaries | |
| if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') | |
| # The smoke validates wrapper install/delegation plumbing, not | |
| # codegen quality, so skip fat LTO + codegen-units=1 for a much | |
| # cheaper release build. Shipped binaries keep the real profile via | |
| # the Release workflow. | |
| env: | |
| CARGO_PROFILE_RELEASE_LTO: 'off' | |
| CARGO_PROFILE_RELEASE_CODEGEN_UNITS: '16' | |
| run: cargo build --release --locked -p codewhale-cli -p codewhale-tui | |
| - name: Smoke wrapper install and delegated entrypoints | |
| if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') | |
| run: node scripts/release/npm-wrapper-smoke.js | |
| - name: sccache stats | |
| if: needs.changes.outputs.heavy == 'true' && (matrix.os != 'ubuntu-latest' || github.event_name == 'workflow_dispatch') && steps.sccache.outcome == 'success' | |
| continue-on-error: true | |
| shell: bash | |
| run: sccache --show-stats | |
| - name: Linux smoke location | |
| if: needs.changes.outputs.heavy == 'true' && matrix.os == 'ubuntu-latest' && github.event_name != 'workflow_dispatch' | |
| run: echo "Linux npm wrapper smoke runs on CNB for mirrored first-party branches." | |
| mobile-smoke: | |
| name: Mobile runtime smoke | |
| needs: changes | |
| # Not a required PR context. Pull requests run it only when the mobile | |
| # runtime surface changed (see the `mobile` filter above); every push to | |
| # main runs it unconditionally as the pre-release safety net. | |
| if: >- | |
| github.event_name != 'schedule' && | |
| needs.changes.outputs.heavy == 'true' && | |
| (github.event_name != 'pull_request' || needs.changes.outputs.mobile == 'true') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: mozilla-actions/sccache-action@v0.0.10 | |
| id: sccache | |
| continue-on-error: true | |
| - name: Enable sccache | |
| if: steps.sccache.outcome == 'success' | |
| shell: bash | |
| run: | | |
| echo "SCCACHE_GHA_ENABLED=true" >> "${GITHUB_ENV}" | |
| echo "RUSTC_WRAPPER=sccache" >> "${GITHUB_ENV}" | |
| echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1" >> "${GITHUB_ENV}" | |
| - name: Install Linux system dependencies | |
| run: | | |
| for i in 1 2 3 4 5; do | |
| sudo apt-get update && break | |
| echo "apt-get update failed (attempt $i); retrying in 15s" | |
| sleep 15 | |
| done | |
| sudo apt-get install -y libdbus-1-dev pkg-config | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| cache-bin: false | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Run mobile smoke tests | |
| # The smoke exercises HTTP/SSE runtime behaviour, not codegen | |
| # quality; skipping fat LTO + codegen-units=1 cuts the in-script | |
| # release build from ~12min to a fraction of that. | |
| env: | |
| CARGO_PROFILE_RELEASE_LTO: 'off' | |
| CARGO_PROFILE_RELEASE_CODEGEN_UNITS: '16' | |
| run: ./scripts/mobile-smoke.sh | |
| - name: sccache stats | |
| if: steps.sccache.outcome == 'success' | |
| continue-on-error: true | |
| shell: bash | |
| run: sccache --show-stats | |
| actionlint: | |
| name: Workflow lint | |
| needs: changes | |
| if: needs.changes.outputs.actions == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Run actionlint | |
| uses: docker://rhysd/actionlint:1.7.12 | |
| with: | |
| # SC2129 (grouped redirects) is style-only and endemic to the | |
| # existing GITHUB_ENV/GITHUB_OUTPUT append pattern; SC2221/SC2222 | |
| # flag the long-standing `*.md` glob shadowing the PR-template | |
| # entry in change detection, which is intentional. | |
| args: -color -ignore SC2129 -ignore SC2221 -ignore SC2222 | |
| # Check documentation builds without warnings | |
| docs: | |
| name: Documentation | |
| if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - name: Install Linux system dependencies | |
| if: runner.os == 'Linux' | |
| run: | | |
| for i in 1 2 3 4 5; do | |
| sudo apt-get update && break | |
| echo "apt-get update failed (attempt $i); retrying in 15s" | |
| sleep 15 | |
| done | |
| sudo apt-get install -y libdbus-1-dev pkg-config | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| cache-bin: false | |
| - name: Build docs | |
| run: cargo doc --workspace --no-deps | |
| env: | |
| RUSTDOCFLAGS: -Dwarnings |