ci: migrate commit linting #3192
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, feature/holographic-memory] | |
| pull_request: | |
| branches: ['**'] | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CARGO_TERM_COLOR: always | |
| CARGO_INCREMENTAL: "0" | |
| CARGO_PROFILE_DEV_DEBUG: "0" | |
| CARGO_PROFILE_TEST_DEBUG: "0" | |
| AST_GREP_VERSION: "0.44.0" | |
| jobs: | |
| commit-messages: | |
| name: Commit Messages | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - name: Install commit message lint dependencies | |
| run: npm ci | |
| - name: Reject tracked ignored files | |
| run: scripts/check-release-pr-integrity.sh HEAD HEAD | |
| - name: Validate PR commit messages | |
| if: github.event_name == 'pull_request' | |
| env: | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| run: npm run lint:commit -- --from "$BASE_SHA" --to "$HEAD_SHA" | |
| - name: Validate pushed commit messages | |
| if: github.event_name == 'push' | |
| env: | |
| BEFORE_SHA: ${{ github.event.before }} | |
| HEAD_SHA: ${{ github.sha }} | |
| run: | | |
| set -euo pipefail | |
| if [ "$BEFORE_SHA" = "0000000000000000000000000000000000000000" ]; then | |
| if git rev-parse "${HEAD_SHA}^" >/dev/null 2>&1; then | |
| npm run lint:commit -- --from "${HEAD_SHA}^" --to "$HEAD_SHA" | |
| else | |
| git show --no-patch --format=%B "$HEAD_SHA" | npm run lint:commit -- | |
| fi | |
| else | |
| npm run lint:commit -- --from "$BEFORE_SHA" --to "$HEAD_SHA" | |
| fi | |
| release-version-drift: | |
| name: Release Version Drift | |
| if: github.event_name == 'pull_request' && !startsWith(github.head_ref, 'release-plz-') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Test release version drift guard | |
| run: bash tests/release_drift_check_test.sh | |
| - name: Test release workflow contract | |
| run: bash tests/release_workflow_contract_test.sh | |
| - name: Test local dogfood command contract | |
| run: bash tests/dogfood_command_test.sh | |
| - name: Check release version drift | |
| run: scripts/check-release-drift.sh | |
| test: | |
| name: Test ${{ matrix.name }} | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }} | |
| runs-on: ${{ fromJSON(matrix.runner) }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - name: Linux | |
| runner: '"ubuntu-latest"' | |
| - name: macOS | |
| runner: '"macos-14"' | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: ./.github/actions/setup-linux-mold | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Install ast-grep | |
| run: | | |
| npm install --global "@ast-grep/cli@${AST_GREP_VERSION}" | |
| ast-grep --version | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@nextest | |
| - name: Cache Rust build outputs | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: ci-test-full-${{ runner.os }} | |
| cache-on-failure: true | |
| - name: Run tests | |
| run: cargo nextest run --workspace --profile ci --locked | |
| windows-build: | |
| name: Build Windows tests | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }} | |
| runs-on: windows-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Tune Windows runner for build I/O | |
| shell: pwsh | |
| run: | | |
| # Defender real-time scanning taxes the compiler's many short-lived | |
| # object/incremental files; hosted runners are elevated, so turn it | |
| # off and exclude the hot paths as a fallback. | |
| Set-MpPreference -DisableRealtimeMonitoring $true -ErrorAction SilentlyContinue | |
| $exclusions = @( | |
| $env:GITHUB_WORKSPACE, | |
| $env:RUNNER_TEMP, | |
| [System.IO.Path]::GetTempPath(), | |
| $env:CARGO_HOME, | |
| $env:RUSTUP_HOME, | |
| "$env:USERPROFILE\.cargo", | |
| "$env:USERPROFILE\.rustup" | |
| ) | Where-Object { $_ } | Select-Object -Unique | |
| foreach ($path in $exclusions) { | |
| Add-MpPreference -ExclusionPath $path -ErrorAction SilentlyContinue | |
| } | |
| - uses: dtolnay/rust-toolchain@stable | |
| - name: Use lld-link linker | |
| shell: pwsh | |
| run: | | |
| where.exe lld-link.exe | |
| lld-link.exe --version | |
| "CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER=lld-link.exe" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append | |
| # Node is only needed so build.rs can produce the embedded dashboard | |
| # dist assets (npm ci + npm run build) once, instead of once per shard. | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| cache-dependency-path: dashboard/package-lock.json | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@nextest | |
| - name: Cache Windows Rust build outputs | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: ci-test-full-windows-msvc-lld | |
| cache-on-failure: true | |
| - name: Build nextest archive | |
| shell: pwsh | |
| run: cargo nextest archive --workspace --locked --archive-file "$env:RUNNER_TEMP/nextest-archive.tar.zst" | |
| - name: Upload nextest archive | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-nextest-archive | |
| path: ${{ runner.temp }}/nextest-archive.tar.zst | |
| retention-days: 1 | |
| compression-level: 0 | |
| windows-test-shard: | |
| name: Test Windows shard ${{ matrix.partition }}/5 | |
| needs: windows-build | |
| runs-on: windows-latest | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| partition: [1, 2, 3, 4, 5] | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Tune Windows runner for test I/O | |
| shell: pwsh | |
| run: | | |
| # Defender real-time scanning taxes the many short-lived SQLite/tempdir | |
| # files these tests create; hosted runners are elevated, so turn it off | |
| # and exclude the hot paths as a fallback. | |
| Set-MpPreference -DisableRealtimeMonitoring $true -ErrorAction SilentlyContinue | |
| $exclusions = @( | |
| $env:GITHUB_WORKSPACE, | |
| $env:RUNNER_TEMP, | |
| [System.IO.Path]::GetTempPath(), | |
| $env:CARGO_HOME, | |
| $env:RUSTUP_HOME, | |
| "$env:USERPROFILE\.cargo", | |
| "$env:USERPROFILE\.rustup" | |
| ) | Where-Object { $_ } | Select-Object -Unique | |
| foreach ($path in $exclusions) { | |
| Add-MpPreference -ExclusionPath $path -ErrorAction SilentlyContinue | |
| } | |
| # Default user TEMP is on the slow C: drive; RUNNER_TEMP shares the | |
| # faster workspace drive. Point TMP/TEMP there so tempfile::TempDir in | |
| # the nextest test processes follows. | |
| $fastTemp = Join-Path $env:RUNNER_TEMP "tmp" | |
| New-Item -ItemType Directory -Path $fastTemp -Force | Out-Null | |
| "TMP=$fastTemp" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append | |
| "TEMP=$fastTemp" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Install ast-grep | |
| shell: pwsh | |
| run: | | |
| npm install --global "@ast-grep/cli@$env:AST_GREP_VERSION" | |
| ast-grep --version | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@nextest | |
| - name: Download nextest archive | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: windows-nextest-archive | |
| path: ${{ runner.temp }} | |
| - name: Run Windows tests | |
| env: | |
| TRACEDECAY_SQLITE_UNSAFE_FAST: "1" | |
| shell: pwsh | |
| # --extract-to must be the workspace so target/ lands at the same | |
| # absolute path as on the build job: integration tests bake | |
| # env!("CARGO_BIN_EXE_tracedecay") into the binaries at compile time. | |
| run: >- | |
| cargo-nextest nextest run --profile ci | |
| --archive-file "$env:RUNNER_TEMP/nextest-archive.tar.zst" | |
| --extract-to "$env:GITHUB_WORKSPACE" | |
| --workspace-remap "$env:GITHUB_WORKSPACE" | |
| --partition slice:${{ matrix.partition }}/5 | |
| --test-threads num-cpus --status-level slow | |
| - name: Clean abandoned Windows test children | |
| if: always() | |
| shell: pwsh | |
| run: | | |
| $workspace = (Resolve-Path $env:GITHUB_WORKSPACE).Path | |
| $all = Get-CimInstance Win32_Process | |
| $liveProcessIds = [System.Collections.Generic.HashSet[uint32]]::new() | |
| foreach ($process in $all) { | |
| [void]$liveProcessIds.Add([uint32]$process.ProcessId) | |
| } | |
| $stale = foreach ($process in $all) { | |
| if ($process.Name -ne "tracedecay.exe") { | |
| continue | |
| } | |
| if (-not $process.CommandLine) { | |
| continue | |
| } | |
| $inCurrentWorkspace = $process.CommandLine.IndexOf($workspace, [StringComparison]::OrdinalIgnoreCase) -ge 0 | |
| if (-not $inCurrentWorkspace) { | |
| continue | |
| } | |
| if (-not $liveProcessIds.Contains([uint32]$process.ParentProcessId)) { | |
| $process | |
| } | |
| } | |
| foreach ($process in $stale) { | |
| Write-Host "Stopping abandoned tracedecay child pid=$($process.ProcessId)" | |
| Stop-Process -Id $process.ProcessId -Force -ErrorAction SilentlyContinue | |
| } | |
| - name: Upload Windows nextest report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-nextest-junit-${{ matrix.partition }} | |
| path: target/nextest/ci/junit.xml | |
| if-no-files-found: ignore | |
| retention-days: 7 | |
| windows-test: | |
| name: Test Windows | |
| if: ${{ !cancelled() && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref)) }} | |
| needs: windows-test-shard | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check Windows shards | |
| run: | | |
| if [ "${{ needs.windows-test-shard.result }}" != "success" ]; then | |
| echo "Windows shard result: ${{ needs.windows-test-shard.result }}" | |
| exit 1 | |
| fi | |
| echo "All Windows shards passed." | |
| clippy: | |
| name: Clippy | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: clippy | |
| - uses: ./.github/actions/setup-linux-mold | |
| - name: Cache Rust build outputs | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: ci-clippy-full-${{ runner.os }} | |
| cache-on-failure: true | |
| - name: Run blocking Clippy policy | |
| run: cargo clippy --workspace --all-targets --locked -- -D warnings | |
| fmt: | |
| name: Format | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt | |
| - run: cargo fmt --all -- --check | |
| dashboard: | |
| name: Dashboard | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 25 | |
| env: | |
| DASHBOARD_DIST_FILES: >- | |
| shell/dist/shell.js | |
| shell/dist/shell.css | |
| holographic/dist/index.js | |
| holographic/dist/style.css | |
| lcm/dist/index.js | |
| lcm/dist/style.css | |
| graph/dist/index.js | |
| graph/dist/style.css | |
| savings/dist/index.js | |
| savings/dist/style.css | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: dashboard/package-lock.json | |
| - name: Install dashboard dependencies | |
| working-directory: dashboard | |
| run: npm ci | |
| - name: Build dashboard assets | |
| working-directory: dashboard | |
| run: npm run build | |
| - name: Run dashboard unit tests | |
| working-directory: dashboard | |
| run: npm test | |
| - name: Verify embedded dist artifacts | |
| working-directory: dashboard | |
| run: | | |
| set -euo pipefail | |
| for f in $DASHBOARD_DIST_FILES; do | |
| if [ ! -s "$f" ]; then | |
| echo "missing or empty dist artifact: $f" >&2 | |
| exit 1 | |
| fi | |
| done | |
| # shellcheck disable=SC2086 | |
| sha256sum $DASHBOARD_DIST_FILES > /tmp/dashboard-dist.sha | |
| npm run build | |
| sha256sum -c /tmp/dashboard-dist.sha | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: ./.github/actions/setup-linux-mold | |
| - name: Install ast-grep | |
| run: | | |
| npm install --global "@ast-grep/cli@${AST_GREP_VERSION}" | |
| ast-grep --version | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@nextest | |
| - name: Cache Rust build outputs | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: ci-dashboard-full-${{ runner.os }} | |
| cache-on-failure: true | |
| - name: Run dashboard integration tests | |
| run: cargo nextest run --test dashboard_api_test --no-fail-fast --locked | |
| - name: Cache Playwright browsers | |
| uses: actions/cache@v6 | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: playwright-${{ runner.os }}-${{ hashFiles('dashboard/package-lock.json') }} | |
| restore-keys: | | |
| playwright-${{ runner.os }}- | |
| - name: Install Playwright Chromium | |
| working-directory: dashboard | |
| run: npx playwright install --with-deps chromium | |
| - name: Prebuild dashboard binary for smoke | |
| run: cargo build --bin tracedecay --locked | |
| - name: Run dashboard smoke (empty LCM) | |
| working-directory: dashboard | |
| env: | |
| TRACEDECAY_GLOBAL_DB: ${{ runner.temp }}/tracedecay-dashboard-smoke-empty.db | |
| run: | | |
| set -euo pipefail | |
| rm -f "$TRACEDECAY_GLOBAL_DB" | |
| attempts=0 | |
| max_attempts=3 | |
| until [ "$attempts" -ge "$max_attempts" ]; do | |
| attempts=$((attempts + 1)) | |
| echo "Smoke attempt ${attempts}/${max_attempts}..." | |
| if npm run smoke -- --expect-lcm=empty; then | |
| exit 0 | |
| fi | |
| if [ "$attempts" -lt "$max_attempts" ]; then | |
| sleep 10 | |
| fi | |
| done | |
| echo "Dashboard smoke failed after ${max_attempts} attempts" >&2 | |
| exit 1 | |
| - name: Run dashboard mobile smoke | |
| working-directory: dashboard | |
| env: | |
| TRACEDECAY_GLOBAL_DB: ${{ runner.temp }}/tracedecay-dashboard-smoke-mobile.db | |
| run: | | |
| set -euo pipefail | |
| rm -f "$TRACEDECAY_GLOBAL_DB" | |
| npm run smoke:mobile -- --expect-lcm=empty | |
| hermes-integration: | |
| name: Hermes integration (stock) | |
| if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON('["master","feature/holographic-memory"]'), github.event.pull_request.base.ref) }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| env: | |
| # Stock (upstream) Hermes pin — the generated plugin must keep working | |
| # against this exact upstream commit. Bump deliberately after rerunning | |
| # scripts/hermes_stock_integration.sh against the new ref locally. | |
| HERMES_UPSTREAM_REPO: https://github.com/NousResearch/hermes-agent.git | |
| HERMES_UPSTREAM_REF: 9dd9ef0ec99a87f078f7272b4323df5440b4b3f9 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: ./.github/actions/setup-linux-mold | |
| - name: Install ast-grep | |
| run: | | |
| npm install --global "@ast-grep/cli@${AST_GREP_VERSION}" | |
| ast-grep --version | |
| - name: Cache Rust build outputs | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| shared-key: ci-hermes-integration-full-${{ runner.os }} | |
| cache-on-failure: true | |
| - name: Build tracedecay binary | |
| run: cargo build --bin tracedecay --locked | |
| - name: Run generated-plugin unit checks (no Hermes required) | |
| run: python3 scripts/hermes_plugin_unit_check.py | |
| - name: Clone stock Hermes at pinned ref | |
| run: | | |
| set -euo pipefail | |
| git init -q "$RUNNER_TEMP/hermes-upstream" | |
| git -C "$RUNNER_TEMP/hermes-upstream" remote add origin "$HERMES_UPSTREAM_REPO" | |
| git -C "$RUNNER_TEMP/hermes-upstream" fetch --depth 1 origin "$HERMES_UPSTREAM_REF" | |
| git -C "$RUNNER_TEMP/hermes-upstream" checkout --detach FETCH_HEAD | |
| - uses: astral-sh/setup-uv@v8.2.0 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: ${{ runner.temp }}/hermes-upstream/uv.lock | |
| cache-suffix: hermes-${{ env.HERMES_UPSTREAM_REF }} | |
| - name: Set up stock Hermes environment | |
| working-directory: ${{ runner.temp }}/hermes-upstream | |
| run: uv sync --frozen --no-dev | |
| - name: Run stock Hermes integration checks | |
| env: | |
| TRACEDECAY_BIN: ${{ github.workspace }}/target/debug/tracedecay | |
| HERMES_UPSTREAM_DIR: ${{ runner.temp }}/hermes-upstream | |
| run: scripts/hermes_stock_integration.sh |