Merge: v1.3.7 — the weak-model result, and the first hook it demanded #11
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |