Skip to content

fix(tui): stop saying "openboot" twice on the boot screen (#153) #73

fix(tui): stop saying "openboot" twice on the boot screen (#153)

fix(tui): stop saying "openboot" twice on the boot screen (#153) #73

Workflow file for this run

name: Auto Release
# Sensor: when unreleased commits on main trip a threshold, either auto-tag
# (patch fast lane) or open a release-ready issue (feat threshold).
# See docs/HARNESS.md.
#
# Triggers (any one fires):
# - >=5 feat/fix commits since last stable tag
# - >=7 days since last stable tag AND new commits exist
# - any fix: present (patch fast lane)
#
# Version bump:
# - any feat: present -> minor (X.Y+1.0) — opens issue, does NOT tag
# - else -> patch (X.Y.Z+1) — auto-tags + dispatches release.yml
#
# Why the split: feat: changes carry more risk than fix: patches and benefit
# from a human running the local Tart VM e2e suite before tag. The issue is
# a nudge, not a hard gate — a human can tag without running it.
#
# Why we dispatch release.yml on patch instead of relying on the tag-push
# trigger: GitHub deliberately suppresses workflow events fired by
# GITHUB_TOKEN, so a tag pushed from here would NOT start release.yml.
# We push the tag and then explicitly
# `gh workflow run release.yml -f version=<tag>`.
on:
push:
branches: [main]
workflow_dispatch:
inputs:
dry_run:
description: 'Dry-run: compute decision and exit without tagging.'
type: boolean
default: true
concurrency:
group: auto-release
cancel-in-progress: false
permissions:
contents: write
actions: write
issues: write
jobs:
evaluate:
name: auto-release sensor
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch tags
run: git fetch --tags --force
- name: Decide
id: decide
env:
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail
last_tag=$(git tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname \
| grep -Ev -- '-(rc|beta|alpha)' \
| head -n1 || true)
if [ -z "$last_tag" ]; then
echo "::warning::no stable tag found — skipping auto-release."
echo "release=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "last_tag=$last_tag"
commits=$(git log --format='%s' "${last_tag}..HEAD")
if [ -z "$commits" ]; then
echo "no commits since $last_tag — skip"
echo "release=false" >> "$GITHUB_OUTPUT"
exit 0
fi
feat_count=$(printf '%s\n' "$commits" | grep -cE '^feat(\(.+\))?!?:' || true)
fix_count=$(printf '%s\n' "$commits" | grep -cE '^fix(\(.+\))?!?:' || true)
combined=$((feat_count + fix_count))
last_ts=$(git log -1 --format=%ct "$last_tag")
now=$(date -u +%s)
days=$(( (now - last_ts) / 86400 ))
echo "feat=$feat_count fix=$fix_count combined=$combined days_since=$days"
reason=""
if [ "$combined" -ge 5 ]; then
reason="${combined} feat/fix commits since ${last_tag}"
elif [ "$days" -ge 7 ]; then
reason="${days} days since ${last_tag} with new commits"
elif [ "$fix_count" -ge 1 ]; then
reason="fix present (patch fast lane)"
else
echo "no threshold tripped — skip"
echo "release=false" >> "$GITHUB_OUTPUT"
exit 0
fi
IFS='.' read -r major minor patch <<<"${last_tag#v}"
if [ "$feat_count" -ge 1 ]; then
minor=$((minor + 1))
patch=0
bump="minor"
else
patch=$((patch + 1))
bump="patch"
fi
new_tag="v${major}.${minor}.${patch}"
echo "decision: release ${new_tag} (${bump}) — ${reason}"
{
echo "release=true"
echo "new_tag=${new_tag}"
echo "bump=${bump}"
echo "reason=${reason}"
} >> "$GITHUB_OUTPUT"
if [ "${DRY_RUN:-false}" = "true" ]; then
echo "::notice::DRY_RUN — would tag ${new_tag} (${reason})"
echo "dry_run=true" >> "$GITHUB_OUTPUT"
fi
- name: Tag and dispatch release (patch fast lane)
if: |
steps.decide.outputs.release == 'true'
&& steps.decide.outputs.dry_run != 'true'
&& steps.decide.outputs.bump == 'patch'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NEW_TAG: ${{ steps.decide.outputs.new_tag }}
REASON: ${{ steps.decide.outputs.reason }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$NEW_TAG" -m "Auto-release: $REASON"
git push origin "$NEW_TAG"
gh workflow run release.yml -f version="$NEW_TAG"
echo "::notice::tagged $NEW_TAG and dispatched release.yml — $REASON"
- name: Open release-ready issue (feat threshold)
if: |
steps.decide.outputs.release == 'true'
&& steps.decide.outputs.dry_run != 'true'
&& steps.decide.outputs.bump == 'minor'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NEW_TAG: ${{ steps.decide.outputs.new_tag }}
REASON: ${{ steps.decide.outputs.reason }}
run: |
set -euo pipefail
# If an open release-ready issue already exists for this tag, skip
# (we don't want to spam the queue every push to main).
existing=$(gh issue list \
--label release-ready \
--state open \
--search "in:title ${NEW_TAG}" \
--json number --jq '.[0].number' || true)
if [ -n "$existing" ]; then
echo "::notice::release-ready issue #${existing} already open for ${NEW_TAG} — skipping"
exit 0
fi
body=$(cat <<EOF
Auto-release sensor wants to cut **${NEW_TAG}** (minor bump).
**Why:** ${REASON}
Because this release includes \`feat:\` changes, the sensor did
**not** auto-tag. Run the destructive e2e suite locally, then
cut the release manually:
- [ ] L4 CI (\`vm-e2e-spike.yml\`) is green on the latest commit on \`main\`
- [ ] sanity-check the curl|bash smoke and cli-compat results in the most recent test.yml run on main
- [ ] \`git tag -a ${NEW_TAG} -m "..."\` and \`git push origin ${NEW_TAG}\`
- [ ] close this issue
L4 CI is not yet a hard merge gate, but \`feat:\` changes carry
more risk than \`fix:\` patches — verify it before tagging.
EOF
)
gh issue create \
--title "Release ready: ${NEW_TAG}" \
--label release-ready \
--body "$body"
echo "::notice::opened release-ready issue for ${NEW_TAG} — ${REASON}"