diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bf9c424..48ec119 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,35 +1,71 @@ name: release -# Push a tag like v1.1.0 to publish a GitHub Release with personawriter.zip attached, -# ready to upload in claude.ai / Claude Desktop (Settings -> Capabilities -> Skills). +# Publishes a GitHub Release with personawriter.zip attached whenever main carries a version in +# .claude-plugin/plugin.json that has no release yet. Bumping the version is all it takes; +# the tag (v) is created by the release. Other pushes to main are a no-op. on: push: - tags: ["v*"] + branches: [main] + workflow_dispatch: permissions: contents: write +concurrency: + group: release + cancel-in-progress: false + jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 + + - name: Read version and check for an existing release + id: version + env: + GH_TOKEN: ${{ github.token }} + run: | + version=$(python3 -c "import json; print(json.load(open('.claude-plugin/plugin.json'))['version'])") + echo "tag=v$version" >> "$GITHUB_OUTPUT" + if gh release view "v$version" > /dev/null 2>&1; then + echo "Release v$version already exists, nothing to do." + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "Releasing v$version." + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + - uses: actions/setup-python@v7 + if: steps.version.outputs.exists == 'false' with: python-version: "3.13" - - run: python -m unittest discover tests - - name: Check tag matches plugin version - run: | - version=$(python -c "import json; print(json.load(open('.claude-plugin/plugin.json'))['version'])") - if [ "$GITHUB_REF_NAME" != "v$version" ]; then - echo "::error::Tag $GITHUB_REF_NAME does not match plugin.json version $version" - exit 1 - fi + + - name: Run tests (manifests, changelog entry, skill package, scripts) + if: steps.version.outputs.exists == 'false' + run: python -m unittest discover tests + - name: Build skill zip + if: steps.version.outputs.exists == 'false' run: | cd skills zip -r ../personawriter.zip personawriter -x '*/__pycache__/*' + - name: Publish release + if: steps.version.outputs.exists == 'false' env: GH_TOKEN: ${{ github.token }} - run: gh release create "$GITHUB_REF_NAME" personawriter.zip --title "PersonaWriter $GITHUB_REF_NAME" --generate-notes + TAG: ${{ steps.version.outputs.tag }} + run: | + # release notes = this version's CHANGELOG section + python3 - "$TAG" > notes.md <<'EOF' + import re, sys + version = sys.argv[1][1:] + text = open("CHANGELOG.md", encoding="utf-8").read() + m = re.search(rf"^## {re.escape(version)}\s*\n(.*?)(?=^## |\Z)", text, re.S | re.M) + print(m.group(1).strip() if m else "") + EOF + gh release create "$TAG" personawriter.zip \ + --target "$GITHUB_SHA" \ + --title "PersonaWriter $TAG" \ + --notes-file notes.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ebd290..4ca9587 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ - New **Profile only** mode; Update mode also refreshes markers and anchors and works on style skills built elsewhere. - Generated skills get `markers.json`, the three-step drift check, and a "Rewriting a draft" section. +- Releases publish automatically: when `main` carries a version without a release, the workflow + tests, tags and publishes `personawriter.zip`. No manual tags. - SKILL.md: intake strips non-author material, one voice per skill, a manual path when code cannot run, a 70+ score target in the proving step, and a "drift watch-list" in the guide. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ef0d5ef..966498f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,6 +43,8 @@ python -m unittest discover tests -v ## Releasing (maintainers) -Bump `version` in `.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json`, update -`CHANGELOG.md`, then push a tag: `git tag v1.2.0 && git push --tags`. The release workflow -attaches `personawriter.zip` automatically. +Bump `version` in `.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json` and add a +`## ` section to `CHANGELOG.md` (the tests check all three agree). When that reaches +`main`, the release workflow sees a version without a release, runs the tests, creates the +`v` tag and publishes a GitHub Release with `personawriter.zip` and the changelog +section as notes. No manual tagging. Pushes that don't change the version do nothing.