name: beta-prerelease description: Publish an OTGW-firmware beta prerelease — bump _VERSION_PRERELEASE, push to dev, tag, and let CI build + publish the GitHub prerelease disable-model-invocation: true
/beta-prerelease - OTGW-firmware Beta Prerelease Skill
Publish a single beta build to field testers in Discord #beta-testing. Lightweight, repeatable many times within one minor cycle. Does NOT merge to main, does NOT touch the stable _SEMVER_CORE.
Usage
/beta-prerelease
No arguments. Auto-detects the current _VERSION_PRERELEASE and increments it via bin/bump-prerelease.sh.
Token-efficiency rules (apply throughout all phases)
- P1 Build output: Pipe through
tee .tmp/build_beta.log | tail -5. Only read.tmp/build_beta.logif exit code != 0. - P2 Phase 3 reads: Use targeted
grep/sedto extract only the relevant section from each file. Never read README, CHANGELOG, or RELEASE_NOTES in full. - P3 Phase 3 writes: Edit each file and note "Updated." immediately. Do NOT keep all edited content in context — keep only the filename.
- P4 Known Traps: Summarized as 5 bullets below. Read
.github/workflows/beta-prerelease.ymlonly if a trap is actually hit. - P5 Phase 8 CI poll: Use
gh run watchinstead of opening a browser.
When to use
Run when a firmware change under src/OTGW-firmware/** or src/libraries/** is committed and ready for field testing. Do NOT use for docs-only commits or full releases to main (use /release <version> for those).
How this differs from /release
| Aspect | /release |
/beta-prerelease |
|---|---|---|
| Target branch | dev then merge to main |
dev only |
Bumps _SEMVER_CORE |
yes | no |
| GitHub release | stable, not prerelease | prerelease: true |
| Discord channels | #nederlandse-ondersteuning, #english-support |
#beta-testing |
| Mandatory checkpoints | 2 | 1 (Discord announcement) |
Writing style rules
- Never use em dashes — use colons, periods, commas, or parentheses instead.
- All release text MUST be in English (international audience).
- No emojis in release notes or Discord posts.
Process
Phase 0: Prepare — clean state on dev
- Ensure you are on
dev:git checkout dev - Verify clean state:
git statusmust be clean (or only the firmware change about to be bumped). Thengit pull origin dev. - Detect the latest prerelease tag:
git fetch --tags PREV_TAG=$(git tag --list 'v*-*.*' --sort=-v:refname | head -1) - Detect the latest public stable release (store as
LATEST_PUBLIC):LATEST_PUBLIC=$(gh release view --json tagName --jq '.tagName' 2>/dev/null \ || git tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | grep -v -- '-' | head -1) - Read current prerelease:
grep _VERSION_PRERELEASE src/OTGW-firmware/version.h
Phase 1: ADR validation
Skip by default. Only pause if the staged firmware change introduces a new architectural pattern, dependency, or NFR shift. Most beta cycles do not need this gate.
Phase 2: Bump prerelease
bin/bump-prerelease.sh
Prints the transition (e.g. beta.3 -> beta.4). Store the new value as NEW_PRERELEASE. The helper does NOT git-add — you stage in Phase 6.
Assemble the tag: TAG="v${SEMVER_CORE}-${NEW_PRERELEASE}"
Phase 3: Refresh README + CHANGELOG + RELEASE_NOTES (mandatory, P2, P3)
The GitHub Action reads these files at the tagged commit. Stale narrative at the tag = stale release page (Trap 1). Refresh immediately after the bump so NEW_PRERELEASE is known.
Staleness check (P2 — targeted extractions, not full reads):
# 1. Commits since last public release (the change set to account for)
git log --pretty=format:'%h %s' "${LATEST_PUBLIC}..HEAD" -- src/OTGW-firmware/ src/libraries/ docs/
# 2. Existing narrative — extract only the relevant sections
grep -A 50 "What's new on dev" README.md | head -55
grep -A 35 "## \[Unreleased\]" CHANGELOG.md | head -40
sed -n '1,/<!-- digest:end -->/p' RELEASE_NOTES_*-beta.md 2>/dev/null
Decision:
- All commit subjects appear in at least one narrative AND dev-banner version label matches
NEW_PRERELEASE→ pass silently, continue to Phase 4. - Any commit missing from the narrative OR dev-banner label stale → refresh now. Stop and ask only if the gap is ambiguous; otherwise edit in-session.
Authoring rules (P3 — write immediately, keep only filename in context):
CHANGELOG.md— append under## [Unreleased]using Keep-a-Changelog headings (### Added/Changed/Fixed/Removed/Documentation). One bullet per change, with ADR/TASK/PR reference. → note "Updated."README.md— update dev-banner version label toNEW_PRERELEASEand "What's new on dev" section. Do NOT touch the "What's New in v" section. → note "Updated." RELEASE_NOTES_<base>-beta.md— update the digest region above<!-- digest:end -->. → note "Updated."
Skip all three only when this is a re-cut at the same change surface (previous tag hit Trap 2). Note the reason in the commit message.
Phase 4: Build verification (P1)
mkdir -p .tmp
python build.py --firmware 2>&1 | tee .tmp/build_beta.log | tail -5
echo "Exit: $?"
Must exit 0. On failure: read .tmp/build_beta.log for diagnosis, fix, retry. Do NOT push a tag on a broken build.
Phase 5: Evaluator
python evaluate.py --quick
Must show no new failures. Pre-existing baseline failures unrelated to this change: document in the commit message.
Phase 6: Commit and push to dev
git add src/OTGW-firmware/version.h src/OTGW-firmware/data/version.hash \
<firmware-files> \
CHANGELOG.md README.md \
RELEASE_NOTES_*-beta.md
git commit -m "chore(release): ${NEW_PRERELEASE}
<one-line summary of what is in this beta>"
git push origin dev
If the pre-commit hook blocks: re-stage version.h + data/version.hash and retry. Do NOT bypass with OTGW_BUMP_HOOK_DISABLE=1.
Phase 7: Create and push the prerelease tag
SEMVER_CORE=$(grep '_SEMVER_CORE ' src/OTGW-firmware/version.h | awk -F'"' '{print $2}')
TAG="v${SEMVER_CORE}-${NEW_PRERELEASE}"
git tag -a "${TAG}" -m "Beta prerelease ${NEW_PRERELEASE}"
git push origin "${TAG}"
The push fires .github/workflows/beta-prerelease.yml. CI builds firmware + filesystem, creates a GitHub prerelease, uploads .ino.bin, .littlefs.bin, SHA256SUMS, flash scripts, and the flash-bundle zip.
Phase 8: Wait for the GitHub Action (P5)
gh run watch --exit-status
When it exits 0, verify the release:
gh release view "${TAG}" --json tagName,isPrerelease,assets \
--jq '{tag: .tagName, prerelease: .isPrerelease, assets: [.assets[].name]}'
Expected assets: *.ino.bin, *.littlefs.bin, SHA256SUMS, flash_otgw.sh, flash_otgw.bat, OTGW-firmware-*-flash-bundle.zip.
On failure: inspect logs with gh run view --log-failed, fix the issue, and re-run via workflow_dispatch or push a new tag.
Phase 9: Discord announcement (CHECKPOINT)
Prepare announcement for #beta-testing (channel ID 914498730001072149). Diff link points at LATEST_PUBLIC (testers want to see what changed since the last stable, not since a previous beta).
Beta ${NEW_PRERELEASE} is up.
Version: ${SEMVER_CORE}-${NEW_PRERELEASE}
What is new: <one or two sentences>
Download: https://github.com/rvdbreemen/OTGW-firmware/releases/tag/${TAG}
Diff vs ${LATEST_PUBLIC}: https://github.com/rvdbreemen/OTGW-firmware/compare/${LATEST_PUBLIC}...${TAG}
Changelog: https://github.com/rvdbreemen/OTGW-firmware/blob/${TAG}/CHANGELOG.md
Please flash and report findings here (good and bad).
CHECKPOINT: Show the announcement to the user before sending.
Dry-run (testing without publishing)
git checkout -b test/beta-prerelease-dryrun
git tag -a v0.0.0-beta.dryrun -m "dryrun" && git push origin v0.0.0-beta.dryrun
# watch the Action, then clean up:
gh release delete v0.0.0-beta.dryrun --yes
git push --delete origin v0.0.0-beta.dryrun && git tag -d v0.0.0-beta.dryrun
git checkout dev && git branch -d test/beta-prerelease-dryrun
Known traps (P4 — full detail in beta-prerelease.yml)
- Trap 1: Immutable-releases locks publish — upload after
gh release createreturns HTTP 422. Workaround already in CI: draft-first, attach all assets, then flip--draft=false. If you hit this manually, re-tag under the next beta number. - Trap 2: Deleted immutable release reserves the tag forever — even after deletion, the tag cannot be reused. Bump the prerelease number and note the skipped tag in the commit message.
- Trap 3:
GITHUB_TOKENevents do not chain —release-assets.yml(triggered byrelease: published) does not fire when the release is created byGITHUB_TOKEN.beta-prerelease.ymlis therefore self-contained (generates SHA256SUMS + zip + flash scripts itself). - Trap 4: Diff link to previous beta hides what testers care about — always link vs
LATEST_PUBLIC(the non-prerelease "Latest" release), not the previous beta tag. - Trap 5: Stale narrative at tagged commit is permanent — Phase 3 refresh runs before the tag is pushed for this reason. Skipping Phase 3 = stale release page that cannot be edited after publish.
Important rules
- Never use em dashes in any generated text.
- Always push to remote after every commit.
- Never force-push to dev.
- Build and evaluator gates are mandatory — do not push a tag if either is red.
- One checkpoint: the Discord announcement in Phase 9.
- Do NOT bypass the bump-check hook with
OTGW_BUMP_HOOK_DISABLE=1— if the hook blocks, you forgot to stageversion.h/data/version.hash.