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
90 changes: 90 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: Release

on:
pull_request:
types: [closed]
branches: [main]

permissions:
contents: write

jobs:
# Only run when a release/* PR is merged (not closed without merge)
check:
if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/v')
runs-on: ubuntu-latest
outputs:
version: ${{ steps.extract.outputs.version }}
steps:
- name: Extract version from branch name
id: extract
run: |
BRANCH="${{ github.event.pull_request.head.ref }}"
VERSION="${BRANCH#release/v}"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "Releasing v${VERSION}"

# ── 1. Build + test gate ─────────────────────────────────────────────────
verify:
needs: check
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build
- run: node --test test/*.test.mjs

# ── 2. Tag + GitHub Release → triggers publish.yml (npm) ────────────────
github-release:
needs: [check, verify]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create tag and GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ needs.check.outputs.version }}
run: |
git tag "v${VERSION}"
git push origin "v${VERSION}"
gh release create "v${VERSION}" --generate-notes --title "v${VERSION}"

# ── 3. Publish to ClawHub ──────────────────────────────────────────────
clawhub-publish:
needs: [check, verify]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- name: Publish skill to ClawHub
env:
CLAWHUB_TOKEN: ${{ secrets.CLAWHUB_TOKEN }}
VERSION: ${{ needs.check.outputs.version }}
run: |
npx clawhub@latest auth login --token "$CLAWHUB_TOKEN" --no-browser
npx clawhub@latest publish "$(pwd)/skills/declaw" --version "$VERSION"

# ── 4. Backmerge main → develop ────────────────────────────────────────
backmerge:
needs: github-release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Merge main into develop
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout develop
git merge main --no-edit
git push origin develop
76 changes: 46 additions & 30 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,16 @@ git flow release finish <version>
git push origin main develop --tags
```

### Important: Features Must Use PRs
### Important: All Changes Via PR

**Never directly merge feature branches.** Always:
1. Push feature branch to origin
2. Create PR targeting `develop`
3. Get review and merge via GitHub
4. **Close the corresponding issue** when merging (use `Fixes #N` or `Closes #N` in the PR description)
**Both `main` and `develop` are branch-protected. No direct push allowed.**

1. Push feature/fix branch to origin
2. Create PR targeting `develop` (features/fixes) or `main` (releases/hotfixes)
3. CI must pass (`test (20)` + `test (22)`)
4. Squash merge only — one commit per PR
5. **Close the corresponding issue** when merging (use `Fixes #N` or `Closes #N` in the PR description)
6. Merged branches are auto-deleted

### Commit Convention

Expand Down Expand Up @@ -168,28 +171,53 @@ When creating new issues:

## Release Process

### Automated Release (`scripts/release.sh`)
### Release Pipeline (Local + CI)

One command handles the full release pipeline:
One command kicks off the release via PR — CI handles the rest on merge:

```bash
bash scripts/release.sh patch # 0.2.2 → 0.2.3
bash scripts/release.sh minor # 0.2.2 → 0.3.0
bash scripts/release.sh major # 0.2.2 → 1.0.0
```

The script performs these steps automatically:
**Local (`scripts/release.sh`):**
1. **Preflight**: verifies on `main`, clean tree, synced with remote
2. **Build + test**: `npm run build` + `node --test test/*.test.mjs`
3. **Version bump**: updates all 3 version-bearing files:
- `package.json` + `package-lock.json` (via `npm version --no-git-tag-version`)
- `openclaw.plugin.json` (plugin manifest)
- `skills/declaw/SKILL.md` (ClawHub skill frontmatter)
4. **Changelog check**: warns if `CHANGELOG.md` is missing a section for the new version
5. **Commit + tag**: `chore: release vX.Y.Z` + git tag `vX.Y.Z`
6. **Push**: main branch + tags to origin
7. **GitHub Release**: `gh release create` with auto-generated notes → triggers npm publish via CI
8. **Backmerge**: main → develop → push
3. **Version bump**: syncs all 3 version-bearing files
4. **Changelog check**: warns if `CHANGELOG.md` is missing new version section
5. **Create Release PR**: pushes `release/vX.Y.Z` branch → creates PR targeting `main`

**CI (`.github/workflows/release.yml`, triggered when `release/v*` PR merges into `main`):**
6. **Build + test gate**: Node 20 + 22 matrix
7. **Tag + GitHub Release**: creates `vX.Y.Z` tag + auto-generated release notes → triggers npm publish
8. **ClawHub publish**: `npx clawhub@latest publish` with `CLAWHUB_TOKEN` secret
9. **Backmerge**: main → develop (via github-actions bot)

### CI Workflows

| Workflow | Trigger | What it does |
|---|---|---|
| `release.yml` | Release PR merged into `main` | Verify → Tag → GH Release → ClawHub → Backmerge |
| `publish.yml` | GH Release published | npm publish with `NPM_TOKEN` |
| `test.yml` | Push/PR to main/develop | Build + test (Node 20+22) |
| `auto-close-issues.yml` | PR merged | Close linked issues |
| `bootstrap-health.yml` | Scheduled | Ping all 5 bootstrap nodes |

### Branch Protection

Both `main` and `develop` are protected:
- **No direct push** — all changes via PR (squash merge only)
- **Required CI**: `test (20)` + `test (22)` must pass
- **No force push** or branch deletion
- **Enforced for admins** — no bypass

### Repo Security

- **Secret scanning + push protection**: enabled (GitHub catches leaked tokens)
- **Squash merge only**: one commit per PR, clean history
- **Auto-delete branches**: merged PR branches are cleaned up automatically
- **Required secrets**: `NPM_TOKEN` (npm), `CLAWHUB_TOKEN` (ClawHub)

### Pre-release: Update CHANGELOG

Expand All @@ -198,18 +226,6 @@ Before running the release script, update `CHANGELOG.md`:
- Categorize entries: `Breaking Changes`, `Added`, `Changed`, `Fixed`
- Reference issues and PRs (e.g., `PR #8`, `Closes #7`)

### Post-release Checklist

The script prints these reminders after release:
1. Verify npm: `https://www.npmjs.com/package/@resciencelab/declaw`
2. Publish skill: `npx clawhub@latest publish skills/declaw`
3. Deploy bootstrap if `bootstrap/server.mjs` changed (see below)

### ClawHub Skill Publish
- Verify login: `npx clawhub@latest whoami`
- Publish: `npx clawhub@latest publish skills/declaw`
- Version is already synced by the release script

### Bootstrap Node Deployment
- Only needed when `bootstrap/server.mjs` or `bootstrap/package.json` changes
- Deploy via AWS SSM (no SSH):
Expand Down
87 changes: 41 additions & 46 deletions scripts/release.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail

# DeClaw — automated release script
# DeClaw — release script (creates Release PR)
# Usage:
# bash scripts/release.sh patch # 0.2.2 → 0.2.3
# bash scripts/release.sh minor # 0.2.2 → 0.3.0
# bash scripts/release.sh major # 0.2.2 → 1.0.0
#
# Flow:
# 1. Local: preflight → build+test → version bump → create Release PR
# 2. CI: PR merge triggers release.yml → tag + GH Release + npm + ClawHub + backmerge

LEVEL="${1:-patch}"

Expand All @@ -16,27 +20,24 @@ fi

echo "=== DeClaw Release (${LEVEL}) ==="

# ── 0. Preflight checks ──────────────────────────────────────────────────────
# ── 0. Preflight ─────────────────────────────────────────────────────────────

# Must be on main
BRANCH=$(git branch --show-current)
if [[ "$BRANCH" != "main" ]]; then
echo "Error: must be on 'main' branch (currently on '${BRANCH}')"
exit 1
fi

# Working tree must be clean
if [[ -n "$(git status --porcelain)" ]]; then
echo "Error: working tree is not clean. Commit or stash changes first."
exit 1
fi

# Remote must be up to date
git fetch origin main --quiet
LOCAL=$(git rev-parse main)
REMOTE=$(git rev-parse origin/main)
if [[ "$LOCAL" != "$REMOTE" ]]; then
echo "Error: local main ($LOCAL) differs from origin/main ($REMOTE). Pull or push first."
echo "Error: local main differs from origin/main. Pull or push first."
exit 1
fi

Expand All @@ -50,69 +51,63 @@ node --test test/*.test.mjs

# ── 2. Version bump ──────────────────────────────────────────────────────────

# Bump package.json + package-lock.json (no git tag yet)
VERSION=$(npm version "$LEVEL" --no-git-tag-version | tr -d 'v')
echo "New version: ${VERSION}"

# Sync version to all version-bearing files:
# - openclaw.plugin.json (plugin manifest)
# - skills/declaw/SKILL.md (ClawHub skill frontmatter)
sed -i '' "s/\"version\": \"[^\"]*\"/\"version\": \"${VERSION}\"/" openclaw.plugin.json
sed -i '' "s/^version: .*/version: ${VERSION}/" skills/declaw/SKILL.md

