From 20495642cdba8aefd9049da3308a41ec1969a4da Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 26 Jun 2026 11:08:55 +0800 Subject: [PATCH 1/4] ci: add GitHub Actions workflow (fmt + clippy + nextest) The repo previously had no remote CI, so PRs touching the calc core merged unverified by any automated gate (see PR #2/#3). This wires the documented merge gate (CLAUDE.md: fmt + clippy + test) into GitHub Actions, running on every PR to master and on pushes to master. Three parallel jobs: - fmt : `cargo fmt --all --check` - clippy : `cargo clippy --workspace --all-targets -- -D warnings` - test : `cargo nextest run --workspace` + workspace doctests clippy/test use Swatinem/rust-cache; test installs nextest and luajit (so the fixture-based extract-lua tooling tests run rather than self-skip). Tests that need the gitignored vendor PoB2 checkout skip gracefully in CI by design. --- .github/workflows/ci.yml | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..d637ab7f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,62 @@ +name: CI + +# Gate per CLAUDE.md: fmt + clippy + test. Runs on every PR to master and on +# pushes to master (so the default branch is always validated post-merge). +on: + push: + branches: [master] + pull_request: + branches: [master] + +# Cancel superseded runs for the same ref (e.g. force-push to a PR branch). +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: 1 + +jobs: + fmt: + name: rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - run: cargo fmt --all --check + + clippy: + name: clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - uses: Swatinem/rust-cache@v2 + # Warnings are failures, matching the local merge gate. + - run: cargo clippy --workspace --all-targets -- -D warnings + + test: + name: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@v2 + with: + tool: nextest + # luajit lets the fixture-based extract-lua tooling tests run instead of + # self-skipping. Tests that need the gitignored vendor PoB2 checkout still + # skip gracefully (it is not present in CI) — see CLAUDE.md "Parity 体系". + - name: Install luajit + run: sudo apt-get update && sudo apt-get install -y luajit + - name: Test (nextest) + run: cargo nextest run --workspace + # nextest does not run doctests; cover them separately. + - name: Doctests + run: cargo test --workspace --doc From 42865cee956262352d04773ba47ca3c9b349ed0f Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 26 Jun 2026 11:16:12 +0800 Subject: [PATCH 2/4] ci: only write rust-cache on master, PRs restore read-only Avoids every PR push saving its own large target cache (which churns the 10 GB per-repo cache limit and adds a redundant save step). PRs still restore master's warm dependency cache via GitHub's base-branch cache scoping. --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d637ab7f..cbc19033 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,11 @@ jobs: with: components: clippy - uses: Swatinem/rust-cache@v2 + with: + # Only master writes the cache; PRs restore it read-only. Keeps cache + # storage lean (no per-PR-branch copies churning the 10 GB repo limit) + # and skips the save step on PRs. PRs still restore master's warm cache. + save-if: ${{ github.ref == 'refs/heads/master' }} # Warnings are failures, matching the local merge gate. - run: cargo clippy --workspace --all-targets -- -D warnings @@ -47,6 +52,9 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 + with: + # See clippy job: master produces the cache, PRs consume it read-only. + save-if: ${{ github.ref == 'refs/heads/master' }} - uses: taiki-e/install-action@v2 with: tool: nextest From 2c64bbc3a45e10c4621babacc331bda69d69b939 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 26 Jun 2026 11:23:11 +0800 Subject: [PATCH 3/4] ci: collapse to a single sequential job to cut billed minutes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub Actions bills per job rounded up to the whole minute, so three jobs floored at ~7 min/run (test alone rounds 4m18s up to 5). One sequential job (fmt -> clippy -> test) bills ~5 min/run, and running the cheap lints first means a fmt/clippy failure exits before the ~4-min test compile — broken PRs cost almost nothing. Trade-off: slightly slower wall-clock, no per-check parallelism (the failing step is still obvious in the log). --- .github/workflows/ci.yml | 48 ++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbc19033..ea8c1237 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,11 @@ name: CI # Gate per CLAUDE.md: fmt + clippy + test. Runs on every PR to master and on # pushes to master (so the default branch is always validated post-merge). +# +# Single sequential job (private repo, cost-optimized): GitHub bills per job +# rounded up to the minute, so one job = one rounding instead of three. Cheapest +# checks run first, so a fmt/clippy failure exits before the ~4-min test compile +# and no billed minutes are wasted on test for a broken PR. on: push: branches: [master] @@ -18,49 +23,34 @@ env: RUST_BACKTRACE: 1 jobs: - fmt: - name: rustfmt + ci: + name: fmt + clippy + test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: - components: rustfmt - - run: cargo fmt --all --check - - clippy: - name: clippy - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - with: - components: clippy + components: rustfmt, clippy - uses: Swatinem/rust-cache@v2 with: # Only master writes the cache; PRs restore it read-only. Keeps cache - # storage lean (no per-PR-branch copies churning the 10 GB repo limit) - # and skips the save step on PRs. PRs still restore master's warm cache. - save-if: ${{ github.ref == 'refs/heads/master' }} - # Warnings are failures, matching the local merge gate. - - run: cargo clippy --workspace --all-targets -- -D warnings - - test: - name: test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - # See clippy job: master produces the cache, PRs consume it read-only. + # storage lean (no per-PR-branch copies churning the 10 GB repo limit). + # PRs still restore master's warm dependency cache. save-if: ${{ github.ref == 'refs/heads/master' }} - uses: taiki-e/install-action@v2 with: tool: nextest + + # Cheapest first — fail fast before the expensive test compile. + - name: Format + run: cargo fmt --all --check + - name: Clippy + run: cargo clippy --workspace --all-targets -- -D warnings + # luajit lets the fixture-based extract-lua tooling tests run instead of # self-skipping. Tests that need the gitignored vendor PoB2 checkout still - # skip gracefully (it is not present in CI) — see CLAUDE.md "Parity 体系". + # skip gracefully in CI by design (see CLAUDE.md "Parity 体系"). Installed + # after clippy so a lint failure skips it too. - name: Install luajit run: sudo apt-get update && sudo apt-get install -y luajit - name: Test (nextest) From 369a343f0df053ad88296863f0c435035f4e5e13 Mon Sep 17 00:00:00 2001 From: ackness Date: Fri, 26 Jun 2026 11:30:33 +0800 Subject: [PATCH 4/4] ci: trigger on version tags + manual dispatch only (not every push/PR) Private-repo cost control: run CI on release (pushed v* tag) or on explicit `workflow_dispatch`, instead of on every push/PR. Day-to-day commits no longer spend Actions minutes; you pay only when tagging a release or manually asking for a run. Dropped the master/PR push triggers and the save-if cache gate (no per-PR churn to guard against anymore). --- .github/workflows/ci.yml | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea8c1237..7cea96cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,19 +1,24 @@ name: CI -# Gate per CLAUDE.md: fmt + clippy + test. Runs on every PR to master and on -# pushes to master (so the default branch is always validated post-merge). +# Gate per CLAUDE.md: fmt + clippy + test. # -# Single sequential job (private repo, cost-optimized): GitHub bills per job -# rounded up to the minute, so one job = one rounding instead of three. Cheapest -# checks run first, so a fmt/clippy failure exits before the ~4-min test compile -# and no billed minutes are wasted on test for a broken PR. +# Trigger strategy (cost-optimized for a private repo): CI does NOT run on every +# push/PR. It runs only on: +# - a pushed version tag (v0.0.1, v1.2.3, ...) — i.e. on release; +# - a manual dispatch (Actions tab "Run workflow", or `gh workflow run ci.yml`), +# to validate a branch on demand. +# Day-to-day commits cost nothing; you pay only when releasing or when you ask. +# +# Single sequential job: GitHub bills per job rounded up to the minute, so one +# job = one rounding. Cheapest checks run first, so a fmt/clippy failure exits +# before the ~4-min test compile. on: push: - branches: [master] - pull_request: - branches: [master] + tags: + - 'v*' + workflow_dispatch: -# Cancel superseded runs for the same ref (e.g. force-push to a PR branch). +# Cancel superseded runs for the same ref (e.g. re-dispatch on the same branch). concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -31,12 +36,10 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: components: rustfmt, clippy + # Best-effort dependency cache. With tag/manual-only triggers runs are + # infrequent, so a cache often evicts (7-day idle) before reuse — that is + # expected; a release run starting cold is fine. - uses: Swatinem/rust-cache@v2 - with: - # Only master writes the cache; PRs restore it read-only. Keeps cache - # storage lean (no per-PR-branch copies churning the 10 GB repo limit). - # PRs still restore master's warm dependency cache. - save-if: ${{ github.ref == 'refs/heads/master' }} - uses: taiki-e/install-action@v2 with: tool: nextest