From efadede8ca424b300b22ec056d14a1e01fc8f6a4 Mon Sep 17 00:00:00 2001 From: Denis_Drobyshev Date: Wed, 5 Aug 2026 17:13:38 +0300 Subject: [PATCH 1/2] Test the platforms the shell ships binaries for, and add the rest release-app.yml builds Windows and macOS binaries; nothing ran the library underneath them on either platform. CI was one job doing five things on Linux, with actions two majors behind. - lint split from test, so a ruff failure is not reported as a test failure three times over - the suite on Windows and macOS - a build job that installs the wheel into a clean environment: the distribution is glia-agents and the import package is glia, and that mismatch is the packaging mistake nobody catches locally - an aggregate CI job for branch protection - CodeQL, Dependabot, auto-merge for patch and action bumps, pre-commit - actions to current majors across all four workflows The coverage gate and the examples loop stay on Linux only. Measuring them three times says nothing new. --- .github/dependabot.yml | 34 ++++++ .github/workflows/ci.yml | 118 ++++++++++++++++++-- .github/workflows/codeql.yml | 39 +++++++ .github/workflows/dependabot-auto-merge.yml | 49 ++++++++ .github/workflows/docs.yml | 4 +- .github/workflows/publish.yml | 4 +- .github/workflows/release-app.yml | 8 +- .pre-commit-config.yaml | 42 +++++++ 8 files changed, 281 insertions(+), 17 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/dependabot-auto-merge.yml create mode 100644 .pre-commit-config.yaml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f661c53 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,34 @@ +version: 2 + +updates: + # Actions first: a stale third-party action is a supply-chain risk in every + # workflow that uses it, and the bump is almost always safe to take. + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly + day: monday + commit-message: + prefix: "ci" + groups: + actions: + patterns: ["*"] + + - package-ecosystem: pip + directory: "/" + schedule: + interval: weekly + day: monday + commit-message: + prefix: "deps" + groups: + dev-tooling: + patterns: ["ruff", "mypy", "pytest*", "pre-commit", "build", "twine"] + dependency-type: development + docs: + patterns: ["mkdocs*"] + # The core has zero required dependencies and every provider lives behind an + # extra. There is nothing here Dependabot can bump that a user is forced to + # install, which is why the limit is low: the queue should stay short enough + # to read. + open-pull-requests-limit: 5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 413dd94..56fff13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,34 +4,65 @@ on: push: branches: [master] pull_request: - branches: [master] + workflow_dispatch: + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +# Read-only by default. A job that needs more asks for it itself, so a +# compromised dependency in one step cannot push to the repository. +permissions: + contents: read + +env: + PYTHONUNBUFFERED: "1" + PIP_DISABLE_PIP_VERSION_CHECK: "1" + FORCE_COLOR: "1" jobs: + lint: + name: Lint and types + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: pyproject.toml + - run: pip install -e ".[dev]" + # No `ruff format --check` yet: twenty files predate the formatter and + # reformatting them belongs in its own commit, not in a lint gate that + # would be red from the day it lands. pre-commit formats what a + # contributor touches in the meantime. + - name: ruff check + run: python -m ruff check . + - name: mypy + run: python -m mypy glia + test: + name: Tests (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: python-version: ["3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: pyproject.toml - name: Install (dev extras) run: | python -m pip install --upgrade pip pip install -e ".[dev]" - - name: Lint (ruff) - run: python -m ruff check . - - - name: Types (mypy) - run: python -m mypy glia - - name: Tests (pytest, fully offline) + coverage gate run: python -m pytest -q --cov=glia --cov-report=term-missing --cov-fail-under=90 @@ -41,3 +72,72 @@ jobs: echo "--- $f ---" python "$f" done + + platforms: + name: Tests (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [windows-latest, macos-latest] + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: pyproject.toml + - run: pip install -e ".[dev]" + # The suite only, without the coverage gate and the examples loop. The + # shell ships Windows and macOS binaries, so the library underneath them + # has to be known to work there; the gate and the examples are already + # measured once on Linux and measuring them three times says nothing new. + - name: Tests + run: python -m pytest -q + + build: + name: Package builds and installs cleanly + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: "3.12" + - run: pip install build twine + - name: Build the wheel and sdist + run: python -m build + - name: Check the metadata + run: twine check dist/* + # An editable install hides packaging mistakes: it puts the working tree + # on the path, so a module missing from the wheel still imports and a + # broken console script still resolves. + - name: Install the wheel into a clean environment + run: | + python -m venv /tmp/fresh + /tmp/fresh/bin/pip install dist/*.whl + # The distribution is glia-agents and the import package is glia. + # That mismatch is the one packaging mistake nobody notices locally. + /tmp/fresh/bin/python -c "import glia; print(glia.__name__)" + /tmp/fresh/bin/glia-shell --help > /dev/null + - uses: actions/upload-artifact@v7 + with: + name: dist + path: dist/ + retention-days: 14 + + ci: + name: CI + runs-on: ubuntu-latest + if: always() + needs: [lint, test, platforms, build] + steps: + # One aggregate check to require in branch protection. Without it, adding + # a job to the matrix silently leaves it unrequired, and a red job stops + # blocking merges. + - name: Fail if any job did not succeed + if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') + run: | + echo "One or more jobs failed:" + echo '${{ toJSON(needs) }}' + exit 1 + - run: echo "All checks passed." diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..419104b --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,39 @@ +name: CodeQL + +on: + push: + branches: [master] + pull_request: + branches: [master] + schedule: + # Weekly, because advisories land between pushes: a rule added after the + # last commit would otherwise never run against this code. + - cron: "29 5 * * 1" + workflow_dispatch: + +permissions: + contents: read + +jobs: + analyze: + name: Analyze Python + runs-on: ubuntu-latest + if: github.event.repository.visibility == 'public' + permissions: + security-events: write + actions: read + contents: read + steps: + - uses: actions/checkout@v7 + + - uses: github/codeql-action/init@v4 + with: + languages: python + # security-and-quality over the default: this library executes + # user-declared tools and serves HTTP from the shell, so the extra + # checks earn their noise. + queries: security-and-quality + + - uses: github/codeql-action/analyze@v4 + with: + category: "/language:python" diff --git a/.github/workflows/dependabot-auto-merge.yml b/.github/workflows/dependabot-auto-merge.yml new file mode 100644 index 0000000..d06aeda --- /dev/null +++ b/.github/workflows/dependabot-auto-merge.yml @@ -0,0 +1,49 @@ +name: Dependabot auto-merge + +# What this does and does not do: +# +# auto-merged - GitHub Actions bumps, and patch-level bumps of anything else +# left for you - every minor and major bump of a runtime dependency +# +# A patch release and an action bump are the updates that pile up unread until +# the queue is too long to review honestly. A minor bump can change behaviour, +# so it keeps a human. Auto-merge is queued, not immediate: GitHub still waits +# for the required checks to pass, and a red build leaves the PR open. + +on: pull_request_target + +permissions: + contents: read + +jobs: + auto-merge: + if: github.actor == 'dependabot[bot]' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + # Reads the update type from the PR that Dependabot opened. Nothing from + # the branch is checked out or executed, which is what makes + # pull_request_target safe to use here. + - id: metadata + uses: dependabot/fetch-metadata@v2 + + - name: Approve and queue the merge + if: >- + steps.metadata.outputs.package-ecosystem == 'github_actions' || + steps.metadata.outputs.update-type == 'version-update:semver-patch' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR: ${{ github.event.pull_request.html_url }} + run: | + gh pr review --approve "$PR" + gh pr merge --auto --squash "$PR" + + - name: Explain why this one was left alone + if: >- + steps.metadata.outputs.package-ecosystem != 'github_actions' && + steps.metadata.outputs.update-type != 'version-update:semver-patch' + env: + TYPE: ${{ steps.metadata.outputs.update-type }} + run: echo "$TYPE is not auto-merged; this pull request needs a human." diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 33a20fc..4bd4a85 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -20,11 +20,11 @@ jobs: deploy-docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 with: fetch-depth: 0 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v7 with: python-version: "3.12" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 34188d0..96dfd9e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,9 +18,9 @@ jobs: permissions: id-token: write # required for Trusted Publishing (OIDC) steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v7 with: python-version: "3.12" diff --git a/.github/workflows/release-app.yml b/.github/workflows/release-app.yml index 0ed577e..58edac1 100644 --- a/.github/workflows/release-app.yml +++ b/.github/workflows/release-app.yml @@ -36,9 +36,9 @@ jobs: bin: dist/glia-shell runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v7 with: python-version: "3.12" @@ -64,7 +64,7 @@ jobs: run: cp "${{ matrix.bin }}" "${{ matrix.asset }}" - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ matrix.asset }} path: ${{ matrix.asset }} @@ -76,7 +76,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download all binaries - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: bins merge-multiple: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..3e4f250 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,42 @@ +# Install with: pre-commit install +# +# ruff-format runs here and not in CI on purpose. Twenty files predate the +# formatter; a CI gate would be red on day one, while this hook only touches +# what a commit already changes. +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.20 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-added-large-files + args: [--maxkb=512] + - id: check-merge-conflict + - id: check-toml + - id: check-yaml + - id: end-of-file-fixer + - id: mixed-line-ending + args: [--fix=lf] + - id: trailing-whitespace + + - repo: local + hooks: + # A PyInstaller build directory is the output of release-app.yml. It is + # large, it is per-platform, and it has no business in the history. + - id: reject-pyinstaller-output + name: reject PyInstaller build output + entry: build/ and dist/ are release output, not source + language: fail + files: ^(build|dist)/ + + - id: pytest + name: pytest + entry: pytest -q + language: system + pass_filenames: false + stages: [pre-push] From 4ff3a78bbaa3888a401ba13763734901445a41f9 Mon Sep 17 00:00:00 2001 From: Denis_Drobyshev Date: Wed, 5 Aug 2026 22:03:55 +0300 Subject: [PATCH 2/2] Leave dependabot.yml to the pull request that already adds it PR #1 has been open since 3 August with that file. Duplicating it here would have made whichever merged second a conflict. --- .github/dependabot.yml | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index f661c53..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: 2 - -updates: - # Actions first: a stale third-party action is a supply-chain risk in every - # workflow that uses it, and the bump is almost always safe to take. - - package-ecosystem: github-actions - directory: "/" - schedule: - interval: weekly - day: monday - commit-message: - prefix: "ci" - groups: - actions: - patterns: ["*"] - - - package-ecosystem: pip - directory: "/" - schedule: - interval: weekly - day: monday - commit-message: - prefix: "deps" - groups: - dev-tooling: - patterns: ["ruff", "mypy", "pytest*", "pre-commit", "build", "twine"] - dependency-type: development - docs: - patterns: ["mkdocs*"] - # The core has zero required dependencies and every provider lives behind an - # extra. There is nothing here Dependabot can bump that a user is forced to - # install, which is why the limit is low: the queue should stay short enough - # to read. - open-pull-requests-limit: 5