echo "Version synced to: package.json, openclaw.plugin.json, skills/declaw/SKILL.md"
echo "Version synced: package.json, openclaw.plugin.json, skills/declaw/SKILL.md"

# ── 3. Verify CHANGELOG ──────────────────────────────────────────────────────
# ── 3. Changelog check ───────────────────────────────────────────────────────

if ! grep -q "\[${VERSION}\]" CHANGELOG.md; then
echo ""
echo "Warning: CHANGELOG.md does not contain a [${VERSION}] section."
echo "Please update CHANGELOG.md before releasing."
echo ""
read -p "Continue without changelog entry? (y/N) " -n 1 -r
echo
if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then
echo "Aborting. Update CHANGELOG.md and re-run."
# Revert version bumps
git checkout -- package.json package-lock.json openclaw.plugin.json skills/declaw/SKILL.md
exit 1
fi
fi

# ── 4. Commit + tag ──────────────────────────────────────────────────────────
# ── 4. Create release branch + PR ────────────────────────────────────────────

RELEASE_BRANCH="release/v${VERSION}"

git checkout -b "$RELEASE_BRANCH"
git add -A
git commit -m "chore: release v${VERSION}"
git tag "v${VERSION}"

echo "Committed and tagged v${VERSION}"

# ── 5. Push main + tag ───────────────────────────────────────────────────────

