From 216df96a48b9281a75a7ddaa0b0cde52511d567b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 17:08:42 +0000 Subject: [PATCH 1/2] CI: on demand only, one job on Python 3.12 Six jobs (lint, tests on 3.11/3.12/3.13, conformance, build), each with its own checkout, Python setup and install, on every push and pull request, become one manually dispatched job on the Python releases are built with: one install of the dev extra, ruff, one pytest run that includes the specification fixtures corpus (a sparse checkout of fixtures only, switchable off with the 'conformance' input), and the build with twine check. A 10-minute timeout caps a runaway run. README, CONFORMANCE.md and PUBLISHING.md say CI is on demand. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz --- .github/PUBLISHING.md | 3 +- .github/workflows/ci.yml | 87 ++++++++++++++++------------------------ CONFORMANCE.md | 2 +- README.md | 9 ++++- 4 files changed, 46 insertions(+), 55 deletions(-) diff --git a/.github/PUBLISHING.md b/.github/PUBLISHING.md index 9e7d1b3..a02edf1 100644 --- a/.github/PUBLISHING.md +++ b/.github/PUBLISHING.md @@ -44,7 +44,8 @@ version number can never be reused once published. 1. Bump `__version__` in [`src/legaldown/__init__.py`](../src/legaldown/__init__.py) — it is the single source of truth; `pyproject.toml` reads it via `[tool.hatch.version]`. -2. Commit, then publish a GitHub Release with the tag `vX.Y.Z`. Its notes are where this +2. Commit and run **CI** on it (*Actions → CI → Run workflow*; it runs only on demand), then publish a + GitHub Release with the tag `vX.Y.Z`. Its notes are where this project records what changed in a release. The workflow builds the sdist and wheel, runs `twine check --strict`, **verifies the tag matches diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9868f9..a3feb8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,9 +1,17 @@ name: CI +# On demand only: Actions → CI → Run workflow (pick the branch). One job on one +# Python, the one the release is built with, so a run costs one runner's setup +# rather than six: lint, the tests (with the specification fixtures corpus +# unless switched off), and the build with its metadata check. + on: - push: - branches: [main] - pull_request: + workflow_dispatch: + inputs: + conformance: + description: "Also run the specification fixtures corpus (checks out ForLegalAI/LegalDown)" + type: boolean + default: true permissions: contents: read @@ -13,76 +21,51 @@ concurrency: cancel-in-progress: true jobs: - lint: - name: Lint (ruff) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - cache: pip - - run: pip install ruff - - run: ruff check . - - test: - name: Tests (Python ${{ matrix.python-version }}) + check: + name: Lint, test, build runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.11", "3.12", "3.13"] + timeout-minutes: 10 steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: pip - - run: pip install -e ".[dev]" - - run: pytest -q - conformance: - name: Specification conformance - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Check out the LegalDown specification (fixtures corpus) + - name: Check out the LegalDown specification (fixtures corpus only) + if: inputs.conformance uses: actions/checkout@v4 with: repository: ForLegalAI/LegalDown ref: v0.2 path: .legaldown-spec + sparse-checkout: fixtures # A private specification repository needs a token with read access; # GITHUB_TOKEN cannot read another repository. token: ${{ secrets.SPEC_REPO_TOKEN || github.token }} + - uses: actions/setup-python@v5 with: python-version: "3.12" cache: pip + cache-dependency-path: pyproject.toml + + # The dev extra holds pytest, ruff, build and twine: one install for every step. - run: pip install -e ".[dev]" - - name: Validate against the fixtures corpus + + - run: ruff check . + + - name: Tests and specification conformance + if: inputs.conformance env: LEGALDOWN_FIXTURES_DIR: .legaldown-spec/fixtures - # Cases for unimplemented rules are skipped and named, so a failure - # here is a regression against a rule this implementation claims. - run: pytest tests/conformance -q + # Cases for unimplemented rules are skipped and named, so a failure in + # tests/conformance is a regression against a rule this implementation claims. + run: pytest -q + + - name: Tests + if: ${{ !inputs.conformance }} + run: pytest -q - build: - name: Build distribution - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - cache: pip - - run: pip install build twine - run: python -m build + - name: Check package metadata # --strict matches the publish workflow, so a metadata problem fails - # here on the pull request rather than at release time. + # here rather than at release time. run: twine check --strict dist/* - - uses: actions/upload-artifact@v4 - with: - name: dist - path: dist/ diff --git a/CONFORMANCE.md b/CONFORMANCE.md index 7014d2b..00e44a4 100644 --- a/CONFORMANCE.md +++ b/CONFORMANCE.md @@ -201,7 +201,7 @@ and 38 skipped: eight of the other skips are the implemented rules named above, case, which is marked Full (fixtures README, step 4) — `tests/test_assembly.py` assembles it with a loader instead. Cases that need the final option run with it; cases that need an answers set are assembled with it, and each assembly case is compared byte for byte with its expected output. -CI runs this on every push and pull request. +CI runs this on demand (Actions → CI → Run workflow), with the `conformance` input on. ## Assembly (§15.7, §17.6) diff --git a/README.md b/README.md index e6cbfdd..86af9c2 100644 --- a/README.md +++ b/README.md @@ -604,9 +604,16 @@ checks it does not perform, so those are listed in git clone https://github.com/ForLegalAI/legaldown-validator cd legaldown-validator pip install -e ".[dev]" +ruff check . pytest ``` +**CI** runs on demand only — *Actions → CI → Run workflow*, on the branch you pick — as one job on +Python 3.12 (the version releases are built with): lint, the tests with the specification fixtures +corpus (switch it off with the `conformance` input for a quicker run), and the build with its +metadata check. Nothing runs on a push or a pull request, so run it before merging and before a +release. + ### Conformance suite The fixtures corpus lives in the specification repository, so point the harness at a checkout: @@ -618,7 +625,7 @@ LEGALDOWN_FIXTURES_DIR=../LegalDown/fixtures pytest tests/conformance -q Cases for rules outside Core are skipped and named, so the run doubles as the coverage ledger in [CONFORMANCE.md](https://github.com/ForLegalAI/legaldown-validator/blob/main/CONFORMANCE.md), -which accounts for every skip. CI runs it on every push and pull request. +which accounts for every skip. CI runs it with the rest of the checks, on demand (below). Bug reports and pull requests are welcome in [Issues](https://github.com/ForLegalAI/legaldown-validator/issues); questions about the format From 2f5b5165f1721afc800e941792925be50162adc6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 2 Oct 2026 17:13:13 +0000 Subject: [PATCH 2/2] CI: also on a pull request carrying the 'ci' label Adding the label runs CI, and it runs again on each push to that pull request until the label is removed; other labels and unlabeled pull requests run nothing. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_013WmBAc5T7UCKVdpUmg9qxz --- .github/PUBLISHING.md | 2 +- .github/workflows/ci.yml | 27 ++++++++++++++++++++------- CONFORMANCE.md | 2 +- README.md | 11 ++++++----- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/.github/PUBLISHING.md b/.github/PUBLISHING.md index a02edf1..8baf496 100644 --- a/.github/PUBLISHING.md +++ b/.github/PUBLISHING.md @@ -44,7 +44,7 @@ version number can never be reused once published. 1. Bump `__version__` in [`src/legaldown/__init__.py`](../src/legaldown/__init__.py) — it is the single source of truth; `pyproject.toml` reads it via `[tool.hatch.version]`. -2. Commit and run **CI** on it (*Actions → CI → Run workflow*; it runs only on demand), then publish a +2. Commit and run **CI** on it (*Actions → CI → Run workflow*; it runs only on demand, or with the `ci` label on a pull request), then publish a GitHub Release with the tag `vX.Y.Z`. Its notes are where this project records what changed in a release. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3feb8d..1dca856 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,11 +1,16 @@ name: CI -# On demand only: Actions → CI → Run workflow (pick the branch). One job on one -# Python, the one the release is built with, so a run costs one runner's setup -# rather than six: lint, the tests (with the specification fixtures corpus -# unless switched off), and the build with its metadata check. +# On demand only, in either of two ways: +# - Actions → CI → Run workflow, on the branch you pick; +# - the `ci` label on a pull request: adding it runs CI, and CI then runs again +# on each push to that pull request until the label is removed. +# One job on one Python, the one the release is built with, so a run costs one +# runner's setup rather than six: lint, the tests (with the specification fixtures +# corpus unless switched off), and the build with its metadata check. on: + pull_request: + types: [labeled, synchronize, reopened] workflow_dispatch: inputs: conformance: @@ -23,13 +28,21 @@ concurrency: jobs: check: name: Lint, test, build + # A push or a reopening of a pull request runs it only while the pull request has + # the `ci` label; adding some other label does not. + if: >- + github.event_name == 'workflow_dispatch' || + (contains(github.event.pull_request.labels.*.name, 'ci') && + (github.event.action != 'labeled' || github.event.label.name == 'ci')) runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 + # The corpus is checked out and run for a pull request, and for a manual run + # unless its `conformance` input is off. - name: Check out the LegalDown specification (fixtures corpus only) - if: inputs.conformance + if: github.event_name != 'workflow_dispatch' || inputs.conformance uses: actions/checkout@v4 with: repository: ForLegalAI/LegalDown @@ -52,7 +65,7 @@ jobs: - run: ruff check . - name: Tests and specification conformance - if: inputs.conformance + if: github.event_name != 'workflow_dispatch' || inputs.conformance env: LEGALDOWN_FIXTURES_DIR: .legaldown-spec/fixtures # Cases for unimplemented rules are skipped and named, so a failure in @@ -60,7 +73,7 @@ jobs: run: pytest -q - name: Tests - if: ${{ !inputs.conformance }} + if: github.event_name == 'workflow_dispatch' && !inputs.conformance run: pytest -q - run: python -m build diff --git a/CONFORMANCE.md b/CONFORMANCE.md index 00e44a4..c743054 100644 --- a/CONFORMANCE.md +++ b/CONFORMANCE.md @@ -201,7 +201,7 @@ and 38 skipped: eight of the other skips are the implemented rules named above, case, which is marked Full (fixtures README, step 4) — `tests/test_assembly.py` assembles it with a loader instead. Cases that need the final option run with it; cases that need an answers set are assembled with it, and each assembly case is compared byte for byte with its expected output. -CI runs this on demand (Actions → CI → Run workflow), with the `conformance` input on. +CI runs this on demand (Actions → CI → Run workflow with the `conformance` input on, or the `ci` label on a pull request). ## Assembly (§15.7, §17.6) diff --git a/README.md b/README.md index 86af9c2..e179cb1 100644 --- a/README.md +++ b/README.md @@ -608,11 +608,12 @@ ruff check . pytest ``` -**CI** runs on demand only — *Actions → CI → Run workflow*, on the branch you pick — as one job on -Python 3.12 (the version releases are built with): lint, the tests with the specification fixtures -corpus (switch it off with the `conformance` input for a quicker run), and the build with its -metadata check. Nothing runs on a push or a pull request, so run it before merging and before a -release. +**CI** runs on demand only, as one job on Python 3.12 (the version releases are built with): lint, +the tests with the specification fixtures corpus, and the build with its metadata check. Start it +with *Actions → CI → Run workflow* on the branch you pick (the `conformance` input switches the +corpus off for a quicker run), or by adding the **`ci` label** to a pull request: it then runs again +on each push to that pull request until the label is removed. Nothing else triggers it, so run it +before merging and before a release. ### Conformance suite