Skip to content

docs: sync README to current contract + surface bench + P11 cross-ref… #34

docs: sync README to current contract + surface bench + P11 cross-ref…

docs: sync README to current contract + surface bench + P11 cross-ref… #34

Workflow file for this run

name: Release on merge
# Auto-tag + auto-publish GitHub Release whenever VERSION changes on main.
# Composes with validate-release.yml (PR gate) so this workflow trusts that
# the merged VERSION is semver, monotonic, and matched by a CHANGELOG section.
#
# Idempotent: if `vX.Y.Z` already exists (e.g. tagged manually before this
# workflow shipped) the run skips silently — no overwrite, no duplicate.
on:
push:
branches: [main]
paths:
- VERSION
permissions:
contents: write # tag push + gh release create
concurrency:
group: release-on-merge
cancel-in-progress: false
jobs:
release:
name: Tag + GitHub Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history so we can check existing tags
- name: Read VERSION
id: version
run: |
set -euo pipefail
v="$(tr -d '[:space:]' < VERSION)"
if ! printf '%s' "$v" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::error file=VERSION::not semver X.Y.Z: '$v'"
exit 1
fi
echo " version=$v"
echo "version=$v" >> "$GITHUB_OUTPUT"
echo "tag=v$v" >> "$GITHUB_OUTPUT"
- name: Check existing tag
id: tag_check
run: |
set -euo pipefail
tag="${{ steps.version.outputs.tag }}"
if git rev-parse "$tag" >/dev/null 2>&1; then
echo " $tag already exists — skipping release."
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo " $tag is new — will create."
echo "exists=false" >> "$GITHUB_OUTPUT"
fi
- name: Extract release notes from CHANGELOG
if: steps.tag_check.outputs.exists == 'false'
id: notes
run: |
set -euo pipefail
v="${{ steps.version.outputs.version }}"
awk -v ver="$v" '
$0 ~ "^## " ver "( |$)" { flag=1; next }
flag && /^## / { exit }
flag { print }
' CHANGELOG.md > /tmp/release-notes.md
if [ ! -s /tmp/release-notes.md ]; then
echo "::error file=CHANGELOG.md::no '## $v' section found"
exit 1
fi
# Title = first `### ` heading inside the section, else fall back to vX.Y.Z.
title=$(awk '/^### / { sub(/^### /, ""); print; exit }' /tmp/release-notes.md)
[ -z "$title" ] && title="${{ steps.version.outputs.tag }}"
echo " title=$title"
# Multi-line outputs need the heredoc form.
{
echo "title<<EOT"
echo "$title"
echo "EOT"
} >> "$GITHUB_OUTPUT"
- name: Tag + create GitHub Release
if: steps.tag_check.outputs.exists == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
tag="${{ steps.version.outputs.tag }}"
title="${{ steps.notes.outputs.title }}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "$tag" -m "$tag — $title"
git push origin "$tag"
gh release create "$tag" \
--title "$tag — $title" \
--notes-file /tmp/release-notes.md
echo " ✓ released $tag"
# Package the skill directory as a tarball and publish it as a release
# asset, so vendored installs (no `.git`) can self-upgrade via
# `bstack upgrade --self` (≥ 0.9.0). The tarball excludes ephemeral
# state — only the in-repo skill source ships.
- name: Package + publish vendored upgrade tarball
if: steps.tag_check.outputs.exists == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
tag="${{ steps.version.outputs.tag }}"
tarball="bstack-${tag}.tar.gz"
# Build a clean staging tree — the extracted root must contain
# VERSION + bin/ + scripts/ + assets/ + references/ + schemas/ +
# SKILL.md + CHANGELOG.md, mirroring a fresh `npx skills add` layout.
staging="$(mktemp -d)"
dest="$staging/bstack-${tag}"
mkdir -p "$dest"
# Copy the canonical skill payload; exclude .git, .github, tests
# (downstream installs don't need CI workflow or development tests).
tar --exclude='./.git' \
--exclude='./.github' \
--exclude='./tests' \
--exclude='./broomva.tech-worktrees' \
--exclude='./bstack-worktrees' \
--exclude='./.cache' \
-cf - . | tar -xf - -C "$dest"
# Build the tarball in a deterministic order for reproducible
# sha256 across runs (though we don't fail-close on this yet).
( cd "$staging" && tar --sort=name -czf "$tarball" "bstack-${tag}" )
mv "$staging/$tarball" "./$tarball"
# sha256 sidecar — single canonical line, "<hash> <filename>" format.
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$tarball" > "${tarball}.sha256"
else
shasum -a 256 "$tarball" > "${tarball}.sha256"
fi
echo " packaged $tarball ($(wc -c < "$tarball") bytes)"
echo " sha256: $(awk '{print $1}' "${tarball}.sha256")"
# Upload as release assets.
gh release upload "$tag" "$tarball" "${tarball}.sha256" --clobber
echo " ✓ uploaded $tarball + sha256 to $tag"