Skip to content

Merge: v1.3.7 — the weak-model result, and the first hook it demanded #11

Merge: v1.3.7 — the weak-model result, and the first hook it demanded

Merge: v1.3.7 — the weak-model result, and the first hook it demanded #11

Workflow file for this run

name: release
# Publishes a verifiable release on tag push (ADR-3).
#
# Sage used to be installed with `curl | bash` against main, and upgraded with
# `git pull --ff-only` against main. There was no tag, no checksum, and no
# signature — a compromised or mid-push main landed straight in ~/.sage/framework
# and, from there, into every project on its next `sage update`.
#
# Now: an annotated tag vX.Y.Z produces sage-X.Y.Z.tar.gz plus a checksums.txt
# that install.sh verifies before unpacking anything.
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
tag:
description: 'Tag to build (e.g. v1.1.11)'
required: true
permissions:
contents: write
jobs:
release:
name: build and publish release artifacts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # git archive needs the tag's history
- uses: actions/setup-python@v5
with:
python-version: '3.8'
- name: Resolve the tag
id: tag
run: echo "name=${{ github.event.inputs.tag || github.ref_name }}" >> "$GITHUB_OUTPUT"
- name: Tag must match VERSION
# A tag naming a version the tree does not claim would ship a tarball
# whose contents contradict its filename.
run: |
tag="${{ steps.tag.outputs.name }}"
version="$(cat VERSION)"
if [ "$tag" != "v$version" ]; then
echo "::error::tag $tag does not match VERSION $version"
exit 1
fi
- name: No version drift
run: python3 runtime/tools/release.py --check
# A tag can point at any commit, including one CI never saw. Re-run the
# checks that guard the release rather than trusting the branch build.
- name: Install fixture toolchains
run: |
python3 -m pip install --quiet pytest
npm install --global --silent typescript
- name: Gate regression tests
run: bash develop/validators/gates/run-gate-tests.sh
- name: Static validators
run: |
python3 develop/validators/check-bash-arrays.py
python3 develop/validators/check-portability.py
python3 develop/validators/tools/test_release.py
- name: Build tarball + checksums
run: python3 runtime/tools/release.py --artifacts --ref "${{ steps.tag.outputs.name }}"
- name: Verify the checksum we are about to publish
working-directory: dist
run: sha256sum -c checksums.txt
- name: Publish
env:
GH_TOKEN: ${{ github.token }}
run: |
tag="${{ steps.tag.outputs.name }}"
# Extracting this inline as a heredoc made the whole workflow file
# invalid YAML (the heredoc body sits at column 0, which closes the
# `run: |` block scalar). release.py owns CHANGELOG parsing and is
# unit-tested; ask it.
notes="$(python3 runtime/tools/release.py --notes "$tag")"
gh release create "$tag" \
dist/sage-*.tar.gz dist/checksums.txt \
--title "Sage $tag" \
--notes "$notes"
# The Claude Code plugin is generated, not committed (P3-T2b). main carries no
# plugin tree, so the marketplace entry pins `"ref": "plugin-dist"` — this job
# is what puts a tree there. If it stops running, the plugin stops installing.
publish-plugin:
name: publish the generated plugin to plugin-dist
needs: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.8'
- name: Resolve the tag
id: tag
run: echo "name=${{ github.event.inputs.tag || github.ref_name }}" >> "$GITHUB_OUTPUT"
- name: Build and audit the plugin
run: |
python3 runtime/tools/build_plugin.py --check
python3 runtime/tools/build_plugin.py --out "$RUNNER_TEMP/sage-claude-plugin"
- name: Publish to plugin-dist
env:
GH_TOKEN: ${{ github.token }}
run: |
set -eu
tag="${{ steps.tag.outputs.name }}"
tree="$RUNNER_TEMP/sage-claude-plugin"
# A marketplace `path` of tools/sage-claude-plugin means the branch must
# carry the tree at exactly that path. Nothing else belongs on it.
if [ ! -f "$tree/.claude-plugin/plugin.json" ]; then
echo "::error::the build produced no plugin.json — refusing to publish"
exit 1
fi
work="$RUNNER_TEMP/plugin-dist"
mkdir -p "$work/tools"
cp -R "$tree" "$work/tools/sage-claude-plugin"
# plugin-dist is a build output, not history: each release replaces it
# wholesale. The tag and the tarball are the archive; this branch only
# ever needs to answer "what is current".
cd "$work"
git init -q -b plugin-dist
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A
git commit -q \
-m "build: sage-claude-plugin $tag" \
-m "Generated by runtime/tools/build_plugin.py from ${GITHUB_SHA}." \
-m "Do not edit — this branch is overwritten on every release."
git push --force \
"https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \
plugin-dist
echo "published $(find "$tree" -type f | wc -l) files to plugin-dist"