ci: shard behavior tests by measured duration #459
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: [main] | |
| pull_request: | |
| branches: [main] | |
| permissions: | |
| contents: read | |
| jobs: | |
| lint: | |
| name: Lint shell scripts | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install pinned ShellCheck | |
| run: | | |
| set -eu | |
| bin/fm-install-shellcheck.sh "$RUNNER_TEMP/bin" | |
| echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH" | |
| # Single owner of the lint definition (file set + config + version). Do not | |
| # re-spell the shellcheck command here; keep CI and the pre-push gate on it. | |
| - run: bin/fm-lint.sh | |
| behavior-test-plan: | |
| name: Behavior test shard plan | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Prove complete, disjoint shard coverage | |
| run: bin/fm-behavior-shards.sh --check 8 | |
| behavior-tests: | |
| name: Behavior tests (shard ${{ matrix.shard }}/8) | |
| needs: behavior-test-plan | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2, 3, 4, 5, 6, 7, 8] | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| - name: Install pinned ShellCheck | |
| run: | | |
| set -eu | |
| bin/fm-install-shellcheck.sh "$RUNNER_TEMP/bin" | |
| echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH" | |
| - name: Require tmux for e2e tests | |
| run: | | |
| set -eu | |
| command -v tmux >/dev/null || { | |
| echo "::error::tmux is required for real afk injection e2e coverage" | |
| exit 1 | |
| } | |
| tmux -V | |
| - name: Install tasks-axi for backlog-handoff delegation | |
| run: | | |
| set -eu | |
| npm install -g tasks-axi | |
| tasks-axi --version | |
| - name: Prepare private shard state | |
| run: | | |
| set -eu | |
| shard_root="$RUNNER_TEMP/fm-behavior-${{ matrix.shard }}" | |
| install -d -m 700 "$shard_root/tmp" "$shard_root/tmux" | |
| { | |
| echo "TMPDIR=$shard_root/tmp" | |
| echo "TMP=$shard_root/tmp" | |
| echo "TEMP=$shard_root/tmp" | |
| echo "TMUX_TMPDIR=$shard_root/tmux" | |
| } >> "$GITHUB_ENV" | |
| - name: Run behavior shard ${{ matrix.shard }}/8 | |
| run: | | |
| set -eu | |
| manifest="$RUNNER_TEMP/executed-${{ matrix.shard }}.tsv" | |
| bin/fm-behavior-shards.sh --run "${{ matrix.shard }}" 8 "$manifest" | |
| - name: Upload executed-test manifest | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: behavior-shard-${{ matrix.shard }} | |
| path: ${{ runner.temp }}/executed-${{ matrix.shard }}.tsv | |
| if-no-files-found: warn | |
| overwrite: true | |
| behavior-tests-complete: | |
| # Preserve the historical required-check name while proving the union of | |
| # what the six runners actually executed, not only the planned inventory. | |
| name: Behavior tests | |
| needs: behavior-tests | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Download executed-test manifests | |
| uses: actions/download-artifact@v4 | |
| continue-on-error: true | |
| with: | |
| pattern: behavior-shard-* | |
| path: ${{ runner.temp }}/behavior-manifests | |
| merge-multiple: true | |
| - name: Verify complete execution union | |
| run: bin/fm-behavior-shards.sh --verify 8 "$RUNNER_TEMP/behavior-manifests" | |
| agent-fleet: | |
| name: Agent Fleet package | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| - name: Install pinned uv | |
| run: python -m pip install uv==0.9.10 | |
| - name: Verify locked package | |
| working-directory: tools/agent-fleet | |
| run: | | |
| set -eu | |
| uv sync --locked | |
| uv run --locked ruff check . | |
| uv run --locked pytest | |
| uv run --locked python -m compileall -q src | |
| uv build --out-dir "$RUNNER_TEMP/agent-fleet-dist" | |
| invariants: | |
| name: Repo invariants | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Symlinks must stay intact | |
| run: | | |
| set -eu | |
| [ "$(readlink CLAUDE.md)" = "AGENTS.md" ] || { echo "::error::CLAUDE.md must be a symlink to AGENTS.md"; exit 1; } | |
| [ "$(readlink .claude/skills)" = "../.agents/skills" ] || { echo "::error::.claude/skills must be a symlink to ../.agents/skills"; exit 1; } | |
| - name: Personal fleet paths must not be tracked | |
| run: | | |
| set -eu | |
| tracked=$(git ls-files -- data state config projects .no-mistakes) | |
| if [ -n "$tracked" ]; then | |
| echo "::error::Personal fleet paths are tracked in git:" | |
| printf '%s\n' "$tracked" | |
| exit 1 | |
| fi |