Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 48 additions & 12 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -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<version>) 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
8 changes: 5 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
`## <version>` 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<version>` 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.
Loading