git push origin main --tags
echo "Pushed main + v${VERSION} tag"

# ── 6. GitHub Release (triggers npm publish via CI) ──────────────────────────

gh release create "v${VERSION}" --generate-notes --title "v${VERSION}"
echo "GitHub release created → npm publish will trigger via CI"

# ── 7. Backmerge to develop ──────────────────────────────────────────────────

git checkout develop
git merge main --no-edit
git push origin develop
git checkout main

echo "Backmerged main → develop"

# ── 8. Done ──────────────────────────────────────────────────────────────────
git push -u origin "$RELEASE_BRANCH"

PR_URL=$(gh pr create \
--base main \
--head "$RELEASE_BRANCH" \
--title "chore: release v${VERSION}" \
--body "## Release v${VERSION}

### Version bump
- \`package.json\` → ${VERSION}
- \`openclaw.plugin.json\` → ${VERSION}
- \`skills/declaw/SKILL.md\` → ${VERSION}

### What happens on merge
CI (\`.github/workflows/release.yml\`) will automatically:
1. Create git tag \`v${VERSION}\`
2. Create GitHub Release (triggers npm publish)
3. Publish skill to ClawHub
4. Backmerge main → develop")

echo ""
echo "=== Released v${VERSION} ==="
echo "=== Release PR created ==="
echo " ${PR_URL}"
echo ""
echo "Post-release checklist:"
echo " 1. Verify npm: https://www.npmjs.com/package/@resciencelab/declaw"
echo " 2. Publish skill: npx clawhub@latest publish skills/declaw"
echo " 3. Deploy bootstrap (if server.mjs changed):"
echo " bash -c 'B64=\$(base64 -i bootstrap/server.mjs); for pair in \"i-04670f4d1a72c7d5d:us-east-2\" \"i-096ba79b9ae854339:us-west-2\" \"i-084242224f1a49b13:eu-west-1\" \"i-0b909aacd92097e43:ap-northeast-1\" \"i-0141cd0f56a902978:ap-southeast-1\"; do IID=\${pair%%:*}; REGION=\${pair##*:}; aws ssm send-command --instance-ids \$IID --region \$REGION --document-name AWS-RunShellScript --parameters \"{\\\"commands\\\":[\\\"echo '\\'\\''\${B64}'\\'\\'\" | base64 -d > /opt/declaw-bootstrap/server.mjs\\\",\\\"systemctl restart declaw-bootstrap\\\"]}\" --query Command.CommandId --output text; done'"
echo "Next steps:"
echo " 1. Wait for CI checks to pass"
echo " 2. Merge the PR (squash)"
echo " 3. CI handles: tag → GH Release → npm → ClawHub → backmerge"
echo ""

git checkout main