/beta-prerelease - OTGW-firmware Beta Prerelease Skill

SkillDev tools

Publish an OTGW-firmware beta prerelease — bump _VERSION_PRERELEASE, push to otgw-1.x.x, tag, and let CI build + publish the GitHub prerelease

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the /beta-prerelease - OTGW-firmware Beta Prerelease Skill skill

What this skill tells your AI

The instructions your AI receives, as published by rvdbreemen/otgw-firmware in .claude/skills/beta-prerelease/SKILL.md and read by ahel’s review.

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.log if exit code != 0.
  • P2 Phase 3 reads: Use targeted grep/sed to 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.yml only if a trap is actually hit.
  • P5 Phase 8 CI poll: Use gh run watch instead 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 branchdev then merge to mainotgw-1.x.x only
Bumps _SEMVER_COREyesno
GitHub releasestable, not prereleaseprerelease: true
Discord channels#nederlandse-ondersteuning, #english-support#beta-testing
Mandatory checkpoints21 (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 otgw-1.x.x

The 1.x maintenance/LTS line lives in its own worktree. Run this skill FROM the wt-otgw-1.x.x worktree. Do NOT git checkout otgw-1.x.x inside the dev tree: the branch is already checked out in the worktree and the checkout will fail.

  1. Confirm the branch: git rev-parse --abbrev-ref HEAD must print otgw-1.x.x.
  2. Verify clean state: git status must be clean (or only the firmware change about to be bumped). Then git pull origin otgw-1.x.x.
  3. Detect the latest prerelease tag:
    git fetch --tags
    PREV_TAG=$(git tag --list 'v*-*.*' --sort=-v:refname | head -1)
    
  4. 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)
    
  5. 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 — CHANGELOG [Unreleased] is the rolling beta log on the
#    1.x line. (The README "What's New in v<stable>" sections are refreshed at
#    STABLE release, not per beta, and there is no RELEASE_NOTES_*-beta file.)
grep -A 35 "## \[Unreleased\]" CHANGELOG.md | head -40

Decision:

  • Every commit subject since LATEST_PUBLIC appears under CHANGELOG ## [Unreleased] → pass silently, continue to Phase 4.
  • Any commit missing → refresh the CHANGELOG now. Stop and ask only if the gap is ambiguous; otherwise edit in-session.

Authoring rules (P3 — write immediately, keep only filename in context):

  1. CHANGELOG.md — append under ## [Unreleased] using Keep-a-Changelog headings (### Added/Changed/Fixed/Removed/Documentation). One bullet per change, with ADR/TASK/PR/GH-issue reference. → note "Updated." This is the only mandatory narrative for a 1.x beta.
  2. README.md — leave untouched. The 1.x README's "What's New in v" sections are refreshed at STABLE release (via /release), not per beta.
  3. RELEASE_NOTES_<next-stable>.md (e.g. RELEASE_NOTES_1.7.1.md) — optional during a beta cycle (authored in full at stable release). Update it now only if you keep a running draft. → note "Updated."

Skip the CHANGELOG edit 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 otgw-1.x.x

# The 1.x bin/bump-prerelease.sh updates version banners across ~24 files and
# does NOT auto-stage. Stage the real change set (on Windows, --ignore-cr-at-eol
# filters out EOL-only churn). On a clean Phase-0 tree this is exactly the bump
# banners + version.h + data/version.hash + your firmware change + CHANGELOG.md:
git add $(git diff --ignore-cr-at-eol --name-only)

git commit -m "chore(release): ${NEW_PRERELEASE}

<one-line summary of what is in this beta>"

git push origin otgw-1.x.x

If the pre-commit hook blocks: re-stage version.h + data/version.hash and retry. Do NOT bypass with OTGW_BUMP_HOOK_DISABLE=1.

Note: the .githooks/commit-msg task-hook requires a TASK-NNN whose backlog/tasks/task-NNN-*.md is tracked in THIS worktree. The backlog lives in the dev tree, so a 1.x firmware commit usually cannot satisfy it. Use a chore(release): ... subject (exempt from the task-hook) for the bump commit, or OTGW_TASK_HOOK_DISABLE=1 if you reference a cross-tree TASK-NNN.

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 otgw-1.x.x && git branch -d test/beta-prerelease-dryrun

Known traps (P4 — full detail in beta-prerelease.yml)

  1. Trap 1: Immutable-releases locks publish — upload after gh release create returns 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.
  2. 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.
  3. Trap 3: GITHUB_TOKEN events do not chainrelease-assets.yml (triggered by release: published) does not fire when the release is created by GITHUB_TOKEN. beta-prerelease.yml is therefore self-contained (generates SHA256SUMS + zip + flash scripts itself).
  4. 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.
  5. 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 otgw-1.x.x.
  • 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 stage version.h / data/version.hash.

Signals

GitHub stars
203
Forks
43
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
beta-prerelease
Source
github.com/rvdbreemen/otgw-firmware