diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c246aa2..5a5dc33 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,20 +4,38 @@ updates: directory: "/" schedule: interval: "weekly" + assignees: + - "eschaar" open-pull-requests-limit: 10 labels: - "dependencies" - "python" commit-message: prefix: "chore(deps)" + groups: + pip-patch-minor: + patterns: + - "*" + update-types: + - "patch" + - "minor" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" + assignees: + - "eschaar" open-pull-requests-limit: 5 labels: - "dependencies" - "github-actions" commit-message: prefix: "chore(ci)" + groups: + gha-patch-minor: + patterns: + - "*" + update-types: + - "patch" + - "minor" diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000..78f15df --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,26 @@ +# GitHub Workflows + +This folder contains repository automation workflows. + +For the full CI/CD design and release model, see `docs/design/cicd.md`. + +## Quick Map + +| Workflow | Trigger | Purpose | +| --- | --- | --- | +| `commit.yml` | push to non-main branches and PRs to `main` | Commit/branch policy and lint/typecheck | +| `check.yml` | push to non-main branches and PRs to `main` | Single-version unit test feedback (py3.11) | +| `verify.yml` | pull_request to `main` | Required verification checks: cross-version test matrix + artifact verify | +| `security.yml` | pull_request to `main` | Required security checks | +| `automerge.yml` | pull_request_target to `main` | Dependabot safe auto-merge policy | +| `release.yml` | push to `main` | Release Please orchestration | +| `publish.yml` | release published | Build and publish to PyPI | + +## Operational Notes + +1. `commit.yml` handles commit policy and lint/typecheck on branch pushes and PRs. +2. `check.yml` provides single-version test feedback on branch pushes and PRs. +3. `verify.yml` and `security.yml` are the required PR gates. +4. `release.yml` is the only release orchestrator. +5. `publish.yml` is publish-only and never computes versions. +6. Ruleset on `main` should require `Commit`, `Check`, `Verify` (all jobs), and `Security` before merge. diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 0000000..a639a05 --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,93 @@ +name: "Dependabot Safe Auto-merge for Patch and Minor Updates" + +on: + pull_request_target: + branches: + - main + types: + - opened + - synchronize + - reopened + +permissions: + contents: write + pull-requests: write + +jobs: + auto-merge: + if: github.actor == 'dependabot[bot]' + runs-on: ubuntu-latest + + steps: + - name: Fetch Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Decide if this PR is eligible + id: decision + env: + PACKAGE_ECOSYSTEM: ${{ steps.metadata.outputs.package-ecosystem }} + UPDATE_TYPE: ${{ steps.metadata.outputs.update-type }} + run: | + should_automerge=false + + if [[ "$PACKAGE_ECOSYSTEM" == "github-actions" ]]; then + if [[ "$UPDATE_TYPE" == "version-update:semver-patch" || "$UPDATE_TYPE" == "version-update:semver-minor" ]]; then + should_automerge=true + fi + fi + + if [[ "$PACKAGE_ECOSYSTEM" == "pip" ]]; then + if [[ "$UPDATE_TYPE" == "version-update:semver-patch" ]]; then + should_automerge=true + fi + fi + + echo "should_automerge=$should_automerge" >> "$GITHUB_OUTPUT" + echo "package_ecosystem=$PACKAGE_ECOSYSTEM" >> "$GITHUB_OUTPUT" + echo "update_type=$UPDATE_TYPE" >> "$GITHUB_OUTPUT" + + - name: Approve eligible PR + if: steps.decision.outputs.should_automerge == 'true' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + await github.rest.pulls.createReview({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + event: "APPROVE", + body: "Auto-approved for safe Dependabot update policy." + }) + + - name: Enable auto-merge for eligible PR + if: steps.decision.outputs.should_automerge == 'true' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + try { + await github.graphql( + `mutation($pullRequestId: ID!) { + enablePullRequestAutoMerge(input: {pullRequestId: $pullRequestId, mergeMethod: SQUASH}) { + pullRequest { number } + } + }`, + { pullRequestId: context.payload.pull_request.node_id } + ) + } catch (error) { + core.setFailed( + "Could not enable auto-merge. Ensure repository auto-merge is enabled and branch protections are satisfied.\n" + + error.message + ) + } + + - name: Log skipped PR + if: steps.decision.outputs.should_automerge != 'true' + run: | + echo "Automerge skipped by policy." + echo "ecosystem=${{ steps.decision.outputs.package_ecosystem }}" + echo "update_type=${{ steps.decision.outputs.update_type }}" diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..4a4a383 --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,55 @@ +# Check workflow. +# Purpose: run fast single-version unit tests for branch and PR feedback. +# This complements verify.yml, which runs the full cross-version matrix and artifact checks. +name: "Check" + +on: + push: + # Mainline, merge-queue, and release-please refs are covered by other workflows. + branches-ignore: + - main + - master + - merge/** + - gh-readonly-queue/** + - release-please--branches--** + pull_request: + branches: [main] + +concurrency: + # Cancel superseded runs for the same ref. + group: check-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +env: + PYTHON_VERSION: "3.11" + POETRY_VERSION: "2.3.4" + POETRY_VIRTUALENVS_IN_PROJECT: "true" + +jobs: + test: + # Single-version unit test run for fast feedback. + name: Unit Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install Poetry + run: pipx install "poetry==${POETRY_VERSION}" + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: poetry + cache-dependency-path: poetry.lock + + - name: Install dependencies + run: poetry install --no-interaction --no-ansi + + - name: Test + run: make test-local diff --git a/.github/workflows/commit.yml b/.github/workflows/commit.yml index 919e0cb..20fc7c2 100644 --- a/.github/workflows/commit.yml +++ b/.github/workflows/commit.yml @@ -1,30 +1,37 @@ # Commit workflow. -# Purpose: fail fast on branch pushes when commit messages do not match -# repository commit conventions. +# Purpose: enforce commit policy and quality checks (format/lint/typecheck). +# Runs on branch pushes and PRs so commit/quality policy can be merge-blocking. +# Unit tests are split into check.yml (single-version) and verify.yml (matrix + artifact verify). name: Commit on: - # Validate commits on branch pushes before PR merge. push: - # Mainline and merge-queue refs are validated by PR/release workflows. + # Mainline, merge-queue, and release-please refs are covered by other workflows. branches-ignore: - main - master - merge/** - gh-readonly-queue/** + - release-please--branches--** + pull_request: + branches: [main] concurrency: - # Cancel superseded commit-message checks on the same branch. + # Cancel superseded runs on the same branch. group: commit-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: - # Read-only is enough for commit metadata checks. contents: read +env: + PYTHON_VERSION: "3.11" + POETRY_VERSION: "2.3.4" + POETRY_VIRTUALENVS_IN_PROJECT: "true" + jobs: - validate-commit-messages: - # Single gate that validates message format/types and custom scope policy. + validate: + # Validates commit message format, branch naming, and reserved scope policy. name: Validate Commit Messages runs-on: ubuntu-latest steps: @@ -35,6 +42,7 @@ jobs: fetch-depth: 0 - name: Validate commits with commit-check + if: github.event_name == 'push' uses: commit-check/commit-check-action@v2 with: # Commit and branch policy is read from cchk.toml in repo root. @@ -44,3 +52,104 @@ jobs: author-email: false job-summary: true pr-comments: false + + - name: Validate commit messages with commit-check + if: github.event_name == 'pull_request' + uses: commit-check/commit-check-action@v2 + with: + # On PR events, validate commit messages only (branch refs are pull/*). + message: true + branch: false + author-name: false + author-email: false + job-summary: true + pr-comments: false + + - name: Validate PR branch name + if: github.event_name == 'pull_request' + shell: bash + run: | + BRANCH="${{ github.head_ref }}" + + # Release-please uses its own generated branch naming format. + if [[ "$BRANCH" =~ ^release-please--branches--.+$ ]]; then + echo "release-please branch is allowed: $BRANCH" + exit 0 + fi + + if [[ ! "$BRANCH" =~ ^([a-z0-9-]+)/.+$ ]]; then + echo "ERROR: invalid branch name '$BRANCH'. Expected 'type/description'." + exit 1 + fi + + TYPE="${BASH_REMATCH[1]}" + case "$TYPE" in + feature|bugfix|hotfix|release|chore|feat|fix|docs|refactor|perf|test|ci|build|style|opt|patch|dependabot) + echo "branch type '$TYPE' is allowed." + ;; + *) + echo "ERROR: branch type '$TYPE' is not allowed." + echo "Allowed types: feature bugfix hotfix release chore feat fix docs refactor perf test ci build style opt patch dependabot" + exit 1 + ;; + esac + + - name: Reserve docs(changelog) scope for automation + shell: bash + run: | + TRUSTED_AUTHORS='^(github-actions\[bot\]|vstack-release-bot\[bot\])$' + + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + COMMITS="$(git log --format='%h%x09%s%x09%an' "${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}")" + else + if [[ "${GITHUB_ACTOR}" =~ $TRUSTED_AUTHORS ]]; then + echo "docs(changelog) scope allowed for automation actor." + exit 0 + fi + COMMITS="$(git log -1 --format='%h%x09%s%x09%an' "${{ github.sha }}")" + fi + + VIOLATION_FOUND=false + while IFS=$'\t' read -r SHA SUBJECT AUTHOR; do + [[ "$SUBJECT" =~ ^docs\(changelog\): ]] || continue + [[ "$AUTHOR" =~ $TRUSTED_AUTHORS ]] && continue + echo "ERROR: commit $SHA uses reserved docs(changelog) scope (author: $AUTHOR)." + VIOLATION_FOUND=true + done <<< "$COMMITS" + + if [[ "$VIOLATION_FOUND" == "true" ]]; then + echo "ERROR: docs(changelog) scope is reserved for automated changelog/release commits." + echo "Use another scope for manual documentation commits." + exit 1 + fi + + quality: + # Lint, format, and typecheck on the baseline Python version. + name: Format Lint Typecheck + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install Poetry + run: pipx install "poetry==${POETRY_VERSION}" + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: poetry + cache-dependency-path: poetry.lock + + - name: Install dependencies + run: poetry install --no-interaction --no-ansi + + - name: Format check + run: make format-check + + - name: Lint + run: make lint + + - name: Typecheck + run: make typecheck diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..b1dda99 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,120 @@ +# Publish workflow. +# Purpose: build and publish artifacts when a GitHub release is published. +# This workflow does not compute versions or create tags. +name: "Publish" + +on: + release: + types: + - published + +permissions: + contents: read + id-token: write + +env: + PYTHON_VERSION: "3.11" + POETRY_VERSION: "2.3.4" + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + TRUSTED_RELEASE_ACTORS: "vstack-release-bot[bot],eschaar" + +jobs: + publish: + name: Build and Publish to PyPI + if: github.event.release.prerelease == false + runs-on: ubuntu-latest + environment: pypi + + steps: + - name: Validate release tag format + # Fail fast before checkout if the tag is not a plain SemVer X.Y.Z. + # Prevents non-versioned or pre-release tags from reaching the publish step. + shell: bash + run: | + TAG="${{ github.event.release.tag_name }}" + if [[ ! "$TAG" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "ERROR: release tag '$TAG' is not in expected SemVer X.Y.Z format." + echo "Only plain version tags (e.g. 1.2.3) are permitted to trigger publish." + exit 1 + fi + echo "tag=$TAG format is valid." + + - name: Validate release actor + # Only allow trusted actors to trigger a PyPI publish. + # vstack-release-bot[bot] covers release-please GitHub App automation. + # eschaar covers direct maintainer releases. + shell: bash + run: | + ACTOR="${{ github.event.release.author.login }}" + IFS=',' read -r -a ALLOWED <<< "$TRUSTED_RELEASE_ACTORS" + + TRUSTED=false + for allowed_actor in "${ALLOWED[@]}"; do + if [[ "$ACTOR" == "$allowed_actor" ]]; then + TRUSTED=true + break + fi + done + + if [[ "$TRUSTED" != "true" ]]; then + echo "ERROR: release created by '$ACTOR' which is not in the trusted actor list." + echo "Allowed: $TRUSTED_RELEASE_ACTORS" + echo "Releases must be created by vstack-release-bot automation or a trusted maintainer." + exit 1 + fi + + echo "release_actor='$ACTOR' is trusted." + + - name: Checkout release tag + uses: actions/checkout@v6 + with: + ref: refs/tags/${{ github.event.release.tag_name }} + fetch-depth: 0 + + - name: Install Poetry + run: pipx install "poetry==${POETRY_VERSION}" + + - name: Install poetry-dynamic-versioning plugin + run: pipx inject poetry "poetry-dynamic-versioning[plugin]>=1.0.0,<2.0.0" + + - name: Setup Python + uses: actions/setup-python@v6 + with: + python-version: ${{ env.PYTHON_VERSION }} + cache: poetry + cache-dependency-path: poetry.lock + + - name: Validate release tag checkout + run: | + EXPECTED="${{ github.event.release.tag_name }}" + ACTUAL_TAG="$(git tag --points-at HEAD | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1)" + echo "expected_tag=$EXPECTED" + echo "head_tag=$ACTUAL_TAG" + if [[ "$ACTUAL_TAG" != "$EXPECTED" ]]; then + echo "ERROR: build checkout is not pinned to expected release tag." + exit 1 + fi + + - name: Build distributions + run: poetry build + + - name: Smoke test built wheel + run: | + python -m pip install --upgrade pip + python -m pip install --no-deps dist/*.whl + vstack --help >/dev/null + + - name: Validate built artifact version + run: | + EXPECTED="${{ github.event.release.tag_name }}" + shopt -s nullglob + MATCHES=(dist/*"$EXPECTED"*.whl dist/*"$EXPECTED"*.tar.gz) + echo "expected_version=$EXPECTED" + ls -1 dist/ + if [[ ${#MATCHES[@]} -eq 0 ]]; then + echo "ERROR: no built artifacts contain expected version '$EXPECTED'." + exit 1 + fi + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml deleted file mode 100644 index 37d1e06..0000000 --- a/.github/workflows/qa.yml +++ /dev/null @@ -1,87 +0,0 @@ -# Quality gate for branch pushes (excluding main). -# Purpose: keep fast feedback for formatting/lint/type checks and run tests across -# supported Python versions. -name: QA - -on: - # Run on every non-main branch push. - push: - branches-ignore: [main] - -concurrency: - # Cancel superseded QA runs for the same branch. - group: qa-${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - # Workflow only needs read access to repository contents. - contents: read - -env: - # Baseline interpreter for non-matrix checks. - PYTHON_VERSION: "3.11" - # Pin Poetry CLI version for deterministic CI behavior. - POETRY_VERSION: "2.3.4" - # Keep Poetry virtual environments inside the workspace for deterministic paths. - POETRY_VIRTUALENVS_IN_PROJECT: "true" - -jobs: - quality: - # Fast quality checks run once on the baseline Python version. - name: Format Lint Typecheck - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Install Poetry - run: pipx install "poetry==${POETRY_VERSION}" - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: ${{ env.PYTHON_VERSION }} - cache: poetry - cache-dependency-path: poetry.lock - - - name: Install dependencies - run: poetry install --no-interaction --no-ansi - - - name: Format check - run: make format-check - - - name: Lint - run: make lint - - - name: Typecheck - run: make typecheck - - test-matrix: - # Cross-version test coverage for all supported Python runtimes. - name: Tests (py${{ matrix.python-version }}) - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.11", "3.12", "3.13", "3.14"] - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Install Poetry - run: pipx install "poetry==${POETRY_VERSION}" - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: ${{ matrix.python-version }} - cache: poetry - cache-dependency-path: poetry.lock - - - name: Install dependencies - run: poetry install --no-interaction --no-ansi - - - name: Test - run: make test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f285d9b..6ac0f2f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,234 +1,66 @@ -# Release workflow. -# Trigger model: runs on pushes to main. -# Using push instead of pull_request so the workflow runs under refs/heads/main, -# which satisfies the PyPI environment deployment branch protection rule. -name: Release +# Release orchestration workflow. +# Purpose: maintain a release PR and, once merged, create the real tag/release. +# This workflow owns versioning, changelog, and GitHub release notes via release-please. +name: "Release" on: push: - branches: [main] + branches: + - main + workflow_dispatch: concurrency: - # Serialize main releases to avoid concurrent tag/release races. + # Serialize release orchestration to avoid overlapping release-please runs. group: release-${{ github.ref }} cancel-in-progress: false permissions: - # Default to read-only; release job elevates to write for tagging/releases. contents: read -env: - # Shared interpreter version for build and metadata steps. - PYTHON_VERSION: "3.11" - # Pin Poetry CLI version for deterministic release builds. - POETRY_VERSION: "2.3.4" - # Opt in to Node.js 24 for JavaScript-based actions ahead of runner defaults. - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" - jobs: release: - # Computes semantic version from conventional commits and creates git tag. - name: Compute Version and Tag + if: github.ref == 'refs/heads/main' + name: Release Please (PR + Tag + Notes) runs-on: ubuntu-latest - permissions: - # Needed for creating tags and GitHub releases. - contents: write - outputs: - changed: ${{ steps.version.outputs.changed }} - version: ${{ steps.version.outputs.version }} steps: - - name: Checkout - uses: actions/checkout@v6 - with: - # Full history is required by semver-action to inspect commit history. - fetch-depth: 0 - - - name: Compute semantic version from conventional commits - id: semver - uses: ietf-tools/semver-action@v1.11.0 + - name: Generate app token + id: app-token + uses: actions/create-github-app-token@v2 with: - token: ${{ github.token }} - branch: main - # Accept only plain SemVer tags in this repository (no v-prefix). - tagFilter: '^[0-9]+\.[0-9]+\.[0-9]+$' - prefix: "" - skipInvalidTags: true - maxTagsToFetch: 50 - - # Conventional commit mappings for release bump policy. - minorList: "feat,feature" - patchList: "fix,bugfix,hotfix,opt,patch,perf,refactor,chore,revert" - - # Keep release workflow non-failing when a rerun has no new commits, - # or when no commit maps to a bump category. - noNewCommitBehavior: current - noVersionBumpBehavior: current - - - name: Normalize version outputs - id: version - run: | - if [[ "${{ steps.semver.outputs.bump }}" == "none" ]]; then - echo "changed=false" >> "$GITHUB_OUTPUT" - echo "version=${{ steps.semver.outputs.current }}" | sed 's/^version=v/version=/' >> "$GITHUB_OUTPUT" - else - echo "changed=true" >> "$GITHUB_OUTPUT" - echo "version=${{ steps.semver.outputs.nextStrict }}" >> "$GITHUB_OUTPUT" - fi - - - name: Show computed version - run: | - echo "bump=${{ steps.semver.outputs.bump }}" - echo "changed=${{ steps.version.outputs.changed }}" - echo "version=${{ steps.version.outputs.version }}" + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} - - name: Create and push git tag - if: steps.version.outputs.changed == 'true' - run: | - VERSION="${{ steps.version.outputs.version }}" - [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { - echo "ERROR: release tag must match X.Y.Z without a v-prefix (got '$VERSION')." - exit 1 - } - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git show-ref --verify --quiet "refs/tags/$VERSION" && { - echo "ERROR: tag '$VERSION' already exists."; - exit 1; - } - git tag -a "$VERSION" -m "release $VERSION" - git push origin "$VERSION" - - build: - # Build distributions only when semver-action reports a new release. - name: Build Package Artifacts - runs-on: ubuntu-latest - needs: release - if: needs.release.outputs.changed == 'true' - - steps: - - name: Checkout + - name: Checkout repository metadata uses: actions/checkout@v6 with: - # Build from the newly created release tag to guarantee package version. - ref: refs/tags/${{ needs.release.outputs.version }} fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} - - name: Install Poetry - run: pipx install "poetry==${POETRY_VERSION}" - - - name: Install poetry-dynamic-versioning plugin - run: pipx inject poetry "poetry-dynamic-versioning[plugin]>=1.0.0,<2.0.0" - - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: ${{ env.PYTHON_VERSION }} - cache: poetry - cache-dependency-path: poetry.lock - - - name: Validate release tag checkout + - name: Validate release-please manifest matches latest tag + shell: bash run: | - EXPECTED="${{ needs.release.outputs.version }}" - ACTUAL_TAG="$(git tag --points-at HEAD | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1)" - echo "expected_tag=$EXPECTED" - echo "head_tag=$ACTUAL_TAG" - if [[ "$ACTUAL_TAG" != "$EXPECTED" ]]; then - echo "ERROR: build checkout is not pinned to expected release tag."; - exit 1 - fi + git fetch --tags --force - - name: Show active Poetry plugins - run: poetry self show plugins + latest_tag="$(git tag --list | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 1)" + manifest_version="$(jq -r '.["."]' .release-please-manifest.json)" - - name: Build distributions - run: poetry build - - - name: Smoke test built wheel - run: | - python -m pip install --upgrade pip - python -m pip install --no-deps dist/*.whl - vstack --help >/dev/null - - - name: Validate built artifact version - run: | - EXPECTED="${{ needs.release.outputs.version }}" - shopt -s nullglob - MATCHES=(dist/*"$EXPECTED"*.whl dist/*"$EXPECTED"*.tar.gz) - echo "expected_version=$EXPECTED" - ls -1 dist/ - if [[ ${#MATCHES[@]} -eq 0 ]]; then - echo "ERROR: no built artifacts contain expected version '$EXPECTED'." - exit 1 + if [[ -z "$latest_tag" ]]; then + echo "No SemVer tags found; skipping manifest drift check." + exit 0 fi - - name: Upload build artifacts - uses: actions/upload-artifact@v7 - with: - name: python-dist - path: dist/ - - publish: - # Publish distributions to PyPI via OIDC trusted publishing (no API tokens). - name: Publish to PyPI - runs-on: ubuntu-latest - needs: [release, build] - if: needs.release.outputs.changed == 'true' - environment: pypi - permissions: - # Required for OIDC trusted publishing. - id-token: write - - steps: - - name: Download build artifacts - uses: actions/download-artifact@v8 - with: - name: python-dist - path: dist/ - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - - cleanup-failed-release-tag: - # Delete freshly created release tag when downstream jobs fail. - name: Cleanup Failed Release Tag - runs-on: ubuntu-latest - needs: [release, build, publish] - if: ${{ always() && needs.release.outputs.changed == 'true' && (needs.build.result == 'failure' || needs.publish.result == 'failure') }} - permissions: - contents: write - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Delete failed release tag - run: | - VERSION="${{ needs.release.outputs.version }}" - if git ls-remote --exit-code --tags origin "refs/tags/$VERSION" >/dev/null; then - git push origin ":refs/tags/$VERSION" - echo "Deleted failed release tag '$VERSION' from origin." - else - echo "Tag '$VERSION' already absent; nothing to delete." + if [[ "$manifest_version" != "$latest_tag" ]]; then + echo "ERROR: .release-please-manifest.json is stale." + echo "manifest version: $manifest_version" + echo "latest git tag: $latest_tag" + echo "Update .release-please-manifest.json before running release orchestration." + exit 1 fi - github-release: - # Create GitHub Release only after package build and publish succeed. - name: Publish GitHub Release - runs-on: ubuntu-latest - needs: [release, publish] - if: needs.release.outputs.changed == 'true' - permissions: - contents: write - - steps: - - name: Compute release date - id: release_date - run: echo "date=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT" - - - name: Create GitHub release - uses: softprops/action-gh-release@v3 + - name: Run release-please + uses: googleapis/release-please-action@v5 with: - tag_name: ${{ needs.release.outputs.version }} - name: Release v${{ needs.release.outputs.version }} (${{ steps.release_date.outputs.date }}) - generate_release_notes: true + token: ${{ steps.app-token.outputs.token }} + config-file: .release-please-config.json + manifest-file: .release-please-manifest.json diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 51b6ff3..8c85521 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -22,6 +22,8 @@ env: PYTHON_VERSION: "3.11" # Pin Poetry CLI version for deterministic CI behavior. POETRY_VERSION: "2.3.4" + # Pin pip-audit to avoid non-deterministic CI failures from tool updates. + PIP_AUDIT_VERSION: "2.9.0" jobs: security: @@ -44,7 +46,7 @@ jobs: cache-dependency-path: poetry.lock - name: Install pip-audit - run: pipx install pip-audit + run: pipx install "pip-audit==${PIP_AUDIT_VERSION}" - name: Install dependencies run: poetry install --no-interaction --no-ansi diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index bdb1b4d..ef79361 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -1,5 +1,6 @@ # Pull request verification workflow. -# Purpose: validate both source tests and generated/installable artifacts. +# Purpose: cross-version test matrix and artifact install verify as required PR gates. +# This is the primary merge gate for all PRs to main. name: Verify on: @@ -25,10 +26,15 @@ env: POETRY_VIRTUALENVS_IN_PROJECT: "true" jobs: - unit-tests: - # Fast correctness check for repository source code. - name: Unit Tests + test-matrix: + # Cross-version test coverage for all supported Python runtimes. + # Runs on all PRs to main as a required merge gate. + name: Tests (py${{ matrix.python-version }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.11", "3.12", "3.13", "3.14"] steps: - name: Checkout @@ -40,15 +46,15 @@ jobs: - name: Setup Python uses: actions/setup-python@v6 with: - python-version: ${{ env.PYTHON_VERSION }} + python-version: ${{ matrix.python-version }} cache: poetry cache-dependency-path: poetry.lock - name: Install dependencies run: poetry install --no-interaction --no-ansi - - name: Run unit tests - run: make test + - name: Test + run: make test-local vstack-verify: # Ensures generated artifacts can be installed and validated in an isolated target. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a65d9ce..055bf11 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: check-yaml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.11 + rev: v0.15.12 hooks: - id: ruff args: [--fix] @@ -32,7 +32,7 @@ repos: exclude: ^\.github/ - repo: https://github.com/DavidAnson/markdownlint-cli2 - rev: v0.22.0 + rev: v0.22.1 hooks: - id: markdownlint-cli2 args: diff --git a/.release-please-config.json b/.release-please-config.json new file mode 100644 index 0000000..9832f73 --- /dev/null +++ b/.release-please-config.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "include-v-in-tag": false, + "pull-request-title-pattern": "docs(changelog): ${version}", + "packages": { + ".": { + "release-type": "simple", + "changelog-path": "CHANGELOG.md", + "changelog-sections": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "feature", + "section": "Features" + }, + { + "type": "fix", + "section": "Fixes" + }, + { + "type": "bugfix", + "section": "Fixes" + }, + { + "type": "hotfix", + "section": "Fixes" + }, + { + "type": "perf", + "section": "Performance" + }, + { + "type": "refactor", + "section": "Refactoring" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "opt", + "section": "Maintenance" + }, + { + "type": "patch", + "section": "Maintenance" + }, + { + "type": "chore", + "section": "Maintenance" + }, + { + "type": "revert", + "section": "Maintenance" + } + ] + } + } +} diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..47fb725 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "2.0.2" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a6a6fc..fae7462 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,17 @@ # Changelog -## 2.0.0 - 2026-04-26 + + +## 2.0.0 (2026-04-26) CLI architecture refactor and manifest package extraction. **BREAKING CHANGE** — import paths have changed. -### Breaking changes in 2.0.0 +### BREAKING CHANGES - `vstack.cli.commands` removed. `CommandService` and command classes now live in dedicated modules (`vstack.cli.service`, `vstack.cli.install`, `vstack.cli.verify`, etc.). - Manifest persistence classes moved to new `vstack.manifest` package (`vstack.manifest.store`). -### Added in 2.0.0 +### Features - `vstack manifest upgrade --backfill`: retroactively compute and write checksums for tracked manifest entries with no checksum. - New `vstack.manifest` package with schema-versioned manifest read/write and `content_hash` utility. @@ -17,20 +19,20 @@ CLI architecture refactor and manifest package extraction. **BREAKING CHANGE** - mypy type checking added as a quality gate (106 files, 0 errors). - 4 new ADRs: manifest schema versioning (014), conservative install defaults (015), atomic manifest writes (016), checksum backfill (017). -### Fixed in 2.0.0 +### Fixes - `InstallCommand._version_gt` no longer raises `TypeError` when `existing` version is `None` on first install. -### Internal in 2.0.0 +### Maintenance - Full test suite restructured: per-module test files, `TestClass` layout, catch-all files deleted. Test count: 288 → 342. - End-to-end integration tests consolidated into `tests/vstack/test_integration.py`. -## 1.3.6 - 2026-04-22 +## 1.3.6 (2026-04-22) README and PyPI README badge/layout alignment. -### Fixed in 1.3.6 +### Fixes - Fixed oversized logo rendering in `README-pypi.md` by constraining image width. - Fixed duplicate title/branding in `README-pypi.md` by removing redundant `# vstack` heading. @@ -38,26 +40,26 @@ README and PyPI README badge/layout alignment. - Fixed PyPI version badge formatting in both `README.md` and `README-pypi.md` to show the raw version (no `v` prefix). - Fixed verify/security workflow badges in both `README.md` and `README-pypi.md` by removing `branch=main` filter so PR-based workflows report status correctly. -## 1.3.5 - 2026-04-22 +## 1.3.5 (2026-04-22) Changelog and PyPI packaging metadata alignment update. -### Changed in 1.3.5 +### Maintenance - Corrected changelog version history from 1.3.0 onward so entries align with actual created tags and release chronology. - Switched published long description source from `README.md` to `README-pypi.md` for PyPI-compatible rendering. - Added PyPI-focused project metadata in `pyproject.toml`: `keywords`, `classifiers`, and `project.urls`. - Added explicit repository guidance to keep `README-pypi.md` in sync with `README.md`. -### Added in 1.3.5 +### Features - Added a dedicated `README-pypi.md` with PyPI-safe links, badges, and a concise DX-first quickstart. -## 1.3.4 - 2026-04-22 +## 1.3.4 (2026-04-22) Release build versioning fix: explicit plugin activation and full history checkout. -### Fixed in 1.3.4 +### Fixes - Fixed CI release builds still producing `0.0.0` artifacts by calling `poetry dynamic-versioning` explicitly before `poetry build`. Poetry reads the version once at load time — the plugin must be active and called before the build step runs. - Fixed release build tag visibility by setting `fetch-depth: 0` on the tag-pinned checkout so `git describe` can traverse full history. @@ -70,40 +72,40 @@ Release build versioning fix: explicit plugin activation and full history checko - Added post-build wheel smoke test (`pip install --no-deps` + `vstack --help`) before artifact upload to avoid network-dependent dependency resolution. - Clarified PR workflow concurrency comments in `security.yml` and `verify.yml` to match `github.ref` behavior (`refs/pull//merge`). -## 1.3.3 - 2026-04-22 +## 1.3.3 (2026-04-22) Release build plugin activation fix. -### Fixed in 1.3.3 +### Fixes - Fixed CI release builds producing `0.0.0` artifacts by installing `poetry-dynamic-versioning` as a Poetry plugin via `pipx inject` in the release build job. - Fixed release build reproducibility by pinning the Poetry CLI version (`POETRY_VERSION`) in workflow environment configuration. - Fixed CI drift by aligning Poetry installation to the same pinned version across `release.yml`, `qa.yml`, `verify.yml`, and `security.yml`. -## 1.3.2 - 2026-04-22 +## 1.3.2 (2026-04-22) Release build version-guard fix. -### Fixed in 1.3.2 +### Fixes - Fixed release build determinism by checking out `refs/tags/` in the build job instead of building from a moving branch ref. - Fixed accidental `0.0.0` package publishing by validating that HEAD is pinned to the expected release tag before build and that produced artifacts include the expected version. -## 1.3.1 - 2026-04-22 +## 1.3.1 (2026-04-22) Release workflow and test isolation fixes. -### Fixed in 1.3.1 +### Fixes - Fixed release workflow trigger: switched from `pull_request: closed` to `push: branches: [main]` so the workflow runs under `refs/heads/main` and satisfies the PyPI environment deployment branch protection rule. - Fixed `build` job checkout configuration so `poetry-dynamic-versioning` can read git tags during the build. - Fixed `download-artifact` version mismatch (`v5` → `v7`) to align with `upload-artifact@v7`. -## 1.3.0 - 2026-04-22 +## 1.3.0 (2026-04-22) DX, onboarding, and PyPI publishing release. -### Added in 1.3.0 +### Features - Added GitHub Discussion templates for onboarding and adoption feedback: - `onboarding-feedback` @@ -113,66 +115,66 @@ DX, onboarding, and PyPI publishing release. - Added explicit expected output examples for first install validation in `README.md`. - Added a troubleshooting decision flowchart in `README.md`. -### Changed in 1.3.0 +### Maintenance - Restructured `README.md` for faster onboarding with clearer quick paths, role usage guidance, and troubleshooting navigation. - Updated architect and product agent template model ordering and regenerated installed agent artifacts. - Updated generated artifact metadata and aligned generation tests with current template output. - Added PyPI publish job to release workflow using OIDC trusted publishing (no API tokens required). -### Fixed in 1.3.0 +### Fixes - Fixed `test_install_and_verify_exits_zero` writing generated artifacts into the repository root instead of an isolated `tmp_path`. -## 1.2.5 - 2026-04-21 +## 1.2.5 (2026-04-21) CI dependency maintenance release. -### Changed in 1.2.5 +### Maintenance - GitHub Actions: bumped `actions/checkout` from v5 to v6. -## 1.2.4 - 2026-04-21 +## 1.2.4 (2026-04-21) CI dependency maintenance release. -### Changed in 1.2.4 +### Maintenance - GitHub Actions: bumped `actions/upload-artifact` from v4 to v7. -## 1.2.3 - 2026-04-21 +## 1.2.3 (2026-04-21) Release workflow dependency maintenance. -### Changed in 1.2.3 +### Maintenance - GitHub Actions: bumped `softprops/action-gh-release` from v2 to v3. -## 1.2.2 - 2026-04-21 +## 1.2.2 (2026-04-21) Security workflow dependency maintenance. -### Changed in 1.2.2 +### Maintenance - GitHub Actions: bumped `trufflesecurity/trufflehog` from `3.88.2` to `3.94.3`. -## 1.2.1 - 2026-04-21 +## 1.2.1 (2026-04-21) README rendering fix release. -### Fixed in 1.2.1 +### Fixes - Fixed Mermaid flowchart syntax in `README.md` so GitHub renders the role-flow diagram correctly. -## 1.2.0 - 2026-04-21 +## 1.2.0 (2026-04-21) CLI provenance verification and documentation system alignment. -### Added in 1.2.0 +### Features - CLI artifact provenance verification against the install manifest. -### Changed in 1.2.0 +### Maintenance - Refactored CLI parser flow into a `CommandLineParser` class and simplified install and verify control flow. - Refactored frontmatter serialization internals (instance-method serializer, naming cleanup, and reduced nested parse/validation flow). @@ -185,11 +187,11 @@ CLI provenance verification and documentation system alignment. - Updated `CONTRIBUTING.md` commit-policy wording to match the current `cchk.toml` enforcement model. - Strengthened generated skill footer tests to assert the `AUTO-GENERATED` and `VSTACK-META` footer structure at end-of-file. -## 1.1.0 — 2026-04-20 +## 1.1.0 (2026-04-20) Runtime response-style control via the new `concise` skill. -### Added in 1.1.0 +### Features - New `concise` skill — runtime response-style toggle with three density modes: - `concise normal` — full, explicit explanation depth. @@ -200,7 +202,7 @@ Runtime response-style control via the new `concise` skill. - Per-role default concise modes wired into all 6 agent templates: `product=compact`, `architect=normal`, `designer=compact`, `engineer=compact`, `tester=ultra`, `release=compact`. - Auto-clarity override: security warnings, destructive actions, and multi-step sequences always force `normal` regardless of active mode. -### Changed in 1.1.0 +### Maintenance - All 6 role agent templates now reference `@#concise` in their `## skills you use` section. - `EXPECTED_CANONICAL_NAMES` in `tests/conftest.py` now imports from `vstack.cli.constants` instead of duplicating the list. @@ -223,26 +225,26 @@ Runtime response-style control via the new `concise` skill. - Added explicit observability checks in verification flows (`inspect` and `verify`) for logs, metrics, traces, and alert/runbook evidence. - Regenerated `.github` installed artifacts to match updated templates and policies. -## 1.0.5 — 2026-04-19 +## 1.0.5 (2026-04-19) Workflow hardening and release-manifest refresh. -### Fixed in 1.0.5 +### Fixes - GitHub Actions workflows now declare explicit `permissions` to satisfy policy checks and follow least-privilege defaults. -### Changed in 1.0.5 +### Maintenance - `release.yml` now defaults to read-only workflow permissions and scopes `contents: write` to the `version-and-release` job only. - `qa.yml`, `security.yml`, and `verify.yml` now declare explicit workflow-level `permissions`. - `verify.yml` normalized to use `on:` (unquoted) for style consistency with other workflows. - `.github/vstack.json` refreshed via install to record the latest generated artifact manifest metadata. -## 1.0.4 — 2026-04-19 +## 1.0.4 (2026-04-19) Skill expansion and documentation alignment update. -### Added in 1.0.4 +### Features - Six new skills: `migrate`, `openapi`, `refactor`, `onboard`, `dependency`, `incident`. - `migrate` — database migration review: zero-downtime analysis, expand/contract, rollback plans, index safety, batched backfills. @@ -252,12 +254,12 @@ Skill expansion and documentation alignment update. - `dependency` — full dependency health audit: vulnerability scanning, outdated packages, licence compliance, transitive risk, pinning policy, supply chain hygiene. - `incident` — incident analysis and blameless post-mortem writing: timeline reconstruction, 5-Whys root cause, contributing factors matrix, action items → `docs/postmortems/YYYY-MM-DD-*.md`. -### Fixed in 1.0.4 +### Fixes - `refactor` skill: removed outer ```` ```bash ```` fences wrapping `{{RUN_TESTS}}` partial (which already includes its own fence). - `onboard` skill: fixed nested fence issues in step 5 CONTRIBUTING.md example and step 6 README snippet. -### Changed in 1.0.4 +### Maintenance - `engineer`, `designer`, `tester`, `product` agent templates updated with skill references for all new skills. - `docs/design/skills.md` updated with full skill table including all new skills and their primary roles. @@ -265,61 +267,58 @@ Skill expansion and documentation alignment update. - `README.md` project structure diagram updated to include `instructions/` and `prompts/` template directories and the correct `docs/` subdirectory layout. - `.github/copilot-instructions.md` updated: system structure diagram now includes all four template artifact types (`skills`, `agents`, `instructions`, `prompts`); hand-authored `.github/` exceptions listed explicitly; install table extended with `instructions` and `prompts` rows. -## 1.0.3 — 2026-04-19 +## 1.0.3 (2026-04-19) Community health and release workflow update. -### Added in 1.0.3 +### Features - `CODEOWNERS` file. - GitHub issue templates: `bug_report.yml`, `feature_request.yml`, `config.yml`. - Pull request template (`.github/pull_request_template.md`). - `CODE_OF_CONDUCT.md`, `CONTRIBUTING.md`, `SECURITY.md` community health files. -### Changed in 1.0.3 +### Maintenance - Release workflow title format updated to `Release vX.Y.Z (YYYY-MM-DD)`. -## 1.0.2 — 2026-04-17 +## 1.0.2 (2026-04-17) Release workflow fix. -### Fixed in 1.0.2 +### Fixes - Release workflow: corrected Poetry setup order and opted in to Node 24 to resolve runner deprecation warnings. -## 1.0.1 — 2026-04-17 +## 1.0.1 (2026-04-17) Tooling and release hygiene update focused on making local and CI verification match. -### Added in 1.0.1 +### Features - Repo-local `.python-version` for consistent pyenv interpreter resolution. - Tox-based multi-version test execution across Python 3.11, 3.12, 3.13, and 3.14. - Pre-commit hooks expanded: `trailing-whitespace`, `end-of-file-fixer`, `check-toml`, `check-yaml`, `ruff`, `ruff-format`. -### Changed in 1.0.1 +### Maintenance - `pyproject.toml` migrated from `[tool.poetry]` to PEP 621 `[project]` form; `version = "0.0.0"` is a build-time placeholder overwritten by `poetry-dynamic-versioning`. - Python support metadata is now explicitly bounded to 3.11–3.14. - QA workflow now runs a Python test matrix across all supported runtimes. - Local developer workflow now documents pyenv as the standard multi-version setup. +- Coverage gate raised back to 100%. +- `make test` now exercises every supported Python version when interpreters are installed. -### Fixed in 1.0.1 +### Fixes - CI workflows: `pipx install poetry` now runs before `actions/setup-python` so the `cache: poetry` lookup always succeeds. -- CI workflows: bumped `actions/checkout@v4` → `v5` and `actions/setup-python@v5` → `v6` to resolve Node.js 20 deprecation warnings. +- CI workflows: bumped `actions/checkout@v4` -> `v5` and `actions/setup-python@v5` -> `v6` to resolve Node.js 20 deprecation warnings. -### Quality in 1.0.1 - -- Coverage gate raised back to 100%. -- `make test` now exercises every supported Python version when interpreters are installed. - -## 1.0.0 — 2026-04-01 +## 1.0.0 (2026-04-01) Initial public baseline for the VS Code-native vstack system. -### Added +### Features - Python package layout under `src/vstack/` with runtime entrypoints: - `vstack` CLI @@ -339,7 +338,7 @@ Initial public baseline for the VS Code-native vstack system. - ADR set under `docs/architecture/adr/` - Test suite for artifacts, frontmatter, CLI, agents, and skills. -### Changed +### Maintenance - Migrated to VS Code Agent artifacts and install-time generation model. - Skill metadata model moved to per-skill `config.yaml`; `template.md` is body-only. @@ -348,13 +347,7 @@ Initial public baseline for the VS Code-native vstack system. - `version` kept in source config for install/version tracking, not emitted in generated `SKILL.md` - `allowed-tools` not emitted due to inconsistent support - Placeholder governance moved to explicit registry mapping. - -### Removed - - Legacy generator scripts and template/registry structure from earlier layout. - Deprecated skill aliases and stale references (including freeze/unfreeze flow remnants). - -### Quality - - Full suite passing at release cut. - Coverage at 100%. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ffbadf1..3e41e03 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -95,7 +95,7 @@ Allowed branch types: - `docs`, `refactor`, `perf`, `test`, `ci`, `build`, `style` - `opt`, `patch`, `dependabot` -A dedicated CI workflow validates branch-push commit messages (non-main branches) using commit-check with policy from `cchk.toml`. +A dedicated CI workflow validates commit messages on branch pushes and PRs using commit-check with policy from `cchk.toml`. Scope names are guidance-level in this document and are not currently hard-enforced by CI. ## Pull Request Expectations @@ -109,3 +109,23 @@ Scope names are guidance-level in this document and are not currently hard-enfor Please do not report security issues in public issues. Use the process in `SECURITY.md`. + +## Repository Settings Checklist (Maintainers) + +Keep these GitHub settings aligned with the CI/CD design in `docs/design/cicd.md`. + +1. Ruleset on `main` (Settings → Rules → Rulesets): + - require pull request before merging + - required approvals: at least 1 + - require status checks: `Commit`, `Check`, `Verify`, and `Security` + - allowed merge methods: include **Squash** (required by `automerge.yml`) + - restrict force pushes +1. Actions permissions (Settings → Actions → General): + - allow GitHub Actions to create pull requests + - allow GitHub Actions to approve pull requests +1. Auto-merge (Settings → General): + - enabled at repository level (required for Dependabot auto-merge path) +1. PyPI environment (Settings → Environments): + - `pypi` environment exists + - OIDC trusted publishing configured + - any required reviewer policy matches release expectations diff --git a/README.md b/README.md index 34a4dc0..ef82764 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ Use repository-scoped installation so every contributor and CI run uses the same 1. Install artifacts into the repository. 1. Commit the generated `.github/` artifacts. -1. Require `verify.yml` and `security.yml` checks before merge. +1. Require `commit.yml`, `check.yml`, `verify.yml`, and `security.yml` checks before merge. ```bash # From your repository root @@ -686,17 +686,19 @@ ______________________________________________________________________ ## 🚦 CI and Release Automation -| Workflow | Trigger | Purpose | -| -------------- | ----------------------------- | ----------------------------------------------------------- | -| `qa.yml` | Push to non-main branches | fast branch feedback for format, lint, typecheck, and tests | -| `commit.yml` | Push to non-main branches | commit and branch naming policy enforcement | -| `verify.yml` | Pull request to `main` | source validation plus install/verify flow checks | -| `security.yml` | Pull request to `main` | dependency audit and secret scanning | -| `release.yml` | Merged pull request to `main` | SemVer calculation, tag, release, and distributions | +| Workflow | Trigger | Purpose | +| --------------- | ------------------------------------------ | ------------------------------------------------------------------- | +| `commit.yml` | Push to non-main branches and PR to `main` | commit/branch policy and lint/typecheck gate | +| `check.yml` | Push to non-main branches and PR to `main` | single-version unit tests (py3.11) | +| `verify.yml` | Pull request to `main` | cross-version test matrix (py3.11–3.14) and artifact install/verify | +| `security.yml` | Pull request to `main` | dependency audit and secret scanning | +| `automerge.yml` | Pull request target to `main` | safe Dependabot auto-merge policy | +| `release.yml` | Push to `main` | Release Please orchestration (release PR, changelog, tags) | +| `publish.yml` | GitHub release `published` | build from release tag and publish to PyPI | Commit policy specifics: -- Type validation is configured via `CCHK_*` variables in `.github/workflows/commit.yml`. +- Type validation is configured via `cchk.toml` and enforced by `commit-check` in `commit.yml`. - Commit subject length is limited to 100 characters. - Branch names use the `type/description` convention. - Allowed branch types are `feature`, `bugfix`, `hotfix`, `release`, `chore`, `feat`, `fix`, `docs`, `refactor`, `perf`, `test`, `ci`, `build`, `style`, `opt`, `patch`, and `dependabot`. @@ -704,15 +706,18 @@ Commit policy specifics: Recommended branch protection for `main`: - Require PR before merge. -- Require status checks from `verify.yml` and `security.yml`. +- Require status checks from `commit.yml`, `check.yml`, `verify.yml`, and `security.yml`. - Disallow force pushes and branch deletion. +Full pipeline documentation: [docs/design/cicd.md](docs/design/cicd.md) + ______________________________________________________________________ ## 📚 Further Reading - [docs/architecture/architecture.md](docs/architecture/architecture.md) - [docs/design/design.md](docs/design/design.md) +- [docs/design/cicd.md](docs/design/cicd.md) - [docs/design/workflow.md](docs/design/workflow.md) - [docs/design/skills.md](docs/design/skills.md) - [docs/product/roadmap.md](docs/product/roadmap.md) diff --git a/cchk.toml b/cchk.toml index 2d3faa3..0703b69 100644 --- a/cchk.toml +++ b/cchk.toml @@ -9,6 +9,7 @@ allow_commit_types = [ "fix", "bugfix", "hotfix", + "docs", "opt", "patch", "perf", diff --git a/docs/design/cicd.md b/docs/design/cicd.md new file mode 100644 index 0000000..3e400e7 --- /dev/null +++ b/docs/design/cicd.md @@ -0,0 +1,253 @@ +# vstack CI/CD Pipeline + +> Maintained by: **designer** role\ +> Last updated: 2026-04-27 + +## Overview + +This is the canonical CI/CD execution model for this repository. + +Key rule: work starts on a non-main branch. Nothing is committed directly to `main`. + +Note: `.github/workflows/commit.yml` and `.github/workflows/check.yml` run on branch pushes and PRs to `main`. +Their jobs are merge-blocking when configured as required status checks in the `main` branch Ruleset. + +## Step Label Legend + +- `[N]`: deterministic linear step in the sequence. +- `[Na]` / `[Nb]`: alternate branches of the same decision point. + +## What Runs On Which Event + +| Workflow | Trigger | Responsibility | +| --------------------------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------- | +| `.github/workflows/commit.yml` | push to non-main branches, pull_request to `main` | Commit/branch policy and lint/typecheck | +| `.github/workflows/check.yml` | push to non-main branches, pull_request to `main` | Single-version unit tests (py3.11) | +| `.github/workflows/verify.yml` | pull_request to `main` | Cross-version test matrix (py3.11–3.14) and artifact install verify | +| `.github/workflows/security.yml` | pull_request to `main` | Dependency vulnerability scan and secret scan | +| `.github/workflows/automerge.yml` | pull_request_target to `main` | Dependabot auto-approve/auto-merge policy gate | +| `.github/workflows/release.yml` | push to `main`, workflow_dispatch | Release Please orchestration: release PR lifecycle, changelog, tag, GitHub release | +| `.github/workflows/publish.yml` | release `published` | Build artifacts from release tag and publish to PyPI | + +## Human Sequence (Primary) + +The human path below is the primary reference for timing and execution order. +Checklist numbers map to the `[N]` labels in sequence messages. + +```mermaid +sequenceDiagram + participant Dev as Developer + participant GH as GitHub Actions + participant RP as Release Please + participant PyPI as PyPI + + Dev->>GH: [1] Push commit to feature branch + GH->>GH: [2a] commit.yml / Validate Commit Messages + GH->>GH: [2b] commit.yml / Format Lint Typecheck + GH->>GH: [2c] check.yml / Unit Tests (py3.11) + + Dev->>GH: [3] Open PR to main + GH->>GH: [4a] commit.yml / Validate Commit Messages + GH->>GH: [4b] commit.yml / Format Lint Typecheck + GH->>GH: [4c] check.yml / Unit Tests (py3.11) + GH->>GH: [4d] verify.yml / Tests (py3.11) — Tests (py3.14) [matrix ×4] + GH->>GH: [4e] verify.yml / Artifact Install Verify + GH->>GH: [4f] security.yml / Dependency and Secret Scan + Dev->>GH: [5] Approve and merge PR + + GH->>GH: [6] Push to main triggers release.yml + GH->>RP: [7] release.yml / Release Please (PR + Tag + Notes) + RP-->>GH: [8] Create or update release PR branch + PR + GH->>GH: [9a] verify.yml / Tests (py3.11) — Tests (py3.14) [matrix ×4] + GH->>GH: [9b] verify.yml / Artifact Install Verify + GH->>GH: [9c] security.yml / Dependency and Secret Scan + + Dev->>GH: [10] Approve and merge release PR + GH->>GH: [11] Push to main triggers release.yml again + GH->>RP: [12] release.yml / Release Please (PR + Tag + Notes) → tag + GitHub release + + GH->>GH: [13] release: published triggers publish.yml + Note over GH: [13] Condition: prerelease == false AND tag matches X.Y.Z AND trusted actor + GH->>PyPI: [14] publish.yml / Build and Publish to PyPI +``` + +## Human Checklist + +1. `[1]` Push commit to feature branch. +1. `[2a]` `commit.yml / Validate Commit Messages` — checks commit format, branch name, and reserved scope policy. +1. `[2b]` `commit.yml / Format Lint Typecheck` — runs `make format-check`, `make lint`, `make typecheck` on py3.11. +1. `[2c]` `check.yml / Unit Tests` — runs `make test-local` on py3.11. +1. `[3]` Open PR to `main`. +1. `[4a]` `commit.yml / Validate Commit Messages` — validates commit messages, branch policy, and reserved `docs(changelog)` scope. +1. `[4b]` `commit.yml / Format Lint Typecheck` — runs `make format-check`, `make lint`, `make typecheck` on py3.11. +1. `[4c]` `check.yml / Unit Tests` — runs `make test-local` on py3.11. +1. `[4d]` `verify.yml / Tests (py3.11)` through `Tests (py3.14)` — 4 parallel matrix jobs run `make test-local`. +1. `[4e]` `verify.yml / Artifact Install Verify` — installs vstack into a temp dir and runs `vstack verify`. +1. `[4f]` `security.yml / Dependency and Secret Scan` — pip-audit + trufflehog diff scan. +1. `[5]` Approve and merge PR. +1. `[6]` Push to `main` triggers `release.yml`. +1. `[7]` `release.yml / Release Please (PR + Tag + Notes)` runs. +1. `[8]` Release Please creates or updates the release PR branch + PR. +1. `[9a]`–`[9c]` `verify.yml` and `security.yml` run on the release PR — release-please uses the GitHub App token, which triggers `pull_request` events normally. +1. `[10]` Approve and merge release PR. +1. `[11]` Push to `main` triggers `release.yml` again. +1. `[12]` `release.yml / Release Please (PR + Tag + Notes)` creates the tag and GitHub release. +1. `[13]` `release: published` triggers `publish.yml`. Job runs only if `prerelease == false`, tag matches `X.Y.Z`, and release actor is trusted. +1. `[14]` `publish.yml / Build and Publish to PyPI` — builds wheel + sdist, smoke tests, validates artifact version, publishes via OIDC. + +## Dependabot Sequence + +```mermaid +sequenceDiagram + participant DB as Dependabot + participant GH as GitHub Actions + participant BP as Ruleset + participant RP as Release Please + participant PyPI as PyPI + + DB->>GH: [1] Push dependency branch update + GH->>GH: [2a] commit.yml / Validate Commit Messages + GH->>GH: [2b] commit.yml / Format Lint Typecheck + GH->>GH: [2c] check.yml / Unit Tests (py3.11) + + DB->>GH: [3] Open dependency PR to main + GH->>GH: [4a] commit.yml / Validate Commit Messages + GH->>GH: [4b] commit.yml / Format Lint Typecheck + GH->>GH: [4c] check.yml / Unit Tests (py3.11) + GH->>GH: [4d] verify.yml / Tests (py3.11) — Tests (py3.14) [matrix ×4] + GH->>GH: [4e] verify.yml / Artifact Install Verify + GH->>GH: [4f] security.yml / Dependency and Secret Scan + Note over GH: [5] automerge.yml job runs only if actor == dependabot[bot] + GH->>GH: [5] automerge.yml / auto-merge — evaluate policy + alt [6a] Ecosystem + update type is eligible + GH->>GH: [7a] Enable auto-merge (SQUASH) + else [6b] Not eligible + GH->>GH: [7b] Keep manual merge path + end + GH->>BP: [8] Request merge + alt [9a] Required checks green + BP-->>GH: [10a] Merge to main + else [9b] Required checks not green + BP-->>GH: [10b] Block merge until checks pass + end + + GH->>GH: [11] Push to main triggers release.yml + GH->>RP: [12] release.yml / Release Please (PR + Tag + Notes) + RP-->>GH: [13] Create or update release PR branch + PR + GH->>GH: [14a] verify.yml / Tests (py3.11) — Tests (py3.14) [matrix ×4] + GH->>GH: [14b] verify.yml / Artifact Install Verify + GH->>GH: [14c] security.yml / Dependency and Secret Scan + + DB->>GH: [15] Approve and merge release PR manually + GH->>GH: [16] Push to main triggers release.yml again + GH->>RP: [17] release.yml / Release Please (PR + Tag + Notes) → tag + GitHub release + + GH->>GH: [18] release: published triggers publish.yml + Note over GH: [18] Condition: prerelease == false AND tag matches X.Y.Z AND trusted actor + GH->>PyPI: [19] publish.yml / Build and Publish to PyPI +``` + +## Dependabot Checklist + +1. `[1]` Dependabot pushes dependency update branch. +1. `[2a]` `commit.yml / Validate Commit Messages` — commit format and branch name validated (`chore(deps)` / `chore(ci)` prefix, `dependabot/` branch). +1. `[2b]` `commit.yml / Format Lint Typecheck` — lint/typecheck on py3.11. +1. `[2c]` `check.yml / Unit Tests` — `make test-local` on py3.11. +1. `[3]` Dependabot opens PR to `main`. +1. `[4a]` `commit.yml / Validate Commit Messages` — commit message policy on PR commits. +1. `[4b]` `commit.yml / Format Lint Typecheck`. +1. `[4c]` `check.yml / Unit Tests`. +1. `[4d]` `verify.yml / Tests (py3.11)` through `Tests (py3.14)` — 4 parallel matrix jobs. +1. `[4e]` `verify.yml / Artifact Install Verify`. +1. `[4f]` `security.yml / Dependency and Secret Scan`. +1. `[5]` `automerge.yml` job runs only when `github.actor == 'dependabot[bot]'`. +1. `[6a]/[7a]` Eligible (pip patch or GHA patch/minor): auto-merge enabled with SQUASH method. +1. `[6b]/[7b]` Not eligible: manual merge path stays active. +1. `[8]` Merge requested against Ruleset. +1. `[9a]/[10a]` Required checks green → merge to `main`. +1. `[9b]/[10b]` Required checks not green → merge blocked until fixed. +1. `[11]-[13]` Push to `main` triggers `release.yml / Release Please (PR + Tag + Notes)`, release PR created/updated. +1. `[14a]`–`[14c]` `verify.yml` and `security.yml` run on the release PR — GitHub App token triggers `pull_request` events normally. +1. `[15]-[17]` Release PR manually approved and merged, then tag and GitHub release created. +1. `[18]-[19]` `publish.yml / Build and Publish to PyPI` runs if `prerelease == false`, SemVer tag, and trusted actor. + +## Failure and Retry Behavior + +1. If `commit.yml` or `check.yml` fails, fix branch and push again. +1. If `commit.yml`, `check.yml`, `verify.yml`, or `security.yml` fails on a PR, merge is blocked. +1. If `release.yml` fails due to manifest/tag drift, align `.release-please-manifest.json` with latest SemVer tag and rerun. +1. If `publish.yml` fails, fix publish issue and rerun publish from the same release/tag context. + +## Required Repository Configuration + +### Ruleset: `main` branch + +Configure via **Settings → Rules → Rulesets** on GitHub. + +- **Require a pull request before merging**: enabled. +- **Required approvals**: at least 1. +- **Require status checks to pass**: add the following checks: + - `Commit / Validate Commit Messages` + - `Commit / Format Lint Typecheck` + - `Check / Unit Tests` + - `Verify / Tests (py3.11)`, `Verify / Tests (py3.12)`, `Verify / Tests (py3.13)`, `Verify / Tests (py3.14)` + - `Verify / Artifact Install Verify` + - `Security / Dependency and Secret Scan` +- Release PRs created by the GitHub App token (`vstack-release-bot[bot]`) trigger `pull_request` + events normally — these checks run on release PRs the same as on any other PR. +- **Allowed merge methods**: must include **Squash** — required for `automerge.yml` to enable auto-merge for Dependabot PRs. +- **Restrict force pushes**: enabled. + +### Actions permissions + +Configure via **Settings → Actions → General**. + +- Allow workflows to create pull requests. +- Allow workflows to approve pull requests. + +### Repository auto-merge + +Configure via **Settings → General**. + +- Enabled at repository level (required for Dependabot auto-merge path). + +### PyPI environment + +Configure via **Settings → Environments**. + +- `pypi` environment exists. +- OIDC trusted publishing configured. +- Reviewer policy aligns with release expectations. + +## Design Notes + +### Release PR checks (`verify.yml` / `security.yml`) + +Release-please uses a GitHub App token (`APP_ID` + `APP_PRIVATE_KEY` secrets in `release.yml`). +PRs created via a GitHub App token are treated by GitHub as external-actor events, so +`pull_request` triggers fire normally. As a result, `verify.yml` and `security.yml` run on +release PRs the same as on any other PR to `main`. + +Release PRs only modify `CHANGELOG.md` and version metadata (e.g. version in `pyproject.toml`). +The test matrix and artifact verify will pass as normal; the security diff scan will cover only +the changelog and version file changes. No special Ruleset bypass configuration is needed. + +### `pypa/gh-action-pypi-publish@release/v1` + +This action intentionally uses a rolling `release/v1` branch reference, as PyPA's own +documented recommendation. Security patches (OIDC, attestation fixes) are delivered via +this rolling branch without requiring a separately versioned release from PyPA. Dependabot +monitors the `github-actions` ecosystem and opens a PR when the branch advances to a newer +commit. No manual tracking is needed. + +## Related Files + +- `.github/workflows/README.md` +- `.github/workflows/commit.yml` +- `.github/workflows/check.yml` +- `.github/workflows/verify.yml` +- `.github/workflows/security.yml` +- `.github/workflows/automerge.yml` +- `.github/workflows/release.yml` +- `.github/workflows/publish.yml` +- `docs/design/workflow.md` diff --git a/docs/design/workflow.md b/docs/design/workflow.md index ac4587a..214fcf2 100644 --- a/docs/design/workflow.md +++ b/docs/design/workflow.md @@ -8,6 +8,9 @@ This document describes how vstack workflows execute today (single-call execution) and a possible future orchestrated role pipeline. +For a precise GitHub Actions CI/CD and release pipeline specification, see +`docs/design/cicd.md`. + It also documents the repository-level GitHub Actions automation used for quality, security, commit policy, and releases. @@ -24,19 +27,21 @@ ______________________________________________________________________ The repository uses a split workflow model so each automation concern is isolated and easy to reason about. -| Workflow | Trigger | Responsibility | -| -------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------- | -| `.github/workflows/qa.yml` | Push to non-main branches | Fast feedback for formatting, linting, type checks, and tests across Python versions. | -| `.github/workflows/commit.yml` | Push to non-main branches (with explicit branch excludes) | Validate commit message policy before PR merge. | -| `.github/workflows/verify.yml` | Pull request to `main` | Validate source behavior and artifact install/verify flow. | -| `.github/workflows/security.yml` | Pull request to `main` | Dependency vulnerability audit and secret scan. | -| `.github/workflows/release.yml` | Merged pull request to `main` | Compute SemVer, create tag and GitHub release, build distributions. | +| Workflow | Trigger | Responsibility | +| --------------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------- | +| `.github/workflows/commit.yml` | Push to non-main branches and pull requests to `main` | Commit/branch policy and lint/typecheck gate. | +| `.github/workflows/check.yml` | Push to non-main branches and pull requests to `main` | Single-version unit tests (py3.11) for fast feedback. | +| `.github/workflows/verify.yml` | Pull request to `main` | Cross-version test matrix (py3.11–3.14) and artifact install/verify flow. | +| `.github/workflows/security.yml` | Pull request to `main` | Dependency vulnerability audit and secret scan. | +| `.github/workflows/automerge.yml` | Pull request target to `main` | Dependabot safe auto-merge policy for eligible updates. | +| `.github/workflows/release.yml` | Push to `main` | Run release-please to maintain release PRs and create tags/releases when merged. | +| `.github/workflows/publish.yml` | GitHub release published | Build package artifacts from the release tag and publish to PyPI. | ### commit policy enforcement model Commit policy is defined in `cchk.toml` and enforced by `commit-check`: -1. `.github/workflows/commit.yml` runs `commit-check/commit-check-action@v2` on branch pushes. +1. `.github/workflows/commit.yml` runs `commit-check/commit-check-action@v2` on branch pushes and PRs. 1. Local hooks in `.pre-commit-config.yaml` run the same checks at `commit-msg` and `pre-push` stages. Additional commit workflow policy: @@ -48,14 +53,14 @@ Additional commit workflow policy: This keeps CI and local checks aligned through one policy source of truth. -### release bump mapping +### release versioning model -`release.yml` computes SemVer from commit history using these mappings: +`release.yml` uses release-please as the source of truth for version calculation, +CHANGELOG updates, and GitHub release notes based on conventional commits. -- minor: `feat`, `feature` -- patch: `fix`, `bugfix`, `hotfix`, `opt`, `patch`, `perf`, `refactor`, `chore`, `revert` +`publish.yml` only builds and publishes artifacts for already created release tags. -Repository tag policy is strict `X.Y.Z` (no `v` prefix). +Repository tag policy remains strict `X.Y.Z` (no `v` prefix). ______________________________________________________________________ diff --git a/docs/performance-baseline.md b/docs/performance-baseline.md index 41dfc1f..31d078e 100644 --- a/docs/performance-baseline.md +++ b/docs/performance-baseline.md @@ -1,8 +1,8 @@ # Performance Baseline -**Branch:** `feat/improved_cli` -**Date:** 2026-04-26 -**Scope:** CLI hot-path operations — parser build, target resolution, registry build; post-backfill feature addition +**Branch:** `feat/improved_cli`\ +**Date:** 2026-04-26\ +**Scope:** CLI hot-path operations — parser build, target resolution, registry build; post-backfill feature addition\ **Method:** `timeit.repeat` micro-benchmarks (Python 3.13.12, Linux) ______________________________________________________________________ diff --git a/docs/security-report.md b/docs/security-report.md index 66da230..f59f5ad 100644 --- a/docs/security-report.md +++ b/docs/security-report.md @@ -1,8 +1,8 @@ # Security Report -**Branch:** `feat/improved_cli` -**Date:** 2026-04-26 -**Scope:** Full source tree — static analysis (bandit) + dependency audit (pip-audit); security fixes for S-001 (assert guards) and S-002 (subprocess nosec) +**Branch:** `feat/improved_cli`\ +**Date:** 2026-04-26\ +**Scope:** Full source tree — static analysis (bandit) + dependency audit (pip-audit); security fixes for S-001 (assert guards) and S-002 (subprocess nosec)\ **Method:** OWASP Top 10 + STRIDE (static analysis on a local CLI tool; no network surface, no auth surface, no DB) ______________________________________________________________________ diff --git a/docs/test-report.md b/docs/test-report.md index 117946f..8bf6578 100644 --- a/docs/test-report.md +++ b/docs/test-report.md @@ -1,7 +1,7 @@ # Test Report -**Branch:** `feat/improved_cli` -**Date:** 2026-04-26 +**Branch:** `feat/improved_cli`\ +**Date:** 2026-04-26\ **Scope:** Full repository — CLI refactor (`catalog`, `report`, `registry`, `parser`, `interface`, `service`, `helpers`); manifest checksum backfill feature (`manifest/store.py`, `cli/service.py`, `cli/parser.py`, `cli/manifest.py`); security fixes (`cli/report.py` assert guards, `constants.py` nosec); **full test suite restructure** (per-module test files, TestClass layout, 342 tests) ______________________________________________________________________ diff --git a/poetry.lock b/poetry.lock index 046f3d2..3a9fc94 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,15 +1,15 @@ -# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.4 and should not be changed by hand. [[package]] name = "cachetools" -version = "7.0.5" +version = "7.0.6" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "cachetools-7.0.5-py3-none-any.whl", hash = "sha256:46bc8ebefbe485407621d0a4264b23c080cedd913921bad7ac3ed2f26c183114"}, - {file = "cachetools-7.0.5.tar.gz", hash = "sha256:0cd042c24377200c1dcd225f8b7b12b0ca53cc2c961b43757e774ebe190fd990"}, + {file = "cachetools-7.0.6-py3-none-any.whl", hash = "sha256:4e94956cfdd3086f12042cdd29318f5ced3893014f7d0d059bf3ead3f85b7f8b"}, + {file = "cachetools-7.0.6.tar.gz", hash = "sha256:e5d524d36d65703a87243a26ff08ad84f73352adbeafb1cde81e207b456aaf24"}, ] [[package]] @@ -309,63 +309,63 @@ files = [ [[package]] name = "mypy" -version = "1.20.1" +version = "1.20.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "mypy-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3ba5d1e712ada9c3b6223dcbc5a31dac334ed62991e5caa17bcf5a4ddc349af0"}, - {file = "mypy-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e731284c117b0987fb1e6c5013a56f33e7faa1fce594066ab83876183ce1c66"}, - {file = "mypy-1.20.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8e945b872a05f4fbefabe2249c0b07b6b194e5e11a86ebee9edf855de09806c"}, - {file = "mypy-1.20.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fc88acef0dc9b15246502b418980478c1bfc9702057a0e1e7598d01a7af8937"}, - {file = "mypy-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:14911a115c73608f155f648b978c5055d16ff974e6b1b5512d7fedf4fa8b15c6"}, - {file = "mypy-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:76d9b4c992cca3331d9793ef197ae360ea44953cf35beb2526e95b9e074f2866"}, - {file = "mypy-1.20.1-cp310-cp310-win_arm64.whl", hash = "sha256:b408722f80be44845da555671a5ef3a0c63f51ca5752b0c20e992dc9c0fbd3cd"}, - {file = "mypy-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c01eb9bac2c6a962d00f9d23421cd2913840e65bba365167d057bd0b4171a92e"}, - {file = "mypy-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55d12ddbd8a9cac5b276878bd534fa39fff5bf543dc6ae18f25d30c8d7d27fca"}, - {file = "mypy-1.20.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0aa322c1468b6cdfc927a44ce130f79bb44bcd34eb4a009eb9f96571fd80955"}, - {file = "mypy-1.20.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f8bc95899cf676b6e2285779a08a998cc3a7b26f1026752df9d2741df3c79e8"}, - {file = "mypy-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:47c2b90191a870a04041e910277494b0d92f0711be9e524d45c074fe60c00b65"}, - {file = "mypy-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:9857dc8d2ec1a392ffbda518075beb00ac58859979c79f9e6bdcb7277082c2f2"}, - {file = "mypy-1.20.1-cp311-cp311-win_arm64.whl", hash = "sha256:09d8df92bb25b6065ab91b178da843dda67b33eb819321679a6e98a907ce0e10"}, - {file = "mypy-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:36ee2b9c6599c230fea89bbd79f401f9f9f8e9fcf0c777827789b19b7da90f51"}, - {file = "mypy-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fba3fb0968a7b48806b0c90f38d39296f10766885a94c83bd21399de1e14eb28"}, - {file = "mypy-1.20.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef1415a637cd3627d6304dfbeddbadd21079dafc2a8a753c477ce4fc0c2af54f"}, - {file = "mypy-1.20.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef3461b1ad5cd446e540016e90b5984657edda39f982f4cc45ca317b628f5a37"}, - {file = "mypy-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:542dd63c9e1339b6092eb25bd515f3a32a1453aee8c9521d2ddb17dacd840237"}, - {file = "mypy-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:1d55c7cd8ca22e31f93af2a01160a9e95465b5878de23dba7e48116052f20a8d"}, - {file = "mypy-1.20.1-cp312-cp312-win_arm64.whl", hash = "sha256:f5b84a79070586e0d353ee07b719d9d0a4aa7c8ee90c0ea97747e98cbe193019"}, - {file = "mypy-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f3886c03e40afefd327bd70b3f634b39ea82e87f314edaa4d0cce4b927ddcc1"}, - {file = "mypy-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e860eb3904f9764e83bafd70c8250bdffdc7dde6b82f486e8156348bf7ceb184"}, - {file = "mypy-1.20.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4b5aac6e785719da51a84f5d09e9e843d473170a9045b1ea7ea1af86225df4b"}, - {file = "mypy-1.20.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f37b6cd0fe2ad3a20f05ace48ca3523fc52ff86940e34937b439613b6854472e"}, - {file = "mypy-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4bbb0f6b54ce7cc350ef4a770650d15fa70edd99ad5267e227133eda9c94218"}, - {file = "mypy-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:c3dc20f8ec76eecd77148cdd2f1542ed496e51e185713bf488a414f862deb8f2"}, - {file = "mypy-1.20.1-cp313-cp313-win_arm64.whl", hash = "sha256:a9d62bbac5d6d46718e2b0330b25e6264463ed832722b8f7d4440ff1be3ca895"}, - {file = "mypy-1.20.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:12927b9c0ed794daedcf1dab055b6c613d9d5659ac511e8d936d96f19c087d12"}, - {file = "mypy-1.20.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:752507dd481e958b2c08fc966d3806c962af5a9433b5bf8f3bdd7175c20e34fe"}, - {file = "mypy-1.20.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c614655b5a065e56274c6cbbe405f7cf7e96c0654db7ba39bc680238837f7b08"}, - {file = "mypy-1.20.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c3f6221a76f34d5100c6d35b3ef6b947054123c3f8d6938a4ba00b1308aa572"}, - {file = "mypy-1.20.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4bdfc06303ac06500af71ea0cdbe995c502b3c9ba32f3f8313523c137a25d1b6"}, - {file = "mypy-1.20.1-cp314-cp314-win_amd64.whl", hash = "sha256:0131edd7eba289973d1ba1003d1a37c426b85cdef76650cd02da6420898a5eb3"}, - {file = "mypy-1.20.1-cp314-cp314-win_arm64.whl", hash = "sha256:33f02904feb2c07e1fdf7909026206396c9deeb9e6f34d466b4cfedb0aadbbe4"}, - {file = "mypy-1.20.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:168472149dd8cc505c98cefd21ad77e4257ed6022cd5ed2fe2999bed56977a5a"}, - {file = "mypy-1.20.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:eb674600309a8f22790cca883a97c90299f948183ebb210fbef6bcee07cb1986"}, - {file = "mypy-1.20.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef2b2e4cc464ba9795459f2586923abd58a0055487cbe558cb538ea6e6bc142a"}, - {file = "mypy-1.20.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dee461d396dd46b3f0ed5a098dbc9b8860c81c46ad44fa071afcfbc149f167c9"}, - {file = "mypy-1.20.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e364926308b3e66f1361f81a566fc1b2f8cd47fc8525e8136d4058a65a4b4f02"}, - {file = "mypy-1.20.1-cp314-cp314t-win_amd64.whl", hash = "sha256:a0c17fbd746d38c70cbc42647cfd884f845a9708a4b160a8b4f7e70d41f4d7fa"}, - {file = "mypy-1.20.1-cp314-cp314t-win_arm64.whl", hash = "sha256:db2cb89654626a912efda69c0d5c1d22d948265e2069010d3dde3abf751c7d08"}, - {file = "mypy-1.20.1-py3-none-any.whl", hash = "sha256:1aae28507f253fe82d883790d1c0a0d35798a810117c88184097fe8881052f06"}, - {file = "mypy-1.20.1.tar.gz", hash = "sha256:6fc3f4ecd52de81648fed1945498bf42fa2993ddfad67c9056df36ae5757f804"}, + {file = "mypy-1.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cf5a4db6dca263010e2c7bff081c89383c72d187ba2cf4c44759aac970e2f0c4"}, + {file = "mypy-1.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7b0e817b518bff7facd7f85ea05b643ad8bdcce684cf29784987b0a7c8e1f997"}, + {file = "mypy-1.20.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97d7b9a485b40f8ca425460e89bf1da2814625b2da627c0dcc6aa46c92631d14"}, + {file = "mypy-1.20.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e1c12f6d2db3d78b909b5f77513c11eb7f2dd2782b96a3ab6dffc7d44575c99"}, + {file = "mypy-1.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:89dce27e142d25ffbc154c1819383b69f2e9234dc4ed4766f42e0e8cb264ab5c"}, + {file = "mypy-1.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:f376e37f9bf2a946872fc5fd1199c99310748e3c26c7a26683f13f8bdb756cbd"}, + {file = "mypy-1.20.2-cp310-cp310-win_arm64.whl", hash = "sha256:6e2b469efd811707bc530fd1effef0f5d6eebcb7fe376affae69025da4b979a2"}, + {file = "mypy-1.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4077797a273e56e8843d001e9dfe4ba10e33323d6ade647ff260e5cd97d9758c"}, + {file = "mypy-1.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cdecf62abcc4292500d7858aeae87a1f8f1150f4c4dd08fb0b336ee79b2a6df3"}, + {file = "mypy-1.20.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c566c3a88b6ece59b3d70f65bedef17304f48eb52ff040a6a18214e1917b3254"}, + {file = "mypy-1.20.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0deb80d062b2479f2c87ae568f89845afc71d11bc41b04179e58165fd9f31e98"}, + {file = "mypy-1.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bba9ad231e92a3e424b3e56b65aa17704993425bba97e302c832f9466bb85bac"}, + {file = "mypy-1.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:baf593f2765fa3a6b1ef95807dbaa3d25b594f6a52adcc506a6b9cb115e1be67"}, + {file = "mypy-1.20.2-cp311-cp311-win_arm64.whl", hash = "sha256:20175a1c0f49863946ec20b7f63255768058ac4f07d2b9ded6a6b46cfb5a9100"}, + {file = "mypy-1.20.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4dbfcf869f6b0517f70cf0030ba6ea1d6645e132337a7d5204a18d8d5636c02b"}, + {file = "mypy-1.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b6481b228d072315b053210b01ac320e1be243dc17f9e5887ef167f23f5fae4"}, + {file = "mypy-1.20.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34397cdced6b90b836e38182076049fdb41424322e0b0728c946b0939ebdf9f6"}, + {file = "mypy-1.20.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5da6976f20cae27059ea8d0c86e7cef3de720e04c4bb9ee18e3690fdb792066"}, + {file = "mypy-1.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:56908d7e08318d39f85b1f0c6cfd47b0cac1a130da677630dac0de3e0623e102"}, + {file = "mypy-1.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:d52ad8d78522da1d308789df651ee5379088e77c76cb1994858d40a426b343b9"}, + {file = "mypy-1.20.2-cp312-cp312-win_arm64.whl", hash = "sha256:785b08db19c9f214dc37d65f7c165d19a30fcecb48abfa30f31b01b5acaabb58"}, + {file = "mypy-1.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:edfbfca868cdd6bd8d974a60f8a3682f5565d3f5c99b327640cedd24c4264026"}, + {file = "mypy-1.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e2877a02380adfcdbc69071a0f74d6e9dbbf593c0dc9d174e1f223ffd5281943"}, + {file = "mypy-1.20.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7488448de6007cd5177c6cea0517ac33b4c0f5ee9b5e9f2be51ce75511a85517"}, + {file = "mypy-1.20.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb9c2fa06887e21d6a3a868762acb82aec34e2c6fd0174064f27c93ede68ad15"}, + {file = "mypy-1.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d56a78b646f2e3daa865bc70cd5ec5a46c50045801ca8ff17a0c43abc97e3ee"}, + {file = "mypy-1.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:2a4102b03bb7481d9a91a6da8d174740c9c8c4401024684b9ca3b7cc5e49852f"}, + {file = "mypy-1.20.2-cp313-cp313-win_arm64.whl", hash = "sha256:a95a9248b0c6fd933a442c03c3b113c3b61320086b88e2c444676d3fd1ca3330"}, + {file = "mypy-1.20.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:419413398fe250aae057fd2fe50166b61077083c9b82754c341cf4fd73038f30"}, + {file = "mypy-1.20.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e73c07f23009962885c197ccb9b41356a30cc0e5a1d0c2ea8fd8fb1362d7f924"}, + {file = "mypy-1.20.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c64e5973df366b747646fc98da921f9d6eba9716d57d1db94a83c026a08e0fb"}, + {file = "mypy-1.20.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a65aa591af023864fd08a97da9974e919452cfe19cb146c8a5dc692626445dc"}, + {file = "mypy-1.20.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fef51b01e638974a6e69885687e9bd40c8d1e09a6cd291cca0619625cf1f558"}, + {file = "mypy-1.20.2-cp314-cp314-win_amd64.whl", hash = "sha256:913485a03f1bcf5d279409a9d2b9ed565c151f61c09f29991e5faa14033da4c8"}, + {file = "mypy-1.20.2-cp314-cp314-win_arm64.whl", hash = "sha256:c3bae4f855d965b5453784300c12ffc63a548304ac7f99e55d4dc7c898673aa3"}, + {file = "mypy-1.20.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2de3dcea53babc1c3237a19002bc3d228ce1833278f093b8d619e06e7cc79609"}, + {file = "mypy-1.20.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:52b176444e2e5054dfcbcb8c75b0b719865c96247b37407184bbfca5c353f2c2"}, + {file = "mypy-1.20.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:688c3312e5dadb573a2c69c82af3a298d43ecf9e6d264e0f95df960b5f6ac19c"}, + {file = "mypy-1.20.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29752dbbf8cc53f89f6ac096d363314333045c257c9c75cbd189ca2de0455744"}, + {file = "mypy-1.20.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:803203d2b6ea644982c644895c2f78b28d0e208bba7b27d9b921e0ec5eb207c6"}, + {file = "mypy-1.20.2-cp314-cp314t-win_amd64.whl", hash = "sha256:9bcb8aa397ff0093c824182fd76a935a9ba7ad097fcbef80ae89bf6c1731d8ec"}, + {file = "mypy-1.20.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e061b58443f1736f8a37c48978d7ab581636d6ab03e3d4f99e3fa90463bb9382"}, + {file = "mypy-1.20.2-py3-none-any.whl", hash = "sha256:a94c5a76ab46c5e6257c7972b6c8cff0574201ca7dc05647e33e795d78680563"}, + {file = "mypy-1.20.2.tar.gz", hash = "sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665"}, ] [package.dependencies] librt = {version = ">=0.8.0", markers = "platform_python_implementation != \"PyPy\""} mypy_extensions = ">=1.0.0" pathspec = ">=1.0.0" -typing_extensions = ">=4.6.0" +typing_extensions = {version = ">=4.6.0", markers = "python_version < \"3.15\""} [package.extras] dmypy = ["psutil (>=4.0)"] @@ -401,33 +401,32 @@ files = [ [[package]] name = "packaging" -version = "26.1" +version = "26.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f"}, - {file = "packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de"}, + {file = "packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e"}, + {file = "packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661"}, ] [[package]] name = "pathspec" -version = "1.0.4" +version = "1.1.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723"}, - {file = "pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645"}, + {file = "pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189"}, + {file = "pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a"}, ] [package.extras] hyperscan = ["hyperscan (>=0.7)"] optional = ["typing-extensions (>=4)"] re2 = ["google-re2 (>=1.1)"] -tests = ["pytest (>=9)", "typing-extensions (>=4.15)"] [[package]] name = "platformdirs" @@ -459,14 +458,14 @@ testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "4.5.1" +version = "4.6.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77"}, - {file = "pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61"}, + {file = "pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b"}, + {file = "pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9"}, ] [package.dependencies] @@ -657,30 +656,30 @@ files = [ [[package]] name = "ruff" -version = "0.15.11" +version = "0.15.12" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "ruff-0.15.11-py3-none-linux_armv6l.whl", hash = "sha256:e927cfff503135c558eb581a0c9792264aae9507904eb27809cdcff2f2c847b7"}, - {file = "ruff-0.15.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7a1b5b2938d8f890b76084d4fa843604d787a912541eae85fd7e233398bbb73e"}, - {file = "ruff-0.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4176f3d194afbdaee6e41b9ccb1a2c287dba8700047df474abfbe773825d1cb"}, - {file = "ruff-0.15.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b17c886fb88203ced3afe7f14e8d5ae96e9d2f4ccc0ee66aa19f2c2675a27e4"}, - {file = "ruff-0.15.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49fafa220220afe7758a487b048de4c8f9f767f37dfefad46b9dd06759d003eb"}, - {file = "ruff-0.15.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2ab8427e74a00d93b8bda1307b1e60970d40f304af38bccb218e056c220120d"}, - {file = "ruff-0.15.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:195072c0c8e1fc8f940652073df082e37a5d9cb43b4ab1e4d0566ab8977a13b7"}, - {file = "ruff-0.15.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a0996d486af3920dec930a2e7daed4847dfc12649b537a9335585ada163e9e"}, - {file = "ruff-0.15.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bef2cb556d509259f1fe440bb9cd33c756222cf0a7afe90d15edf0866702431"}, - {file = "ruff-0.15.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:030d921a836d7d4a12cf6e8d984a88b66094ccb0e0f17ddd55067c331191bf19"}, - {file = "ruff-0.15.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e783b599b4577788dbbb66b9addcef87e9a8832f4ce0c19e34bf55543a2f890"}, - {file = "ruff-0.15.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ae90592246625ba4a34349d68ec28d4400d75182b71baa196ddb9f82db025ef5"}, - {file = "ruff-0.15.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f111d62e3c983ed20e0ca2e800f8d77433a5b1161947df99a5c2a3fb60514f0"}, - {file = "ruff-0.15.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:06f483d6646f59eaffba9ae30956370d3a886625f511a3108994000480621d1c"}, - {file = "ruff-0.15.11-py3-none-win32.whl", hash = "sha256:476a2aa56b7da0b73a3ee80b6b2f0e19cce544245479adde7baa65466664d5f3"}, - {file = "ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3"}, - {file = "ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4"}, - {file = "ruff-0.15.11.tar.gz", hash = "sha256:f092b21708bf0e7437ce9ada249dfe688ff9a0954fc94abab05dcea7dcd29c33"}, + {file = "ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c"}, + {file = "ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c"}, + {file = "ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5"}, + {file = "ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002"}, + {file = "ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5"}, + {file = "ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6"}, + {file = "ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33"}, + {file = "ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847"}, + {file = "ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0"}, + {file = "ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339"}, + {file = "ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5"}, + {file = "ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd"}, + {file = "ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b"}, + {file = "ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e"}, + {file = "ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20"}, + {file = "ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d"}, + {file = "ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f"}, + {file = "ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6"}, ] [[package]] @@ -736,14 +735,14 @@ files = [ [[package]] name = "virtualenv" -version = "21.2.4" +version = "21.3.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "virtualenv-21.2.4-py3-none-any.whl", hash = "sha256:29d21e941795206138d0f22f4e45ff7050e5da6c6472299fb7103318763861ac"}, - {file = "virtualenv-21.2.4.tar.gz", hash = "sha256:b294ef68192638004d72524ce7ef303e9d0cf5a44c95ce2e54a7500a6381cada"}, + {file = "virtualenv-21.3.0-py3-none-any.whl", hash = "sha256:4d28ee41f6d9ec8f1f00cd472b9ffbcedda1b3d3b9a575b5c94a2d004fd51bd7"}, + {file = "virtualenv-21.3.0.tar.gz", hash = "sha256:733750db978ec95c2d8eb4feadaa57091002bce404cb39ba69899cf7bd28944e"}, ] [package.dependencies] @@ -755,4 +754,4 @@ python-discovery = ">=1.2.2" [metadata] lock-version = "2.1" python-versions = ">=3.11,<3.15" -content-hash = "2d7100af3a60d80e213f99733bf17ad81af3081850b6e9e143f1218ea9e2b474" +content-hash = "70281e6bd9bca0658ec7da4e5647a65a071fe717d39cb61c1bf2d51226421222" diff --git a/pyproject.toml b/pyproject.toml index ae6d959..d4d9764 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,9 +65,9 @@ poetry-dynamic-versioning = { version = ">=1.0.0,<2.0.0", extras = ["plugin"] } [tool.poetry.group.dev.dependencies] pytest = ">=9.0" pytest-cov = ">=6.0" -ruff = ">=0.9" -mypy = ">=1.0" -pre-commit = ">=3.8" +ruff = ">=0.15.12" +mypy = ">=1.20.2" +pre-commit = ">=4.6.0" tox = ">=4.15" @@ -172,7 +172,7 @@ commands = description = Run ruff lint checks package = skip deps = - ruff>=0.9 + ruff>=0.15 commands = ruff check src tests @@ -180,7 +180,7 @@ commands = description = Run mypy type checks package = skip deps = - mypy>=1.0 + mypy>=1.20 commands = mypy src tests """