Skip to content

Release

Release #17

Workflow file for this run

name: Release
# Synthesizes the `main` branch from `dev` by stripping dev-only artifacts.
# `main` is the public distribution surface — it must NEVER carry the
# plugin's own `.archcore/` (which would leak our dev docs into every
# user install via Cursor's plugin-MCP spawn-from-install-dir bug — see
# the analysis in docs/release.md and the cursor-mcp-architecture ADR).
#
# Trigger: push tag `v*` (e.g. v0.4.1) on dev. The tagged commit becomes
# the release point; the workflow strips the blocklist, force-pushes the
# result to main, and publishes a GitHub Release.
#
# Manual trigger via workflow_dispatch is provided for the first
# `dev → main` sync and emergency re-syncs.
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
source_ref:
description: 'Source ref (default: dev HEAD)'
required: false
default: 'dev'
permissions:
contents: write
jobs:
test-before-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: true
- name: Install bats-core
run: sudo apt-get update -qq && sudo apt-get install -y -qq bats shellcheck
- name: Verify dev distribution is releasable
run: make all
release:
needs: test-before-release
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v6
with:
# Tag push: GITHUB_REF points at the tag commit.
# Manual dispatch: use the supplied source_ref.
ref: ${{ github.event.inputs.source_ref || github.ref }}
fetch-depth: 0
- name: Verify source is on dev lineage
if: github.event_name == 'push'
run: |
set -e
git fetch origin dev
if ! git merge-base --is-ancestor "$GITHUB_SHA" origin/dev; then
echo "::error::Tag commit $GITHUB_SHA is not reachable from origin/dev."
echo "Releases must be cut from the dev branch."
exit 1
fi
- name: Strip dev-only artifacts (blocklist)
run: |
set -e
# Blocklist — see docs/release.md for the contract. Anything
# added here MUST be justified there.
rm -rf .archcore
rm -rf reference-materials
rm -rf test
rm -rf .claude
rm -rf .codex
rm -rf .github
rm -f Makefile
rm -f docs/release.md
rm -f plugins/archcore/cursor.mcp.json # belt-and-suspenders: must already be gone
# Sanity: bundled .archcore must be gone, and no distributable
# file may reference our internal docs.
if [ -d .archcore ]; then
echo "::error::.archcore/ still present after strip"
exit 1
fi
# Any leftover reference to the plugin's own .archcore docs is
# a hard failure (mirrors test/structure/no-bundled-archcore-refs.bats)
pattern='\.archcore/(plugin|knowledge|vision|experience)/[a-zA-Z0-9_-]+\.(adr|spec|prd|plan|idea|rule|guide|doc|task-type|cpat|rfc|mrd|brd|urd|brs|strs|syrs|srs)\.md'
if grep -rEn "$pattern" plugins/archcore/skills plugins/archcore/agents plugins/archcore/commands plugins/archcore/rules plugins/archcore/hooks plugins/archcore/bin README.md 2>/dev/null | \
grep -v -E '\.archcore/<' | \
grep -v -E '\.archcore/auth/jwt-strategy\.adr\.md' | \
grep -v -E '\.archcore/<path>/<slug>' | \
grep .; then
echo "::error::distributable files reference plugin-internal .archcore docs"
exit 1
fi
- name: Commit stripped tree to a release branch
run: |
set -e
git config user.email "release-bot@archcore.ai"
git config user.name "Archcore Release Bot"
git checkout --orphan release-staging
git add -A
local_msg="release: ${GITHUB_REF_NAME:-manual-dispatch}"
git commit -m "$local_msg" -m "Synthesized from dev — see docs/release.md on dev for the blocklist."
- name: Force-push to main
run: git push --force origin HEAD:main
- name: Create GitHub Release
if: github.event_name == 'push'
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
tag_name: ${{ github.ref_name }}