diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 0000000..6c4a30e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,11 @@ +## Summary + + +## Why + + +## Version impact +- [ ] None +- [ ] Patch +- [ ] Minor +- [ ] Major diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 8826347..96cc833 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -40,7 +40,7 @@ docs/ ← architecture.md, design.md, skills.md, workflow.md, roadmap ``` *Hand-authored exceptions in `.github/`: `copilot-instructions.md`, `CODEOWNERS`, -`pull_request_template.md`, `ISSUE_TEMPLATE/`, `workflows/`. +`PULL_REQUEST_TEMPLATE/`, `ISSUE_TEMPLATE/`, `workflows/`. Generated files are written to `.github/` at install time: @@ -132,6 +132,13 @@ For Python modules in this repository, treat code as the source of truth and kee - Default to microservices and libraries unless UI is explicitly present. - After each change: summary + files changed + how to test. +## Pull Request Response Format + +- When asked to provide PR content, strictly follow the repository template at `.github/PULL_REQUEST_TEMPLATE/pull_request_template.md`. +- Always provide PR content in English. +- Always provide PR content as copy-paste-ready Markdown in a fenced `markdown` code block. +- Do not add extra sections that are not present in the active PR template. + ## Context Exclusions When searching or reading repository content, ignore generated and third-party directories by default: diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index 0b917a3..0000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,20 +0,0 @@ -## Summary - -Describe what changed and why. - -## Related Issues - -Link issues, discussions, or ADRs. - -## Validation - -- [ ] Tests pass locally -- [ ] CI checks pass -- [ ] Docs updated (if needed) - -## Release Impact - -- [ ] `feat:` (minor) -- [ ] `fix:` (patch) -- [ ] `BREAKING CHANGE` (major) -- [ ] No release impact diff --git a/.github/workflows/commit.yml b/.github/workflows/commit.yml index 8191ee4..919e0cb 100644 --- a/.github/workflows/commit.yml +++ b/.github/workflows/commit.yml @@ -13,6 +13,11 @@ on: - merge/** - gh-readonly-queue/** +concurrency: + # Cancel superseded commit-message checks 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 diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index a0268b0..37d1e06 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -8,6 +8,11 @@ on: 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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9b932cb..9999807 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,11 @@ on: push: branches: [main] +concurrency: + # Serialize main releases to avoid concurrent tag/release races. + group: release-${{ github.ref }} + cancel-in-progress: false + permissions: # Default to read-only; release job elevates to write for tagging/releases. contents: read @@ -22,8 +27,7 @@ env: jobs: release: - # Computes semantic version from conventional commits, creates git tag, - # and publishes GitHub release. + # Computes semantic version from conventional commits and creates git tag. name: Compute Version and Tag runs-on: ubuntu-latest permissions: @@ -88,26 +92,13 @@ jobs: } git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git rev-parse "$VERSION" >/dev/null 2>&1 && { + 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" - - name: Compute release date - if: steps.version.outputs.changed == 'true' - id: release_date - run: echo "date=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT" - - - name: Create GitHub release - if: steps.version.outputs.changed == 'true' - uses: softprops/action-gh-release@v3 - with: - tag_name: ${{ steps.version.outputs.version }} - name: Release v${{ steps.version.outputs.version }} (${{ steps.release_date.outputs.date }}) - generate_release_notes: true - build: # Build distributions only when semver-action reports a new release. name: Build Package Artifacts @@ -121,16 +112,13 @@ jobs: with: # Build from the newly created release tag to guarantee package version. ref: refs/tags/${{ needs.release.outputs.version }} - fetch-depth: 1 - fetch-tags: true + fetch-depth: 0 - name: Install Poetry - run: | - pipx install "poetry==${POETRY_VERSION}" - pipx inject poetry "poetry-dynamic-versioning[plugin]>=1.0.0,<2.0.0" + run: pipx install "poetry==${POETRY_VERSION}" - - name: Show Poetry plugins - run: poetry self show plugins + - 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 @@ -150,9 +138,21 @@ jobs: exit 1 fi + - name: Show active Poetry plugins + run: poetry self show plugins + + - name: Apply dynamic version + run: poetry dynamic-versioning + - 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 }}" @@ -191,3 +191,47 @@ jobs: - 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." + 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 + with: + tag_name: ${{ needs.release.outputs.version }} + name: Release v${{ needs.release.outputs.version }} (${{ steps.release_date.outputs.date }}) + generate_release_notes: true diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 879d6a6..ff243de 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -7,6 +7,11 @@ on: pull_request: branches: [main] +concurrency: + # Cancel superseded security scans for the same PR. + group: security-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: # Required by checkout and PR-context scanners. contents: read diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 0a1082a..bdb1b4d 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -7,6 +7,11 @@ on: pull_request: branches: [main] +concurrency: + # Cancel superseded verification runs for the same PR. + group: verify-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: # Verify jobs only read repository contents. contents: read diff --git a/CHANGELOG.md b/CHANGELOG.md index 15b0f0b..ef42632 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,29 @@ # Changelog +## 1.3.4 - 2026-04-22 + +Release build versioning fix: explicit plugin activation and full history checkout. + +### Fixed in 1.3.4 + +- 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. +- Fixed release race conditions by adding workflow `concurrency` controls, including serialized main release execution. +- Fixed release integrity by creating the GitHub release only after PyPI publish succeeds. +- Fixed tag existence validation to check `refs/tags/` directly instead of a generic ref lookup. +- Fixed rerun friction after failed release attempts by automatically deleting the freshly created tag when build or publish fails. +- 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 Release build plugin activation fix. ### Fixed in 1.3.3 -- Fixed CI release builds producing `0.0.0` artifacts by installing/enabling `poetry-dynamic-versioning` in the Poetry runtime used by the build job. -- Fixed release build reproducibility by pinning the Poetry CLI version in workflow environment configuration. +- 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`. -- Fixed release workflow noise by inlining SemVer format validation into the tag creation step. -- Added Poetry plugin visibility in release logs via `poetry self show plugins` to simplify troubleshooting. ## 1.3.2 - 2026-04-22